chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

STM32C0でUART

背景:STM32C0をハンドアセンブルで動かしたい。動かしたいとは、クロスコンパイラを必要とせず、スタンドアローンで対話的にプログラミングできるようにしたい*1
取り組み:Lチカができたので、次はUARTを動かしてみる。UARTでホストPCとやり取りができたら、簡単なモニタ機能を作ることができるはず
結論:GDBからレジスタを直接操作したレベルだが、5番ピンからシリアルデータを送信できることまでは確認した(ボーレートとクロック分周の計算式が分かっておらず)
詳細:

UARTで文字を送信するにはおおよそ以下のプログラムになる(と理解)

  1. 5番pin, 6番pinがPA11, PA12に割り当てられているが、PA11,PA12ではUSARTが使えないので、PA9,PA10に再割り当てする(SYSCFG_CFGR1で設定)
  2. GPIOのAlternate FunctionでPA9/PA10をUSART1に切り替え
  3. 転送仕様を(8bit 1 stopbit, non parity)に設定 (USART_CR1/USART_CR2)
  4. ボーレートを設定(115200 , USART_BBRに0x1a1を設定)
  5. FIFOを有効化 (USART_CR1)
  6. UE(USART Enable)を1
  7. TE(Transfer Enable)を1
  8. USART_TDRに送信データ書き込み
  9. TXFNF(TxFIFO Not Full)を見て、1ならUSART_TDRに送信データ書き込み
  10. 全てのデータをUSART_TDRに書き込んだら TC(Transfer Complete)まで待つ
  11. 送信終了

■追記
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)

*1:セルフホスティング、セルフプログラミングと言われるらしい