Open32

STM32

nicopinnicopin

マイルストーン1: LED点滅プログラム

目標: ボード上のLEDを一定間隔で点滅させる。
学習内容: マイクロコントローラの基本的な入出力制御、開発環境のセットアップ、プログラムのコンパイルとボードへの書き込み方法。
成果物: 点滅するLED。

マイルストーン2: ボタン入力でLED制御

目標: 外部からのボタン入力によってLEDの点灯/消灯を制御する。
学習内容: 入力ピンの読み取り、イベント駆動プログラミングの基礎。
成果物: ボタン操作に応じて点灯/消灯するLED。

マイルストーン3: シリアル通信

目標: PCとマイクロコントローラ間でシリアル通信を行い、メッセージの送受信をする。
学習内容: シリアル通信の基礎、割り込み、バッファの管理。
成果物: PCからの文字列を受信してLEDを制御し、マイクロコントローラからPCへ状態を報告するプログラム。

マイルストーン4: センサーデータの読み取り

目標: 温度センサーや加速度センサーからデータを読み取り、シリアル通信を通じてPCに送信する。
学習内容: I2CやSPIなどの通信プロトコル、センサーからのデータ読み取りと処理。
成果物: センサーデータを読み取り、PCに表示するプログラム。

マイルストーン5: 簡易的な組み込みアプリケーションの開発

目標: 複数のセンサーからの入力を組み合わせ、簡単なロギングやアラームシステムを開発する。
学習内容: 複数のデバイスの統合、データのロギング、タイマーと時間管理。
成果物: 特定の条件下でアラームを鳴らすシステムや、センサーデータを定期的に記録するシステム。

マイルストーン6: リアルタイムオペレーティングシステム(RTOS)の導入

目標: RTOSを使って、タスクのスケジューリングとリソース管理を行う。
学習内容: マルチタスク処理、リアルタイムオペレーティングシステムの基本、タスク間通信。
成果物: 複数のタスクを同時に実行し、効率的にリソースを管理するプログラム。

nicopinnicopin
nicopinnicopin

Create Project in STM32CubeMX

  • マイコンの種類?を選択
  • 次へ
  • プロジェクトマネジャーのタブでプロジェクト名や保存先を設定
  • (どのモジュールを使うのか?みたいな設定も必要っぽい)
  • Generate Code
    • 足りないものがあるとインストールウィザードが出るのでそれに従う。
  • 正常に完了すると、ここに保存したよとダイアログが出る
nicopinnicopin
nicopinnicopin

ハードウェア
NUCLEO-F446RE
Macbook pro 2023 m2
コネクション
USBハブ:バッファロー、BSH4U120C1 series
USB:amazon basics

Debug

  • Mac左1のポート:Hub先頭
    • ボードLD1が赤く点滅、LD3も点滅、STM32CubeProgrammerでもST-Link configuration以下に情報が表示されず接続不可
  • Mac左1のポート:Hub真ん中
    • 同上
  • Mac左1のポート:Hub最後
    • 同上
  • Mac左2のポート:Hub先頭
    • 同上
  • Mac左1のポート:Hub真ん中
    • 同上
  • Mac左1のポート:Hub最後
    • 同上
  • Mac右1のポート:Hub先頭
    • Working

赤点滅、LD3フラッシュは繋がってない
繋がってるときはどちらも点灯し続ける、なんか書き込んでるとき?またはバイナリを流してないときは緑?で光るのかもしれない(多分ここら辺はドキュメントがあるはずなので探しておくこと

nicopinnicopin

LightingUp LED

  /* USER CODE BEGIN WHILE */
  while (1)
  {
      HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // Led On
	  HAL_Delay(1000); //delay in ms
	  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // Led Off
	  HAL_Delay(1000); //delay in ms

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
nicopinnicopin

Toggle LED

.iocファイルを開いて、NVICタブでenableにチェック

保存してダイアログにOK

変更内容に基づいてライブラリなどをコード生成して書き換えてくれるみたい

MX_GPIO_Initに割り込みについての追記があることを確認

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

受け付けたい操作について追記

/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // Led On
}

/* USER CODE END 4 */
nicopinnicopin

Serial




/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include <string.h>
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define TIMEOUT 100
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */
	char msg[] = "Hello World!\n";
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  HAL_UART_Transmit(&huart2,(const uint8_t*)msg,strlen(msg),TIMEOUT);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

文字列が切れてしまうのはなぜなのか

nicopinnicopin

電源

電源の接続方法
+(プラス)側への接続:Nucleoボードの3V3ピンから出る電源線(ジャンプワイヤー)を、ブレッドボードのプラス側にあるレール(通常、赤い線で示される)に接続します。これが電源のプラス側、つまりVCCになります。
-(マイナス)側への接続:NucleoボードのGND(グラウンド)ピンから出る別の電源線を、ブレッドボードのマイナス側にあるレール(通常、青い線で示される)に接続します。これが電源のマイナス側、つまりGNDになります。

nicopinnicopin

必要な抵抗の計算方法

抵抗(R)の計算にはオームの法則を使用し、次の式を用います。

R = \frac{V_{source} - V_{LED}}{I_{LED}}

ここで、

  • V_{source}は電源の電圧です。
  • V_{LED}はLEDの順方向電圧です。
  • I_{LED}はLEDに流したい電流の大きさです(アンペア単位)。

電源が3.3Vで、LEDの順方向電圧が2.1V、LEDに流したい電流が20mAの場合、必要な抵抗値は以下のように計算されます。

R = \frac{3.3V - 2.1V}{0.02A} = 60\Omega

この計算結果に基づいて、最も近い標準の抵抗値を選択します。

nicopinnicopin

System Core

DMA (Direct Memory Access)

DMAは、データをCPUの介入なしにメモリとペリフェラルデバイス間で直接転送するための機能です。これにより、データ転送中にCPUを他のタスクに使用できるため、システムの効率が向上します。

GPIO (General-Purpose Input/Output)

GPIOは、マイクロコントローラの汎用入出力ポートです。これらのピンは、デジタル信号の入力と出力の両方に使用でき、LEDの点灯、ボタンの読み取り、他のデジタルデバイスとのインターフェースなどに利用されます。

IWDG (Independent Watchdog Timer)

IWDGは、システムが予期せず停止した場合にマイクロコントローラをリセットする独立型のウォッチドッグタイマーです。プログラムが定期的にこのタイマーをリセットしなければ、タイムアウト時にシステムリセットが発生します。

NVIC (Nested Vectored Interrupt Controller)

NVICは、割り込み処理を管理するためのコントローラです。複数の割り込みソースの優先順位を設定し、効率的な割り込み応答と処理を可能にします。

RCC (Reset and Clock Control)

RCCは、マイクロコントローラのリセットとクロック管理を担当します。このユニットを通じて、システムクロック、ペリフェラルクロックの設定や、デバイスのリセットが行われます。

SYS (System Configuration)

SYSコンフィギュレーションは、マイクロコントローラの低レベルのシステム設定を管理します。この部分では、デバイスの電源電圧監視、I/Oポートの設定などが行われます。

WWDG (Window Watchdog Timer)

WWDGは、IWDGと同様にシステムの監視を行いますが、こちらは「ウィンドウ」機能を持っています。特定のウィンドウ期間内でタイマーをリセットする必要があり、それによりより精密なエラー検出が可能になります。

nicopinnicopin

Sensor

ステップ 1: プロジェクトのセットアップ

STM32CubeIDEを開きます。
新しいSTM32プロジェクトを作成します。
「File」→「New」→「STM32 Project」を選択します。
使用しているSTM32マイクロコントローラまたはNucleoボードを選択します。
プロジェクト名を入力し、必要な設定を完了します。

ステップ 2: ハードウェアの接続

DHT22センサーをSTM32マイクロコントローラに接続します。
DHT22のVCCをマイクロコントローラの3.3V出力に接続します。
GNDをマイクロコントローラのGNDに接続します。
DATAピンをマイクロコントローラの任意のGPIOピンに接続します(例: PA0)。
https://www.st.com/ja/evaluation-tools/nucleo-f446re.html#cad-resources
Schematic Packで回路図をチェックしてPA0で検索するとどこが何と繋がってるかわかる

ステップ 3: プロジェクトの設定

GPIOピンの設定。
プロジェクト内で使用しているGPIOピンを入出力として設定します。
USARTを設定してシリアル通信を有効にします。
STM32CubeIDEのプロジェクト設定で、USARTを有効にし、適切なピン割り当てとパラメータを設定します。
ボーレートなどのシリアル通信設定を行います。

ステップ 4: コードの実装

DHT22からのデータ読み取り用のコードを実装します。
DHT22用のライブラリを使用するか、データシートに基づいてデータの読み取りと解析を行う関数を自分で実装します。
シリアル通信を介してデバッグコンソールにデータを出力するコードを実装します。
printfを使用してUSART経由でデータを送信するために、syscalls.cに_write関数の実装を追加することが一般的です。

ステップ 5: プロジェクトのビルドと実行

プロジェクトをビルドします。
STM32CubeIDEで「Project」→「Build Project」を選択します。
プロジェクトをマイクロコントローラにフラッシュします。
プロジェクトをビルドした後、「Run」→「Debug」を選択してプログラムをマイクロコントローラに書き込みます。
シリアルモニタを使用してデータを表示します。
STM32CubeIDEのシリアルモニタを開くか、PuTTYやTera Termなどの外部ツールを使用してシリアルポートに接続し、送信されたデータを確認します。
これらのステップを通じて、DHT22センサーからの温度と湿度のデータを読み取り、デバッグコンソールに出力するプロジェクトを実行できます。

nicopinnicopin

浮動小数点

The float formatting support is not enabled, check your MCU Settings from "Project Properties > C/C++ Build > Settings > Tool Settings", or add manually "-u _printf_float" in linker flags.


エラーメッセージは、GCCコンパイラのデフォルト設定で printf 関数での浮動小数点数のフォーマットがサポートされていないことを示しています。これは、埋め込みシステムでコードサイズを削減するための一般的な最適化ですが、printf を使用して浮動小数点数を出力する必要がある場合は、リンカー設定を変更してこの機能を有効にすることができます。

nicopinnicopin
    while (1) {
        // debug
    	float data = 0.1f;
    	char floatStr[50];  // 十分なサイズの文字列バッファを確保
    	sprintf(floatStr, "%.2f \r\n", data);  // floatStrにデータをフォーマットして格納
    	HAL_UART_Transmit(&huart2, (uint8_t*)floatStr, strlen(floatStr), HAL_MAX_DELAY);
        HAL_Delay(2000);
    }
nicopinnicopin

Screen

シリアル通信を見るにはシリアルモニタが必要
macの場合にはterminalでscreenコマンドを利用して見ることができる

デバイスファイルの確認

ls /dev/tty.*

screen

screen /dev/tty.usbmodem21403 115200

消し方

CTR + A, CTR + \でプロセスを終了するかどうか出るのでyで終了させられる

バックグラウンドのセッションに再接続する

screen -ls
screen -r [セッションIDまたは名前]
nicopinnicopin

ボーレートが一致していないと文字化けする

nicopinnicopin

char[N]

char msg[50]; という宣言は、50バイトの char 型の配列を定義しています。この配列は、最大で49文字の文字列を格納することができます。最後の1バイトは文字列を終了させるためのヌル文字 \0 用に予約されています。

文字列が配列のサイズを超える場合

もし50文字を超える文字列をこの配列に代入しようとすると、いくつかの問題が発生する可能性があります:

バッファーオーバーフロー:

配列のサイズを超えるデータを書き込むことは「バッファーオーバーフロー」と呼ばれるセキュリティリスクを引き起こします。この状況では、隣接するメモリ領域に不正なデータが書き込まれることになり、プログラムの予期せぬ動作やクラッシュ、またはセキュリティ上の脆弱性を引き起こす可能性があります。

データ損失:

配列のサイズを超える部分のデータは単純に切り捨てられ、保存されません。その結果、期待していた文字列が完全には保存されないことになります。
対策
安全な文字列操作関数の使用: strncpy() や snprintf() などの関数を使用して、バッファーのサイズを超えないように制限することが重要です。これらの関数は、指定されたサイズまでのデータのみをコピーまたはフォーマットし、バッファーオーバーフローを防ぎます。例:

char msg[50];
snprintf(msg, sizeof(msg), "This is a long message that will not overflow the buffer");

snprintf() 関数は最大 sizeof(msg)-1 文字を msg に書き込み、最後の文字でヌル終端します。
データ長の事前検証: 文字列の長さを先にチェックし、配列に収まるかどうかを確認することも一つの方法です。

nicopinnicopin

DWT_Init()

μsで計測したい

void DWT_Init(void) {
    if (!(CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk)) {
        CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;  // トレース機能を有効にする
        DWT->CYCCNT = 0;                                // サイクルカウンタをリセット
        DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;            // サイクルカウンタを有効にする
    }
}

なぜ HAL_Init() の後に DWT_Init() を呼び出す必要があるのか?

ハードウェアの依存性: DWT (Data Watchpoint and Trace)ユニットの機能は、デバイスのデバッグ機能に依存しています。CoreDebug ユニットが適切に有効化されていないと、DWT 機能は期待通りに動作しない可能性があります。HAL_Init() は、このような基本的なハードウェア機能の初期設定を行うため、これを先に実行することが重要です。

割り込みとタイマーの初期化: HAL_Init() はシステムの割り込みとタイマーを初期化するため、これが完了した後でないと、DWT->CYCCNT の正確なカウントが保証されません。

SystemClock_Config() の前後について

DWT_Init() を SystemClock_Config() の前に配置しても問題なく機能することが多いですが、DWT->CYCCNT はシステムクロックに依存してカウントされるため、理想的にはシステムクロックが設定された後に DWT を初期化する方が、より確実です。ただし、DWT->CYCCNT の初期化自体はシステムクロックの速度に依存しないため、SystemClock_Config() の前に DWT_Init() を呼び出しても、カウンタが開始されるのに必要な最小限の条件は満たされています。

総じて、HAL_Init() で必要なシステム設定が行われた後に DWT_Init() を呼び出すことが、DWT 機能を確実に利用するための一般的なアプローチです。そして、可能であれば、システムクロックが設定された後に DWT_Init() を配置することを推奨します。

nicopinnicopin

delay_us

void delay_us(uint16_t micros) {
	debugPrint(&huart2, "Delay Start\r\n");
    uint32_t start = DWT->CYCCNT;
    uint32_t end = start + micros * (SystemCoreClock/1000000);
    char startTime[50];
    sprintf(startTime, "Start At: %lu\r\n", start);
    debugPrint(&huart2, startTime);
    char endTime[50];
    sprintf(endTime, "End At: %lu\r\n", end);
    debugPrint(&huart2, endTime);
    if (end < start) {  // Handle overflow.
        while (DWT->CYCCNT > start);
    }
    while (DWT->CYCCNT < end) {
//    	char currentTime[50];
//    	sprintf(currentTime, "CurrentTime: %lu\r\n", DWT->CYCCNT);
//    	debugPrint(&huart2, currentTime);
//    	debugPrint(&huart2, endTime);
    };
}

nicopinnicopin

Pin State


STM32CubeMX(またはSTM32CubeIDEの内蔵コンフィギュレータ)では、各GPIOピンの初期設定を行います。ピンが一つのモード(入力、出力、アナログ、代替機能など)でのみ設定されます。ただし、実際のアプリケーションでは、プログラムの実行中にピンのモードを動的に変更することがしばしば要求されます。

STM32のハードウェア抽象化レイヤー(HAL)や低レベル(LL)ライブラリを使用して、プログラム実行時にピンのモードを変更することができます。例えば、一つのピンを出力モードから入力モードへ、またはその逆へと切り替えることが可能です。

HALライブラリを使用して GPIO_InitTypeDef 構造体を設定し、HAL_GPIO_Init 関数を呼び出すことで、実行時に任意のGPIOピンのモードを変更することができます。

しかし、一つのピンが同時に入力と出力の機能を持つことはできません。そのため、必要に応じてピンのモードをプログラム内で切り替える必要があります。