2020年10月10日

STM32 USB CDC注意点

STM32マイコンでUSBデバイス(USB_HSやUSB_FS)に対応している場合、CubeMX上で容易にUSB-CDC(Communications Device Class:CDCもしくはVirtual COM Port:VCP)を利用することができます。CubeMXの設定については今回は割愛しますが、CDCを利用する際に知っておきたい挙動、注意点について下記の3点を紹介します。

@usbd_cdc_if.cの関数ポインタ
Aバッファは割込み専用バッファ
B64byte以上のデータの送受信


@usbd_cdc_if.cの関数ポインタ
 CubeMX上で生成されてユーザがCDC機能を利用する場合、usbd_cdc_if.c内の関数を利用することになります。usbd_cdc_if.c内ではCDCの送受信の関数の他にバッファの初期化設定やCDCクラスの要求処理等の関数があります。これらの関数は関数ポインタで呼び出されるため、プロジェクト内で関数をgrepしても個々の関数は見つかりません。USBD_Interface_fops_FSでポインタが管理されています。そのため、usbd_cdc_if.cの関数の初期化や受信処理等の一通りの処理はCubeMX生成時点で既に実装されており、自動的に関数が実行されます。関数ポインタで管理、呼び出されるため、usbd_cdc_if.c内に安易に自作関数を配置することは避けたほうが賢明です。自作関数を置く場合、関数ポインタにも自作関数を加えて、さらに他のUSBライブラリとの整合を取る必要があります。関数ポインタの整合を取らないと関数が正常に呼び出されず、挙動がおかしくなります。

Aバッファは割込み専用バッファ
CubeMX生成時点で自動でバッファUserRxBufferFS、UserTxBufferFSが生成されます。このバッファは割込みorDMAのバッファとして使用されるため、ユーザー用途のデータバッファとして利用するのは避けた方が賢明です。バッファは受信時に読み込み、送信時の書き込みだけで利用します。このバッファは受信時はUserRxBufferFSにデータが入り、次の受信で自動的にクリアされて新しいデータが上書きされます。送信のUserTxBufferFSについても同様です。MX_USB_DEVICE_Init()の初期化の際にバッファの設定が有効化されます。CDC受信時のCDC_Receive_FS関数内のUSBD_CDC_SetRxBufferでバッファを再度設定していますが、例えば受信の都度、バッファをクリアやバッファの位置を変更、他のバッファに設定するといった自動で生成されたバッファに手を加えると2回目以降の送受信ができないといったCDCの不具合が発生しました。

B64byte以上のデータの送受信
USB_FSの場合、USBの仕様上、通信1回のデータサイズが最大64byteに制限されます。通常は相手側(通常はPC)ではデータの分割、結合が自動で行われるため、無理に独自規格で64byte以上に実装し直す必要はありません。一方、STM32マイコン側はデータの分割、結合の処理が記述されていないため、64byte以上の送受信をするとデータが欠落します。Aで少し触れましたが、CDCの通信バッファであるUserRxBufferFSとUserTxBufferFSは毎回の通信で自動的に上書きされます。例えば受信の場合は毎回上書きされるため、64byte以上のデータを受信した場合は64byteの端数(余り)のデータがバッファに残ります。

64byte以上を送信する際にはUSBD_StatusTypeDefを64byte毎にCDC_Transmit_FSを呼び出して、USBの状態を見てバッファが空になってから次の64byteを送信するようにします。

64byte以上を受信する際には下記のようにUserRxBufferFSとは別にバッファ(CDC_RxBuffer、CDC_RxBufferLen)を用意し、上書きされる前にグローバル変数の受信バッファにデータを退避させます。必要に応じてユーザープログラム側でCDC_RxBufferの読み出し、CDC_RxBufferLenのクリアなどの処理を行ってください。


//グローバル変数としての受信バッファ
uint16_t CDC_RxBufferLen=0;
uint8_t CDC_RxBuffer[2048];


static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, Buf);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
memcpy(CDC_RxBuffer+CDC_RxBufferLen,Buf,*Len);//データ退避処理追加
CDC_RxBufferLen=CDC_RxBufferLen+*Len;//データ長さ処理追加
return (USBD_OK);
/* USER CODE END 6 */
}


STM32マイコンのCDCに関する記事は多くありますが、CDC機能を応用しようとするとCubeMXで生成された関数の挙動を理解する必要があります。その他にもUSBの接続状態やポートをソフトウェアで開いたタイミングの検知などもできるため、機会があれば紹介したいと思います。
posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年10月03日

RTC水晶発振子

多くのSTM32マイコンではRTC機能を備えており、VBATにリチウム電池と32.768kHzの水晶発振子を接続することでVCC電源断でも時刻を保持することが容易に実現できます。一方で設計によっては精度20ppm前後の一般的な水晶発振子を使用したにも関わらず、20ppmよりも時刻が経時でずれるといった場合があります。

時刻が経時でずれるといった場合、環境温度や周辺ノイズ、不安定な電源を除くと、多くは水晶発振子の外部負荷容量が適切でないことが原因です。安物の時計で時刻が進む、遅れるということが生じる原因も多くはこれが原因です。安物の時計の場合、水晶発振子の外部負荷容量のコンデンサ自体のばらつきが大きい部品を使用していることも一因です。


水晶発振子をRTCで使用する場合には水晶発振子と外部負荷容量の調整としてコンデンサを回路に入れます。一部のRTC ICでは外部負荷容量のコンデンサを内蔵しているものもありますが、使用する水晶発振子と接続先に合わせて外部負荷容量を調整する必要があります。STM32マイコンの評価ボードとして販売されているNucleoボードでは下記のように負荷容量6pFの水晶発振子X2に対して4.3pFのコンデンサC31,C32が外部負荷容量として接続されています。

STM32_NUCLEO64.jpg



負荷容量と周波数偏差の関係は下記のような反比例のような関係があります。水晶発振子によって適切な負荷容量は異なりますが、下記の例では6pF前後が周波数偏差0付近となり、適切な負荷容量と分かります。

Crystal.jpg

負荷容量と周波数偏差の関係から負荷容量が適切でない場合は周波数偏差がプラスにもマイナスにもずれることを意味します。

例えば、周波数偏差マイナスの場合は1秒間の発振回数が減少することになります。32.768kHzの水晶発振子で32768回カウントして1秒になるはずが、32768回カウントで1秒よりも多く秒数を要するため、経時で時計が遅れることになります。反対に周波数偏差プラスの場合は1秒間の発振回数が増加することになります。32768回カウントして1秒になるはずが、1秒よりも少ない秒数で32768回カウントされるため、経時で時計が進むことになります。

つまり、安い時計で時計が遅れるという場合は外部負荷容量を交換して容量の少ないコンデンサに置き換える必要があります。一方、時計が進む場合は外部負荷容量を容量の大きいコンデンサに置き換える必要があります。

実際に手持ちの時計が1か月で数分遅れるため、12pF程度から8pFに変更したところ、遅れがほぼなくなりました。外部負荷容量は接続する先のマイコンデータシート等から計算で求められますが、構成によっては理論通りにならないこともあります。なお、STM32マイコンの場合はソフトウェア側で周波数偏差を微調整、キャリブレーションすることが可能ですが、ハードウェア側で可能な限り対応した上でソフトウェアで微調整する方が確実です。RTCの水晶発振子は時刻のずれとして明確に影響が出るため、最終的には数台の実物を準備し、個体差を含めて半月や1か月で何秒くらいずれるか負荷容量を数pF単位で調整した方が良さそうです。
posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年09月26日

無印ダイヤル式キッチンタイマ構造

今回は先日、近所の無印良品で購入したダイヤル式キッチンタイマについて紹介したいと思います。シンプルで小型なデザインでありながら、START / STOPボタン、RESETボタン、時間設定のダイヤル(筐体側面のリングを回転させる)と3つのインタフェースを搭載しています。小型な筐体にどのように3つのインタフェースを搭載しているのか、以前から気になったため、分解してみました。


timer1.jpg

分解は裏側のマグネットにネジが隠れており、両面テープで貼られたマグネットを剥がすことでネジを外すことができます。

timer2.jpg

ネジを外すと比較的簡単に内部の構造を確認できました。ケーブルは音のスピーカ配線とマイナス側の電池の配線がありました。電池のプラス側は基板側に接点がある構造です。汎用マイコンでなく、タイマー用IC(IC1)を使用しているようです。スピーカを駆動させるためのTrかFetとしてQ1があり、ノイズを除くため(想定)にスピーカと直列で大きめのインダクタL1があります。上下にSTART / STOPボタン、RESETボタンのプッシュスイッチが基板裏にあります。また、スイッチのチャタリング防止としてC1、R1、C2、R2でローパスフィルタが実装されています。右側の白い部分は時間合わせのための回転エンコーダです。


timer3.jpg


側面からみると中央の2つの軸を中心としてシーソーのような構造をしており、筐体表面が押しボタンとして押せるような構造になっています。片側に倒すとSTART / STOPボタン、もう片側に倒すとRESETボタンが押せるようになっています。また、写真中央の水色の部分がエンコーダとなっており、同軸上のギヤに繋がっています。エンコーダのギヤは筐体側面の筒内部のギヤとかみ合わさるようになっています。水色のエンコーダはパソコンのマウスのホイールに搭載されるものと同じようなエンコーダが搭載されていました。シーソー構造の回転中心付近にエンコーダのギヤのかみ合わせ部があり、ボタン操作と回転ダイヤル操作の干渉が少なくなるように工夫されています。

timer4.jpg

筐体や基板を除いて使用されている部品としては汎用的な部品を多く使用していることが分かりました。シンプルで小型なデザインでありながら、3つのインタフェースを搭載するためにシーソー構造やダイヤルのギヤなど様々な工夫があり、勉強になりました。構造を確認後は再度、組み立ててキッチンタイマとして使用することにしました。
posted by Crescent at 00:00| Comment(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする

2020年09月19日

STM32G030J6 注意点

STM32G0シリーズは従来のSTM32F0シリーズに比べてクロックが向上し、価格も安くなっています。さらに新しいラインアップとしてG0**Jが登場し、SO8パッケージも追加されています。特にF0やG0といった低価格帯かつ小さいパッケージでI2Cを2つ搭載しているものはG0シリーズしかないため、I2Cを複数使用したい場合には非常に魅力的です。

ただ、SO8パッケージはSTM32マイコンのペリフェラル機能を8ピンに凝縮しているため、様々な犠牲が出ています。その1つとして困った現象が生じたため、今回紹介します。


STM32G030J6.jpg

主なSTM32G030J6、STM32G031J6のパッケージ起因犠牲点
@NRST、BOOTといったシステム関連のピンも他のピン機能と共用
AVBATが使用できない
BHSE(高速外部クロック入力)が使用できない

A、Bは用途によっては全く問題ありませんが、@がペリフェラル設定によっては厄介な問題を引き起こします。

データシートを見るとP.34 Table 12. Pin assignment and descriptionの注釈に「For the device in SO8N package, the PA0, PA1, and PA2 GPIOs are bonded with NRST on the pin 4. In order not to interfere with device functions, they must not be set in alternate function or in output but remain at all times in input configuration.」と記載がありました。

デバイスの機能と干渉を防ぐためには4番ピンは他の機能ピンや出力ピンとして設定禁止ということです。他の機能割付けや出力として設定するとリセットが効かなくなり、ファームの書き換え等ができなくなります。

また、PA14、PA13をSWCLK、SWDIOとして設定しない場合も注意が必要です。一度、I2CやUARTとして設定したファームを書き込みすると以降はST-Link Utility等からボタン操作1つでは認識できず、ファーム書き換えが簡単にはいきません。今回、困った現象としてPC14、PA14をUartに設定したところ、ST-Link Utilityから認識できない現象が発生し、ファームの書き換えができなくなってしまいました。

この現象はSO8パッケージ以外でもSWD端子に他の機能を割り付けると発生する問題ですが、従来のSTM32シリーズではBOOTピンがあったため、BOOTピンをHighにすればファームがロードされずに起動させることでファーム書き換えができました。STM32G0シリーズのパッケージによってはBOOTピンが除かれているため、従来の方法が使えません。

Cube1.jpg
このような場合にファーム書き換えをする場合は4番ピンをGNDにつないだリセット状態で電源を投入します。ST-Link Utility等のConnectボタンを押してすぐに4番ピンをGNDから外すと再び認識し書き換えできるようになります。4番ピンを他の機能割付けや出力ピンとして設定するとこの方法も使用できないため、完全にファーム書き換えができなくなります。

Cube2.jpg
STM32G030J6では4番ピンは未使用にした上で、ピンの制約はありますが、可能な限りPA14、PA13はデバッグ、書き込み用としてSWCLK、SWDIOを確保して設定した方が良さそうです。
posted by Crescent at 00:00| Comment(0) | 電子部品 | このブログの読者になる | 更新情報をチェックする

2020年09月12日

STM32H7とNAND Flashメモリ

以前に紹介したNAND Flashメモリ を搭載したNucleo144 Memory Shieldを用いて、STM32H743と接続した際にH7シリーズ固有の問題に直面したたため、対処方法を少し紹介します。

STM32H7シリーズは非常に高速な上にIキャッシュとDキャッシュを搭載しているため、さらに効率的に処理をすることが可能です。一方でDキャッシュはDMA転送時等にデータ化け(キャッシュの一貫性が崩れる)が生じるため、なかなか厄介です。データ化けが生じる可能性を意識して実装する必要があります。データ化け、キャッシュの一貫性が崩れるとは、例えば、DMA転送の際にメモリからDMAコントローラに書き込んだはずが、キャッシュに溜まっているだけで、DMAコントローラに書き込まれていない、その逆も然りでDMAコントローラのデータを読み出そうとメモリを見てもキャッシュに溜まっているだけでメモリは更新されてないといったことが生じます。

DMA転送時のデータ化けの対処方法についてはこちらのサイトに紹介されています。今回はDMA転送だけでなく、FMCメモリコントローラでも同様のデータ化けが発生することが分かりました。考えてみれば、メモリコマンド指令も同じデータメモリのため、当たり前といえば当たり前ですが、FMCコントローラの場合、データが化けるという現象ではなく、NAND Flashメモリの初期化や書き込みでエラーが発生するという現象でした。データ化けによってメモリコマンドの書き込み、読み込みのデータが化けてエラーとなったようです。HAL_BUSY等のエラーレスポンスからデータ化けと気づくまでに時間を要してしまいました。

NAND Flashメモリの場合、初期化のMPU_Config内に下記のようにEthernetDMAの領域に加えて、NAND Flashメモリで使用する0x80000000以降の領域に対してライトスルーモードに設定します。EthernetDMAの領域の設定についての詳細はこちらに説明があります。

設定の注意点としては領域はMPU_REGION_NUMBER*で順番に設定することです。*が同じだと設定が上書きされてしまいます。

/* Configure the MPU attributes as Device not cacheable for ETH DMA descriptors */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30040000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256B;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Configure the MPU attributes as Normal Non Cacheable for LwIP RAM heap which contains the Tx buffers */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30044000;
MPU_InitStruct.Size = MPU_REGION_SIZE_16KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Configure the MPU attributes  for NAND Flash */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x80000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256MB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);


今回はNAND Flashメモリについて紹介しましたが、NOR Flash、QSPI等でも同様だと思います。0x80000000以降の領域に対してライトスルーモードに設定することでエラーが発生せずに読み書きできるようになりました。FMCコントローラの場合、データが化けるという現象ではなく、NAND Flashメモリの初期化や書き込みでエラーが発生しました。当初、タイミング信号のパラメータ設定ミスや各サイズの設定ミスを疑いましたが、FMC単体では動作して、様々な他の機能を入れると動作しないため、機能を削って確認すると最終的にDキャッシュと判明しました。Dキャッシュを完全に無効にすると正常に動作しますが、パフォーマンスを考えると必要な領域のみを個別に設定した方が良さそうです。
FMCの設定についてはこちらのPDFに分かりやすく説明されています。
posted by Crescent at 00:00| Comment(0) | 組込ソフト | このブログの読者になる | 更新情報をチェックする