Sodium

自分でプログラミング可能なエフェクターは高価なものが多く(2~3万円)、デジタルエフェクターを始める上でのハードルの一つとなっていると思います。そこで、低価格なデジタルエフェクター入門機の販売に向けて製作したのがこのペダルです。
→【販売ページはこちら】
金属ケースの加工は自分で行うには大変すぎるし、外注すると高額になってしまうので、Elecrowのアクリル加工サービスを利用した筐体となっています。本体は下写真のように基板とアクリル板計7枚から構成されています。アクリルのデータもKiCadで作成しました。(図面データはGitHubへ)


メイン基板は表面実装部品のみElecrowのPCBA(部品実装)サービスを利用しました。詳細は別記事にまとめる予定です。
▽回路図(KiCadデータはGitHubへ)

最低限の機能ということで、ステレオ不可、入出力は単純な1次ローパスフィルタとなっています。レギュレータはそれなりの発熱が予想されたため、抵抗器を入れるといった対策を行っていますが、実測では消費電流150mA程度だったのであまり要らない心配だったかもしれません。
操作系は以前製作したSrempyと同じで、ディスプレイとスイッチ5つという構成です。ポットだとどうしてもフットスイッチとの干渉が気になりますし、ディスプレイを付けたかったのでこの形になりました。
オペアンプはOwm PedalではOPA1678でしたが、入力がRail to Railでないことに気づいていませんでした。やはり許容入力が大きい方がよいので、TLV9062(5V駆動可、入出力Rail to Rail)を使っています。未検証ですがGS8632もよいかもしれません。(TL072等を9V駆動で使い、オーディオコーデックの入出力にカップリングコンデンサを入れる方式が標準的だと思います。少しスペース的に厳しくなりますが…)
オーディオコーデックICはPCM3060を採用しました。CS4270、ES8388を使ったプロトタイプとのノイズ比較データは下図の通りです。GNDにスリットを入れてみましたが、効果の程はわかりません。概ねICのスペック通りの結果なのかなと思います。

コーデックICの検討を進めている時、旭化成の工場火災の影響かコーデックIC全体が品薄となりました。CS4270を使いたかったのですが、入手が難しく採用をあきらめました。PCM3060はリードタイムが比較的短く、ある程度供給は大丈夫だろうと思います。ES8388もコストパフォーマンスがよいので別の機会に使ってみたいところです。
マイコンはSTM32F722で、処理能力やメモリ容量を考えるとそれなりの価格になってしまいますが、ここは妥協できないところです。SAIペリフェラルを使いこなすのはあまり自信がなかったので、いつも通りI2Sを2つ使う方式で通信しています。
見た目はあまり気にしていないのですが、アクリルを使っているおかげで暗いところでは目立ちそうです。赤LEDにPWM出力が設定できないピンを選んでしまったので、ここは変更するかもしれません。

そもそもデジタルエフェクターのプログラミングをやってみたいという人はそんなに多くはないと思います。それでもデジタルエフェクター普及を進めたいので、ほとんど利益はない価格設定です。私自身プログラミングは初心者に毛が生えたようなものなので、うまくいかない部分も出てくるかもしれませんが、できるだけ継続販売していきたいと思います。
STM32 LittlevGLを使う

LittlevGLは、組み込み機器のGUI向けのグラフィックライブラリです。これを使いこなせれば、きれいな画面や使いやすい操作が実現できると思われます。公式の導入方法ではわかりにくい部分があるので、デモの実行方法をまとめておくことにしました。使用したNucleoボードはNUCLEO-F401REです。※メモリ使用量はRAM46kB、FLASH407kBとなったので、それなりに容量が必要となります。
<準備>
まずは以前の記事(STM32 SPI接続タッチパネル付ディスプレイを使う)の手順を行って、ディスプレイの動作確認をします。そしてそのプロジェクトをそのまま利用し、以下の操作をしていきます。
<ダウンロード>
- LittlevGL本体(v6.1.1)
右側の [Clone or download] ボタンからZIPダウンロード、解凍
→フォルダ名「lvgl-master」を「lvgl」へ変更後、プロジェクトのIncフォルダへフォルダごとコピー - LittlevGLサンプル集
右側の [Clone or download] ボタンからZIPダウンロード、解凍
→フォルダ名「lv_examples-master」を「lv_examples」へ変更後、プロジェクトのIncフォルダへフォルダごとコピー
<LittlevGLコンフィグファイル編集>
- lvglフォルダ内のlv_conf_template.hを開き、下記3か所編集
10行目 #if 1 /*Set it to "1" to enable content*/
23行目 #define LV_HOR_RES_MAX (240)
36行目 #define LV_COLOR_16_SWAP 1
→ファイル名「lv_conf_template.h」を「lv_conf.h」へ変更後、プロジェクトのIncフォルダへ移動 - lv_examplesフォルダ内のlv_ex_conf_templ.hを開き、下記2か所編集
9行目 #if 1 /*Set it to "1" to enable content*/
42行目 #define LV_USE_DEMO 1
→ファイル名「lv_ex_conf_templ.h」を「lv_ex_conf.h」へ変更後、プロジェクトのIncフォルダへ移動
※壁紙画像不使用にするとメモリ使用量を減らせます。→44行目 #define LV_DEMO_WALLPAPER 0
<main.cファイル編集>
以前の記事(STM32 SPI接続タッチパネル付ディスプレイを使う)で記述したものは消去し、下記4か所に記載
(参考ページ→How start with the LittlevGL on the Disco F746NG with Mbed)
▼/* USER CODE BEGIN Includes */ の下
#include "ili9341.h"
#include "ili9341_touch.h"
#include "lvgl/lvgl.h"
#include "lv_examples/lv_apps/demo/demo.h"
▼/* USER CODE BEGIN 0 */ の下
lv_disp_buf_t disp_buf; // ディスプレイバッファの構造体
lv_color_t buf_1[ILI9341_WIDTH * 10]; // ディスプレイバッファ1
lv_color_t buf_2[ILI9341_WIDTH * 10]; // ディスプレイバッファ2
lv_disp_drv_t disp_drv; // ディスプレイドライバの構造体
lv_indev_drv_t indev_drv; // タッチパッドドライバの構造体
void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) // 画面描画
{
int32_t w = area->x2 - area->x1 + 1;
int32_t h = area->y2 - area->y1 + 1;
ILI9341_DrawImage(area->x1, area->y1, w, h, (uint16_t*)color_p);
lv_disp_flush_ready(disp_drv); // 描画終了をライブラリへ知らせる
}
bool my_input_read(lv_indev_drv_t * drv, lv_indev_data_t * data) // タッチパッドのタッチ検出とタッチ位置取得
{
static uint16_t x = 0, last_x = 0;
static uint16_t y = 0, last_y = 0;
if(ILI9341_TouchPressed())
{
ILI9341_TouchGetCoordinates(&x, &y);
data->point.x = x;
data->point.y = ILI9341_HEIGHT - y; // 上下逆さま
last_x = data->point.x;
last_y = data->point.y;
data->state = LV_INDEV_STATE_PR; // タッチあり
}
else
{
data->point.x = last_x;
data->point.y = last_y;
data->state = LV_INDEV_STATE_REL; // タッチなし
}
return false;
}
void init() // ディスプレイ初期化
{
ILI9341_Unselect();
ILI9341_TouchUnselect();
ILI9341_Init();
}
my_flush_cb関数に画面描画を行うコードを、my_input_read関数にタッチパッドに関するコードを記述します。他のディスプレイを使う場合でも、ここに該当するコードを記述すればうまく動作するはずです。▼/* USER CODE BEGIN 2 */ の下
init(); // ディスプレイ初期化
lv_init(); // ライブラリ初期化
lv_disp_buf_init(&disp_buf, buf_1, buf_2, ILI9341_WIDTH*10); // ディスプレイバッファ初期化
lv_disp_drv_init(&disp_drv); // ディスプレイドライバ初期化
disp_drv.buffer = &disp_buf; // ディスプレイバッファ登録
disp_drv.flush_cb = my_flush_cb; // 画面描画関数登録
lv_disp_drv_register(&disp_drv); // ディスプレイドライバ登録
lv_indev_drv_init(&indev_drv); // タッチパッドドライバ初期化
indev_drv.type = LV_INDEV_TYPE_POINTER; // 入力タイプをタッチパッドに指定
indev_drv.read_cb = my_input_read; // タッチ検出関数登録
lv_indev_drv_register(&indev_drv); // タッチパッドドライバ登録
demo_create(); // デモ呼び出し
▼/* USER CODE BEGIN 3 */ の下
HAL_Delay(100);
lv_tick_inc(100); // ライブラリへ経過時間を知らせる
lv_task_handler(); // ライブラリ実行(画面更新)
メインループ内で100msごとに画面を更新します。画面描画が速ければもっと短い間隔で更新してもよいでしょう。ライブラリへ経過時間を知らせるのは、アニメーションの動作に関係していると思われます。ビルドには1分程度かかる場合があります。SPIの通信速度が遅いので、アニメーションはさすがに無理がありそうです。狭い範囲の表示更新については特に問題なさそうな感じです。
タグ : マイコン
Owm Pedal H7 ソフトウェア編
自作デジタルエフェクター「Owm Pedal H7」の各設定、内部プログラミングをしていきます。
Owm Pedal H7 ハードウェア編はこちら
内部データはGitHubへ
<開発環境について>
STM32CubeIDEのバージョン1.1.0ではクロック設定がうまく動作しなかったので、STM32CubeMXを併用していました。バージョン1.2.1では修正されているようで、今のところ問題は起きていません。
main.cファイルを「main.cpp」にリネームすればC++を使うことができます。main.cppに追加記載しているのは下記3か所です。主にuser_main.cppを編集することになります。
「/* USER CODE BEGIN Includes */」の下→#include "user_main.hpp"
「/* USER CODE BEGIN 2 */」の下→mainInit();
「/* USER CODE BEGIN 3 */」の下→mainLoop();
コンパイラの最適化設定を高速動作向けに変更しておきます。
Project→Properties→C/C++ Build→Settings→Tool Settingsタブ
→MCU G++ Compiler→Optimization→Optimization level [Optimize for speed (-Ofast)]
<STM32CubeIDE iocファイル設定>
・ST-LINK接続(結線はOwm Pedal ソフトウェア編参照)
Trace and Debug→DEBUG→Debug [Serial Wire]
・処理高速化のためDキャッシュを有効化
System Core→CORTEX_M7→Cortex Interface Settings→CPU DCache : Enabled
・クロック源として外部水晶振動子を使用
System Core→RCC→High Speed Clock (HSE) [Crystal/Ceramic Resonator]
・動作モード設定(480MHz動作のために必要)
System Core→RCC→Power Parameters→Power Regulator Voltage Scale : Power Regulator Voltage Scale 0
▽SPI設定
タッチパネル付ディスプレイの接続はSPIです。詳細は以前の記事(STM32 SPI接続タッチパネル付ディスプレイを使う)をご参照ください。
※通信速度を上げ過ぎるとタッチ位置をうまく取得できないので、本来はディスプレイとタッチを別々のSPIにした方がよいと思われます。
▽SAI設定

Multimedia→SAI1→SAI A→Mode [Asynchronous Slave] I2S/PCM Protocol にチェック
同SAI B→Mode [Synchronous Slave] I2S/PCM Protocol にチェック
SAI A Data Size : 32 Bits
SAI A FIFO Threshold : Half Full
SAI B Audio Mode : Slave Transmit
SAI B Data Size : 32 Bits
SAI B FIFO Threshold : Half Full
NVIC Settingsタブ→SAI1 global interrupt→Enabledにチェック(2020年3月16日追記)
※STM32H7でDMAを利用するのは大変そうなので、今回は利用していません。
→参考ページ:STM32H7でキャッシュ一貫性を保持したDMA転送(Memory-to-Peripheral)
▽クロック設定図

SAIクロックは、オーディオサンプリングレート44.1kHzに対する誤差が少なくなるような設定としました(44.1×2048=90316.8)。
▽全ピン設定図

<信号処理>
Owm Pedalと同じように半分ずつデータ処理しようとしましたが、HalfCpltCallbackが呼び出されないようなので、2つのバッファ配列を準備しました(下図ではaとb、ソースコード内では[0]と[1])。

mainInit処理の中で最初のSAI受信呼び出しを行います(HAL_SAI_Receive_IT)。その後、受信完了時にHAL_SAI_RxCpltCallbackが呼び出され、その中で次の受信とSAI送信(HAL_SAI_Transmit_IT)を行います。送受信にFIFOを設定しておかないと、処理が連続的にならず途切れ途切れになってしまうことがあります。遅延時間(レイテンシ)実測値は、ブロックサイズ32で2.5ms程度でした。まとめて信号処理ではなく、1サンプルずつ処理するようなやり方をすればバッファ配列は1つで済むかもしれません。
<SAIエラー対処>
Owm Pedalのときの反省を生かし、オーディオコーデックV4220Mをマイコンからリセットできるようにしました。mainInit処理内でSAIのエラーが起こった場合、リセット処理するようになっています。ただ、今のところエラーが発生したことがなく、正しい処理なのかわかりません。
<スイッチ・タッチ検出>
SAIの割り込みは32ブロックサイズ(約0.7ms)ごとに起こるので、これをタイマーのように利用しています。割り込みが起こるたびにGPIOの状態をチェックし、スイッチ操作やタッチを検出するというやり方です。タッチ検出時はすぐにタッチ位置を算出したいところですが、SPI通信にそれなりに時間がかかるので、タッチ位置関係処理は割り込み内ではなくメインループ処理内に入れています。現状の操作方法は下記の通りです。
・画面上部:パラメータページ切替、長押しでデータ保存(→STM32 内蔵フラッシュメモリへのデータ保存)
・各ノブ部:上側で増加、下側で減少(長押しで10ずつ変化)
・画面下部:長押しでエフェクト切替
・スイッチ:エフェクトオン・オフ
<エフェクト処理>
とりあえず6つのエフェクト(オーバードライブ、ディレイ、トレモロ、イコライザ、コーラス、リバーブ)を実装していますが、細かい音質については検討していません。今後別記事で紹介していきたいと考えています。
Owm Pedal H7 ハードウェア編はこちら
内部データはGitHubへ
<開発環境について>
STM32CubeIDEのバージョン1.1.0ではクロック設定がうまく動作しなかったので、STM32CubeMXを併用していました。バージョン1.2.1では修正されているようで、今のところ問題は起きていません。
main.cファイルを「main.cpp」にリネームすればC++を使うことができます。main.cppに追加記載しているのは下記3か所です。主にuser_main.cppを編集することになります。
「/* USER CODE BEGIN Includes */」の下→#include "user_main.hpp"
「/* USER CODE BEGIN 2 */」の下→mainInit();
「/* USER CODE BEGIN 3 */」の下→mainLoop();
コンパイラの最適化設定を高速動作向けに変更しておきます。
Project→Properties→C/C++ Build→Settings→Tool Settingsタブ
→MCU G++ Compiler→Optimization→Optimization level [Optimize for speed (-Ofast)]
<STM32CubeIDE iocファイル設定>
・ST-LINK接続(結線はOwm Pedal ソフトウェア編参照)
Trace and Debug→DEBUG→Debug [Serial Wire]
・処理高速化のためDキャッシュを有効化
System Core→CORTEX_M7→Cortex Interface Settings→CPU DCache : Enabled
・クロック源として外部水晶振動子を使用
System Core→RCC→High Speed Clock (HSE) [Crystal/Ceramic Resonator]
・動作モード設定(480MHz動作のために必要)
System Core→RCC→Power Parameters→Power Regulator Voltage Scale : Power Regulator Voltage Scale 0
▽SPI設定
タッチパネル付ディスプレイの接続はSPIです。詳細は以前の記事(STM32 SPI接続タッチパネル付ディスプレイを使う)をご参照ください。
※通信速度を上げ過ぎるとタッチ位置をうまく取得できないので、本来はディスプレイとタッチを別々のSPIにした方がよいと思われます。
▽SAI設定

Multimedia→SAI1→SAI A→Mode [Asynchronous Slave] I2S/PCM Protocol にチェック
同SAI B→Mode [Synchronous Slave] I2S/PCM Protocol にチェック
SAI A Data Size : 32 Bits
SAI A FIFO Threshold : Half Full
SAI B Audio Mode : Slave Transmit
SAI B Data Size : 32 Bits
SAI B FIFO Threshold : Half Full
NVIC Settingsタブ→SAI1 global interrupt→Enabledにチェック(2020年3月16日追記)
※STM32H7でDMAを利用するのは大変そうなので、今回は利用していません。
→参考ページ:STM32H7でキャッシュ一貫性を保持したDMA転送(Memory-to-Peripheral)
▽クロック設定図

SAIクロックは、オーディオサンプリングレート44.1kHzに対する誤差が少なくなるような設定としました(44.1×2048=90316.8)。
▽全ピン設定図

<信号処理>
Owm Pedalと同じように半分ずつデータ処理しようとしましたが、HalfCpltCallbackが呼び出されないようなので、2つのバッファ配列を準備しました(下図ではaとb、ソースコード内では[0]と[1])。

mainInit処理の中で最初のSAI受信呼び出しを行います(HAL_SAI_Receive_IT)。その後、受信完了時にHAL_SAI_RxCpltCallbackが呼び出され、その中で次の受信とSAI送信(HAL_SAI_Transmit_IT)を行います。送受信にFIFOを設定しておかないと、処理が連続的にならず途切れ途切れになってしまうことがあります。遅延時間(レイテンシ)実測値は、ブロックサイズ32で2.5ms程度でした。まとめて信号処理ではなく、1サンプルずつ処理するようなやり方をすればバッファ配列は1つで済むかもしれません。
<SAIエラー対処>
Owm Pedalのときの反省を生かし、オーディオコーデックV4220Mをマイコンからリセットできるようにしました。mainInit処理内でSAIのエラーが起こった場合、リセット処理するようになっています。ただ、今のところエラーが発生したことがなく、正しい処理なのかわかりません。
<スイッチ・タッチ検出>
SAIの割り込みは32ブロックサイズ(約0.7ms)ごとに起こるので、これをタイマーのように利用しています。割り込みが起こるたびにGPIOの状態をチェックし、スイッチ操作やタッチを検出するというやり方です。タッチ検出時はすぐにタッチ位置を算出したいところですが、SPI通信にそれなりに時間がかかるので、タッチ位置関係処理は割り込み内ではなくメインループ処理内に入れています。現状の操作方法は下記の通りです。
・画面上部:パラメータページ切替、長押しでデータ保存(→STM32 内蔵フラッシュメモリへのデータ保存)
・各ノブ部:上側で増加、下側で減少(長押しで10ずつ変化)
・画面下部:長押しでエフェクト切替
・スイッチ:エフェクトオン・オフ
<エフェクト処理>
とりあえず6つのエフェクト(オーバードライブ、ディレイ、トレモロ、イコライザ、コーラス、リバーブ)を実装していますが、細かい音質については検討していません。今後別記事で紹介していきたいと考えています。
Owm Pedal H7 ハードウェア編

STM32F405を搭載した「Owm Pedal」に引き続き、STM32H753とタッチパネル付ディスプレイを備えたデジタルエフェクターを製作しました。今回も穴加工はTabby's工房さんにお願いしました。(Owm Pedal H7 ソフトウェア編はこちら)
▽入出力・電源部 回路図

4回路入りオペアンプOPA1679を使用することにしたので、以前と違い入力部のローパスフィルタが2次になっています。デジタル電源は絶縁型DC-DCコンバータで分離するようにしました。コンバータなしにもできるようにしましたが、ノイズ面でよくないと思われるので非推奨です。
▽マイコン・オーディオコーデック部 回路図

当初はSTM32F722を使っていたのですが、さらなる発展を見据えSTM32H7を使うことにしました。注文時の在庫の関係でSTM32H753にしましたが、STM32H743でも問題ないと思います。今回はオーディオサンプリングレート44.1kHzで、SAIペリフェラルを使います。
▽基板レイアウトについて(KiCadデータはGitHubへ)
上側のマイコン基板は4層にしてみました。JLCPCBの場合、50mm×50mm以内であれば4層でも13ドルで済みます。マイコンは可能な限りピンを引き出していますが、全部は使わないので内側は小さいパッドです。GNDは基本一面プレーンで、基板が2枚に分かれているのでコネクタ部分でGNDを接続することになります。DC-DCコンバータは高さを低くするため寝かせて配置しました。
▽ディスプレイ・スイッチ基板レイアウト

念のためダンピング抵抗を入れています。消費電流がそれなりにあるので、マイコン用とは別に3.3Vを準備しました。ディスプレイの詳細は以前の記事(STM32 SPI接続タッチパネル付ディスプレイを使う)をご参照ください。下写真のように基板を合体させます。

▽周波数特性・ノイズ
ZOOM MS-50Gと特性を比較しました。MS-50GはParaEQを6個並べて+20dBになるよう調節しています。ノイズは1kHz正弦波入力時のものです。

Owm PedalはMS-50Gより高域のノイズが多いように見えますが、MS-50Gはもともと高域が下がっているので、それを加味するとほぼ同じノイズレベルといってよいと思います。Owm Pedal H7では1dB程度ノイズが多いですが原因はよくわかりません。基板設計としては、部品配置に注意すればOwm PedalのようにGNDを1面プレーンにしても問題ないとわかりました。入出力のフィルタも、簡単な1次ローパスフィルタで充分かもしれません。
STM32 内蔵フラッシュメモリへのデータ保存
HALライブラリを利用し、STM32の内蔵フラッシュメモリへデータを保存します。これにより電源を切ってもデータを残したままにすることができます。下記参考ページにも詳細な内容がありますので、併せてご参照ください。
・STM32 + HAL Flashの書き込み・読み込み
フラッシュメモリへのデータ保存時には、単純に書き込み関数を呼び出せばよいわけではなく、あらかじめ書き込む場所を消去しておく必要があります。さらに、消去等の操作前にはロックを解除しなければいけません。つまり下記の流れとなります。
・ロック解除→消去→書き込み→再度ロック
フラッシュメモリはセクタという単位に分割されており、消去操作時にはセクタごとに消去します。STM32F401REのセクタ7の場合、フラッシュメモリの終わり側128kB分が全て消えることになります。
<STM32F4、F7での書き込みサンプルコード>
テストとして配列を書き込みます。NUCLEO-F401REで動作確認しました。セクタの開始アドレスはそれぞれのICのリファレンスマニュアルをご確認ください。
<データ読み出し>
保存したデータを読み出す場合は特別な操作は必要なく、下記のように記載します。
<STM32H7での書き込みサンプルコード>
セクタの他にバンク1と2があるため、バンク指定が追加になっています。また、書き込みはFLASHWORD(256ビット)単位限定で、256ビットのデータを準備しなければいけないようです。データ読み出しについては上記と同じです。NUCLEO-H743ZI2で動作確認しました。
<リンカスクリプトについて>
内蔵フラッシュメモリにはプログラムが書き込まれています。万が一大きなプログラムとなった場合には、データ保存したいフラッシュ領域と重なってしまうおそれがあります。そこで、プログラムの領域とデータ保存の領域を分けておきます。
STM32CubeIDEのプロジェクトフォルダ内に、リンカスクリプトと呼ばれるSTM32○○○○_FLASH.ldというファイルがあります。このファイル内のMEMORYの記述を変更すればOKです。例えばSTM32F401REのセクタ7(開始アドレス0x08060000)をデータ保存領域(DATA)とする場合は下記の記述とします。
・STM32 + HAL Flashの書き込み・読み込み
フラッシュメモリへのデータ保存時には、単純に書き込み関数を呼び出せばよいわけではなく、あらかじめ書き込む場所を消去しておく必要があります。さらに、消去等の操作前にはロックを解除しなければいけません。つまり下記の流れとなります。
・ロック解除→消去→書き込み→再度ロック
フラッシュメモリはセクタという単位に分割されており、消去操作時にはセクタごとに消去します。STM32F401REのセクタ7の場合、フラッシュメモリの終わり側128kB分が全て消えることになります。
<STM32F4、F7での書き込みサンプルコード>
テストとして配列を書き込みます。NUCLEO-F401REで動作確認しました。セクタの開始アドレスはそれぞれのICのリファレンスマニュアルをご確認ください。
const uint32_t flash_addr = 0x08060000; // データ保存先(セクタ7)開始アドレス
uint16_t testData[8] = {1, 3, 5, 7, 2, 4, 6, 8}; // 書き込むデータ
void saveData()
{
/* フラッシュ ロック解除 */
HAL_FLASH_Unlock();
/* フラッシュ 消去 */
FLASH_EraseInitTypeDef erase; // 消去に関する構造体を定義
uint32_t error = 0; // エラーコードを格納する変数
erase.TypeErase = FLASH_TYPEERASE_SECTORS; // 消去方法: セクタ消去
erase.Sector = FLASH_SECTOR_7; // 消去するセクタ: セクタ7
erase.NbSectors = 1; // 消去するセクタの数: 1
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 電圧設定
HAL_FLASHEx_Erase(&erase, &error); // 消去
/* フラッシュ 書き込み */
uint32_t addr = flash_addr; // 書き込み先アドレスを代入
for (int i = 0; i < 8; i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr, testData[i]); // 書き込み
addr += 2; // 2バイト(16ビット)分アドレスを進める
}
/* フラッシュ ロック */
HAL_FLASH_Lock();
}
今回は16ビットのデータを書き込んでいますが、8ビットであれば「FLASH_TYPEPROGRAM_BYTE」を使い、アドレスは1バイトずつ進めます(32ビットであれば「FLASH_TYPEPROGRAM_WORD」を使い、アドレスは4バイトずつ進めます)。<データ読み出し>
保存したデータを読み出す場合は特別な操作は必要なく、下記のように記載します。
uint32_t addr = flash_addr;
testData[0] = *((uint16_t*)addr);
「(uint16_t*)」で書き込み先アドレスをポインタ型へキャストし、そのポインタ(アドレス)にあるデータを「*」で読み出しています。何も書き込まれていない場合は、ビットが全て1のデータ(uint16_tなら65535)が読み出されます。<STM32H7での書き込みサンプルコード>
セクタの他にバンク1と2があるため、バンク指定が追加になっています。また、書き込みはFLASHWORD(256ビット)単位限定で、256ビットのデータを準備しなければいけないようです。データ読み出しについては上記と同じです。NUCLEO-H743ZI2で動作確認しました。
const uint32_t flash_addr = 0x081E0000; // データ保存先(バンク2 セクタ7)開始アドレス
uint16_t testData[16] = {1, 3, 5, 7, 2, 4, 6, 8, 9, 7, 5, 3, 0, 8, 6, 4}; // 書き込むデータ
void saveData()
{
/* フラッシュ ロック解除 */
HAL_FLASH_Unlock();
/* フラッシュ 消去 */
FLASH_EraseInitTypeDef erase; // 消去に関する構造体を定義
uint32_t error = 0; // エラーコードを格納する変数
erase.TypeErase = FLASH_TYPEERASE_SECTORS; // 消去方法: セクタ消去
erase.Banks = FLASH_BANK_2; // 消去するバンク: バンク2
erase.Sector = FLASH_SECTOR_7; // 消去するセクタ: セクタ7
erase.NbSectors = 1; // 消去するセクタの数: 1
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 電圧設定
HAL_FLASHEx_Erase(&erase, &error); // 消去
/* フラッシュ 書き込み */
uint32_t addr = flash_addr; // 書き込み先アドレスを代入
HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, addr, (uint32_t)testData); // 書き込み
addr += 32; // 続けて他のデータを書き込む場合は32バイト(256ビット)分アドレスを進める
/* フラッシュ ロック */
HAL_FLASH_Lock();
}
<リンカスクリプトについて>
内蔵フラッシュメモリにはプログラムが書き込まれています。万が一大きなプログラムとなった場合には、データ保存したいフラッシュ領域と重なってしまうおそれがあります。そこで、プログラムの領域とデータ保存の領域を分けておきます。
STM32CubeIDEのプロジェクトフォルダ内に、リンカスクリプトと呼ばれるSTM32○○○○_FLASH.ldというファイルがあります。このファイル内のMEMORYの記述を変更すればOKです。例えばSTM32F401REのセクタ7(開始アドレス0x08060000)をデータ保存領域(DATA)とする場合は下記の記述とします。
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 384K
DATA (rx) : ORIGIN = 0x08060000, LENGTH = 128K
}
タグ : マイコン