STM32 に基づくスマートバンドの設計(lvgl なし)継続更新中#
一、ピン割り当て#
デバイス | リソース | ピン |
---|---|---|
OLED ディスプレイ | ソフト IIC | SCL: PB8 SDA: PB9 |
MPU6050 | ハード IIC | I2C2: SCL: PB10 SDA: PB11 |
MAX30102 | ソフト IIC | SCL: PC14 SDA: PC15 |
DHT11 | Wire | PB6 |
W25Q64 | ハード SPI | SPI1: SS: PA4 SCK: PA5 MOSI: PA7 MISO: PA6 |
ESP01 | Uart | Uart2: esp01(Tx): PA3 esp01(Rx): PA2 |
MQ-2 | ADC/GPIO | ADC: PB0 GPIO: PB1 |
ブザー | GPIO | PB5 |
ボタン (4 つ) | GPIO | PA8 PA9 PA10 PA1 |
この図は設計初稿で使用されたモジュールとマイコンリソースを示しており、実際の設計では完全には適用されていませんが、図中には依然として保持されています。今後機能を追加する際に呼び出されます。
二、設計の可視化操作インターフェース#
- 起動画面
- 時間
- ネットワーク接続情報
- メニューオプション
- バッテリー表示
- メニュー画面
- 天気表示
- 温湿度情報
- 脈拍情報
- 個人センター
- 歩数情報
本プロジェクトでは lvgl グラフィックライブラリ技術を使用せず、ドット描画形式で可視化インターフェース設計を行っています。これは著者の美術技術を試すものであり、この方法は効率が悪く、他の面でも明らかな欠点があるため、使用をお勧めしません。時間が忙しいため、機能は完全には開発されておらず、今後の空き時間に機能を順次補完していきます。
三、クラウド設計#
1. クラウドアプリ端設計#
データアップロードにおいて、esp8266 モジュール + 点灯科技を利用して stm32 から取得したデータをアップロードします。対応する点灯科技アプリ上にいくつかのデータストレージユニットを作成しました。点灯科技アプリを利用することで、右上のコンパイルボタンをクリックするだけで、自由にさまざまなデバイスを追加できます。
2. データアップロードと通信#
2.1 データアップロード#
データアップロードにはesp8266 モジュールとArduino フレームワークを使用し、点灯科技は Arduino 上で esp シリーズとクラウド間のインタラクションをサポートするソフトウェアフレームワークを設計しています。ライブラリ関数を呼び出すだけで、必要な機能を実現できます。
(1)開発環境の準備#
-
まず、esp8266 の開発ボードサポートパッケージを取得する必要があります。ネット上には多くのチュートリアルがあります。このブログを通じて esp8266 サポートパッケージのインストールができます。(13 条消息) Arduino 搭建 Esp8266 开发环境(两种方法)arduino esp8266℡四叶草~のブログ - CSDN ブログ
-
ESP8266 のサポートパッケージをすでにインストールしている場合は、Blinker の公式ライブラリをインストールする必要があります。
- まず「プロジェクト」をクリックし、「ライブラリの読み込み」オプションを見つけてクリックし、ライブラリ管理に入ります。
- このダイアログボックスの入力ボックスにBlinkerと入力し、次にバージョンを選択します。私のバージョンは0.3.9で、インストールをクリックし、しばらく待つと完了します。ここで外部ネットワークを使用しているため、ダウンロード速度が遅くなるので、辛抱強く待ってください。
(2)プログラム設計#
- まず、ヘッダーファイルを含める必要があります。
#define BLINKER_PRINT Serial
#define BLINKER_WIFI
#include <Blinker.h>
-
Wi-Fi に接続し、点灯科技に接続します。
(1) まず、3 つの文字配列を定義し、自分の情報を追加します。
//接続情報
char auth[] = "あなたのキー";
char ssid[] = "あなたのWi-Fi名";
char pswd[] = "あなたのWi-Fiパスワード";
(2) void setup () 内で以下の関数を呼び出し、Wi-Fi と点灯科技サーバーへの接続を完了します。
Blinker.begin(auth, ssid, pswd);
(3)データアップロードを実行するには、まずコンポーネントを作成し、次にデータストレージオブジェクトを作成し、最後にハートビートパケットまたは他の関数内でデータを印刷します。こうすることで、取得したデータは作成したコンポーネントの下に表示され、点灯科技を通じて esp01 にデータを送信することもできます。ここでは、スイッチ点灯機能を実現しました。
注意:コンポーネントオブジェクトを作成する際に渡す文字列は、点灯科技で作成したコンポーネントオブジェクト名と完全に一致する必要があります。そうでないと、必要な機能を実現できません。
// 新しいコンポーネントオブジェクトを作成
BlinkerButton Button1("btn-1");
BlinkerNumber Tem("tem");
BlinkerNumber Hum("hum");
BlinkerNumber Smoke("smoke");
BlinkerNumber Maibo("maibo");
BlinkerNumber Bushu("bushu");
// ボタンが押されるとこの関数が実行されます
void button1_callback(const String & state) {
BLINKER_LOG("ボタンの状態を取得: ", state);
digitalWrite(Esp01_Led, !digitalRead(Esp01_Led));
}
//クラウドストレージ温湿度データ関数
void dataStorage()
{
Blinker.dataStorage("temp_chart", (float)tem);//温度を保存
Blinker.dataStorage("hum_chart", (float)tem);//湿度を保存
Blinker.dataStorage("smoke_chart", (float)smoke);//湿度を保存
Blinker.dataStorage("maibo_chart", (int)maibo);//湿度を保存
Blinker.dataStorage("bushu_chart", (uint32_t)bushu);//湿度を保存
}
//ハートビートパケット
void heartbeat(){
Hum.print((float)hum);
Tem.print((float)tem);
Smoke.print((float)smoke);
Maibo.print((int)maibo);
Bushu.print((uint32_t)bushu);
}
void setup() {
// シリアルポートを初期化
Serial.begin(115200);
#if defined(BLINKER_PRINT)
BLINKER_DEBUG.stream(BLINKER_PRINT);
#endif
// LEDのあるIOを初期化
pinMode(Esp01_Led, OUTPUT);
digitalWrite(Esp01_Led, HIGH);
// blinkerを初期化
Blinker.begin(auth, ssid, pswd);
Button1.attach(button1_callback);
Blinker.attachHeartbeat(heartbeat);
}
2.2 データ通信#
スマートバンド設計システム全体において、使用される esp01 は補助モジュールに過ぎず、実際にデータ取得とインタラクション設計を実行するモジュールは私たちの stm32 です。esp01 はシリアル通信をサポートしているため、シリアルポートを使用して stm32 とデータをインタラクションさせます。このニーズに応じて、データ通信ルールのセットを設計しました。
(1)esp01 側通信設計#
- 私たちのニーズは大きく分けて 2 つの部分、送信とアップロードがあります。総合的に考慮した結果、esp01 と stm32 のインタラクション通信方式を採用し、CRC チェックサムや加算チェックなどの方法でデータ検証を行うことにしました。この方法に基づいて、esp01 側で多くのデータコマンドを定義しました。例えば、stm32 が esp01 に0xffを送信すると、esp01 は取得した時間データ(時分秒 3 桁 + 1 桁のチェックビット)を返します。
void CmdMode::Run(ModeManager *manager){
cmd = 0x00;
if(Serial.available()){
cmd = Serial.read();
}
//時間データコマンドを取得し、esp01は時分秒とチェックビットをstm32に送信します
if(cmd == 0xff){
Serial.write(hour1); //時
Serial.write(minute1); //分
Serial.write(sec1); //秒
Serial.write(hour1 + minute1 + sec1); //チェックビット
}
//温度データコマンドを取得し、温度受信モードに切り替えます
else if(cmd == 0xfe){
Mode *tem_pin;
tem_pin = manager->rd;
manager->setRd(new TemdataMode());
delete tem_pin;
}
//煙データコマンドを取得し、煙受信モードに切り替えます
else if(cmd == 0xfd){
Mode *tem_pin;
tem_pin = manager->rd;
manager->setRd(new SmokedataMode());
delete tem_pin;
}
//温湿度モードを取得し、温湿度受信モードに切り替えます
else if(cmd == 0xfc){
Mode *tem_pin;
tem_pin = manager->rd;
manager->setRd(new MaibodataMode());
delete tem_pin;
//歩数モードを取得し、歩数受信モードに切り替えます
else if(cmd == 0xfb){
Mode *tem_pin;
tem_pin = manager->rd;
manager->setRd(new BushudataMode());
delete tem_pin;
}
}
- データコマンドが多いため、開発に不必要な混乱を引き起こす可能性があります。プログラム設計の過程で、23 種類のオブジェクト指向設計パターンの状態パターンを採用し、異なるコマンドのデカップリングを実現しました。状態切り替えの過程でオブジェクトを作成・破棄する必要がありますが、このパターンを利用することで、プログラムの保守性が大幅に向上し、多くの条件判断を省くことができました。
(2)stm32 側通信設計#
esp01側で設計した通信プログラムに基づいて、stm32 側ではコマンドを使用してデータのアップロードを受信するだけで済み、操作が簡単で便利です。例えば、温湿度情報をアップロードする必要がある場合、stm32 側でコマンド0xfeを送信し、次に送信するデータを esp01 に送信します(2 つのデータビット(温度、湿度)、チェックビット(ここでは 2 つの合計))。
Serial_SendByte(0xfe);
Serial_SendByte(tem);
Serial_SendByte(hum);
Serial_SendByte(tem+hum);
四、機能設計#
4.1 DHT11 温湿度情報取得#
DHT11はデジタル温湿度センサー モジュールであり、その変換結果は単一バスデジタル信号出力を介して直接出力される、一般的な環境温湿度測定モジュールです。このモジュールは高品質の温湿度センサーを採用しており、高精度、長寿命、安定性が良いなどの特徴があります。迅速な応答と安定した出力特性を持ち、大多数のマイクロコントローラーに簡単にインターフェースでき、単一バスを介して通信できます。このモジュールは環境の温湿度値を測定するだけでなく、上限値と下限値を設定することもでき、環境温度と湿度が設定範囲を超えるとアラーム信号が発生します。DHT11 モジュールは家庭自動化、IoT、環境監視などの分野で広く使用されています。
(1)DHT11 はWire 単一バス通信プロトコルを採用しており、半二重、非同期の通信方式です。ホストである STM32 マイコンは、DHT11 にデータ受信の指示を事前に送信する必要があり、その後データ受信を行うことができます。したがって、ドライバプログラムの設計では、STM32 ポートを出力モードに設定する必要があります。ここではプッシュプル出力を設定し、ポートの電平を高電平に設定します。
/**
* @brief DHT11初期化
* @param なし(初期化時に、ピンモードをプッシュプル出力に設定します)
* @retval なし
*/
void DHT11_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = DHT11_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_GPIO, &GPIO_InitStruct);
GPIO_SetBits(DHT11_GPIO, DHT11_PIN);
}
(2)Wire 単一バスでデータを読み取る周期は少なくとも60usであり、2 回の独立した読み取りスロットの間には少なくとも1usの回復時間が必要です。各読み取りスロットはホストによって開始され、少なくとも1us 間バスを引き下げる必要があります。ホストが読み取りスロットを開始した後、単一バスデバイスは 0 または 1 をバス上に送信し始めます。
スレーブが 1 を送信すると、バスは高電平のまま保持されます。0 を送信すると、バスが引き下げられます。0 を送信すると、スレーブはそのスロットの終了後にバスを解放し、プルアップ抵抗によってバスがアイドル高電平状態に戻ります。スレーブから送信されたデータは開始スロットの後に有効時間 15us を保持し、したがってホストは読み取りスロットの間にバスを解放し、スロット開始後の 15us 以内にバスの状態をサンプリングする必要があります。
(3)したがって、プログラム設計では、30us 待機操作を通じて、ホストが読み取るバイトの時間端に遅延します。大体の論理は次のとおりです:ホストが読み取りコマンドの低電平終了を待つ -> 30us 待機 -> 電平を読み取る -> 電平終了を待つ。
/**
* @brief DHT11バイトを読み取る
* @param なし(判断ロジックは:バスが引き下げられた後、30us待機し、その後高電平であれば1、そうでなければ0と判断します)
* @retval なし
*/
uint8_t DHT11_Read_Byte(void)
{
uint8_t i, data = 0;
for (i = 0; i < 8; i++)
{
while (!GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN)); //低電平終了を待つ
Delay_us(30);
if (GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN)) //高電平開始
data |= (1 << (7 - i));
while (GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN)); //高電平終了を待つ
}
return data;
}
(4)DHT11 に電平読み取り指示を送信するには、データラインをRX 受信モードに切り替える必要があります。まず、バス電平を少なくとも 480us引き下げ、その後バスを高電平に戻します。コード内では、この時点で IO ポートモードを再構成し、フローティング入力モードに設定し、データを受信します。
/**
* @brief DHT11データ
* @param なし
* @retval なし
*/
void DHT11_Read_Data(uint8_t* temp, uint8_t* humi)
{
uint8_t data[5], i;
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = DHT11_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_GPIO, &GPIO_InitStruct);
//少なくとも480us引き下げ、RX受信モードに入る
GPIO_ResetBits(DHT11_GPIO, DHT11_PIN);
Delay_us(600);
GPIO_SetBits(DHT11_GPIO, DHT11_PIN);
Delay_us(30);
//データを読み取る際に、GPIOポートを切り替える必要があります
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DHT11_GPIO, &GPIO_InitStruct);
if (!GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN))
{
while (!GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN)); //低電平終了を待つ
while (GPIO_ReadInputDataBit(DHT11_GPIO, DHT11_PIN)); //高電平開始を待つ
for (i = 0; i < 5; i++)
data[i] = DHT11_Read_Byte();
//チェックサムは最初の4バイトデータの合計
if (data[4] == (data[0] + data[1] + data[2] + data[3]))
{
*humi = data[0];
*temp = data[2];
}
}
}
4.2 Max30102 脈拍検出#
Max30102 は高度に統合された精密心拍センサーモジュールであり、独自の光学センサ技術と先進のデジタル信号処理装置を採用しており、人体の心拍数、脈拍酸素飽和度(SpO2)などの生理指標を迅速かつ正確に測定できます。このモジュールは高輝度 LED 光源と光感知器を内蔵しており、非常に短い時間で被験者の皮膚領域の反射光信号を収集し、それを電気信号に変換して出力します。Max30102 は異なるサンプリングレートと解像度設定をサポートし、内蔵の DC 可変増幅器と 8 ビット ADC を介してオンチップデータフィルタリング、キャリブレーションなどの機能を実現しています。このモジュールはI2C インターフェースを提供しており、大多数のマイクロコントローラーに簡単にインターフェースでき、データの取得、閾値の設定、LED の発光制御などの操作が可能です。現在、Max30102 モジュールは健康監視、医療機器、スポーツトレーニングなどの分野で主に使用されています。
(1)I2C(Inter-Integrated Circuit)はシリアル通信バスプロトコルであり、通常は集積回路間の通信に使用されます。これは Philips 社(現在の NXP 社)によって導入され、さまざまな組み込みシステムの通信に一般的に使用されます。I2C プロトコルはデュアルラインを採用しており、一方はSDA(シリアルデータ)、もう一方はSCL(シリアルクロック)と呼ばれます。SDA ラインはデータ転送に使用され、SCL ラインはクロック同期に使用され、これら 2 本のラインを介してデバイス間で双方向通信が行われます。特性から、これは半二重、同期の通信プロトコルであることがわかります。
(2)MAx30102 のデータシートに基づき、このチップに関するいくつかのデータコマンドを収集し、マクロ定義を使用してここに列挙します。
#define max30102_WR_address 0xAE
#define I2C_WRITE_ADDR 0xAE
#define I2C_READ_ADDR 0xAF
//レジスタアドレス
#define REG_INTR_STATUS_1 0x00
#define REG_INTR_STATUS_2 0x01
#define REG_INTR_ENABLE_1 0x02
#define REG_INTR_ENABLE_2 0x03
#define REG_FIFO_WR_PTR 0x04
#define REG_OVF_COUNTER 0x05
#define REG_FIFO_RD_PTR 0x06
#define REG_FIFO_DATA 0x07
#define REG_FIFO_CONFIG 0x08
#define REG_MODE_CONFIG 0x09
#define REG_SPO2_CONFIG 0x0A
#define REG_LED1_PA 0x0C
#define REG_LED2_PA 0x0D
#define REG_PILOT_PA 0x10
#define REG_MULTI_LED_CTRL1 0x11
#define REG_MULTI_LED_CTRL2 0x12
#define REG_TEMP_INTR 0x1F
#define REG_TEMP_FRAC 0x20
#define REG_TEMP_CONFIG 0x21
#define REG_PROX_INT_THRESH 0x30
#define REG_REV_ID 0xFE
#define REG_PART_ID 0xFF
(3)データコマンドセットに基づいて、データライン(SDA)を使用してコマンドを送信し、関数形式に封装します。
void max30102_init(void);
void max30102_reset(void);
u8 max30102_Bus_Write(u8 Register_Address, u8 Word_Data);
u8 max30102_Bus_Read(u8 Register_Address);
void max30102_FIFO_ReadWords(u8 Register_Address,u16 Word_Data[][2],u8 count);
void max30102_FIFO_ReadBytes(u8 Register_Address,u8* Data);
void maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data);
void maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data);
void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led);
4.3 時間と天気の取得#
(1)時間取得#
時間取得に関しては、Blinker 公式ライブラリに封装された API 関数があり、それを呼び出すだけで時間を取得できます。その後、シリアル通信を利用して、時間データをパッケージ化し、STM32 に送信し、最終的に可視化インターフェースに表示します。
//時間データ取得
hour1 = Blinker.hour();
minute1 = Blinker.minute();
sec1 = Blinker.second();
//時間データを返す
if(cmd == 0xff){
Serial.write(hour1);
Serial.write(minute1);
Serial.write(sec1);
Serial.write(hour1 + minute1 + sec1);
}
(2)天気の取得#
4.4 歩数の計算と取得#
4.5 個人センター設計#
個人センターは実際には装着者の健康情報のリアルタイム監視を提供し、主に 2 つの側面に焦点を当てています:「脈拍と血中酸素飽和度の監視」、「喫煙監視」。煙モジュールとMax30102 モジュールを利用し、煙、脈拍、血中酸素濃度の定期的な監視を行い、参考として装着者が喫煙しているか、心拍数や血中酸素が正常かを判断し、データをリアルタイムでクラウドにアップロードし、装着者の健康状態をリアルタイムで監視し、装着者の健康を保障します。
(1)私たちが使用する煙モジュールは MQ2 であり、このモジュールには 4 つのピンがあり、それぞれ VCC、GND、AO、DO です。AO と DO はそれぞれデジタル出力とアナログ出力ポートに対応し、私たちが使用するポートは AO アナログ出力ポートです。
(2)データを読み取る際、STM32 のポート(ADC 機能をサポートするポートを選択する必要があります)モードをアナログ入力モードに設定する必要があります。その後、STM32 内蔵の ADC モジュールが自動的に ADC 変換を完了します。最後にアナログ値を読み取り、データをパーセンテージ形式に変換するために、現在の値を 0〜1 の間に正規化し、100 を掛けて、最終的に希望するパーセンテージ形式を得ます。
#include "Smoke.h" // デバイスヘッダ
/**
* @brief ADC初期化
* @param なし
* @retval なし
*/
void Smoke_Init(void)
{
//関連構造体を定義し、パラメータを設定
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
//ADCとGPIOAのバスを有効にする
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//クロック分周を設定
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//GPIOモードを設定。モードはADC入力
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//
ADC_RegularChannelConfig(ADC1,ADC_Channel_8,1,ADC_SampleTime_55Cycles5);
//ADCの関連設定パラメータを設定
ADC_InitStructure.ADC_Mode =ADC_Mode_Independent; //単独動作モードに設定
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //データを右揃えに指定
//ADC関連のパラメータを設定
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部トリガを使用しない
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //連続変換モードをオフ
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//スキャンモードをオフ
ADC_InitStructure.ADC_NbrOfChannel = 1; //ADCチャンネルを指定
ADC_Init(ADC1,&ADC_InitStructure);
//ADC1を起動
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1); //キャリブレーションレジスタをリセット
while(ADC_GetResetCalibrationStatus(ADC1) == SET); //キャリブレーション完了を待つ
ADC_StartCalibration(ADC1); //キャリブレーション機能を有効にする
while(ADC_GetCalibrationStatus(ADC1) == SET); //有効化完了を待つ
}
/**
* @brief ADCから取得したアナログ値を量子化したデータを取得
* @param なし
* @retval 変換後のADC値
*/
uint16_t Smoke_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE); //ソフトウェアでADC1のクロックを開始
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET); //ADC変換完了を待つ
return ADC_GetConversionValue(ADC1); //ADC変換後の値を返す
}
(3)装着者の健康状態をリアルタイムで監視し、監視頻度が高すぎてメインプログラムの実行をブロックし、スムーズさに影響を与えないように、脈拍、血中酸素濃度、煙の取得タスクを定期的に設定します。設定したサンプリング間隔は 1 分であり、点灯科技アプリまたはバンドの個人センターで具体的なデータを確認できます。これら 3 つのデータのいずれかが正常値を下回ると、バンドはアラームを鳴らし、データをクラウドに送信し、通行人や家族に通知します。