記事の概要
温湿度センサSHT31をSTM32マイコンで使用する方法を説明します
参考資料
既に以下のサイトなどで詳しく解説されているのでご参照ください。正直に言えば、私の記事を読まずに、以下だけを見れば十分だと思います。
本記事は屋上屋を架しているだけの補足情報のようなものと思ってください。
開発環境
- マイコン:STM32 Nucleo F411RE
- 温湿度センサ:SHT31
- 統合開発環境:STM32CubeIDE
これらは秋月電子で購入できます。
I2C通信
CubeIDE設定
SHT31の制御にはI2C通信を使用します。STM32がMaster、SHT31がSlaveです。
CubeIDEは以下の図のようにツールで選択すれば、I2Cの初期設定レジスタを自動的に設定してくれます。
SDAがPB7端子で、SCLがPB6端子です。
I2Cについて詳しくない方がいれば、以下をご参照ください。
SHT31について
SDAとSCL端子はプルアップ抵抗でHigi固定しないといけないですが、SHT31モジュールにはあらかじめ抵抗も実装されているので、こちらで何もする必要はありません。
I2Cモジュールは固有のアドレスを持ち、通信時に指定しないといけません。(アドレスでモジュールを識別できるので、1つのI2Cに複数のI2Cモジュールを接続できます。)
SHT31は0x44と0x45のどちらかを選べます。今回はSHT31のAddress端子をOPENにすることで、0x45を選択します。
注意すべきなのはAddress[7:1]が0x45だということです。Address[0]が1の場合はread通信(MasterがSlaveから受信)、0の場合はwrite通信(MasterがSlaveへ送信)になります。
なのでプログラムで指定するアドレスは、0x45を左に1ビットシフトした0x8A(write)と0x8B(read)になります。
面倒なことに、マニュアルによっては初めから1ビット左シフトしたアドレスが記載されていることもあります。
プログラムがどうしても動かないで悩んでいたら、アドレスの左シフトが不要だったということもあります。
クロックストレッチについて
SHT31にはクロックストレッチ機能があります。
I2Cは基本的にはMasterが送信するクロック信号SCLのタイミングで通信します。
ですが、クロックストレッチという機能を使用すると、Slave側がMaster側のSCLを止めることができます。
SHT31は、測定コマンド発行後、測定完了まで4msから15msの待機時間が必要になります。
クロックストレッチを使わない場合は、測定コマンド発行→測定完了時間待機→読み取りコマンド処理、というフローになります。
測定開始コマンド発行直後に読み取り処理を実行すると、測定値を読み込むことができません。
一方、クロックストレッチを使用すると、測定開始コマンド発行直後に読み取り処理を実行しても、測定完了待機時間の間、SCLを強制的にLOWにしてMaster側(STM32)を止めてくれるので測定ができます。
つまり測定コマンド発行→読み取り処理、というフローになります
ですが、STM32のクロックストレッチが最大13msまでなので、SHT31の低精度測定と中精度測定の待機時間には間に合うのですが、高精度の待機時間15msには間に合いません。
よって、今回はこの機能は使用しないことにしました。
ファームウェア
以下のファームウェアにより温湿度を測定できます。
// 定数
#define SHT31_I2C_ADDR 0x45 //SHT31アドレス
void sht31_oneshot(void)
{
double s_rh = 0; // 湿度
double s_t = 0; // 温度
uint8_t sh31_data[6] = {0};
uint8_t sht31_measure_cmd[2] = {0x24, 00};
// 測定コマンド送信 クロックストレッチ有効 高精度 OneShot
HAL_I2C_Master_Transmit(&hi2c1, SHT31_I2C_ADDR << 1, sht31_measure_cmd, 2, 1000);
// 測定完了待機 高精度15msec以上
HAL_Delay(20);
// 測定値読み出し
HAL_I2C_Master_Receive(&hi2c1, SHT31_I2C_ADDR << 1, sh31_data, 6, 1000);
// 湿度計算
s_rh = 100.0 * (sh31_data[3] << 8 | sh31_data[4]) / 65535.0;
// 温度計算
s_t = -45.0 + 175.0 * (sh31_data[0] << 8 | sh31_data[1]) / 65535.0;
}
STM32のI2Cに使用するHALライブラリについては以下をご参照ください。
今回使用したのは、HAL_I2C_Master_Transmit
関数とHAL_I2C_Master_Receive
関数です。
第2引数にアドレスを1ビット左シフトしたものを代入しています。これらの関数はアドレス0bitの0と1は自動的に付加してくれるので、こちらで設定する必要はありません。
最初に送信する測定コマンドの設定は、ストレッチ機能なしの高精度、1回のみの測定にしました。
測定コマンド送信後、HAL_Delay
関数で20msec待機してから、read関数で値を読み出し、読み出し値から温度と湿度を計算します。
計算式は以下になります。
(補足)タイマを使用した制御
上記のファームウェアでも何の問題もないのですが、個人的な趣味として、待機時間にdelay関数を使用するのが私は好きではありません。
20msecの待期期間には、割り込み処理を除いて、他の処理ができません。
たった20msecの処理中断が問題になることは、ほとんどないかもしれません、
ですが、もし測定の誤差を減らすために、10回の測定の平均値を取ることにしたら200msecを要します。
センサーの数が5個に増えたら1000msecです。 1秒の処理中断は好ましくない時間です。
そこで私はタイマを利用して、以下のフローで測定をします。
ファームウェアは以下になります。
TIM3を1msecごとに割り込みで呼び出すようにして待機時間を計測します。
// 温湿度センサ測定用グローバル変数
double s_rh = 0; // 湿度
double s_t = 0; // 温度
// Timer用グローバル変数
bool sys_timer_flag[TIMER_NUM];
bool sys_timer_limit[TIMER_NUM];
uint16_t sys_timer_count[TIMER_NUM];
// 定数
#define SHT31_I2C_ADDR 0x45 //SHT31アドレス
// Timer総数
#define TIMER_NUM (2)
// SHT31測定完了待機
#define SHT31_STANDBY_TIMER (0)
#define SHT31_STANDBY_LIMIT 20 //20msec
// SHT31測定周期
#define SHT31_CYC_TIMER (1)
#define SHT31_CYC_LIMIT 1000 //1000msec
void sht31_start(void)
{
uint8_t sht31_measure_cmd[2] = {0x24, 00};
// 測定コマンド送信 クロックストレッチ有効 高精度 OneShot
HAL_I2C_Master_Transmit(&hi2c1, SHT31_I2C_ADDR << 1, sht31_measure_cmd, 2, 1000);
// SHT31測定完了待機タイマ開始
sys_timer_limit[SHT31_STANDBY_TIMER] = false;
sys_timer_count[SHT31_STANDBY_TIMER] = 0;
sys_timer_flag[SHT31_STANDBY_TIMER] = true;
}
void sht31_read(void)
{
uint8_t sh31_data[6] = {0};
if(true == sys_timer_limit[SHT31_STANDBY_TIMER])
{
// SHT31測定完了待機タイマ フラグ リセット
sys_timer_limit[SHT31_STANDBY_TIMER] = false;
// 測定値読み出し
HAL_I2C_Master_Receive(&hi2c1, SHT31_I2C_ADDR << 1, sh31_data, 6, 1000);
// 湿度計算
s_rh = 100.0 * (sh31_data[3] << 8 | sh31_data[4]) / 65535.0;
// 温度計算
s_t = -45.0 + 175.0 * (sh31_data[0] << 8 | sh31_data[1]) / 65535.0;
// 次の測定開始
sys_timer_flag[SHT31_CYC_TIMER] = true;
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim3)
{
for (int i = 0; i < TIMER_NUM; i++)
{
if (true == sys_timer_flag[i])
{
sys_timer_count[i] += 1;
}
else
{
sys_timer_count[i] = 0;
}
}
// SHT31測定完了待機用タイマ
if (sys_timer_count[SHT31_STANDBY_TIMER] >= SHT31_STANDBY_LIMIT)
{
sys_timer_count[SHT31_STANDBY_TIMER] = 0;
sys_timer_limit[SHT31_STANDBY_TIMER] = true;
sys_timer_flag[SHT31_STANDBY_TIMER] = false;
}
// SHT31測定周期用タイマ
if (sys_timer_count[SHT31_CYC_TIMER] >= SHT31_CYC_LIMIT)
{
sys_timer_count[SHT31_CYC_TIMER] = 0;
sys_timer_limit[SHT31_CYC_TIMER] = true;
sys_timer_flag[SHT31_CYC_TIMER] = false;
}
}
}
void timer_start(void)
{
if (HAL_TIM_Base_Start_IT(&htim3) != HAL_OK)
{
/* Starting Error */
Error_Handler();
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_I2C1_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
timer_start();
// SHT31測定周期タイマ開始
sys_timer_limit[SHT31_CYC_TIMER] = false;
sys_timer_count[SHT31_CYC_TIMER] = 0;
sys_timer_flag[SHT31_CYC_TIMER] = true;
/* USER CODE END 2 */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(true == sys_timer_limit[SHT31_CYC_TIMER])
{
// SHT31測定周期タイマ フラグ リセット
sys_timer_limit[SHT31_CYC_TIMER] = false;
// SHT31測定開始
sht31_start();
}
sht31_read();
}
/* USER CODE END 3 */
}