FatFS+FreeRTOSでDiskErrorが発生

マイコンカーラリー用高速データロガー(シリアル送信データをSDカードに記録することで1mS毎のデータログを可能にするタイプ)

のプログラムを書いて実験しているとDiskErrorがランダムに出て記録が出来ないトラブルにぶちあたってしまいました。
DiskErrorはFatFSライブラリの下位レイヤーで発生するエラーなのでFatFSそのものに問題があるわけでもなさそうです。

CPUはstm32F765VGTで使っていたHAL_Driverのバージョンは
STM32CubeF7 Firmware Package V1.6.0 / 30-December-2016

STM32FCubeMX のバージョンはVer4.19.0でした。

HAL_Driverの中身まで調べるのは面倒なのでWebで同様の症状を探してみるとねむいさんが「stMicroのCommunityでF7_1.6.0はDiskErrorが出るよ。」と書いているのを見つけました。

Problem with FatFs and STM32Cube_FW_F7_V1.6.0 (Possible STM32Cube Bug)

これが原因に違いないと喜んで、ねむいさん推薦のFW_F7_V1.5.0に替えてもやはりランダムなDiskErrorが出て問題は解決しません。

それでHALライブラリのドキュメントに、

と書いてある資料を最後まで読んでみると(おいおい、最初に読めよ :-? )何となく原因がわかりました。
このアプリケーションはデバッグがやりやすいようにFreeRTOSを使って作っていたのですがFatFSはFreeRTOS対応版と通常版で別のドライバーを使うようになっていてFreeRTOS版の時はスタックサイズとヒープ領域の指定に気を付けないとエラーが起きるということでした。

FreeRTOSでエラーが起きないスタックサイズとヒープサイズを調べるのは結構面倒ですし特にFreeRTOSを使う必要があったわけではないので通常版でテストしてみるとあっさりエラー無しで動作するようになりました 8-)

ただ、SDMMCCLK clock divided factor は設定画面の注釈には

SDMMC_CK = SDMMCCLK / [CLKDIV + 2]. The output clock frequency can vary between 187 KHz and 24 MHz. It is advised to keep the default ClockDiv value (0) to have a maximum SDMMC_CK frequency of 24 MHz.

(0にするようお勧めします)

と書いてあるにもかかわらず 1 (48/(2+1)=16MHz)に設定する必要がありました。

stm32FのDMA Circular mode を使ったUART受信

stm32FのUARTでシリアルデータを受信するのにDMAのcircularモードを使うとDMAだけでデータをリングバッファに読み込むことが出来ます。
リングバッファからの読み出し関数は、DMAのポインタ(huart_cobs->hdmarx->Instance->CNDTR)をリングバッファへの書き込みポインタとして、読み出しポインタで書き込みポインタを追いかけるようにするだけです。

この手法は割り込みン数を使わずにデータをリングバッファに取り込めるのでとても簡単で効率が良いのですがSTM32CubeMXのHALライブラリを使っているとエラーが起きた時に勝手に送受信を止められてしまうという困った現象が起きます。

解決法を探していると同じことをやっている人がいて、「受信エラーはCRCチェックで対処するからHALライブラリのエラーハンドリングは邪魔なのでこうやってやめさせたよ。」という回答をみつけました :-P (下のトピック)。

Topic: ‘Best’ way to load UART data to ring buffer with STM32/HAL
このトピックの投稿者が「この方法がとてもクールだと思わないか?」と言ってるのに対して、別の人が「自分もそう思う、だけどエラーハンドリングには注意した方がいいよ。」とアドバイスを書いています。

受信部のソースコード : スマートにまとめられているので参考に転載しておきます。

/*
* The STM32 makes receiving chars into a large circular buffer simple
* and requires no CPU time. The UART receiver DMA must be setup as CIRCULAR.
*/
#define CIRC_BUF_SZ       64  /* must be power of two */
static uint8_t rx_dma_circ_buf[CIRC_BUF_SZ];
static UART_HandleTypeDef *huart_cobs;
static uint32_t rd_ptr;

#define DMA_WRITE_PTR ( (CIRC_BUF_SZ - huart_cobs->hdmarx->Instance->CNDTR) & (CIRC_BUF_SZ - 1) )

void msgrx_init(UART_HandleTypeDef *huart)
{
    huart_cobs = huart;
    HAL_UART_Receive_DMA(huart_cobs, rx_dma_circ_buf, CIRC_BUF_SZ);
    rd_ptr = 0;
}

static bool msgrx_circ_buf_is_empty(void) {
    if(rd_ptr == DMA_WRITE_PTR) {
        return true;
    }
    return false;
}

static uint8_t msgrx_circ_buf_get(void) {
    uint8_t c = 0;
    if(rd_ptr != DMA_WRITE_PTR) {
        c = rx_dma_circ_buf[rd_ptr++];
        rd_ptr &= (CIRC_BUF_SZ - 1);
    }
    return c;
}

 

エラーへの対処 : HALの余計なお世話を無効にする方法、これが私の探していた答えです。

/* These uart interrupts halt any ongoing transfer if an error occurs, disable them */
/* Disable the UART Parity Error Interrupt */
__HAL_UART_DISABLE_IT(&huart1, UART_IT_PE);
/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_DISABLE_IT(&huart1, UART_IT_ERR);

STM32CubeMXをGCC(JDE)で使う(1)

STM32CubeMXについて

Stm32CubeMXはベースとなるライブラリHALも動作が安定してきて既にSTM32CubeMX抜きの開発など考えられないほど便利な開発環境です。
ただ、便利になったぶん構造が複雑になって、以前のライブラリのようにフリーの開発環境で簡単にHALを使うのは難しくなりました。

統合開発環境JDEの生い立ち

私は組込開発に専ら自作の開発環境であるJDEを使っています。
現在、組込用メインCPUとしているtm32Fシリーズを使い始める前は、H8、SHシリーズを使っていて、その前はV25を組込み開発用のCPUとして使っていました。

V25はPCと同じ86系のCPUなので、PC用のコンパイラであるBCC(Borland C Builder)とBCCが生成したPC用のオブジェクトを組込ボードで動作させるために作ったV25モニタを使ってコンパイル、転送、実行が素早くできる工夫をして自社製品にも活用しましたしV25ボードも様々な方に愛用していただきました。
そのV25がディスコンなどで使えなくなりH8,SHシリーズを使い始めた時に作ったのがGCCをベースにしたJDEです。

GCC

H8、SHシリーズを使い始めた時に開発用のコンパイラとして何を選ぶかという問題がありました。
当時はまだオープンソースのソフトをキワモノ扱いする人も多くメーカー純正コンパイラの評価が圧倒的に高い時代でしたが将来的にはGCCが優位に立つという見通しでGCCを選択しました。
他の理由としてはCPUボードや製品を愛用していただいている方に学校関係者や研究職の方が多いため「出来るだけフリーソフトで開発できるようにしたい。」ということもありました。
今ではLinuxが普及したこともあり汎用コンパイラとしてのGCCの評価は完全に固まり、組み込み用としても最適化性能を除いてはGCCが優位になりました。

最適化性能をどれだけ問題にするかは立場によって異なります。
民生機器のように数円のコストが問題になる場合はギリギリの性能と最小限のROM/RAMを持つCPUを使うためにコストや汎用性等他の点を犠牲にしても最適化性能に優れたコンパイラを使う必要があります。
一方私たちのようにFA分野の仕事を手掛ける場合や研究職、教育関係者の方々にとっては最適化性能より汎用性、コストパーフォーマンスに優れたコンパイラを使う方が有利です。
何故なら、組込分野では使えるCPUの性能は様々であり多少のコストアップで最適化性能の差を埋めるCPUを使うことが出来るからです。

具体的な例をみると、マイコンカーラリーのレギュレーションで決まっているR8Cボードに搭載されているR8C/38Aという16ビットCPUは秋月価格450円でROM128KB,RAM20KB、クロック20MHzですが、マイコンカーラリー用のデータロガーボードの試作に使ったDigiKeyで入手できる価格1517円のSTM765VGT6は32ビットでクロック216MHz、ROM1MB、RAM512KBと一桁以上の性能を有しています。
このくらいのROM/RAMがあればリアルタイムOSも楽に動くので制御ループと同時にモニタプログラムを走らせておくことも出来ますし浮動小数形式が使える普通のprintfを使っても全く問題がありません。

つまり最新の高性能組込用CPUを使えば最適化性能を気にする必要は無く、オープンソースのGCCを問題なく使えるということになります。

JDEの履歴

JDEを作った当初の目的はGCCを使うこと以外にV25モニタの思想を引き継いだH8用モニタを使ってコンパイラから転送、実行までの流れを自動化するということにありました。
私が開発を効率化するために最も重視するのはボード上の情報可視化とターンアラウンドタイムの短縮です。
H8はボード上のCPUにシリアルケーブルだけでプログラムを書き込み出来るオンボードプログラミング方式の先駆けとなったCPUでしたがCPUに書き込むために「電源を切る → ブートモードSW ON → 書き込み → 電源を切る → ブートモードSW OFF → 電源投入」という手順を踏む必要がありました。(今のR38C/38Aボードも同じです)
H8モニタを使えば書き込むために一度ボードのリセットボタンを押す必要はありますがボード上のスイッチを操作しなくても転送から実行が出来ますし、モニタモードでボードのメモリ情報を見ることが出来るようになっています。
僅かな差ですが、スイッチ操作の削減とモニタモードでの情報があることで、開発が効率的になるものです。

H8、SHシリーズに見切りをつけたのはルネサスの大手メーカーだけを向いた営業姿勢により突然デバイスの供給が数か月中断するといった事態が起こり始めたのがきっかけでした。(具体的には自動車メーカーの都合で一般へのデバイス供給が中断した件)

JDEはGCCがサポートしているCPUなら何でも使えますから、GCCが組サポートしているアーキテクチャの中で最も前途有望と思われるARM系のCPUにターゲットを絞って検討を開始し、最初はATMEL社のCPUを暫く使ってみてその後、周辺機能が充実したstm32シリーズを使い始めて今に至っています。

JDEにSTM32CubeMXが生成したプロジェクトのインポート機能追加

STM32CubeMXが出てくるまではJDEをプロジェクト管理ツール+OopenOCD仕様の書き込みアダプタを使った書き込みツールとして便利に使ってきました。
それだけならば特にJDEにこだわる必要もありませんでしたが、STM32CubeMXが生成したプロジェクトをインポートする機能を追加出来たことで自分が好きなように改造できるツールJDEを使っていて良かったと、改めて思っているところです。