3. 通信协议设计:I2C、SPI、UART在药盒内部通信中的应用,以及Wi-Fi/蓝牙与手机APP的通信方案
通信协议,说白了就是设备之间交流的「语言」。药盒里那么多模块——主控、传感器、显示、存储、无线模块,它们怎么协同工作?全靠这些协议。
我刚开始做嵌入式那会儿,总觉得协议就是调调库函数。直到有一次,I2C总线上挂了三个从机,死活读不到第二个传感器的数据。查了两天,结果是上拉电阻阻值选错了。嗯,从那以后,我对协议细节再也不敢马虎。
3.1 药盒内部通信:I2C、SPI、UART的选择
药盒内部,主控芯片(比如STM32或ESP32)需要跟多个外设通信。我一般按这个原则选型:
- I2C:适合连接多个低速传感器。比如温湿度传感器、加速度计、EEPROM。两根线(SDA、SCL)就能挂一堆设备,省IO口。
- SPI:适合高速数据传输。比如OLED屏幕、SD卡、Flash芯片。四根线(MOSI、MISO、SCK、CS),速度比I2C快很多。
- UART:适合点对点通信。比如GPS模块、蓝牙模块、调试串口。就两根线(TX、RX),简单可靠。
我的经验法则:传感器用I2C,显示/存储用SPI,模块通信用UART。当然,这不是绝对的,但90%的药盒项目都适用。
3.1.1 I2C:多设备总线,省线但要注意时序
I2C是半双工,主从架构。药盒里我常用它连接温湿度传感器(如SHT30)和实时时钟(如DS3231)。
避坑指南:我曾经在一个项目里,I2C总线上挂了4个设备,结果通信偶尔出错。查了半天,发现是总线电容太大,上拉电阻用了10kΩ,信号上升沿太慢。换成4.7kΩ后问题解决。你想想看,药盒里走线长、设备多,上拉电阻得算一下。
// I2C读取温湿度传感器示例(伪代码)
uint8_t read_sht30(float *temp, float *hum) {
uint8_t data[6] = {0};
// 发送测量命令
i2c_write(0x44, 0x2C, 0x06); // 地址0x44,命令0x2C06
delay_ms(20); // 等待测量完成
// 读取6字节数据
i2c_read(0x44, data, 6);
// 计算温湿度
*temp = -45.0 + 175.0 * (data[0] << 8 | data[1]) / 65535.0;
*hum = 100.0 * (data[3] << 8 | data[4]) / 65535.0;
return 0;
}
3.1.2 SPI:高速传输,适合显示和存储
SPI是全双工,速度可以跑到几十MHz。药盒的OLED屏幕,我习惯用SPI驱动。为什么?因为刷新快,显示动画不卡顿。
我记得有一次,用I2C驱动128x64的OLED,刷新一帧要50ms,显示数字跳动时明显有残影。换成SPI后,刷新时间降到5ms,效果天差地别。
小技巧:SPI的片选信号(CS)一定要用GPIO控制,别偷懒直接接地。否则多个SPI设备挂同一总线时,数据会串扰。我吃过这个亏。
// SPI写入OLED显示缓存示例
void oled_display_buffer(uint8_t *buffer, uint16_t len) {
GPIO_ResetBits(OLED_CS_PORT, OLED_CS_PIN); // 拉低片选
for (uint16_t i = 0; i < len; i++) {
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, buffer[i]);
}
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
GPIO_SetBits(OLED_CS_PORT, OLED_CS_PIN); // 拉高片选
}
3.1.3 UART:简单可靠,调试利器
UART虽然速度慢(通常115200bps),但胜在简单。药盒里,我常用UART连接蓝牙模块(如HC-05)或4G模块。另外,调试时串口打印是救命稻草。
个人习惯:每个药盒项目,我都会预留一个UART口做调试。打印关键状态——比如「药仓1打开」、「服药时间到」、「电量20%」。这样现场出问题时,插上串口线就能定位。
注意:UART的波特率一定要匹配。我曾经把蓝牙模块设成9600,主控用115200,结果通信全是乱码。查了半小时才发现。建议统一用115200,兼容性好。
3.2 与手机APP的通信:Wi-Fi vs 蓝牙
药盒跟手机通信,主流方案就两个:Wi-Fi和蓝牙。怎么选?我一般看场景。
| 特性 | Wi-Fi | 蓝牙(BLE) |
|---|---|---|
| 传输距离 | 几十米(室内) | 10米左右 |
| 功耗 | 高(100mA+) | 低(10mA左右) |
| 传输速率 | 高(Mbps级) | 低(几百Kbps) |
| 联网能力 | 可直接上云 | 需手机中转 |
| 成本 | 稍高 | 较低 |
| 适用场景 | 家庭药盒(需远程监控) | 便携药盒(近场提醒) |
3.2.1 蓝牙方案:低功耗,适合随身药盒
如果药盒是随身携带的,比如一个钥匙扣大小,我推荐BLE(蓝牙低功耗)。为什么?因为省电。一颗CR2032纽扣电池,用BLE能跑半年。
通信流程很简单:药盒作为从机,广播数据;手机APP扫描、连接、读写特征值。我一般定义几个特征值:
- 服药状态:0x2A(可读),返回当前是否已服药
- 提醒设置:0x2B(可写),APP写入下次服药时间
- 电量信息:0x2C(可读/通知),电量低于20%时主动推送
// BLE广播数据包示例(基于ESP32)
static uint8_t adv_data[] = {
0x02, 0x01, 0x06, // Flags: LE General Discoverable
0x03, 0x03, 0xFF, 0x00, // 完整服务UUID(自定义)
0x09, 0xFF, 0x01, 0x02, // 厂商数据:药盒ID=0x0102
0x0A, 0x09, 'P', 'i', 'l', 'l', 'B', 'o', 'x', '1' // 设备名称
};
esp_ble_gap_config_adv_data_raw(adv_data, sizeof(adv_data));
我的建议:蓝牙通信一定要加超时重传机制。手机APP偶尔会卡死或断开,药盒这边如果一直等,就会漏掉服药提醒。我一般设5秒超时,超时后重发3次,还不行就本地蜂鸣器报警。
3.2.2 Wi-Fi方案:远程监控,适合家庭药盒
如果药盒放在家里,需要远程提醒家人(比如老人忘记服药),那Wi-Fi是首选。药盒直接连上家里的路由器,数据上传到云端,手机APP再从云端拉取。
我常用的架构是:
- 药盒(ESP32)通过Wi-Fi连接MQTT服务器
- 定时发布服药状态到主题(如
pillbox/status) - 手机APP订阅该主题,实时接收推送
- 云端记录历史数据,生成服药报告
避坑指南:我曾经遇到一个问题——药盒连上Wi-Fi后,如果路由器重启,它不会自动重连。后来加了Wi-Fi掉线检测,每30秒ping一次网关,不通就重启Wi-Fi模块。你想想看,老人家的路由器说不定什么时候就断电了,自动重连是刚需。
// MQTT发布服药状态示例
void publish_pill_taken(uint8_t slot_id) {
char topic[64];
char payload[32];
sprintf(topic, "pillbox/%s/status", device_id);
sprintf(payload, "{\"slot\":%d,\"taken\":1,\"time\":%lu}", slot_id, time(NULL));
mqtt_client_publish(topic, payload, strlen(payload), 1, 0); // QoS=1, 确保送达
}
3.3 通信协议设计的综合考量
实际项目中,药盒内部和外部通信是联动的。举个例子:
- 主控通过I2C读取温湿度传感器,判断药盒环境是否合适
- 通过SPI驱动OLED显示当前时间和下次服药提醒
- 通过UART连接蓝牙模块,与手机APP配对
- 手机APP通过BLE或Wi-Fi获取数据,远程提醒家人
这里有个关键点:协议转换。主控内部,不同协议的数据要统一格式。我习惯定义一个全局结构体,把所有传感器数据、状态信息打包,然后通过不同接口分发。
// 统一数据格式示例
typedef struct {
float temperature; // 来自I2C传感器
float humidity;
uint8_t pill_taken[4]; // 来自GPIO检测
uint8_t battery_level; // 来自ADC
uint32_t timestamp; // 来自RTC(I2C)
} pillbox_data_t;
// 通过UART发送给蓝牙模块
void send_to_ble(pillbox_data_t *data) {
uart_write_bytes(UART_NUM_1, (uint8_t*)data, sizeof(pillbox_data_t));
}
个人经验:通信协议设计时,一定要考虑容错。药盒是医疗设备,不能因为通信失败就漏掉服药提醒。我每个通信链路都加了CRC校验和重传机制。虽然代码量多了20%,但可靠性提升了不止一个量级。
好了,这一章就到这里。通信协议是药盒的「神经系统」,选对了、用好了,整个系统就稳了一半。下一章我们聊聊电源管理和低功耗设计——这可是药盒能跑多久的关键。