freeRTOSの周辺デバイスの初期化

昨日の記事で、

freeRTOSのユーザーコードを隔離する

debuggerもしくはrunでプログラム実行させると、LCD(st7789)画面が真っ黒な画面になることがままある、デバッガーで見る限りst7789のライブラリに制御は飛んで実行はされてるけど画面出ないというのは、初期化に失敗することがあるということだろう

理由は明確ではないけど、以下のmain.c中のuser code begin2の中から呼び出すとうまくいかないことがあって、気になるのはそのあとでfreeRTOS起動しているから関係あるとすればそれかな(ADC関連もいずれここから移動する、rtos化してない時の残渣コード)

元々がfreeRTOS化した時の処理分担のクリアさから言えば、main.cにはできるだけペリフェラル向けのロジックは記述しない(Nucleoの初期化だけは実装)で、個別ペリフェラル向けの処理はそれぞれのrtos処理モジュールに持っていくのが自然だと思うよね

で以下のようにLCD表示処理のタスクのfor loopの前でST7789_Init()を呼び出すと問題は出ていない、基本思想通り個別のペリフェラルの初期化処理及びその後の実行はすべてrtos側で受け持たせるのが自然だしバグの入る余地も少ないと思う

 

admin

freeRTOSのユーザーコードを隔離する

STM32 シリーズですが、freeRTOSのユーザコードをできる限り自動生成されるコードから分離したい、というか移植性とか考えたらそれが自然

<実行環境>

・STM32F401re

・M4 MacBook Pro Tahoe

・CubeIDE 1.19.0

Core/IncとCore/Srcの下にprocというユーザコード用のディレクトリ作成、Inc/Srcの中ならばbuildはソースコードを見つけて実行してくれる

<やっていること>

defaultTaskはLEDの定期点滅、lcd_taskはst7789 LCDの表示処理を呼び出しているだけ、freeRTOSはpreemptiveなのでdafultTaskの実行が必要になればlcd_taskの実行を中断してLCD表示のtoggle処理をおこなく、処理時間は人間の時間感覚からは全くの瞬時だからLCD表示的には全く認識できないレベル

<task定義とそれを扱う仕組み>

① CubeMXで定義

lcd表示タスクは優先度を下げておく、ADC DMA結果を分析するタスクの方が優先順位高いよね普通は

Task Nameは②のTask_attributeで使われ、Entry Functionはfreertos.c中の関数名になります

② freertos.cでタスクの属性とタスクそのものの定義

③ freertos.cのタスク初期化(osThreadNew())で②の定義が使われ、Task(StartDefualtTask)等に紐付けされる

<ユーザコードの分離>

freertos.c中のコード、defaultTaskではボード上のLEDを点滅させてるだけ、StartTask03は実態はなくて、ユーザコードの呼び出しだけ

void StartDefaultTask(void *argument) {
  /* 生存確認(Lチカ)だけして大半は眠る */
  for(;;) {
    HAL_GPIO_TogglePin(GPIOA, LED_PIN);
    osDelay(500);
  }
}

/* USER CODE BEGIN Header_StartTask03 */
/**
* @brief Function implementing the lcd_Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask03 */
void StartTask03(void *argument)
{
  /* USER CODE BEGIN StartTask03 */
  /* Infinite loop */
  for(;;)
  {
	lcd_process();  // LCD display process
    osDelay(1);
  }
  /* USER CODE END StartTask03 */
}

ユーザ処理(lcd_task.hとlcd_task.c)

cmsis_os.h、これは今は使わないけどいずれかならず使う

/*
 * lcd_task.h
 *
 *  Created on: Jan 13, 2026
 *      Author: usamiryuuichi
 */

#ifndef INC_PROC_LCD_TASK_H_
#define INC_PROC_LCD_TASK_H_

#ifdef __cplusplus
extern "C" {
#endif

void lcd_process(void);

#endif /* INC_PROC_LCD_TASK_H_ */


/*
 * lcd_task.c
 *
 *  Created on: Jan 13, 2026
 *      Author: usamiryuuichi
 */

#include "proc/lcd_task.h"
#include "st7789.h"
#include "cmsis_os.h"

void lcd_process(){
	ST7789_TestColors();
}

実際の動作は以下の動画、

全体の骨組みできてきたから、実際の処理に取り掛かれるかな

 

admin

STM32のADCの精度向上について

STM32F401REの実行精度は12bitsのADCでも10bits程度らしいのですが、条件によって精度が変わってくるのでちょっと比較をしてみた

<実行環境>

・STM32f401RE

・CubeIDE 1.19.0

・macOS Tahoe

 

<AGNDとGND>

普通に考えると、AGNDの方が品質良さそうに思うけれども、実はそれは思い違いで内部的には単にGNDと繋がっているらしく、それどころか通常のGNDよりもノイジーらしい

測定してみた結果

・測定条件

サンプリング周波数:200KHz

DMAバッファーサイズ:2,560 (512*5)

ADCのSampling Time : 56 Cycles

※ Sampling Timeの変更方法

① CubeMXのADC設定プルダウンメニューで選択

② buildでADC初期化処理に反映(赤で囲ったとこ)

ADCの入力をAGNDとGNDに切り替えて実測

計算方法

DMA転送された領域のlow側平均値を求めてるだけ、100以下判定はPWMじゃないときは不要

ということでAGNDの方がよりノイジーでした

 

<Sampling Time違いによる精度の違い>

ADCのサンプリング安定度:ADCの設定でSampling Timeが短すぎると結果が安定しない、なぜならADCサンプリンのCapの電荷蓄積ができない、あるいは外部ノイズの影響受けるから

従って、Sampling Time 最少3 Cyclesは安定性を考えると、可能な限り長く取った方が良い、200KHzサンプリング(5μSインターバル)ADC clock 21MHzだと56Cyclesの選択か

ノイズレベルの目安は1~1.5LSBとするとmax 2ぐらいが許容レベルかと思う、SPI動作は明らかにノイズ源になっているけれども、Sampling Time設定である程度は改善できていそう、実行の繰り返しで値は変わっているので数値は傾向を表しているだけと思った方が良い

Nucleoのボード(F401RE)上にC25の捺印はあるけど、部品は実装されていないけど、おそらくAGND関連じゃないかと思うのでそこにセラミックキャパシタ実装すれば振る舞いも変わってくるだろう、必ずしも改善じゃないかもしれないけども、STM32の12bits ADCの実行精度は10bitsと言われているのでそれほど高いレベルは望めない

 

admin

Debuggerでデフォルトはmain.cの最初の行で止まるを変更する¥

CubeIDEのデバッガーは、デフォルト設定ではmain.cの最初の行で止まるような設定になってるけど、いちいち止まんなくて良いよね、どう変更するの?

Run -> Debug ConfigulationsでSet break point at: のチェックを外せば良い、mainを変えればおそらく他のコードの先頭で止まるようになるんだろうね

単純だけど有用な設定だと思う

 

admin

 

STM32でST7789 LCD(240*320)をDMAで表示させる

LCD表示もDMA転送で表示させるようなライブラリがあるので、それ使ってデモプログラムを動かしてみた

https://github.com/AlexKaut/ST7789-STM32-DMA/tree/master/ST7789_STM32F103C8_Demo

<GPIOのピンの使い方>

実はCSは常時Lowにしてるから使ってないけど、

<CubeMXでのピン名称指定>

st7789.hでは以下のように定義されてるけど、最後の部分は落としてCubeMXで名称設定するのが正しいようです

<表示させてみた>

main.cにst7789関連のコードをデモプログラム参考に追加

はいこの状態では240*240設定なので、残りエリアはノイズが表示されます

<240*320対応の変更>

st7789.hファイルを変更します

<変更後の表示>

全面表示されるようになりました、LCDの種類によってはオフセット調整が必要な場合もあるらしい

<配線>

SPIのclock線(今SPI clockは21MHzに設定してます)はできるだけ短く、かつGND線とツイストしてインピーダンス下げた

実はDMA使うケースはそんなに多くないから、動作確認の比重が高いと思う、実際にはほぼプログラムモードでの制御になるから、データ準備でMCU介在するから

 

admin

STM32の周辺デバイスはCPU停止に関係なく動作継続

前回の記事、

STM32でのPWMの設定方法(コード/初期設定)

この記事に関連して、PWMの周波数が1/2**nだと綺麗にADCからの値取れるけど、任意の周波数入れると値がミックスしてグタグタ状態、なんでかなと色々調べると結局ADC変換のDMAがデバッガーで止めても動き続けるからどんどん同じ領域に上書きされるせい、たまたま1/2**nだと少なくとも位相はずれないから問題ないように見えるだけという結論

この検証のために、

① 正弦波入力してみたけどデータが綺麗に昇順降順にはならなかったから気づいた

10KΩ抵抗でGND – VCCを分割してcap(1.5μF)でカップリング、つまり電源電圧の中点をオフセット電圧にして、周波数5KHz、電圧は2V p-p(USBオシロの信号発生器を使う)、ADCのサンプリング周波数は100KHz

データレディのタイミングでDMA停止させて、ブレークポイントの先で再起動するように変更

概ね、20サンプルで1サイクルの値(単調増加と単調減少)が確認できた、数字的にも1.65 V ± 1Vぐらいの雰囲気

② PWMで5KHzを入力

こちらも概ね20サンプルで周回が確認できた、複数回停止しているので前回と値が変わったところは黄色になってる、上書きされると4095付近と0付近が混合されたメチャクチャな値になってた

<学び>

STM32のペリフェラルはCPUの状態関係なく動作するから、デバッグ時には考慮が必要

 

P.S. あとCubeMXの設定変更後に一度buildしてもdebuggerモード起動すると再度buildが必要になるのはMacのCubeIDEによくある問題らしい、二回buildすればいいだけではあるけども、debugger起動時にbuild結果の整合性チェックが通らないらしい

 

admin

STM32でのPWMの設定方法(コード/初期設定)

PWMでdutyを設定するときに、

__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 250);

のようなやり方と、

CubeMXの設定で設定は等価であると、

<環境>

・M4 MacBook ProでCubeIDE ver1.19.0

・STM32F401RE

・クロック系:ST-linkの水晶発振器を使ってFCLKは84MHz

 

<設定方法>

設定の場合にはPWM Generation *****のPulseでdutyを設定、この場合にはduty 50%になります

設定した結果の出力(84Mhz/(84*200) = 5KHz)、クロックソースが水晶なのでRC発信器と違って正確

この波形を、サンプリング周波数10KHzのADC(DMA転送)の入力に突っ込んでやると、

のように、High/Lowがサンプリングできています、同じクロックで同期しているので周期が狂うことはない

 

admin

 

CubeIDEでペリフェラルごとにソースコードファイルを分離

STM32で初期状態のままにペリフェラルを追加していくと、main.cの中にペリフェラル関連の初期化などの処理が全て組み込まれて、はっきり言って読みづらくなる

で、ペリフェラルごとにソースコード分離するのは実は簡単で、設定だけで対応できてプロジェクト開発の途中で変えても問題ない

 

.iocの設定、Project ManagerでCode Generator選択して、コード分離指定にチェックするだけ、この後プロジェクトを再度ビルドすると、

こんな感じにヘッダファイルとソースファイルがそれぞれ対応ディレクトリ以下に配置される、この方がはるかに普通だと思うよ

 

admin

STM32のクロック系について

今頃気づいたのかという話ですが、STM32のクロック系にはPLLがあるので、任意の周波数にアップコンバートできます、従ってST-Linkの水晶発振器を使ってそれを基準周波数に使えば(Nucleoを切り離さない限り)、安定したクロックになります

で、ST-Linkの水晶使って最大動作周波数に設定した時のコンフィグは以下の通り

前の記事、

https://isehara-3lv.sakura.ne.jp/blog/wp-admin/post.php?post=5082&action=edit

ではこういうコンフィグ

従ってペリフェラルだけじゃなくて、MCUやDMAのクロックまで落とされていました

ちなみにこの状態でADCのCounter Settingを変更して前回と同じサンプリング周波数(10KHz)にした時の実行時間は、

なぜ時間測定がクロック周波数変えても変わらないのか疑問に思いましたが、以下のコードに見るようにクロック周波数は時間の分解能を決めてはいますが、経過時間には無縁だから

float getTimeUs(uint32_t count)
{
  float us = 1000000 * (float)count / (float)SystemCoreClock;
  return us;
}

前回の実行時間は12,829μsだったので微妙に速くなっています、DMA中の処理時間は見えてこない(8MHzで間に合ってしまう)だろうから、それ以外の前後のMCUの処理時間の84MHzと8MHzのクロック周波数の差分だろうと思います

クロックの種類がたくさんありますが、大きく用途ごとに分類すると、

・HCLK:MCU周りのバスクロック

・FCLK:MCUのコアクロック

・APB1:peripheralは低速の周辺デバイスI2C, USART2/3, SPI2/3, CAN, PWR などで最大42MHz、timer clockはTim2~7用

・APB2:APB1より高速の周辺デバイス用でTIM1, ADC, GPIOなど、timer clockは相対的に高速なTim1用

 

admin

ADCの変換タイミングを外部(Timer)で決めてやる

時間測定が、

STM32での実行時間測定(DWT機能を使う)

でできるようになったので、次は今までfree runだったADCとDMA転送に対してADCの変換タイミングをTimerで決めてやることにします

ただ、この場合に内部のRC発信器使うのでは精度も安定性も確保できないので、幸いNucleoではST-Linkボード上の水晶発信器(8MHz)を取り込むことが可能なので、その信号を基準値として使います

<実行環境>

・M4 MacBook Pro Tahoe

・CubeIDE 1.19.0

・STM32F401re

 

<まずはペリフェラルクロックだけ変えてみた:これ間違い、次の記事でクロック系を記述>

クロックの切り替えは単純でMXのClock Configulationの設定を変えるだけ、System Clock MultiplexerでHSIからHSEを選択するだけ

ちなみにこの状態で同じ条件(128ワードのDMA転送実施)すると、HSI使ってたときにはおよそ94μsが、

504μsに変化、system clockは変化してないのでDMA転送とかは高速のままだろうから10倍遅くはならない

 

<ADCの変換タイミングにtimer 3を使う>

timer2は時間測定用に使っているのでTGO(ADCにトリガーかける機能)があるtimer3を使う

・ADCの設定

変換タイミングExternal Trigger ConversionをTimer3のTGOにするのと、後で述べますがContinuous Conversion ModeをDisabledにするのが大切

 

・Timer3の設定(サンプリング周期を10KHzにするようにしたのとADCの起動用にTrigger Event SelectionをUpdate Eventにする)

・ソースコード(該当部分)

ADCスタートするのと合わせてtimer3もスタートする

 

<実行結果>

なんか変だよね、これが先ほどのContinuous Conversion ModeをDisabledと関連していて、ADCにタイマーで変換タイミング作ってもADCのMXで以下の設定変更しないと機能しない

Continuous Conversion = Disable

実はタイマーでサンプリング周期決めたいのに、これがEnableだと以降のタイマートリガは無視してADCは勝手に変換実行してDMA転送してしまうからこのような値になった訳

でDisableにすると、

128wordsで100μs間隔のサンプリングだと、これで正しい

STM32は多機能で設定次第で如何様にも動作させられるから、それゆえはまりどころも多いということ

 

admin