1. はじめに
ygendaです。
仕事の一環として、DHT11という温度・湿度センサーをSTM32マイコン用のNucleoボードのみで測定してみたので、その手順を記すことにしました。
他のブログでは、Arduinoやラズパイを使って測定している記事が多いですが、今回はそれらは用意していません。Nucleoボードしかありません。
2. 用意したもの
・ 評価ボード: Nucleo-F411RE(STM32F411REマイコンが載っている)
・ センサー: DHT11
・ ケーブル: mini USB端子付きのUSBケーブル
・ 開発環境とデザイン環境: IAR Embedded Workbench + STM32CubeMX
IAR Embedded Workbenchは、以下IAR EWARMと称します。
ハード的には、上記画像のみ。あ、これとUSBケーブルがあるけど入れ忘れました(^-^;;)
3. DHT11の各コードの接続先
接続先といっても、電源とGND用のコードは決められた以下のピン固定となります。
・ 電源コード: 5V端子(CN7の18番のピン)
・ CNDコード: GND端子(CN7の20番のピンを選択
残りはデータ用のコードです。これは、汎用のI/Oピンの中から適当に1つ選べばよいです。今回は、以下のようにしました。
・ データ用コード: ポートBの10番(CN10の25番のピン)
こんな感じです。あとは、ノートPCとNucleoをUSBケーブルで接続すれば準備完了です。
4. IAR EWARMプロジェクトの自動生成
まずは、STM32CubeMX上で編集をしてから、IAR EWARMプロジェクトを自動生成します。
4.1 デザイン画面の修正
拡張子.ioc(これがSTM32CubeMXのプロジェクトの拡張子です)のファイルを開きます。新規でも、既存のものでもかまいません。
まずは、Pinout タブを開きます。マイコンのデザイン画面が出ますので、データ用コードに使うと決めたI/Oピンを有効にします。今回はポートBの10番を使うと決めたので、PB10を有効にします。
種別は、GPIO_InputでもGPIO_Outputでもかまいません。いずれにせよ、ソースコードを実装するときに変更しなければなりません。
4.2 動作周波数の設定
いろいろ試してみましたが、8MHz以上を推奨します。
これは、Clock Configurationタブを開いて設定します。以下の画像のようにしました。
4.3 IAR EWARMプロジェクトの自動生成
これは、ツールバーからProjectを選び「Generate Code」を選べばOKです。
このような画面が出ます。Open Projectボタンをクリックすると、IAR EWARMが起動し自動生成されたプロジェクトが開きます。
5. ソースコードの実装
いよいよ、ソースコードの実装です。
さて、今回はArdiunoなどのようにライブラリがついていません。したがって、
・ DHT11 に対して情報提供の要求信号を送る処理
・ DHT11 から送られる信号を解析する処理
を自作する必要があります。
その前に、DHT11のデータシートを確認しましょう。ここでは、URLを記載しておきます。
5.1 DHT11のデータシート
まずは、マイコン->DHT11 の方向で以下のように信号(情報提供要求)を送ります。当然、PB10をOUTPUTモードにしなければなりません。
- 18ms以上のLOWを送る
- DHT11側が応答信号を送るためのスタンバイが整う
- LOWをHIGHを切り替える
- DHT11側が測定結果を含む応答信号を送り始める
となります。
そうすると、DHT11->マイコンの方向で応答信号が送られるので、PB10をINPUTモードに変更して受信します。応答信号の仕様は、以下の通りです。
- 冒頭にHIGHが何μsか送られる(動作周波数次第では、ここは読み取れない。8MHzでは読み取れなかった)
- LOW->HIGHの順で、80μsずつ初期信号が送られる
- 以下のいずれかのLOW->HIGH周期の信号が、合計40周期分送られる
- LOW 50μs、HIGH 26μs (この周期の場合、0のビットと解析する)
- LOW 50μs、HIGH 70μs (この周期の場合、1のビットと解析する)
5.2 実装が必要な処理
さて、プロジェクトの自動生成だけでは作成されなかったものがあります。DHT11の信号解析処理は当然実装が必要ですが、実はそれ以外にもう一つあります。それは、
・ 汎用I/OピンのINPUT,OUTPUTを切り替える処理
です。ピン自体は双方向なのですが、STM32CubeMXってこのモードが動的に切り替わるケースを想定できてないんだよなぁ...(・_・;;)
ないものは仕方ないので、GPIO_Inputで初期化する部分の処理を参考に、汎用でモードを切り替える関数を自作しました。実装対象のソースファイルは、main.cにしました。
関数の実装内容はこんな感じ。
static void GPIO_Change_MODE(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, uint32_t Mode){
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_Pin;
GPIO_InitStruct.Mode = Mode;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
ちなみに、InputはGPIO_MODE_INPUT、OutputはGPIO_MODE_OUTPUT_PP というマクロが用意されています。
したがって、PB10をOutputにしたい場合は
GPIO_Change_MODE(GPIOB, GPIO_PIN_10, GPIO_MODE_OUTPUT_PP);
と記せばよいです。
これを実装したら、いよいよDHT11 との応答を実施する関数の実装です。この関数もmain.cの中で、Read_DHT11() という名前で実装しました。
ここでは、関数の実装内容を公開します。ソースコードの解析は、皆さんでやってみてください。
解析が終わったら、アドレス渡しされたptempに温度、phemiに湿度が格納されて、呼び出し元でも参照できるという仕組みです。
static void Read_DHT11(int *ptemp, int *phemi) {
int i=0, low_time=0, high_time=0;
unsigned char data[40]={0};
GPIO_Change_MODE(GPIOB, GPIO_PIN_10, GPIO_MODE_OUTPUT_PP);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
HAL_Delay(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
GPIO_Change_MODE(GPIOB, GPIO_PIN_10, GPIO_MODE_INPUT);
/* 最初のHIGH, LOW, HIGHは初期信号なので無視する */
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == GPIO_PIN_SET); /* 初期信号の前のHIGH信号受信を読み飛ばし */
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == GPIO_PIN_RESET); /* 初期LOW信号受信が終わるまで待つ */
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == GPIO_PIN_SET); /* 初期HIGH信号受信が終わるまで待つ */
/* LOW信号とHIGH信号の長さでdata[0]~data[39]に0か1を設定する。
* HIGHのほうが長ければ1、短ければ0の信号
*/
for(i = 0; i < 40; i++) {
low_time = high_time = 0;
/* LOW信号の時間計測 */
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == GPIO_PIN_RESET) {
low_time++;
}
/* HIGI信号の時間計測 */
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == GPIO_PIN_SET){
high_time++;
}
if(high_time > low_time) {
data[i] = 1;
}
else {
data[i] = 0;
}
}
/* 湿度 : data[0] ~ data[7]の8ビットを10進数に変換する
* 温度 : data[16] ~ data[23]の8ビットを10進数に変換する
*/
*phemi = data[0] * 128 + data[1] * 64 + data[2] * 32 + data[3] * 16 +
data[4] * 8 + data[5] * 4 + data[6] * 2 + data[7] * 1;
*ptemp = data[16] * 128 + data[17] * 64 + data[18] * 32 + data[19] * 16 +
data[20] * 8 + data[21] * 4 + data[22] * 2 + data[23] * 1;
}
6. 出力結果
あとは、main関数内の無限ループに以下のようなコードを実装して、動作確認しました。
/* main()内の無限ループ */
while (1)
{
int temp, humi;
time_t t;
char *nowtime;
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/* 青いボタン(ユーザボタンB1(PC13(ローアクティブ)と直結)を押したら測定実施 */
while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET);
t = time(NULL) + 3600 * 9;
nowtime = asctime(gmtime(&t));
printf("=========測定開始=========\n");
printf("測定時刻 %s", nowtime);
Read_DHT11(&temp, &humi);
printf("ただいまの温度 %d 度 湿度 %d %% です\n", temp, humi);
printf("=========測定終了=========\n\n");
HAL_Delay(5000); /* 次の測定までは、5秒空ける */
}
ターミナルI/Oでログ取得したときの結果は、以下の通り。
<< ターミナルI/Oのログが開始されました >>
=========測定開始=========
測定時刻 Tue Sep 25 04:08:17 2018
ただいまの温度 29 度 湿度 63 % です
=========測定終了=========
=========測定開始=========
測定時刻 Tue Sep 25 04:08:26 2018
ただいまの温度 29 度 湿度 61 % です
=========測定終了=========
=========測定開始=========
測定時刻 Tue Sep 25 04:08:35 2018
ただいまの温度 29 度 湿度 58 % です
=========測定終了=========
29度...こんな暑い部屋の中で動作確認してたのかっ(°O°/)/
この記事は以上です。皆さんの参考になれば幸いです。