基於 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. 雲上 App 端設計#
在數據上傳上,我們借助esp8266 模塊 + 點燈科技對 stm32 獲取到的數據進行上傳。對應我們點燈科技 App 上創建了一些數據存儲單元。借助點燈科技 App,我們只需要在右上角點擊編譯按鈕便可以自由添加我們想要的各種器件。
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>
-
連接 wifi 和並連接到點燈科技
(1) 首先定義三個字符數組,加上自己的信息。
//連接信息
char auth[] = "你的密鑰";
char ssid[] = "你的wifi名稱";
char pswd[] = "你的wifi密碼";
(2) 在 void setup () 中調用以下函數,完成對 wifi 和點燈科技服務器的連接
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("get button state: ", 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 端通信設計#
- 我們的需求大致有兩個部分,發送和上傳。經過綜合考慮,決定採取 esp01 和 stm32 交互通信的方式,並採用 CRC 校驗和疊加校驗等方式進行數據校驗。基於此方式,我們在 esp01 端定義了很多數據指令,例如,stm32 向 esp01 發送oxff時,esp01 會回傳獲取到的時間數據(3 位時分秒 + 一位校驗位)。
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 (兩位數據位(溫度、濕度),校驗位(這裡是二者之和))。
Serial_SendByte(0xfe);
Serial_SendByte(tem);
Serial_SendByte(hum);
Serial_SendByte(tem+hum);
四、功能設計#
4.1 DHT11 溫濕度信息獲取#
DHT11是一款數字溫濕度傳感器模塊,其轉換結果可直接通過單總線數字信號輸出,是一種常用的環境溫濕度測量模塊。該模塊採用高質量的溫濕度傳感器,具有高精度、長壽命、穩定性好等特點。它具有快速響應、輸出穩定等特性,可以輕鬆地接口到大多數微控制器上,並通過單總線來與其通信。該模塊除了可以測量環境的溫濕度值之外,還可以設置上下限值,當環境溫度和濕度超出設定的範圍時,會產生報警信號。DHT11 模塊廣泛應用於家居自動化、物聯網、環境監測等領域。
(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,且在兩次獨立的讀時隙之間至少需要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(Serial Data),另一條叫作SCL(Serial Clock)。SDA 線用於數據傳輸,SCL 線則用於時鐘同步,通過這兩條線,設備之間可以進行雙向通信。根據其性質,我們可知它是一種半雙工、同步的通信協議。
(2)根據 MAx30102 的數據手冊,我們收集到了,有關該芯片的一些數據指令,我們使用宏定義將其羅列在此
#define max30102_WR_address 0xAE
#define I2C_WRITE_ADDR 0xAE
#define I2C_READ_ADDR 0xAF
//register addresses
#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 個人中心設計#
個人中心實際上提供了對佩戴者健康信息的實時監測,主要在兩個方面:“脈搏與血氧飽和度監測”、"吸煙監測"。利用到了煙霧模塊和Max30102 模塊,通過對煙霧、脈搏、血氧濃度的定時監測,並一次為參考,判斷佩戴者是否吸煙、心率、血氧是否正常,並將數據實時上傳到雲端,實現對佩戴者健康狀態的實時監測,從何保障佩戴者的身體健康。
(1)我們所使用到的煙霧模塊是 MQ2,該模塊共有 4 個引腳,分別是 VCC、GND、AO、DO,其中 AO 和 DO 分別對應數字輸出和模擬輸出端口,我們使用到的口為 AO 模擬輸出端口。
(2)讀取數據時我們需要將 STM32 的端口(需要選擇支持 ADC 功能的端口)模式配置為模擬輸入模式,然後,STM32 內置 ADC 模塊會自動完成 ADC 模數轉化。最後我們將模擬值讀出,為了將數據轉化為百分比的格式,我們用當前值進行歸一化處理映射到 0~1 之間,乘上 100,最後得到我們想要的百分比格式。
#include "Smoke.h" // Device header
/**
* @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 分鐘,我們可以在點燈科技 APP 或者手環上的個人中心上查看到具體數據。如果這三組數據任何一組數據低於正常值,手環會進行報警,並且將數據發送到雲端,提醒路人和家屬。