1. 项目概述与核心需求解析
这个实战项目主要解决STM32与移动设备间的无线通信需求,通过HC-05蓝牙模块建立连接通道,同时整合了0.96寸OLED显示和DS18B20温度传感器的数据采集功能。整套方案采用STM32CubeMX工具进行底层配置,大幅降低开发门槛。
1.1 硬件架构设计要点
典型硬件连接方案如下:
- STM32F103C8T6最小系统板(核心处理单元)
- HC-05蓝牙模块(UART通信接口)
- 0.96寸OLED显示屏(I2C或SPI接口)
- DS18B20温度传感器(单总线协议)
- ST-Link调试器(程序烧录与调试)
关键提示:HC-05模块建议选择带底板版本,内置电平转换电路可直连3.3V单片机,避免信号电平不匹配问题。
1.2 软件工具链选型
开发环境配置需特别注意版本兼容性:
- STM32CubeMX v6.5.0+(图形化配置工具)
- Keil MDK v5.30+(ARM编译环境)
- STM32 HAL库(与CubeMX版本匹配)
- 手机端蓝牙调试APP(推荐"蓝牙串口助手"类应用)
2. CubeMX工程配置详解
2.1 时钟树配置技巧
以STM32F103C8T6为例,推荐时钟配置方案:
- HSE选择8MHz外部晶振
- PLL倍频至72MHz系统时钟
- APB1分频保持72MHz(不超过36MHz规范)
- APB2分频保持72MHz
c复制// 生成的时钟初始化代码片段
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// HSE配置
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 时钟树配置
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}
2.2 外设接口配置
2.2.1 USART蓝牙通信配置
- 模式选择:异步通信(Asynchronous)
- 波特率:9600(HC-05默认速率)
- 数据位:8bit
- 停止位:1bit
- 无校验位
- 开启全局中断
2.2.2 I2C OLED接口配置
- I2C1模式选择:标准模式(100kHz)
- 引脚分配:
- PB6 -> I2C1_SCL
- PB7 -> I2C1_SDA
- 不开启DMA
2.2.3 DS18B20单总线配置
- 选择任意GPIO(如PA0)
- 配置为推挽输出模式
- 初始输出高电平
3. 外设驱动实现
3.1 OLED显示驱动开发
3.1.1 初始化序列
c复制void OLED_Init(void) {
HAL_Delay(100); // 电源稳定等待
// 初始化命令序列
OLED_WriteCmd(0xAE); // 关闭显示
OLED_WriteCmd(0xD5); // 设置时钟分频
OLED_WriteCmd(0x80);
OLED_WriteCmd(0xA8); // 设置多路复用率
OLED_WriteCmd(0x3F);
OLED_WriteCmd(0xD3); // 设置显示偏移
OLED_WriteCmd(0x00);
OLED_WriteCmd(0x40); // 设置起始行
// ... 更多初始化命令
OLED_WriteCmd(0xAF); // 开启显示
}
3.1.2 汉字显示实现
采用取模软件生成字库:
c复制// 16x16汉字点阵示例
const uint8_t HZ_Test[] = {
0x00,0x40,0x20,0x50,0x90,0x48,0x48,0x40,
0x44,0x5F,0xA4,0x24,0x24,0x24,0x24,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
void OLED_ShowChinese(uint8_t x, uint8_t y, const uint8_t *hz) {
uint8_t i,j;
for(j=0;j<2;j++) {
OLED_SetPos(x,y+j);
for(i=0;i<16;i++)
OLED_WriteData(hz[i+j*16]);
}
}
3.2 DS18B20温度采集实现
3.2.1 单总线时序控制
c复制// 复位脉冲
uint8_t DS18B20_Reset(void) {
uint8_t presence = 0;
DS18B20_DQ_OUT_LOW(); // 拉低总线
HAL_Delay_us(480); // 480us复位脉冲
DS18B20_DQ_OUT_HIGH(); // 释放总线
HAL_Delay_us(60); // 等待60us
presence = DS18B20_DQ_IN(); // 检测存在脉冲
HAL_Delay_us(420); // 等待复位完成
return presence; // 返回0表示设备存在
}
// 写一位数据
void DS18B20_WriteBit(uint8_t bit) {
DS18B20_DQ_OUT_LOW();
HAL_Delay_us(2); // 保持1us以上
if(bit) DS18B20_DQ_OUT_HIGH();
HAL_Delay_us(60); // 保持60us
DS18B20_DQ_OUT_HIGH(); // 释放总线
}
3.2.2 温度读取流程
c复制float DS18B20_GetTemp(void) {
uint8_t tempL, tempH;
int16_t temp;
float temperature;
DS18B20_Reset();
DS18B20_WriteByte(0xCC); // 跳过ROM
DS18B20_WriteByte(0x44); // 启动转换
HAL_Delay(750); // 等待转换完成
DS18B20_Reset();
DS18B20_WriteByte(0xCC); // 跳过ROM
DS18B20_WriteByte(0xBE); // 读取暂存器
tempL = DS18B20_ReadByte(); // LSB
tempH = DS18B20_ReadByte(); // MSB
temp = (tempH << 8) | tempL;
temperature = temp * 0.0625; // 12位分辨率
return temperature;
}
4. 蓝牙通信协议设计
4.1 数据帧格式定义
推荐采用结构化数据帧:
code复制[HEADER][LEN][CMD][DATA][CHECKSUM]
- HEADER: 0xAA 0x55(帧头标识)
- LEN: 数据长度(1字节)
- CMD: 指令类型(1字节)
- DATA: 有效载荷(N字节)
- CHECKSUM: 校验和(1字节)
4.2 数据接收处理
c复制void USART1_IRQHandler(void) {
static uint8_t rxBuffer[32], rxCnt = 0;
uint8_t data;
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) {
data = (uint8_t)(huart1.Instance->DR & 0xFF);
// 简单协议处理示例
if(rxCnt == 0 && data == 0xAA) {
rxBuffer[rxCnt++] = data;
} else if(rxCnt == 1 && data == 0x55) {
rxBuffer[rxCnt++] = data;
} else if(rxCnt > 1) {
rxBuffer[rxCnt++] = data;
// 检查帧完整性
if(rxCnt >= 5 && rxCnt == rxBuffer[2] + 4) {
ProcessFrame(rxBuffer); // 处理完整帧
rxCnt = 0;
}
} else {
rxCnt = 0; // 同步丢失重置
}
}
}
5. 系统整合与调试
5.1 主程序逻辑设计
c复制int main(void) {
HAL_Init();
SystemClock_Config();
// 外设初始化
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
OLED_Init();
DS18B20_Init();
// 显示初始界面
OLED_ShowString(0, 0, "BLE Temp Monitor", 16);
OLED_ShowString(0, 2, "Temp:", 16);
OLED_ShowString(0, 4, "Status:", 16);
while (1) {
// 温度采集与显示
float temp = DS18B20_GetTemp();
char tempStr[16];
sprintf(tempStr, "%.2f C", temp);
OLED_ShowString(40, 2, tempStr, 16);
// 蓝牙发送温度数据
uint8_t buf[16];
int len = sprintf((char*)buf, "TEMP:%.2f\r\n", temp);
HAL_UART_Transmit(&huart1, buf, len, 100);
HAL_Delay(1000); // 1秒更新周期
}
}
5.2 常见问题排查指南
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| OLED不显示 | I2C地址错误 | 尝试0x78或0x7A地址 |
| HC-05无法连接 | 波特率不匹配 | 确认模块与代码波特率一致 |
| DS18B20读数异常 | 上拉电阻缺失 | 增加4.7K上拉电阻 |
| 蓝牙数据乱码 | 地线未共接 | 确保STM32与模块共地 |
| 系统频繁复位 | 电源电流不足 | 使用独立电源或增加电容 |
6. 进阶优化建议
-
低功耗设计:
- 配置STM32进入STOP模式,通过蓝牙事件唤醒
- 调整DS18B20转换分辨率平衡精度与功耗
- OLED采用局部刷新减少功耗
-
协议增强:
- 增加JSON格式数据传输
- 实现AT指令透传模式
- 添加数据加密功能
-
手机APP开发建议:
- Android使用Android Studio开发
- iOS使用CoreBluetooth框架
- 跨平台方案考虑Flutter或React Native
调试心得:使用逻辑分析仪抓取单总线时序能极大提高DS18B20调试效率,特别是检查复位脉冲和位时序的精确度。对于OLED显示异常,建议先用已知正常的测试程序排除硬件问题。
