背景:STM32C0をハンドアセンブルで動かしたい。動かしたいとは、クロスコンパイラを必要とせず、スタンドアローンで対話的にプログラミングできるようにしたい*1
取り組み:Lチカができたので、次はUARTを動かしてみる。UARTでホストPCとやり取りができたら、簡単なモニタ機能を作ることができるはず
結論:GDBからレジスタを直接操作したレベルだが、5番ピンからシリアルデータを送信できることまでは確認した(ボーレートとクロック分周の計算式が分かっておらず)
詳細:
UARTで文字を送信するにはおおよそ以下のプログラムになる(と理解)
- 5番pin, 6番pinがPA11, PA12に割り当てられているが、PA11,PA12ではUSARTが使えないので、PA9,PA10に再割り当てする(SYSCFG_CFGR1で設定)
- GPIOのAlternate FunctionでPA9/PA10をUSART1に切り替え
- 転送仕様を(8bit 1 stopbit, non parity)に設定 (USART_CR1/USART_CR2)
- ボーレートを設定(115200 , USART_BBRに0x1a1を設定)
- FIFOを有効化 (USART_CR1)
- UE(USART Enable)を1
- TE(Transfer Enable)を1
- USART_TDRに送信データ書き込み
- TXFNF(TxFIFO Not Full)を見て、1ならUSART_TDRに送信データ書き込み
- 全てのデータをUSART_TDRに書き込んだら TC(Transfer Complete)まで待つ
- 送信終了
■追記
PA11をPA9に割り当て、PA12をPA10に割り当てるには、SYSCFG_CFGR1レジスタのPA11_RMP, PA12_RMPを設定する。1に設定するとRemapが有効となり、PA9,PA10に割り当てられる。アドレスは、SYSCFG:0x4001_0000で、 SYSCFG_CFGR1はoffset:0である。
GDBでダンプしてみたら、0x18であった。
(gdb) x 0x40010000 0x40010000: 0x00000018
0x18とは、すでにremapされていて、PA9, PA10が割り当てられていると。。
GPIOAのMODE設定はどうなっているのかを調べる
(gdb) x 0x50000000 0x50000000: 0x2bebffff
この値が合っているのかちょっと自信がないが、そうだとすると、、、
PA9,PA10はすでにAF(Alternate Function)にわりあたっていると。
どのAFかをCPIOA_AFRHで確認する
(gdb) x 0x50000024 0x50000024: 0x00000110
上記の通り、PA9, PA10のAFは1番で、1番はUSART1であった。よって、GPIOのリマップ等せずとも、誰が設定したのか分からないが、すでにUART1で通信できるように設定済みなのであった。リセット値と違っているのでなぜこのようにリマップが済んでいるのか分からず。
あとはUART1のレジスタを操作するだけで、シリアル通信ができるはず。。。
USART_CR1/CR2の値
(gdb) x 0x40013800 0x40013800: 0x0000140d (gdb) x 0x40013804 0x40013804: 0x00500000
- M0/M1より、1 start bit, 9 Data bits, n Stop bit
- PCEより、enable parity
- STOPより、1stop bit
- Auto Baud Rate mode / 0x55 frame detection
本当は、1start , 8 data bits , 1stop bit, non parityにしたい。baudrateも固定にしたい。
(gdb) x 0x4001380c 0x4001380c: 0x00000000
USART_BRRは0.... Auto detectionだから0になっていると推測
USARTは通信につかっていないが、USART_ISRを確認
(gdb) x 0x4001381c 0x4001381c: 0x006000d0
Statusレジスタは上記の通りで、TC(Transmission Complete)が1になっているので、送り終わったつもりの様である。ただ、TXFE/RXFEが1になっていないので、FIFOには残っている?あるいはFIFOを有効化していない?
USART_CR1を見返すと、FIFOENが0 (FIFO disable)であった。だから、FIFO Empty/FIFO Fulは1にはならず
まずはUSART1を停止にする
(gdb) x 0x40013800 0x40013800: 0x0000140d (gdb) set {int} 0x40013800 = 0 (gdb) x 0x40013800 0x40013800: 0x00001400
USARTが稼働中は、通信仕様が変更できないようであった。
次に、通信仕様関連のフラグ類をクリア
(gdb) set {int} 0x40013800 = 0 (gdb) x 0x40013800 0x40013800: 0x00000000
次にstop bitを決めているUSART_CR2をクリア
(gdb) x 0x40013804 0x40013804: 0x00500000 (gdb) set {int} 0x40013804 = 0 (gdb) x 0x40013804 0x40013804: 0x00000000
これで、8bit長、1stop bit、ノンパリになった。
次にボーレートを設定、115200で通信したいので、USART_BBR(offset 0c)に0x1a1を設定する
(gdb) set {int} 0x4001380c = 0x1a1 (gdb) x 0x4001380c 0x4001380c: 0x000001a1
FIFOを使う設定に変更
0x40013804: 0x00000000 (gdb) set {int} 0x40013800 = 0x20000000 (gdb) x 0x40013800 0x40013800: 0x20000000
Enableに設定してUSARTが動くようにする
(gdb) set {int} 0x40013800=0x2000000d (gdb) x 0x40013800 0x40013800: 0x2000000d
ステータスレジスタを確認
(gdb) x 0x4001381c 0x4001381c: 0x08e000d0
おおよそ、TxFIFOがEmptyだと。
USART_TDR (offset 0x28)に値を書くと送信されるはず
一発勝負なので、5番ピンにオシロもつないでおく。
0x4001381c: 0x08e000d0 (gdb) set {int} 0x40013828=0xa9 (gdb) x 0x40013828 0x40013828: 0x00000000
オシロで観測されず(まぁそんなもんでしょう)。
ステータスレジスタを見てみるが送信した形跡もなし
(gdb) x 0x4001381c 0x4001381c: 0x08e000d0 (gdb) x 0x40013800 0x40013800: 0x2000000d
再確認すると、プローブを設定したピンを間違っていた。正しく計測すると以下の波形が取れた。115200bpsにしているつもりが、半分の58.82kbpsのようであった。クロックのdevide設定が間違っている。
8倍サンプリングか16倍サンプリングかで投入するクロックが変わるのだが、サンプリングレートとdevide設定が合っていないと思われる。場当たり的に再計算すると、USART_BBR(offset 0c)に0xd0を設定すると、115200bpsで通信できるはず。
試しにボーレートを変更すると、停止せずとも変更できるようであった。
(gdb) x 0x4001380c 0x4001380c: 0x000001a1 (gdb) set {int} 0x4001380c=0xd0 (gdb) x 0x4001380c 0x4001380c: 0x000000d0
再度計測すると、119,000bpsとなった。
なぜずれているのか。分周をこじつけで合わせると以下の値になりそうだが
>>> (48 * 1000 * 1000)/0xca/2 118811.88118811882
ここはまたゆっくり考えることにする。
■参考URL
昔にRustでUARTを操作していたようだった(忘れていた)
STM32/RustでHALを使わずレジスタを直接操作してUART send/receive - chakokuのブログ(rev4)
メモ:競技スタートの合図例(プっプっプっピー)
import time import machine p0 = machine.Pin(0) pwm0 = machine.PWM(p0) pwm0.freq(440) for _ in range(3): pwm0.duty_u16(0x8ff) time.sleep(0.3) pwm0.duty_u16(0x0) time.sleep(0.8) pwm0.freq(880) pwm0.duty_u16(0xfff) time.sleep(0.4) pwm0.duty_u16(0x8ff) time.sleep(0.2) pwm0.duty_u16(0x1ff) time.sleep(0.2) pwm0.duty_u16(0xff) time.sleep(0.1) pwm0.duty_u16(0x0)