LSI Jiu-Jitsu

電子工作とブラジリアン柔術

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番地へライトを行うのでシミュレーションが終了する予定です。

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

コンパイル→リンク後、objcopyでelfファイルを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サイクル後にシミュレーションが終了しています。

なお、プログラムを修正してコンパイルした後に、Verilogの修正が無い時は ./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つの温度センサーは秋月電子で購入したADT7410SHT35です。

  • 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))


  • SHT35(1,380円)
    • リード方法は秋月電子の商品ページのサンプルスケッチを参考にしました。
    • 温度と湿度を表示しています。
    • CRC結果が不一致の際はエラーメッセージを表示します。

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 を使用しています。




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メソッドを使用します。


[参考記事]

MicroPythonで遊ぶ (1) - F446RE

Pythonをキチンと勉強しようと思い立ちました。
そこで、組み込みに特化したMicroPythonを使ってみようと考えたところ、タイミング良くインターフェースで記事が特集されていました。
手持ちのNUCLEO-F446REボードのセットアップ手順も書かれていたので、さっそく記事を参考にセットアップとLEDチカチカを試してみました。

  • PCとF446REを接続
  • STM32 ST-LINK Utilityを起動してメニューの「Target」から「Connect」で接続
    • (接続エラーが発生する時は「Target」→「Settings」のSWD Frequencyを下げると安定する場合があるようです)
  • 「Target」→「Program」からMicroPythonのファームウェアを開いて「Start」をクリックして書き込み開始

こちらのコードを実行すると0.5秒単位でF446RE上のLD2が点滅します。

from machine import Pin
import utime

led = Pin('PA5', Pin.OUT_PP)

while True:
  led.value(1)
  utime.sleep_ms(500)
  led.value(0)
  utime.sleep_ms(500)