2017年12月17日

NUCLEO-F767ZIでST-Link経由のシリアル通信をprintfで使う

NUCLEOはUSB経由ST-Linkでボード本体と接続されていますが、同時にVitual Portという機能でST-Link上でシリアルポートを使うことができます。
Arduinoでは「Serial」でホスト側とUART通信を行えるようになっていますが、Virtal Portが使えれば、STM32用のソフトウェアを開発するときもArduinoのSerialと同様に使えて便利そうです。

この記事は、Virtal Portを使用するプロジェクトをCubeMXで作成する方法のメモです。
こちらにSTM32F7 Discovery用の解りやすいチュートリアルがYouTubeありましたので、この記事はほぼこのビデオそのままの内容です。


(1)CubeMXで開発ボードを選択したら、まずPinout→Clear Pinoutsで全てリセットします。
uart01.png

次に、先にプロジェクトの設定をしておきます。
Project→Settingsで設定ウインドウを開き、
・プロジェクト名
・Toolchain/IDE
を設定します。

(2)シリアルポートの設定をしていきます。
UM1974の26ページに、ST-LinkにはUSART3がPD8とPD9で接続されていることが記載されています。
uart02.png

そこで、CubeMXでUSART3を使えるように設定します。

このとき、USART3を有効にしてはいけません。USART3という「機能」をどのピンに割り当てるかは可変なのですが、初期状態からUSART3を有効にするとPD8とPD9ではない別のピンに自動でこの機能が割り当てられてしまいます。

まずPD8とPD9を探します。
CubeMxにはピンを検索する機能がありますので、そこに「PD8」と入力します。
uart03.png

すると、ピン配置図の上で該当のピンが分かりますので、そのピンをクリックします。
uart04.png

そのピンに指定できる機能(Alternate Function)のリストが出ますので、その中から「USART3_TX」を選択します。
uart05.png

PD9についても同様に「USART3_RX」を指定します。最終的に図のようにPD8とPD9の機能が指定できればOKです。
uart06.png

ちなみに、どのピンにどんなAlternate Functionが割り当てられているのかは、CPUのデータシートを見ると一覧表があります。データシートは何をするにも必携です。

ここでようやくUSART3の機能を設定します。
ModeはAsynchronousにします。Hardware Flow ControlはDisabledのままです。
uart07.png

SYSセクションでは、DebugをSerial Wireにしておきます。
uart08.png

(3)Clock Configurationタブに移ります。
uart09.png

ここではHCLKを最高値(NUCLEO-F767ZIでは216MHz)に設定します。
uart10.png

以下のようなダイアログが出た場合にはOKを押してください。
uart11.png

(4)Configurationタブに移り、UARTの設定をします。
uart12.png

「USART3」のボックスをクリックすると設定ウインドウが開きます。
Word Lengthは8bitsにしておくのが良いでしょう。
uart13.png


(5)最後にProject→Generate Codeでスケルトンコードが生成されます。
uart14.png



シリアルポートに読み書きするには、
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

を使います。
第1引数はUARTを表す構造体です。生成されたスケルトンコードでは、UART構造体が
UART_HandleTypeDef huart3;
として定義されています。
第2引数はバッファ、第3引数はバッファ長、第4引数はタイムアウト(msec)です。
第2引数の型はuint8_tへのポインタです。

main()関数からメッセージを表示させるには、こんな感じで書きます。
  HAL_UART_Transmit(&huart3,(uint8_t *)"Hello\r\n", 7, 100);


しかしこれは、ちょっと使い勝手が悪いですね。
CubeMXのサンプルには、printfでUARTに出力できるようにするサンプルが付属していますので、これを自分のプロジェクトでも使ってみましょう。

該当するサンプルは、CubeMXのライブラリ(STM32CubeF7)では
STM32Cube\Repository\STM32Cube_FW_F7_V1.8.0\Projects\STM32F767ZI-Nucleo\Examples\UART\UART_Printf\SW4STM32

にあります。

まず、上記のフォルダにある「syscalls.c」を自分のプロジェクトにコピーしてください。
このファイルにはprintfから呼ばれる_write()の実装が入っています。

このファイル自体は変更する必要はありません。
その代わりに、printfを使いたいファイル(今回はmain.c)の中で、
int __io_putchar(int ch)
を再定義します。

これも上記のプロジェクトのmain.cにあるとおりにすればよいのですが、該当部分を抜き出すと以下のようになります。

ここでは、1文字出力する関数をUSART3へ1文字出力するように書き換えています。
printf()は_write()を経由して最終的に__io_putchar()を呼ぶように実装されているので、上記を追加することでprintfでST-Link経由でPC側へ文字列を送ることができるようになります。

ちなみに読み込みのほうは、syscalls.cにある_read()の実装が、バッファ分のデータを読み込むまで終わらないようになっているので、こちらにも手を入れる必要がありそうです。
posted by boochow at 10:57| Comment(0) | stm32 | このブログの読者になる | 更新情報をチェックする

2017年12月16日

STM32のハードウェアIDからMACアドレスを生成する

先日作成したNUCLEO-F767ZIとMicroPython用のEthernetドライバですが、MACアドレスがソースコードにハードコーディングされていました。
これはCubeMX付属のサンプルがそうなっていたのをそのまま使ってしまっているからですが、これでは1つのネットワークに1台のMicroPythonマシンしか接続できないので、実用上は問題があります。

そこで、STM32がもともと持っている96bitのハードウェアIDからMACアドレスを生成しようと考えました。

MACアドレスは48bitですが、最初の24bitはベンダごとに異なる固定の値(ベンダーID)です。
ベンダーIDには、IPアドレス同様にプライベートアドレスがあり、1バイト目のbit 1が1ならプライベートアドレスです。具体的には「X2:XX:XX、X6:XX:XX、XA:XX:XX、XE:XX:XX」になります。

virtualization - What range of MAC addresses can I safely use for my virtual machines? - Server Fault

プライベートアドレスのどれを使ってもいいのですが、CubeMXのv1.8.1のサンプルでは「02:01:00」が使用されていました。
これはそのまま残すことにし、残り24bitをハードウェアIDから生成します。

ハードウェアIDは96bitですが、ランダムな値ではなく、チップを製造する際のある種の「通し番号」になっています。そのフォーマットは以下の資料に記載されていますが、
「ロット番号」「ウエハー番号」「ウエハー上での座標値(X, Y)」
でそれぞれ32bitずつ割り当てられているようです。

en.STM32L4_System_eSign.pdf

ハードウェアIDは、MicroPythonからは「machine.info()」で取得することができる一連の情報に含まれています。先日購入したNUCLEO-F767ZIでは
>>> import machine
>>> machine.info()
ID=33001f00:07513635:32333134

となり、もっと以前に購入したNUCLEO-F401REでは
>>> import machine
>>> machine.info()
ID=33005100:15513432:36363539

となりました。

上のPDF資料にもありますが、ハードウェアIDは全てのビットが使われているわけではなく、値の範囲が決まっているパラメータもあるので、一部のビットだけを抜き出して使うのが良さそうです。
上の2つの値を見比べただけでも、(違う型番のCPUで購入時期も異なるのに)
・1バイト目が一致(33)
・6バイト目が一致(51)
・2バイト目と4バイト目が0
・7バイト目〜12バイト目は上位8ビットが全て3
といったことに気づきます。
上位8ビットが3というのは、おそらく数値をAsciiコードで表している(0=0x30、9=0x39)からだと思われます。
つまり、このハードウェアIDが文字列だと思って眺めてみると
767ZI: "3...", ".Q65", "2314"
401RE: "3.Q.", ".Q42", "6659"
となるわけです。

乱数に使うわけではないので、規則性そのものは問題にはならないのですが、複数のボードで一致する可能性が高い部分はMACアドレスには使わないほうが望ましいと考えられます。
ということであれこれ考えてみたのですが、頻繁に行う計算でもありませんし、この96ビットからハッシュ値を計算してしまうのが手っ取り早いという結論になりました。

ハッシュ値としては24bitあればいいので、軽量なハッシュアルゴリズムであるFNV-1(→Wikipediaを参照)を用いて計算した32bitのハッシュ値の下位24bitを使うことにしました。
Pythonで書くとこんな感じになります。埋め込まれている「0x811c9dc5」と「16777619」はそれぞれFNV_offset_basisとFNV_primeになります(上記のWikipediaを参照してください)。


これで先に挙げた2つのハードウェアIDからMACアドレスを計算すると、それぞれ以下のようになりました。

33001f00:07513635:32333134 → ec:f2:04
33005100:15513432:36363539 → 8c:4a:41
ラベル:MicroPython
posted by boochow at 16:22| Comment(0) | stm32 | このブログの読者になる | 更新情報をチェックする

2017年12月13日

NUCLEO-F767ZI ethernet driver for MicroPython

先日動作確認した、NUCLEO-F767ZI用Ethernetドライバを追加したMicroPythonを以下で公開しました。

https://github.com/boochow/micropython/tree/stm32-ethernet

ビルド手順は以下の通りです。(CROSS_COMPILE指定は各自の環境に合わせて変更してください)
git clone -b stm32-ethernet https://github.com/boochow/micropython.git
cd micropython
git submodule update --init
make -C mpy-cross
make -C ports/stm32 MICROPY_PY_ETH=RMII BOARD=NUCLEO_F767ZI CROSS_COMPILE=~/gcc-arm-none-eabi-5_4-2016q3/bin/arm-none-eabi-

ボード用のHAL設定ファイル(boards/NUCLEO_F767ZI/stm32f7xx_hal_conf.h)やリンカスクリプト(boards/stm32f767.ld)を修正していますので、NUCLEO-F767ZI以外のボードでは動作しません。
ですが、同じ構成(CPUがSTM32F7、PHYがLAN8742AでCPUとのインタフェースがRMII)であれば、同様の修正を施せば動作するのではないかと思います。

同じ構成になっていると思われるボードはNUCLEO-F746ZG, NUCLEO-H743ZI, STM32F746G-DISCOですが、STM32F746G-DISCOは使用しているGPIOが違う(TXD1がPG14)ため、ethernetif.c内の初期化コードを修正する必要があります。

ちなみに現状のコードではUM1974に記載されているGPIOを使っています。
記載は以下の通りです。

nucleo-ether-config.png
posted by boochow at 01:09| Comment(0) | stm32 | このブログの読者になる | 更新情報をチェックする

2017年12月09日

NUCLEO-F767ZIのMicroPython用Ethernetドライバが動きました

ether02.png

先日着手したNUCLEO-F767ZIのオンボードイーサネットをMicroPythonで動かすためのドライバですが、ようやく動きました。
少しコードを整理したら公開する予定です。
途中で少しデバッグに苦労しましたが、基本的には自分のミスや知識不足が原因で、終わってみればあまり厄介なポイントは無かったように思います。

技術資料としてはUM1974が必須です。この資料のP28-29にEthernet周りのI/Oが記載されています。
CPUにはMAC層は内蔵されていますがPHYチップは外付けで、ドライバはCPUとPHYチップの結線およびPHYチップの仕様に従わなければなりません。もっとも、そのコード自体はSTM32CubeMXに付属するサンプルに含まれています(といってもサンプルは一つしか入っていないのですが)。今回もそのコードを参照しました。

また、TCP/IPとしてLwIPを使っていますので、その資料としてUM1713が有用です。LwIPそのものについての解説も付属しています。

この資料にあるとおり、LwIPにはRaw、NetConn、Socketという3種のAPIがあり、Raw以外の2つはスレッド機能を持つOSが必要となります。MicroPythonはLwIPをRaw APIで使用しています。(上記のSTM32CubeMX付属のサンプルはNetConnを使っています。)
LwIPをEthernetと接続するには、パケットが到着したかどうかをポーリングし、届いたパケットをLwIPに渡す処理を定期的に行う必要がありますが、このあたりは既にWIZNet用ドライバのお手本があります。

ですので、結局は初期化とパケットの読み書きを、STM32CubeMXのサンプルを参考に書くだけです。
・・・という見通しは実は初期段階から見えていたのですが、実際に着手してみると、ちゃんと動くようになるまで何日もかかりました。
やはりマイコンはデバッグがなかなかしんどいなあとあらためて思いました。
ラベル:MicroPython
posted by boochow at 23:03| Comment(0) | stm32 | このブログの読者になる | 更新情報をチェックする

Raspberry Pi ZeroとPico Hat Hackerが届きました

rpi-zero.jpg

英国にPimoroniという電子工作関連のオンラインショップがありますが、先日セールをやっていたのでRaspberry Pi Zero(と、カメラモジュールとか細々としたもの)を買いました。
すぐ使用する予定は無いのですが、なぜか日本では品薄でなかなか購入できないので、この機会にまとめ買いです。
以前Pi Zero Wもこのショップで購入しています。
購入した品物は大体2週間以内には届きますので、感覚的にはAliexpressよりも対応が早いです。

上の写真の中央がPi Zero、左がPi Zero Wです。
並べてみると、サイズは同じですがZero Wはラズベリーのロゴがない代わりにWiFi/BLEのチップが追加されています。

右はオマケでついてきたPico Hat Hackerです。GPIOに他のHatをつけるとGPIOにアクセスできなくなってしまいますが、このボードを挟めば大丈夫!という代物です。
ただ、ネットを見ていると「Pi Zeroの40本のピンヘッダをハンダづけするのが辛い!」という声もあるようなので、その倍の本数をハンダ付けしないといけない本品はちょっとマニアックかもしれませんね。
posted by boochow at 16:01| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする
人気記事