STM32智能药箱DIY全攻略:从元器件选型到APP远程监控(附完整代码)
STM32智能药箱DIY全攻略:从元器件选型到APP远程监控(附完整代码)
几年前,我帮一位独居的长辈整理药箱,发现里面塞满了各种过期和未按时服用的药品,那一刻我意识到,一个简单的提醒功能背后,可能关乎着真实的健康与安全。市面上成熟的智能药箱产品要么价格不菲,要么功能固化,难以满足个性化的用药管理需求。对于嵌入式开发者和创客来说,自己动手打造一个,不仅能精确控制每一个功能细节,更是一次将物联网技术应用于实际生活场景的绝佳实践。
今天,我们就来深入探讨如何基于经典的STM32F103C8T6(俗称“蓝桥杯小蓝板”)和ESP8266 WiFi模块,从零开始构建一个功能完备的智能药箱。这个项目远不止于简单的“定时蜂鸣”,我们将重点攻克压力传感器的精准校准与温漂补偿、ESP8266在复杂家庭WiFi环境下的稳定配网与异常处理,以及如何设计一个简洁高效的手机APP进行远程监控。我会提供完整的Keil工程文件、经过验证的原理图,以及那些在数据手册里找不到的、只有踩过坑才知道的实战技巧。
无论你是想完成一个精彩的毕业设计,还是希望为家人制作一份贴心的健康礼物,这篇文章都将为你提供一条清晰、可落地的技术路径。让我们开始吧。
1. 硬件架构设计与核心元器件选型
搭建一个可靠的硬件平台是项目成功的第一步。智能药箱系统是一个典型的多传感器、多执行器、具备网络通信能力的嵌入式系统,其硬件架构需要兼顾功能、成本、功耗和开发的便利性。
1.1 核心控制器:为什么是STM32F103C8T6?
在众多MCU中,STM32F103C8T6至今仍是许多创客和学生的首选。这并非偶然,而是其在性能、资源、生态和成本之间取得了极佳的平衡。
- 性能与资源足够:基于Cortex-M3内核,主频72MHz,对于处理传感器数据、驱动显示屏、运行轻量级协议栈(如MQTT over WiFi)绰绰有余。它拥有64KB Flash和20KB RAM,足以容纳我们相对复杂的业务逻辑和通信代码。
- 丰富的外设接口:这是我们选择它的关键。项目需要:
- ADC:用于读取压力传感器(HX711)的模拟量或直接读取其数字输出。
- 多个UART:一个用于与ESP8266进行AT指令通信,另一个可以预留用于调试打印。
- I2C:用于连接OLED显示屏、温湿度传感器(如SHT30,精度高于DHT11)。
- GPIO:控制LED指示灯、蜂鸣器、读取按键状态。
- 强大的生态系统:STM32CubeMX可以图形化配置引脚和时钟,生成初始化代码,极大降低底层开发难度。Keil MDK(或免费的STM32CubeIDE)提供了成熟的开发环境。网络上资料、库函数和社区支持极其丰富,遇到问题更容易找到解决方案。
- 极致的性价比:在主流渠道,这款芯片的价格已经非常亲民,使得整个项目的BOM成本可控。
注意:如果你对功耗有极致要求(例如希望药箱仅靠电池长期工作),可以考虑STM32L系列的低功耗型号。但F103对于插电使用的药箱场景,是完全胜任且更易上手的。
1.2 传感器与执行器选型:精度与可靠性的权衡
传感器是系统的“感官”,其选型直接决定了药箱的智能化程度。
1. 药量检测:HX711压力传感器 vs. 光电传感器 药量检测是核心功能。常见方案有压力传感器和光电对管计数。
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| HX711+称重传感器 | 测量药品总重量,通过重量变化推算余量。 | 检测精度高,可感知微小的重量变化;非接触式,不干扰药品;可实现补药提醒。 | 成本相对较高;需要复杂的校准和温漂补偿;安装需要机械结构(托盘)。 | 推荐。适用于片剂、胶囊等规整药品的余量管理。 |
| 光电对管/红外计数 | 药品下落时遮挡光束,进行计数。 | 原理简单,成本低;直接得到片数,直观。 | 安装复杂,对药品形状、下落轨迹要求高;易受灰尘干扰;无法感知药品是否被正确取出(可能卡住)。 | 适用于药丸自动分拣机,在静态药箱中实现难度大。 |
我们选择HX711+铝合金悬臂梁称重传感器。HX711是一款专为电子秤设计的24位A/D转换器芯片,内置低噪声可编程放大器,能直接与称重传感器的桥式电路连接,通过简单的双线串行接口与MCU通信,极大地简化了设计。
2. 环境监测:SHT30温湿度传感器 药品存储对温湿度有要求。DHT11虽然便宜,但响应慢、精度低。SHT30是数字I2C接口传感器,精度高(湿度±2%RH,温度±0.2°C),响应快,且提供工业级可靠性。在代码中,我们可以设置阈值,当环境超出药品存储要求时,通过APP推送告警。
3. 人机交互:0.96寸OLED vs. LCD1602 OLED显示屏自发光、对比度高、可视角度大,且功耗极低。通过I2C驱动,仅需2根信号线,比LCD1602的并口节省大量IO。我们可以显示时间、药品余量、温湿度、WiFi连接状态等信息,界面更美观。
4. 网络连接:ESP-01S WiFi模块 ESP8266系列模块是物联网项目的“明星”。ESP-01S尺寸小巧,价格低廉,通过UART与STM32通信,使用AT指令集。我们需要让它连接家庭WiFi,并将数据上传到我们指定的服务器(可以是私有云、公有云平台如阿里云IoT、OneNET等),同时接收来自APP的指令。
1.3 电源设计:稳定是基石
系统包含数字电路(3.3V)和模拟电路(传感器可能需要5V)。建议采用5V/2A的直流电源适配器作为输入。
- 5V转3.3V:使用AMS1117-3.3线性稳压芯片,为STM32、OLED、ESP-01S等供电。注意在电源入口和芯片电源引脚附近放置足够的滤波电容(如100μF电解电容和0.1μF陶瓷电容)。
- 称重传感器供电:HX711模块通常需要5V供电以获得最佳性能,确保其供电稳定,噪声小,这对测量精度至关重要。
- ESP8266供电:ESP-01S在发射数据时瞬时电流可能超过200mA,必须确保3.3V稳压电路能提供足够的电流,否则会导致模块重启或STM32复位。可以在模块的VCC引脚就近增加一个220μF的钽电容。
一份简化的元器件清单如下(仅供参考,具体可根据设计调整):
- STM32F103C8T6核心板(或最小系统板)
- ESP-01S WiFi模块(带底板)
- HX711模块 + 5kg量程悬臂梁称重传感器
- SHT30温湿度传感器模块
- 0.96寸 I2C OLED显示屏
- 有源蜂鸣器
- 轻触按键 x 3-4个(设置、确认、加、减)
- AMS1117-3.3稳压电路相关元件
- 5V直流电源接口
- 洞洞板或定制PCB
2. 电路设计与PCB布局要点
对于创客项目,使用洞洞板焊接验证原型是快速有效的方法。但如果希望产品更稳定、美观,设计一块PCB是非常值得的。
2.1 关键电路模块设计
1. STM32最小系统 确保复位电路、boot启动模式选择电路(通常将BOOT0下拉接地,从主Flash启动)、8MHz晶振及其负载电容(通常20pF)正确连接。SWD调试接口(SWDIO, SWCLK)务必引出,这是下载和调试的生命线。
2. HX711与称重传感器连接 称重传感器通常有4根线:红(E+)、黑(E-)、白(S+)、绿(S-)。对应连接到HX711模块的E+、E-、A+、A-。HX711模块与STM32的连接仅需2根线:
- DT(数据线) -> 连接至STM32的某个GPIO,如PA0。
- SCK(时钟线) -> 连接至STM32的某个GPIO,如PA1。 HX711的VCC接5V,GND接共地。
3. ESP-01S连接 这是一个典型的UART连接:
- ESP-01S TX -> STM32的UART_RX引脚(如PA3,对应USART2)
- ESP-01S RX -> STM32的UART_TX引脚(如PA2,对应USART2)
- ESP-01S VCC -> 3.3V(确保电流充足)
- ESP-01S GND -> GND
- ESP-01S CH_PD/EN -> 接3.3V(使能)
- ESP-01S GPIO0 -> 悬空或上拉(工作模式),下载固件时需要拉低。
4. I2C总线连接 SHT30和OLED可以挂载在同一个I2C总线上(STM32的PB6-SCL, PB7-SDA)。记得为SDA和SCL线各接一个4.7kΩ的上拉电阻到3.3V。
2.2 PCB布局与布线注意事项
如果设计PCB,以下几点能避免很多后期调试的麻烦:
- 电源分区:模拟部分(称重传感器、HX711)和数字部分(MCU、ESP8266)的电源走线尽量分开,在一点进行单点共地。
- 去耦电容:在每个IC的电源引脚附近(尽可能靠近)放置一个0.1μF的陶瓷电容,用于滤除高频噪声。
- 晶振走线:晶振电路尽量靠近MCU引脚,走线短而粗,下方避免其他信号线穿过,形成“包地”保护。
- ESP8266天线区域:模块上的PCB天线区域下方和周围务必净空,不要铺铜或走线,这是保证WiFi信号强度的关键。
3. 嵌入式软件设计:从驱动到业务逻辑
软件是项目的灵魂。我们将采用模块化设计,确保代码清晰、可维护。
3.1 开发环境与工程搭建
- 安装STM32CubeMX:用于图形化配置引脚、时钟、外设,生成初始化代码框架。
- 安装Keil MDK-ARM:用于编写、编译和调试代码。确保安装了STM32F1的Device Family Pack。
- 使用CubeMX创建工程:
- 选择MCU型号。
- 配置时钟树,将系统时钟设置为72MHz。
- 使能所需外设:ADC、USART2(用于ESP8266)、USART1(用于调试打印)、I2C1、几个GPIO(控制LED、蜂鸣器、按键输入)。
- 配置GPIO引脚模式(推挽输出、上拉输入等)。
- 生成代码,选择MDK-ARM作为工具链。
3.2 核心驱动模块编写
1. HX711驱动程序与校准 这是软件部分第一个难点。HX711的读数会受零点(空载)和满量程(标定重量)影响,且存在温漂。
// hx711.h 部分关键定义
typedef struct {
uint32_t offset; // 零点偏移值
float scale; // 比例系数 (重量/读数)
GPIO_TypeDef* doutPort;
uint16_t doutPin;
GPIO_TypeDef* sckPort;
uint16_t sckPin;
} HX711_t;
// hx711.c
// 读取原始AD值
int32_t HX711_Read(HX711_t* hx) {
uint32_t count = 0;
while (HAL_GPIO_ReadPin(hx->doutPort, hx->doutPin) == GPIO_PIN_SET); // 等待数据就绪
for (uint8_t i = 0; i < 24; i++) {
HAL_GPIO_WritePin(hx->sckPort, hx->sckPin, GPIO_PIN_SET);
delay_us(1); // 短暂延时
count = count << 1;
if (HAL_GPIO_ReadPin(hx->doutPort, hx->doutPin) == GPIO_PIN_SET) {
count++;
}
HAL_GPIO_WritePin(hx->sckPort, hx->sckPin, GPIO_PIN_RESET);
delay_us(1);
}
// 第25个脉冲,选择下一次通道和增益
HAL_GPIO_WritePin(hx->sckPort, hx->sckPin, GPIO_PIN_SET);
delay_us(1);
HAL_GPIO_WritePin(hx->sckPort, hx->sckPin, GPIO_PIN_RESET);
// 将24位有符号数转换为32位有符号数
if (count & 0x800000) {
count |= 0xFF000000;
}
return (int32_t)count;
}
// 校准函数(需要在空载和放置已知重量砝码时调用)
void HX711_Calibrate(HX711_t* hx, float known_weight) {
int32_t raw_zero = 0;
int32_t raw_weight = 0;
// 采集多次空载读数取平均,得到offset
for(int i=0; i<10; i++) {
raw_zero += HX711_Read(hx);
HAL_Delay(50);
}
hx->offset = raw_zero / 10;
// 提示用户放置已知重量的砝码
// ... (通过OLED显示提示)
HAL_Delay(3000);
// 采集多次带载读数取平均
for(int i=0; i<10; i++) {
raw_weight += HX711_Read(hx);
HAL_Delay(50);
}
raw_weight = raw_weight / 10;
// 计算比例系数 scale = known_weight / (raw_weight - offset)
hx->scale = known_weight / (raw_weight - hx->offset);
}
// 获取重量(克)
float HX711_GetWeight(HX711_t* hx) {
int32_t raw = HX711_Read(hx);
float weight = (raw - hx->offset) * hx->scale;
// 可在此处添加软件滤波,如滑动平均滤波
return weight;
}
校准流程:系统上电或通过按键触发校准模式。首先确保药箱空载,执行零点校准。然后提示用户放置一个已知重量的标准物(如一瓶未开封的、重量标识清晰的药瓶),执行满量程校准。将计算出的offset和scale保存到STM32的Flash中(需实现Flash读写函数),以后每次上电直接读取,无需重复校准。
2. ESP8266 AT指令通信与配网 第二个难点是让ESP8266稳定连接网络并处理异常。我们不能简单发送AT+CWJAP就认为万事大吉。
// esp8266.c - 简化版的配网和发送函数
typedef enum {
WIFI_STATE_IDLE,
WIFI_STATE_RESET,
WIFI_STATE_CHECK_AT,
WIFI_STATE_SET_MODE,
WIFI_STATE_SCAN_AP,
WIFI_STATE_CONNECT_AP,
WIFI_STATE_GET_IP,
WIFI_STATE_CONNECT_SERVER,
WIFI_STATE_READY,
WIFI_STATE_ERROR
} WifiState_t;
WifiState_t wifi_state = WIFI_STATE_IDLE;
char ssid[] = "Your_SSID";
char password[] = "Your_PASSWORD";
char server_ip[] = "192.168.1.100"; // 或云平台域名
uint16_t server_port = 8080;
void ESP8266_Task(void) {
static uint32_t retry_timer = 0;
static uint8_t retry_count = 0;
switch(wifi_state) {
case WIFI_STATE_IDLE:
wifi_state = WIFI_STATE_RESET;
break;
case WIFI_STATE_RESET:
UART_SendString(USART2, "AT+RST\r\n");
HAL_Delay(3000); // 等待模块重启
wifi_state = WIFI_STATE_CHECK_AT;
break;
case WIFI_STATE_CHECK_AT:
UART_SendString(USART2, "AT\r\n");
// 在UART中断中解析回复"OK"
// 如果收到OK,则进入下一状态;如果超时,则重试或报错
break;
case WIFI_STATE_SET_MODE:
UART_SendString(USART2, "AT+CWMODE=1\r\n"); // Station模式
break;
case WIFI_STATE_CONNECT_AP:
sprintf(cmd_buffer, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, password);
UART_SendString(USART2, cmd_buffer);
// 等待连接成功回复 "WIFI CONNECTED" 和 "WIFI GOT IP"
// 这里需要设置一个较长的超时(如15秒)
break;
case WIFI_STATE_GET_IP:
UART_SendString(USART2, "AT+CIFSR\r\n");
break;
case WIFI_STATE_CONNECT_SERVER:
sprintf(cmd_buffer, "AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", server_ip, server_port);
UART_SendString(USART2, cmd_buffer);
break;
case WIFI_STATE_READY:
// 连接成功,可以定期发送数据
if(HAL_GetTick() - last_send_time > 5000) { // 每5秒发送一次
ESP8266_SendSensorData();
last_send_time = HAL_GetTick();
}
// 监听服务器下发的指令(如远程提醒、查询状态)
ESP8266_ReceiveProcess();
break;
case WIFI_STATE_ERROR:
// 错误处理,例如重试计数超过阈值后进入深度睡眠或闪烁LED报警
retry_count++;
if(retry_count < 5) {
HAL_Delay(5000);
wifi_state = WIFI_STATE_RESET; // 重试
} else {
// 严重错误,停止尝试
Error_Handler();
}
break;
}
}
// 在UART中断服务函数中解析AT指令回复
void USART2_IRQHandler(void) {
char rx_char;
if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE)) {
rx_char = (char)huart2.Instance->DR;
// 将字符存入环形缓冲区
ring_buffer_write(&uart2_rx_buf, rx_char);
// 简单判断是否收到 "OK" 或 "ERROR"
if(rx_char == '\n') {
// 从缓冲区读取一行进行解析
char line[256];
if(ring_buffer_getline(&uart2_rx_buf, line, sizeof(line))) {
if(strstr(line, "OK")) {
// 根据当前状态机状态,推进到下一状态
if(wifi_state == WIFI_STATE_CHECK_AT) wifi_state = WIFI_STATE_SET_MODE;
// ... 其他状态判断
} else if(strstr(line, "ERROR")) {
wifi_state = WIFI_STATE_ERROR;
} else if(strstr(line, "WIFI CONNECTED")) {
// 连接AP成功
} else if(strstr(line, "+IPD")) {
// 收到服务器数据,进行解析
parse_server_data(line);
}
}
}
}
}
关键点:
- 状态机:使用状态机管理连接流程,逻辑清晰,易于处理超时和重试。
- 超时与重试:每个AT指令发送后都要设置超时。连接AP(
AT+CWJAP)尤其容易因信号弱或密码错误而失败,必须有重试机制。 - 数据解析:在UART中断中及时读取数据,放入缓冲区,在主循环或专门任务中解析,避免阻塞。
- 断线重连:即使在
READY状态,也要定期(如通过AT+CIPSTATUS)检查连接状态,一旦断线,自动触发重连流程。
3. 业务逻辑整合 在主循环中,我们需要协调各个模块:
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
MX_I2C1_Init();
// 初始化各模块
OLED_Init();
HX711_Init(&hx711, GPIOC, GPIO_PIN_0, GPIOC, GPIO_PIN_1);
SHT30_Init();
ESP8266_Init();
// 从Flash读取校准参数
Load_Calibration_From_Flash();
// 创建软件定时器,用于定时任务
uint32_t sensor_read_tick = 0;
uint32_t display_refresh_tick = 0;
while (1) {
uint32_t current_tick = HAL_GetTick();
// 每500ms读取一次传感器
if(current_tick - sensor_read_tick > 500) {
current_weight = HX711_GetWeight(&hx711);
SHT30_Read(&temperature, &humidity);
sensor_read_tick = current_tick;
// 判断余量是否低于阈值
if(current_weight < weight_threshold) {
Trigger_LowStock_Alert();
}
// 判断温湿度是否超标
if(temperature > temp_max_threshold || humidity > humidity_max_threshold) {
Trigger_Env_Alert();
}
}
// 每200ms刷新一次显示(避免OLED残影)
if(current_tick - display_refresh_tick > 200) {
OLED_ShowInfo(current_weight, temperature, humidity, wifi_state);
display_refresh_tick = current_tick;
}
// 处理按键事件(设置时间、阈值、触发校准等)
Key_Process();
// 运行WiFi状态机
ESP8266_Task();
// 处理定时服药提醒(基于RTC或软件计时)
Check_Medicine_Reminder();
HAL_Delay(10); // 适当释放CPU
}
}
4. 云端与APP远程监控方案
设备端的数据需要有一个“归宿”,并能被远程查看和控制。这里提供两种主流思路。
4.1 方案选择:私有化部署 vs. 公有云平台
| 方案 | 实现方式 | 优点 | 缺点 | 适合人群 |
|---|---|---|---|---|
| 私有化部署 | 在家庭局域网内搭建一个服务器(如树莓派),运行简单的TCP/HTTP/MQTT服务端程序。ESP8266直接连接此服务器。 | 数据完全自主可控,无需外网;零费用;网络延迟低。 | 需要一台常开机的设备;需要一定的网络和服务器知识;无法在外网直接访问(需内网穿透)。 | 技术爱好者,注重隐私,有本地服务器。 |
| 公有云平台 | 使用阿里云IoT、腾讯云IoT、OneNET等平台。设备通过MQTT或HTTP协议将数据上报至平台,平台提供数据存储、可视化、告警推送等服务。 | 开箱即用,省去服务器运维;提供稳定可靠的服务和外网访问;功能丰富(数据可视化、规则引擎、APP SDK)。 | 通常有免费额度,超出后可能收费;数据存储在第三方平台。 | 希望快速实现、专注于设备端开发、需要稳定外网访问的用户。 |
对于大多数个人项目,我推荐使用公有云平台的免费套餐,它们足以支撑一个智能药箱的数据量。下面以抽象化的MQTT协议为例,说明数据流。
4.2 数据通信协议设计
我们采用轻量级的MQTT协议进行发布/订阅通信。
- 设备主题(Topic):
device/药箱ID/sensor:设备发布传感器数据到此主题。device/药箱ID/status:设备发布自身状态(如在线、离线、错误)。device/药箱ID/cmd:APP或云端订阅此主题,向设备发送命令。
- 数据格式(JSON):
{
"id": "MED_BOX_001",
"timestamp": 1678886400,
"weight": 125.6,
"temperature": 24.3,
"humidity": 45.2,
"battery": 95
}
- 命令格式(JSON):
{
"cmd": "set_reminder",
"time": "08:00,12:00,20:00",
"enable": true
}
在STM32端,我们需要集成一个轻量级的MQTT客户端库(如 MQTT-C 或自己实现一个最小子集),将ESP8266作为透传模块,通过TCP连接向MQTT Broker(由云平台提供)发送PUBLISH报文和接收SUBSCRIBE的报文。
4.3 简易APP开发思路
开发一个完整的原生APP(Android/iOS)成本较高。对于个人项目,有以下更快捷的方式:
- 微信小程序:利用云开发能力,可以快速搭建界面,通过WebSocket或云函数调用与云端MQTT Broker通信,间接控制设备。界面友好,跨平台。
- Flutter/Dart:编写一个简单的跨平台APP,使用MQTT客户端库(如
mqtt_client)直接连接云端Broker。学习曲线适中,一次编写可编译到Android和iOS。 - 云平台配套APP:许多物联网平台(如OneNET)提供通用的官方APP,可以通过配置数据流和控件,生成简单的监控界面,无需编码。
以Flutter为例,核心代码片段可能如下:
import 'package:mqtt_client/mqtt_client.dart';
class MedicineBoxPage extends StatefulWidget {
@override
_MedicineBoxPageState createState() => _MedicineBoxPageState();
}
class _MedicineBoxPageState extends State<MedicineBoxPage> {
MqttClient client;
double currentWeight = 0.0;
@override
void initState() {
super.initState();
_connectToMqtt();
}
void _connectToMqtt() async {
client = MqttClient('tcp://your-broker-url', 'flutter_client');
client.port = 1883;
client.keepAlivePeriod = 60;
final connMessage = MqttConnectMessage()
.authenticateAs('username', 'password')
.startClean();
client.connectionMessage = connMessage;
try {
await client.connect();
client.subscribe('device/MED_BOX_001/sensor', MqttQos.atMostOnce);
client.updates.listen((List<MqttReceivedMessage<MqttMessage>> c) {
final recv = c[0].payload as MqttPublishMessage;
final jsonString = MqttPublishPayload.bytesToStringAsString(recv.payload.message);
Map<String, dynamic> data = jsonDecode(jsonString);
setState(() {
currentWeight = data['weight'];
});
});
} catch (e) {
print('连接失败: $e');
}
}
void _sendReminderCommand() {
final builder = MqttClientPayloadBuilder();
builder.addString(jsonEncode({
'cmd': 'trigger_reminder_now',
'duration': 10
}));
client.publishMessage('device/MED_BOX_001/cmd', MqttQos.atMostOnce, builder.payload);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('智能药箱')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('当前药量: ${currentWeight.toStringAsFixed(1)} g', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
ElevatedButton(
onPressed: _sendReminderCommand,
child: Text('手动提醒'),
),
],
),
),
);
}
}
5. 系统调试与优化实战
把代码烧录进去只是开始,真正的功夫在调试。
5.1 常见问题与排查
- HX711读数跳动大:
- 检查电源是否稳定,尤其是传感器和HX711的5V供电。
- 确保称重传感器安装稳固,没有机械振动。
- 在代码中增加软件滤波。滑动平均滤波非常有效:
#define FILTER_LEN 10 float weight_buffer[FILTER_LEN] = {0}; uint8_t buffer_index = 0; float GetFilteredWeight(float new_weight) { weight_buffer[buffer_index] = new_weight; buffer_index = (buffer_index + 1) % FILTER_LEN; float sum = 0; for(int i=0; i<FILTER_LEN; i++) { sum += weight_buffer[i]; } return sum / FILTER_LEN; } - 在HX711的电源引脚附近增加更多的去耦电容。
- ESP8266连接WiFi不稳定:
- 检查电源:用万用表测量ESP-01S VCC引脚在发射数据时的电压,不能低于3.0V。如果跌落严重,说明你的3.3V LDO功率不足或布线太细。
- 优化天线:确保模块天线区域远离金属外壳和大的接地铜皮。
- 调整AT指令延时:在发送
AT+RST后,等待足够长的时间(3-5秒)再发送下一条指令。 - 使用更稳定的AT固件:可以考虑给ESP-01S刷写官方最新或经过验证的AT固件。
- STM32莫名复位:
- 检查电源电压。
- 检查复位引脚是否受到噪声干扰。
- 检查堆栈大小是否足够,尤其是使用了printf或较深的函数调用时,可以在启动文件(
startup_stm32f103xe.s)中适当增大堆栈。 - 注意中断服务函数中不要做耗时操作。
5.2 功耗优化(如果考虑电池供电)
如果想让药箱摆脱电源线,需要全面优化:
- MCU睡眠:使用STM32的Stop模式或Standby模式。在无操作时让MCU进入深度睡眠,通过RTC闹钟(定时唤醒)或外部中断(按键按下、传感器事件)唤醒。
- 外设断电:在睡眠前,通过MOS管或电平控制,切断OLED、ESP8266、HX711等非必要外设的电源。
- 间歇性连接:ESP8266仅在需要上报数据或检查命令时才上电连接WiFi,完成后立即断开并进入深度睡眠。
- 降低工作频率:在电池供电时,可以将系统时钟从72MHz降低到更低频率以节省功耗。
5.3 进阶功能拓展
基础功能稳定后,可以尝试增加:
- 语音提醒:加入一个简单的语音合成模块(如SYN6288),在服药时间播放“该吃降压药了”,比蜂鸣器更人性化。
- 人脸识别/指纹识别取药:加入一个简单的摄像头模块或指纹模块,确保只有特定用户才能打开药箱,防止儿童误食。
- 药品信息管理:通过APP扫描药品条形码,自动录入药品名称、功效、禁忌等信息,与药箱数据关联。
- 数据统计分析:在云端记录每次取药的时间和重量,生成服药依从性报告,可供家人或医生查看。
这个项目从硬件选型、电路焊接、驱动编写到网络通信和上层应用,几乎涵盖了嵌入式物联网开发的完整链条。过程中遇到的每一个问题,都是宝贵的经验。我提供的Keil工程和原理图,是一个经过测试的起点,但真正的乐趣和价值在于你根据自己需求进行的修改、优化和创造。当你看到自己制作的药箱成功提醒家人服药,并将数据推送到你手机上的那一刻,那种成就感远非购买一个成品所能比拟。
更多推荐
所有评论(0)