Cortex-M0 RTLで遊ぶ (2) - プログラム実行
前回のシミュレーション環境を少し修正してM0プログラムを動かしてみます。
プログラムのコンパイルはarm-gccを使用しています。
aptからインストールしました。
$ sudo apt install gcc-arm-none-eabi $ arm-none-eabi-gcc -v gcc version 8.3.1 20190703 (release) [gcc-8-branch revision 273027] (15:8-2019-q3-1+b1)
若干、リビジョンが古いようですので気になる方はこちらから最新をダウンロードするのが良いと思います。

https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
my_test.vは以下のように修正しています。
`timescale 1ns/1ns module my_test(); reg clk; reg reset_n; wire [31:0] HADDR; wire [2:0] HSIZE; wire [1:0] HTRANS; wire [31:0] HWDATA; wire HWRITE; wire [31:0] HRDATA; wire HREADY; wire HRESP; parameter ADDR_FINISH = 32'h0000fffc; CORTEXM0INTEGRATION CORTEXM0INTEGRATION( ~省略~ ); cmsdk_ahb_ram_beh #( .AW ( 16 ) ,.filename( "test.vmem" ) ,.WS_N ( 0 ) ,.WS_S ( 0 ) )cmsdk_ahb_ram_beh( ~省略~ ); always #100 clk = ~clk; initial begin clk = 1'b0; reset_n = 1'b0; #250 reset_n = 1'b0; #250 reset_n = 1'b1; repeat(10) @(posedge clk); repeat(20000) @(posedge clk); $display("*** Simulation timeout... ***"); $finish; end initial begin repeat(10) @(posedge clk); forever begin @(posedge clk); if(HADDR==ADDR_FINISH && HTRANS==2'b10 && HWRITE==1'b1 && HREADY==1'b1)begin repeat(3) @(posedge clk); $display("*** Simulation finish... ***"); $finish; end end end initial begin $dumpvars(); end endmodule
修正点としてはメモリモデルのインスタンス時にパラメータを渡しています。
cmsdk_ahb_ram_beh #( .AW ( 16 ) ← アドレスバス幅(内部メモリのwordサイズにも使用されている) ,.filename( "test.vmem" ) ← シミュレーション開始時に内部メモリに展開するファイル名 ,.WS_N ( 0 ) ← アクセス時のウェイト数?(良くわからないのでデフォルトのまま) ,.WS_S ( 0 ) )cmsdk_ahb_ram_beh(
その他、0xfffc番地にライトアクセスが発生したら3サイクル後にシミュレーションを終了するようにしています。
(ライトアクセスが発生しない時は20000サイクル後にタイムアウトのメッセージを表示して終了)
parameter ADDR_FINISH = 32'h0000fffc; initial begin repeat(10) @(posedge clk); forever begin @(posedge clk); if(HADDR==ADDR_FINISH && HTRANS==2'b10 && HWRITE==1'b1 && HREADY==1'b1)begin repeat(3) @(posedge clk); $display("*** Simulation finish... ***"); $finish; end end end
プログラム環境は、4つのファイルで構成しています。
- startup.s:ブートプログラム
.equ STACK_TOP, 0xff00 .global _start .section .isr_vector, "a", %progbits .align 4 _vect_table: .word STACK_TOP .word (_start + 1) .align 4 _start: bl main _loop: nop nop nop b _loop .align 4
0x00番地に配置するスタックアドレスは0xff00に設定しています。
- test.c:メインプログラム
#define ADDR_FINISH 0xfffc #include <sys/types.h> int main(void){ *(volatile uint32_t*)(ADDR_FINISH) = 0xff; return 1; }
実行直後に0xfffc番地へ0xffのライトを行いシミュレーションが終了する予定です。
- test.ld:リンカスクリプト
ENTRY(_start) MEMORY{ ROM (xr) : ORIGIN = 0x00000000, LENGTH = 32K RAM (xw) : ORIGIN = 0x00008000, LENGTH = 32K } SECTIONS{ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) . = ALIGN(4); } > ROM .text : { . = ALIGN(4); *(.text) *(.text*) . = ALIGN(4); } > ROM .rodata : {} > ROM .data : {} > RAM .bss : {} > RAM }
64Kバイトのメモリモデルの前半32KバイトをROM領域に、後半32KバイトをRAM領域に設定しています。
TARGET = test all: arm-none-eabi-as -mcpu=cortex-m0 -mthumb startup.s -o startup.o arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -Os -c $(TARGET).c -o $(TARGET).o arm-none-eabi-ld -T $(TARGET).ld startup.o $(TARGET).o -o $(TARGET).elf arm-none-eabi-objcopy -O verilog $(TARGET).elf $(TARGET).vmem --verilog-data-width=1 dos2unix $(TARGET).vmem arm-none-eabi-objdump $(TARGET).elf --disassemble-all > $(TARGET).lst clean: $(RM) *.elf *.bin *.o *.lst *.vmem
コンパイル→リンク後のelfファイルをobjcopyでVerilogHDLの$readmemh向けのフォーマットに変換します。
また、objdumpで逆アセンブルも行います。
makeでエラーが出なければコンパイル成功です。
逆アセンブルしたtest.lstを確認してみます。
Disassembly of section .isr_vector:
00000000 <_vect_table>:
↓ スタックアドレスに0xff00を設定
0: 0000ff00 andeq pc, r0, r0, lsl #30
↓ 0x10番地の _start へジャンプ(LSBの1はthumb命令の意味)
4: 00000011 andeq r0, r0, r1, lsl r0
...
00000010 <_start>:
↓ test.c のmain関数は0x20番地
10: f000 f806 bl 20 <main>
00000014 <_loop>:
14: 46c0 nop ; (mov r8, r8)
16: 46c0 nop ; (mov r8, r8)
18: 46c0 nop ; (mov r8, r8)
1a: e7fb b.n 14 <_loop>
1c: 00000000 andeq r0, r0, r0
Disassembly of section .text:
00000020 <main>:
↓ 0xffをr2へセット
20: 22ff movs r2, #255 ; 0xff
↓ 0x2c番地にセットされている値(0xfffc)をr3へセット
22: 4b02 ldr r3, [pc, #8] ; (2c <main+0xc>)
24: 2001 movs r0, #1
↓ r3のアドレス(0xfffc)へr2の値(0xff)をライト
26: 601a str r2, [r3, #0]
28: 4770 bx lr
2a: 46c0 nop ; (mov r8, r8)
2c: 0000fffc strdeq pc, [r0], -ip問題無さそうです。
メモリモデルがロードする test.vmem も問題無さそうです。
@00000000 00 FF 00 00 11 00 00 00 00 00 00 00 00 00 00 00 00 F0 06 F8 C0 46 C0 46 C0 46 FB E7 00 00 00 00 @00000020 FF 22 02 4B 01 20 1A 60 70 47 C0 46 FC FF 00 00
test.vmem をシミュレーション実行と同じディレクトリに置いてシミュレーションを実行します。
終了メッセージが表示されたので成功です。
*** Simulation finish... ***
波形で確認しても0xfffcへライトした3サイクル後にシミュレーションが終了しています。

なお、プログラムを再コンパイルしてtest.vmemを更新た際にVerilogHDLの修正が無い時は ./simv のみの実行で再実行できます。
Cortex-M0 RTLで遊ぶ (1) - Sim環境整備

ARM Cortex-M0のRTLシミュレーション向けモデルが無料公開されていると言うのを最近知りました。
ちょうど今月から始まった新規のお仕事がCortex-M0+を使用する案件だったので、予習目的でダウンロードして遊んでみました。

シミュレーションはRaspberryPi Zero2上でIcarus Verilogで実行しました。
波形確認はGtkWaveをXmingを通してWindowsに表示しています。
Icarus VerilogとGtkWaveは、aptに登録されているのでRaspberryPiからコマンド1つでインストールできました。
$ sudo apt install iverilog $ sudo apt install gtkwave

https://developer.arm.com/downloads/view/AT511
RTLモデルパッケージはARMのサイトで「AT511-Cortex-M0 DesignStart Eval」と言う名称で公開されています。
ダウンロードするにはユーザー登録が必須であり、スマホ認証による電話番号入力など多くの個人情報が必要でした(笑)
ダウンロード後の .tar.gz を解凍すると多くのファイルが生成されますが、M0コアは下記の2ファイルから構成されているようです。
cores/cortexm0_designstart_r2p0/logical/cortexm0_integration/verilog/CORTEXM0INTEGRATION.v cores/cortexm0_designstart_r2p0/logical/cortexm0_integration/verilog/cortexm0ds_logic.v
M0コアはリセット解除後AHBバス経由で0x00番地からプログラムのリードを開始します。
AHBバス接続のメモリモデルもパッケージに含まれておりましたのでこちらも使用しました。
logical/models/memories/cmsdk_ahb_ram_beh.v
M0コアとメモリモデルを接続したテストベンチ(my_test.v)を記述して実行してみます。
リセット解除後、1000サイクルで終了する単純なテストです。
`timescale 1ns/1ns module my_test(); reg clk; reg reset_n; wire [31:0] HADDR; wire [2:0] HSIZE; wire [1:0] HTRANS; wire [31:0] HWDATA; wire HWRITE; wire [31:0] HRDATA; wire HREADY; wire HRESP; CORTEXM0INTEGRATION CORTEXM0INTEGRATION( .FCLK ( clk ) // input ,.SCLK ( clk ) // input ,.HCLK ( clk ) // input ,.DCLK ( 1'b0 ) // input ,.PORESETn ( reset_n ) // input ,.DBGRESETn ( reset_n ) // input ,.HRESETn ( reset_n ) // input ,.SWCLKTCK ( 1'b0 ) // input ,.nTRST ( 1'b0 ) // input ,.HADDR ( HADDR ) // output [31:0] ,.HBURST ( ) // output [2:0] ,.HMASTLOCK ( ) // output ,.HPROT ( ) // output [3:0] ,.HSIZE ( HSIZE ) // output [2:0] ,.HTRANS ( HTRANS ) // output [1:0] ,.HWDATA ( HWDATA ) // output [31:0] ,.HWRITE ( HWRITE ) // output ,.HRDATA ( HRDATA ) // input [31:0] ,.HREADY ( HREADY ) // input ,.HRESP ( HRESP ) // input ,.HMASTER ( ) // output ,.CODENSEQ ( ) // output ,.CODEHINTDE ( ) // output [2:0] ,.SPECHTRANS ( ) // output ,.SWDITMS ( 1'b0 ) // input ,.TDI ( 1'b0 ) // input ,.SWDO ( ) // output ,.SWDOEN ( ) // output ,.TDO ( ) // output ,.nTDOEN ( ) // output ,.DBGRESTART ( 1'b0 ) // input ,.DBGRESTARTED ( ) // output ,.EDBGRQ ( 1'b0 ) // input ,.HALTED ( ) // output ,.NMI ( 1'b0 ) // input ,.IRQ ( {32{1'b0}} ) // input [31:0] ,.TXEV ( ) // output ,.RXEV ( 1'b0 ) // input ,.LOCKUP ( ) // output ,.SYSRESETREQ ( ) // output ,.STCALIB ( {26{1'b0}} ) // input [25:0] ,.STCLKEN ( 1'b0 ) // input ,.IRQLATENCY ( {8{1'b0}} ) // input [7:0] ,.ECOREVNUM ( {28{1'b0}} ) // input [27:0] ,.GATEHCLK ( ) // output ,.SLEEPING ( ) // output ,.SLEEPDEEP ( ) // output ,.WAKEUP ( ) // output ,.WICSENSE ( ) // output [33:0] ,.SLEEPHOLDREQn ( 1'b1 ) // input ,.SLEEPHOLDACKn ( ) // output ,.WICENREQ ( 1'b0 ) // input ,.WICENACK ( ) // output ,.CDBGPWRUPREQ ( ) // output ,.CDBGPWRUPACK ( 1'b0 ) // input ,.SE ( 1'b0 ) // input ,.RSTBYPASS ( 1'b0 ) // input ); cmsdk_ahb_ram_beh cmsdk_ahb_ram_beh( .HCLK ( clk ) // input ,.HRESETn ( reset_n ) // input ,.HSEL ( 1'b1 ) // input ,.HADDR ( HADDR[15:0] ) // input [15:0] ,.HTRANS ( HTRANS ) // input [1:0] ,.HSIZE ( HSIZE ) // input [2:0] ,.HWRITE ( HWRITE ) // input ,.HWDATA ( HWDATA ) // input [31:0] ,.HREADY ( 1'b1 ) // input ,.HREADYOUT ( HREADY ) // output ,.HRDATA ( HRDATA ) // output [31:0] ,.HRESP ( HRESP ) // output ); always #100 clk = ~clk; initial begin clk = 1'b0; reset_n = 1'b0; #250 reset_n = 1'b1; repeat(1000) @(posedge clk); $display("*** Simulation finish... ***"); $finish; end initial begin $dumpvars(); end endmodule
メモリモデルはデフォルトの構成ではアドレス16bitバスになっているので、M0コアが出力するHADDRの下位16bitを接続しています。
また、AHBスレーブはメモリモデルだけなのでHSELとHREADY(input)はHigh固定にしています。
cmsdk_ahb_ram_beh cmsdk_ahb_ram_beh( .HCLK ( clk ) // input ,.HRESETn ( reset_n ) // input ,.HSEL ( 1'b1 ) // input ,.HADDR ( HADDR[15:0] ) // input [15:0]
Makefileに実行コマンドを書いて make で実行開始です。
RTL = AT510-MN-80001-r2p0-00rel0/cores/cortexm0_designstart_r2p0/logical/cortexm0_integration/verilog/CORTEXM0INTEGRATION.v \ AT510-MN-80001-r2p0-00rel0/cores/cortexm0_designstart_r2p0/logical/cortexm0_integration/verilog/cortexm0ds_logic.v \ AT510-MN-80001-r2p0-00rel0/logical/models/memories/cmsdk_ahb_ram_beh.v all: iverilog my_test.v $(RTL) -o simv ./simv
エラー無く終了すると波形ファイルの dump.vcd が生成されていますので、GtkWaveから開いて確認します。
文字が小さくて非常に見難いですが、M0コアからAHBリードを開始しているようなので成功です。

次回はプログラムをメモリモデルの初期値に展開して動かしてみようと思います。
MicroPythonで遊ぶ (5) - SDカードシールド(SDカード)

SDカードへライトしてみます。
RTC同様にSDカードドライバをインポートすることで簡単にライトできました。

SDCardクラスのコンストラクタに、I/FのSPI1とCSのPB6を指定します。
from machine import SPI import pyb, sdcard, os sd = sdcard.SDCard(SPI(1), pyb.Pin('PB6')) pyb.mount(sd, '/sd') file = open('/sd/test.txt', 'w') file.write('test 001\n') file.write('test 002\n') file.write('test 003\n') file.write('test 004\n') file.write('test 005\n') file.close() print("Pass")
PCで確認するとライトできていました。

なお、ファイルへ追記するにはopen関数の第二引数を 'a' にします。
MicroPythonで遊ぶ (4) - SDカードシールド(RTC)

Arduino用のSDカードシールドをMicroPythonで使ってみようと思います。
Amazonで購入しました。
https://www.amazon.co.jp/dp/B07GDZ9XLN/
ボードは NUCLEO-F446RE を使用しています。
Arduino互換のピン配置なのでピッタリ収まります。
SDカードへアクセスする前にRTCを動かしてみます。
RTCクラスをインポートして簡単に表示できました。
from machine import RTC import utime rtc = RTC() while True: dt = rtc.datetime() print(dt) utime.sleep_ms(1000)

何も設定していないのに現在時刻・・。
PCの現在時刻を変更したらRTCの時刻も変更されました。

この辺がどういう仕組みなのかは良くわかっていませんが、シールド上のIC「DS1307」を使用しているわけでは無いようです。
秋月電子にあるデータシートから調べるとDS1307へアクセスするI2CのSCL, SDAは、F446REのPC0, PC1に接続されていました。


DS1307用のライブラリをダウンロードしてインストールします。
https://github.com/mcauser/micropython-tinyrtc-i2c

200ミリ毎にDS1307から時刻を読み取りOLEDへ表示してみました。
F446REのPC0, PC1はI2C用のピンでは無いので、SoftI2Cを使ってDS1307へアクセスしています。
from machine import I2C, SoftI2C, RTC import ds1307 import ssd1306 import utime i2c_oled = I2C(1, freq=100000) # Ch.1 SCL=PB8, SDA=PB9 i2c_rtc = SoftI2C(scl='PC0', sda='PC1', freq=100000) display = ssd1306.SSD1306_I2C(128, 64, i2c_oled) display.init_display() display.write_cmd(0xa0) # 上下通常 display.write_cmd(0xc0) # 左右通常 rtc = RTC() dt = rtc.datetime() rtc = ds1307.DS1307(i2c_rtc) rtc.datetime(dt) while True: display.fill(0) dt = rtc.datetime() p0 = "{:d}/{:d}/{:d}".format(dt[0], dt[1], dt[2]) p1 = "{:02d}:{:02d}:{:02d}".format(dt[4], dt[5], dt[6]) display.text(p0, 0, 0, True) display.text(p1, 16, 10, True) display.show() utime.sleep_ms(200)
下記のコードでPCの時刻を読み取り、DS1307へ設定しています。
rtc = RTC() dt = rtc.datetime() rtc = ds1307.DS1307(i2c_rtc) rtc.datetime(dt)
正しく表示できました。

スタンドアロンでも試してみます。
DS1307への書き込みをコメントアウトして、main.pyへ上記のコードを貼り付けて保存します。
rtc = RTC()
dt = rtc.datetime()
rtc = ds1307.DS1307(i2c_rtc)
#rtc.datetime(dt)

バックアップ電池(CR1220)をセットしているので、PCと切断してUSB電源に繋ぐと現在時刻が表示されました。
MicroPythonで遊ぶ (3) - ADT7410 & SHT35

MicroPythonからI2C接続の2つの温度センサーを動かしてみました。
ボードは NUCLEO-F446RE を使用しています。
2つの温度センサーは秋月電子で購入したADT7410とSHT35です。
- ADT7410(600円)
- 16Bit、One-Shot Modeで動作させています。

from machine import I2C import utime i2c = I2C(1, freq=100000) # Ch.1 SCL=PB8, SDA=PB9 dev_addr = i2c.scan() addr = dev_addr[0] print("Addr:" + hex(addr)) wd = bytearray([0x03, 0b10100000]) # Configuration Register: 16bit one-shot while True: i2c.writeto(addr, wd) utime.sleep_ms(1000) rd = i2c.readfrom_mem(addr, 0x00, 2) tmp = (rd[0] << 8) | rd[1] if (tmp & 0x8000)==0x8000: tmp = (tmp - 65536) / 128 else: tmp = tmp / 128 print("{:.3f}".format(tmp))


from machine import I2C import utime def crc8(data): init = 0x31 crc = 0xff for i in range(2): crc ^= data[i] for j in range(8): if(crc & 0x80): crc = (crc << 1) ^ init else: crc = (crc << 1) crc &= 0xff return crc i2c = I2C(1, freq=100000) # Ch.1 SCL=PB8, SDA=PB9 dev_addr = i2c.scan() addr = dev_addr[0] print("Addr:" + hex(addr)) wd = bytearray([0x2C, 0x06]) while True: i2c.writeto(addr, wd) utime.sleep_ms(300) rd = i2c.readfrom(addr, 6) tmp = -45 + 175 * (((rd[0] << 8) | rd[1]) / 65535) hum = 100 * ((rd[3] << 8) | rd[4]) / 65535 crc_tmp = crc8([rd[0], rd[1]]) crc_hum = crc8([rd[3], rd[4]]) if crc_tmp==rd[2] and crc_hum==rd[5]: cc = '' else: cc = '[CRC Fail]' print("{:.3f} {:.3f} {:s}".format(tmp, hum, cc)) utime.sleep_ms(1000)


2つ同時に測定してみました。
from machine import I2C import utime def get_adt7410(): addr = 0x48 wd = bytearray([0x03, 0b10100000]) i2c.writeto(addr, wd) utime.sleep_ms(1000) rd = i2c.readfrom_mem(addr, 0x00, 2) tmp = (rd[0] << 8) | rd[1] if (tmp & 0x8000)==0x8000: tmp = (tmp - 65536) / 128 else: tmp = tmp / 128 return(tmp) def get_sht35(): addr = 0x45 wd = bytearray([0x2C, 0x06]) i2c.writeto(addr, wd) utime.sleep_ms(300) rd = i2c.readfrom(addr, 6) tmp = -45 + 175 * (((rd[0] << 8) | rd[1]) / 65535) return(tmp) i2c = I2C(1, freq=100000) # Ch.1 SCL=PB8, SDA=PB9 while True: tmp_adt7410 = get_adt7410() tmp_sht35 = get_sht35() print("{:.3f} {:.3f}".format(tmp_adt7410, tmp_sht35)) utime.sleep_ms(1000)

左がADT7410、右がSHT35の結果です。
ほぼ同じ結果となりました。
MicroPythonで遊ぶ (2) - SSD1306

MicroPythonからI2C接続のOLEDを動かしてみました。
ボードは NUCLEO-F446RE を使用しています。
- MicroPythonのサイトからソースコードをダウンロードして解凍
- https://micropython.org/download/
- (記事作成時点では micropython-1.19.1.zip が最新でした)

- ボード内にlibディレクトリが無い時はThonnyから作成

- ssd1306.pyをlibディレクトリへインストール

- フレームバッファクラスのドキュメントを参考にメソッドを使用して描画を行います。
from machine import I2C import ssd1306 i2c = I2C(1, freq=100000) # Ch.1 SCL=PB8, SDA=PB9 addr = i2c.scan() display = ssd1306.SSD1306_I2C(128, 64, i2c) display.init_display() display.write_cmd(0xa0) # 上下通常 display.write_cmd(0xc0) # 左右通常 display.text('LSI Jiu-Jitsu', 10, 14, True) display.rect(0, 4, 128, 28, True) display.rect(4, 8, 120, 20, True) display.text('Addr:' + hex(addr[0]), 56, 56, True) display.show()
SSD1306へコマンド送信を行うにはssd1306.pyのwrite_cmdメソッドを使用します。
[参考記事]
