PYNQのMMIOを使ってAXIバスのレジスタを変更してみました。
PWMのdutyを変えて、Lチカ・DCモータを制御しています。
プログラムからテスト動画含めて紹介します。
PYNQからAXIバスのレジスタを触って、PWMを弄ってみた
PYNQのMMIOを使ってAXIバスのレジスタを変更してみました。
使用したFPGAボードはKV260です。
PWMのdutyを変えて、Lチカ・DCモータを制御しています。
プログラムからテスト動画含めて紹介します。
テスト環境
テスト環境は以下の通りです。FPGAのファイルは下記環境で作成しています。
- Vivado, Vitis, PetaLinux…2023.1
使用評価ボードはKV260です。
自作のLED基板
LEDをテストした基板に関しては、下記記事で発注した旨を紹介しています。
回路図の基板データ・実装部品に関しては、下記リポジストリに詳細を残しています。
自作のモータドライバ基板
テストしたモータドライバ基板に関しては、PCBGOGOで基板作成したものです。
その際のブログ記事は下記です。
回路図の基板データ・実装部品に関しては、下記リポジストリに詳細を残しています。
ICのDRV8833としては、2つのDCモータ・1つのステッピングモータを制御できます。
今回はDCモータでPWM制御を試してみました。
今回はDCモータで突入電流も大きく、USBの5V供給ではテスト動作が出来ませんでした。
そのため、外部バッテリの5Vをモータドライバの電源としています。
DCモータ+クランクボックス
DCモータとクランクボックス含めたアーム機構は下記TAMIYAのキットを使っています
下記コンテストで改造使用したものです。
オシロスコープ
オシロスコープは下記で紹介しているRIGOLの趣味向けの4ch品を使用しています。
オシロスコープの使い方!初めての人向けに多くの測定事例を紹介
PYNQのMMIOモジュールからAXIのレジスタ変更
今回この記事を書いた理由はPYNQのMMIOモジュールをテストしたかったためです。
PYNQのMMIOを使うことで、AXIバスのレジスタを変更できます。
今回FPGAを合成する上でレジスタは把握できています。
今回使うIP(Timer)の公式ドキュメントは下記です。
https://docs.amd.com/v/u/en-US/pg079-axi-timer
下記のような形でプログラムしました。PWMのレジスタを変更しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
from pynq import Overlay from pynq import MMIO from pynq.lib import AxiGPIO ol = Overlay("/root/jupyter_notebooks/pynq-pwm/dpu.bit") base_address_0 = 0xA0010000 # axi_timer_0 base_address_1 = 0xA0030000 # axi_timer_1 base_address_2 = 0xA0040000 # axi_timer_2 base_address_3 = 0xA0050000 # axi_timer_3 address_range = 0x10000 # axi_timer_* mmio_0 = MMIO(base_address_0, address_range) mmio_1 = MMIO(base_address_1, address_range) mmio_2 = MMIO(base_address_2, address_range) mmio_3 = MMIO(base_address_3, address_range) register_offset_TSCR0 = 0x0 # Timer 0 Control and Status Register register_offset_TSCR1 = 0x10 # Timer 1 Control and Status Register register_offset_TLR0 = 0x4 # Timer 0 Load Register register_offset_TLR1 = 0x14 # Timer 1 Load Register value_to_write = 0b1010010110 #PWM, TCSR, UDT, Autoreload, timer mmio_0.write(register_offset_TSCR0, value_to_write) mmio_0.write(register_offset_TSCR1, value_to_write) _period_ = 20000 # 50Hz, 20ms _pulse_ = 30 # 0-100 period = int((_period_ & 0x0ffff) * 100) pulse = int((_pulse_ & 0x07f) * period / 100) mmio_0.write(register_offset_TLR0, period) mmio_0.write(register_offset_TLR1, pulse) |
以前にMMIOではなくオーバレイのdictから情報を読み取る方法でも対応しました。
下記コンテストでKR260で実施した内容です。
https://www.hackster.io/iotengineer22/control-pwm-dc-motor-from-pynq-and-kr260-bb0296
どちらが楽か確認してみたかったのも理由があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# utility functions for bit manipulation def set_bit(value, bit): return value | (1 << bit) def clear_bit(value, bit): return value & ~(1 << bit) def get_bit(value, bit): return (value >> bit) & 1 motor_B2 = ol.axi_timer_0 motor_B1 = ol.axi_timer_1 motor_A2 = ol.axi_timer_2 motor_A1 = ol.axi_timer_3 # extract register addresses (will be the same for every Axi Timer) TCSR0 = ol.ip_dict['axi_timer_0']['registers']['TCSR0'] TCSR1 = ol.ip_dict['axi_timer_0']['registers']['TCSR1'] TCSR0_address = TCSR0['address_offset'] TCSR1_address = TCSR1['address_offset'] TCSR0_register = TCSR0['fields'] # bit_offset for address TCSR1_register = TCSR1['fields'] TLR0 = ol.ip_dict['axi_timer_0']['registers']['TLR0'] TLR1 = ol.ip_dict['axi_timer_0']['registers']['TLR1'] TLR0_address = TLR0['address_offset'] TLR1_address = TLR1['address_offset'] # create the configuration values for the control register temp_val_0 = 0 temp_val_1 = 0 # The PWMA0 bit in TCSR0 and PWMB0 bit in TCSR1 must be set to 1 to enable PWM mode. temp_val_0 = set_bit(temp_val_0, TCSR0_register['PWMA0']['bit_offset']) temp_val_1 = set_bit(temp_val_1, TCSR1_register['PWMA1']['bit_offset']) # The GenerateOut signals must be enabled in the TCSR (bit GENT set to 1). The PWM0 # signal is generated from the GenerateOut signals of Timer 0 and Timer 1, so these # signals must be enabled in both timer/counters temp_val_0 = set_bit(temp_val_0, TCSR0_register['GENT0']['bit_offset']) temp_val_1 = set_bit(temp_val_1, TCSR1_register['GENT1']['bit_offset']) # The counter can be set to count up or down. UDT temp_val_0 = set_bit(temp_val_0, TCSR0_register['UDT0']['bit_offset']) temp_val_1 = set_bit(temp_val_1, TCSR1_register['UDT1']['bit_offset']) # set Autoreload (ARHT0 = 1) temp_val_0 = set_bit(temp_val_0, TCSR0_register['ARHT0']['bit_offset']) temp_val_1 = set_bit(temp_val_1, TCSR1_register['ARHT1']['bit_offset']) # enable timer (ENT0 = 1) temp_val_0 = set_bit(temp_val_0, TCSR0_register['ENT0']['bit_offset']) temp_val_1 = set_bit(temp_val_1, TCSR1_register['ENT1']['bit_offset']) def set_motor_B_pwm(duty_cycle, direction): _period_ = 20000 # 50Hz, 20ms _pulse_ = duty_cycle # 0-100 period = int((_period_ & 0x0ffff) * 100) pulse = int((_pulse_ & 0x07f) * period / 100) motor_B1.write(TCSR0['address_offset'], temp_val_0) motor_B1.write(TCSR1['address_offset'], temp_val_1) motor_B1.write(TLR0['address_offset'], period) motor_B2.write(TCSR0['address_offset'], temp_val_0) motor_B2.write(TCSR1['address_offset'], temp_val_1) motor_B2.write(TLR0['address_offset'], period) # direction if direction == 'forward': motor_B1.write(TLR1['address_offset'], pulse) motor_B2.write(TLR1['address_offset'], 0) elif direction == 'reverse': motor_B1.write(TLR1['address_offset'], 0) motor_B2.write(TLR1['address_offset'], pulse) elif direction == 'coast': motor_B1.write(TLR1['address_offset'], 0) motor_B2.write(TLR1['address_offset'], 0) elif direction == 'break': motor_B1.write(TLR1['address_offset'], 100) motor_B2.write(TLR1['address_offset'], 100) else: print("Invalid direction. Please use 'forward' or 'reverse'or 'coast' or 'break'.") |
両方試した感想としては、下記イメージでいます。
- しっかりレジスタ把握しているならば前者
- 雑に把握しているIPから、楽にデータ取得したいなら後者
実際のテストプログラム
実際にKV260で試したプログラムは下記GitHubに置いています。
もしKV260で同様に試したい場合は、下記のようにjupyter_notebooksのフォルダにコピーします。
1 2 3 4 |
sudo su cd $PYNQ_JUPYTER_NOTEBOOKS cd jupyter_notebooks cp -rf /home/ubuntu/kv260-ubuntu-test/jupyter_notebooks/pynq-pwm/ ./ |
PWMのdutyを変えて、Lチカ+オシロの波形
KV260とオシロスコープを使って、PYNQからPWMのdutyを変える様子を確認します。
テスト動画は下記です。
既にjupyter notebookのプログラムは用意しています。
FPGAの.bitファイルをオーバレイしてプログラムを実行していきます。
MMIOモジュール経由でレジスタ設定をしています。
最初は10%のPWM駆動をしています。LED光量も小さいです。
波形も殆どがLowで、僅かにしかHighになっていないことが分かります。
次にPWMを50%に修正してみます。LED光量も大きくなりました。
波形もLowとHighが50%-50%になっています。
最後にPWMを90%にしてみます。LEDも相当光るようになりました。
波形も殆どがHighとなっています。
PWMのdutyを変えて、DCモータ+アーム動作
KV260とDCモータを使って、PYNQからPWMのdutyを変える様子を確認します。
テスト動画は下記です。
最初に90%のPWMでDCモータ+アーム動作を確認しました。スムーズに動作します。
PWMを50%にしても、アーム動作しますが動きが遅くなります。
PWM10%だとパワーが足りず、モータとアームは回転できませんでした。
PWM30%だと何とか回ったという形になります。
まとめ
PYNQのMMIOを使ってAXIバスのレジスタを変更してみました。
PWMのdutyを変えて、Lチカ・DCモータを制御できています。
よろしければ、皆様もLチカやDCモータをPWMで遊んでみてください。
コメント