2018年12月22日

nim言語でNucleo-F030R8を動かした時のメモ 2018/12

nim言語でNucleo-F030R8を動かした時のメモ 2018/12
はじまり
    源流
    nimについて (https://nim-lang.org/)
STM32F3-DiscoveryでLEDチカチカ
Nucleo-F030R8で動かす
STM提供のレジスタ定義ファイルをc2nimで変換して使う
    c2nimのインストール
    変換のポイント:
    c2nimで変換中:
    実際の使用時:
nim言語で書いたSTM32F0用設定コード
    コンパイルに必要なもの
ハマりポイント
    Makefile:
    SPIとNSS設定
    volatile効いてない !? orz
main.nim
ファイル構成
ダウンロード
その後


はじまり
源流
    基本的に、こちらのかたのブログ記事に触発されて、いろいろやってみた記録。感謝。
         なんとなく活動記録。
        電子工作・プログラミングの備忘録
        NimでLチカ on STM32 (1)
        http://blueeyes.sakura.ne.jp/2018/05/22/1243/
        NimでLチカ on STM32 (2)
        http://blueeyes.sakura.ne.jp/2018/05/28/1256/
        Githubオリジナルソース
        https://github.com/shima-529/STM32OnNim

nimについて (https://nim-lang.org/)
    現在の最新版。
    nim Nim Compiler Version 0.19.0

    rust言語に挫折してから。。。時は流れた。。。
    nim言語を発見して勉強用にライフゲームなどを作っていたところ、
    上記ブログに遭遇したのだった。

STM32F3-DiscoveryでLEDチカチカ
最初は、 https://github.com/shima-529/STM32OnNim
このソースを頂いてSTM32F3-Discovery用に変更してLEDチカチカさせました。
結局、変更点はLEDポートの変更だけでした。
コアが同じなのでリンカスクリプトもベクタファイルもLED点灯程度なら無変更でOKでした。(^^)/
https://www.st.com/ja/evaluation-tools/stm32f3discovery.html


Nucleo-F030R8で動かす
STM32F3-Discoveryで簡単に動いたのに気をよくして、
以前よく遊んだmbedボード「Nucleo-F030R8」で動かそうと決意しました。
NucleoボードのほうがUSB-CDC(UART)が付いているので便利。
以下、Nucleo-F030R8で動くまでの記録を記述。

https://os.mbed.com/platforms/ST-Nucleo-F030R8/
 

STM提供のレジスタ定義ファイルをc2nimで変換して使う
使う分のレジスタ構造体だけを定義していく手もあるけど、
STM32CubeMXについてくるヘッダーファイルをc2nimで変換してみました。
変換したファイルは「stm32f030x8.h」、「core_cm0.h」、
あと 「cmsis_gcc.h」 から「wfi」とかを少し持ってきました。
変換のポイント:
    nim言語:STM32のヘッダファイルをc2nimした時のメモ 2018

nim言語で書いたSTM32F0用設定コード
基本的にレジスタをビット単位で直たたき、"Reset初期値のままでいける場合は
あえて変更しない"という方針。(^^;
(1) PLLで48MHzまでクロックアップ。
(2) Systickタイマー1msec又は10msec周期で割り込みハンドラ起動。
(3) USART2(PA3,PA3)を115200bpsで設定。Nucleo-F030R8の標準UARTポート。
(4) TIM3。PWM用の設定。
(5) PWM周期で割り込みハンドラ起動。
(6) PWM出力を4ch分設定。固定デューティで出力。
(7) SPI1をSCK=12MHzで設定。8bit/16bitポーリング通信。
    PB6はCS(チップセレクト)で使用。
(8) printf()関数、呼び出し可能に設定。
    syscalls.cを追加。
(9) ChaNさんのxprintf()関数、呼び出し設定。
    軽量なので、こちらをデバッグ用にメインで使用。
(10) コードサイズを小さくしたいのでgccのオプションは「-Os」。

コンパイルに必要なもの
Winodws上でmakeしますが、Linux上でもそのままmakeできます。
Windowsの場合、下記(1)〜(3)のコマンドへ実行パスを通しておく必要があります。

(1) nimコンパイラ。上記の本家から取ってきてインストールしておきます。
    現時点では以下のバージョン
    nim Nim Compiler Version 0.19.0
    少なくともこれ以降のバージョンが良い。
    Ubuntu系の場合、「apt install nim 」だと古い可能性があるので、
    $nim -vでバージョンを確認します。
    以下から Version 0.19.0のdebファイルを取得して
    https://packages.debian.org/sid/i386/nim/download
    $ sudo dpkg -i nim_0.19.0-1.deb
    で最新版がインストールできます。
(2) gccは、arm-none-eabi-gcc: https://launchpad.net/gcc-arm-embedded ここから
    最新版をもらってきてインストールしておきます。 (最新じゃなくても別にいいけど)
(3) make,rm コマンドが必要。
(4) コンパイルはMakefileがあるトップフォルダに移動、MS-DOSコマンドラインで
    $ make
    成功すれば「nucleo_f030r8.hex」ができているので、
    STM32CubeProgrammer(注2)やOpenOCDなどで書き込みます。
    自分の場合はST-Link Utilityに付属の「ST-LINK_CLI.exe」で書き込んでいます。(注2)
    Makefile内の以下の行の「#」をカットすればmake後、自動でFLASHに書き込まれます。
    #   "d:\STM32-ST-LINK-Utility\ST-LINK Utility\ST-LINK_CLI.exe" -c SWD -P $(TARGET).hex -Rst
    (フォルダ名は書き換える必要あり)
(5) コンパイルがこける場合
    もう一度、”make”でうまくいく場合があります。
    こける原因は今のところ、nimコンパイル側にあると踏んでいます。

ハマりポイント
苦労した点、という意味。
Makefile:
    nimとgccのコンパイルを「Make 一発」できる様に変更。
    これがなんとも悶絶した。orz
    makeの実行直後には、まだnimが生成した*.cファイル群は存在しないが、
    その存在しないファイルに対して依存関係を記述しないといけない、
    えっ? パニック。(^^;
    で、なんとかやっつけた。(オイ
    ただし、状況依存で途中で止まる場合があるので、その時はもう一度
    makeすればOK.

SPIとNSS設定
    これと次の問題が同時に発生して地獄に落ちました。orz
    STMのF0マイコンでNSSピンの制御が可能なものは、「NSSピンを使わない単純なマスター通信」を
    するために、以下のどちらかの設定が必ず必要、
    (a)SPI_CR1_SSM = 1 且つ SPI_CR1_SSI = 1
     または、
    (b) CR2_SSOE = 1
    上のどちらかを設定しないと、SPIはピクリとも動かない。
    orz orz orz

    STM32CubeMXが生成した初期化コードだと以下の行が該当部分。
    SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
    これを基に、
    LL_SPI_Init(SPI1, &SPI_InitStruct)内でこっそり上記のビットが設定されている。
    こっそり !?
    mbedやSTM32CubeMXに頼りすぎた呪いだ。
    呪い !?

volatile効いてない !? orz
    (i)SPI通信がさっぱり動かないので、SPIのレジスタを色々変えてみたり、
       設定を見直してみたりしたけどダメ。
    (ii) printfでSPI_CR1等のレジスタ値を見ると設定したはずの値が返ってこないし、
         設定値を変えるたびに、その値も微妙に変化するも規則性はないようだ。orz
    (iii) な、なんか壊れてる。。。 orz (i) に戻る。
    というループから「break」できなくて、
    orz orz orz だった。
    結局、
    逆アセンブラリストを順に確認していって、最後の最後に送信レジスタに値を設定するところ、
    よく見ると、
    SPIを有効にする前に、送信レジスタ(SPI_DR)に送信データが入り、
    その後、SPIを有効にしていた。    じゅ、順番が逆!
    アセンブラレベルだと順番が逆になっていた。orz
    で、ようやく
    実はvolatileが効いてないことがわかる。

    volatileについてはvolatileLoad/volatileStoreでケリがついていると思ったが
    微妙なようだ。(全部効いてないということではない感じ、ではある)
    正当な対処方法がわからないのでvolatile{Load,Store}関数呼び出しの最後に
    「nop()挿入」でやっつけた。(注1)

    ということで、SPIが意図どおり動くようになって、かなりすっきりしました。
    (^^)/

犯人は自分だった orz
    その後、全てうまく動いたので、このvolatile問題を再調査してみたところ、
    実は、自分で追加したコードが非volatileになっていたことがわかったのだ。
    orz orz orz
    話すと長いんだけどnimコンパイラのバグを避けるためのコードを入れたら、
    それがバグっていたという話。 orz

nimのvolatile関数について
    マイコンのペリフェラルレジスタ(volatileアクセスで)を8bit/16bitのデータ長を明示的に
    指定してアクセスする場合に、volatileStore/volatileLoad関数が不正なコードを
    吐き出すと思っていたが、以下の関数を追加、修正することで回避した。
    volatileStore/volatileLoad関数を使う自分の理解不足が招いた不具合でした。 orz orz orz
template ld8*[T: SomeInteger](reg: T): uint8 =
  volatile_Load8(cast[ptr uint8](reg.addr) )

template ld16*[T: SomeInteger](reg: T): uint16 =
  volatile_Load16(cast[ptr uint16](reg.addr) )

template st*[T,U: SomeInteger](reg: T, val: U) =
  volatileStore(cast[ptr U](reg.addr), val)
問題は解決したので上で入れた「nop」は全部カットしました。 main.nim
# For NUCLEO-F030R8
import startup
import stm32f030x8
import systick, uart, pwm, gpio, sys, spi

#proc printf(formatstr: cstring){.header: "<stdio.h>", importc: "printf", varargs.}
proc xprintf(formatstr: cstring){.header: "xprintf.h", importc: "xprintf", varargs,used.}

proc IO_Test(spi:auto){.used.} =
    var x = 0
    var sdata :uint8 = 0
    while true:
        # Core register viewing
        xprintf("\n%04d",x)
        xprintf("\nSPI.CR1 = %08X(%X)" ,SPI1.CR1,cast[int](SPI1.CR1.addr) )
        xprintf("\nSPI.CR2 = %08X(%X)" ,SPI1.CR2,cast[int](SPI1.CR2.addr) )
        xprintf("\nRCC.AHBENR  = %08X" ,RCC.AHBENR)
        xprintf("\nRCC.APB1ENR = %08X" ,RCC.APB1ENR)
        xprintf("\nRCC.APB2ENR = %08X" ,RCC.APB2ENR)
        xprintf("\nGPIOA.MODER = %08X" ,GPIOA.MODER)
        xprintf("\nGPIOA.AFR[0]= %08X" ,GPIOA.AFR[0])
        xprintf("\nGPIOA.AFR[1]= %08X" ,GPIOA.AFR[1])
        xprintf("\nGPIOB.MODER = %08X" ,GPIOB.MODER)
        xprintf("\nGPIOB.AFR[0]= %08X" ,GPIOB.AFR[0])
        xprintf("\nGPIOB.AFR[1]= %08X" ,GPIOB.AFR[1])
        xprintf("\n")
        inc(x)
        setTickCounter(200)
        while getTickCounter() > 0: # wait 10msec
            # SPI loop back test
            cs_on()
            var n = spi.send(sdata)
            n = spi.send(n)
            n = spi.send(n)
            n = spi.send(n)
            cs_off()
            if sdata != n:
                xprintf("\nSPI data EEROR!")
                while true:
                    discard
            inc(sdata)


block: # main function
    initSysClock48mhz()
    initGPIO()
    # Start systick timer and interrupt
    discard SysTick_Config(SYSTEM_CLOCK div 1000) # 1msec (1000Hz)
    initSerial(USART2)
    initSPI(SPI1)
    spiEnable()
    initPwm()
    pwm_period_timer_start()

    # PWM tset  Duty setting
    pwm_dutyR_hi( 512)
    pwm_dutyL_hi( 512 shr 1)
    #
    pwm_dutyR_low(512 shr 2)
    pwm_dutyL_low(512 shr 3)

    IO_Test(SPI1) 
上のxprintf()のUART出力(115200bps)は以下、
1767
SPI.CR1 = 0000034C(40013000)
SPI.CR2 = 00001700(40013004)
RCC.AHBENR  = 000E0014
RCC.APB1ENR = 00020002
RCC.APB2ENR = 00001000
GPIOA.MODER = 2800A8A0
GPIOA.AFR[0]= 00001100
GPIOA.AFR[1]= 00000000
GPIOB.MODER = 00001A00
GPIOB.AFR[0]= 00110000
GPIOB.AFR[1]= 00000000
ファイル構成
Top
│  LICENSE
│  Makefile
│  README.md
│
└─src
    │  gpio.nim
    │  main.nim
    │  panicoverride.nim
    │  port_setting.txt
    │  pwm.nim
    │  spi.nim
    │  sys.nim
    │  uart.nim
    │
    ├─f0
    │      STM32F030R8Tx_FLASH.ld
    │      stm32f030x8.nim
    │      vector_f030.c
    │
    ├─lib
    │  └─xprintf
    │          xprintf.c
    │          xprintf.h
    │
    ├─nimcache
    │      dummy.txt
    │
    └─sys
            core_cm0.inc
            nimbase.h
            reg_utils.inc
            startup.nim
            syscalls.c
            systick.nim
ダウンロード 準備中 その後 上のソフトをベースに「SDカードをSPIモードで初期化」まで成功したので、 難関は切り抜けたという感じ。 (^^)/ (注1) {.volatile.}プラグマを使ってみると、ゾッとする様な「ゾッとしないコード」を吐いてきたので 使うのやめました。orz 経緯的には、"{.volatile.}だけだと組み込み用では使えない"、じゃあ ってことでvolatileLoad/volatileSoreが作られたらしい。 {.volatile.}は変数の最適化を停止させる時に使用します。 (注2) STM32CubeProgrammerにもコマンドライン版がある様なので、ちょっと使ってみる。


posted by Copyright (C) avrin All Rights Reserved. at 19:49| Comment(0) | nim言語 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。