1.硬件连接
-
RX(Receive):接收数据线,用于从外部设备读取数据。
-
TX(Transmit):发送数据线,用于向外部设备发送数据。
-
连接规则:两个设备的串口需交叉连接,即 A设备的TX → B设备的RX,反之亦然。
stm32 | 蓝牙模块 |
PB10(TX) | RX |
PB11(RX) | TX |
GND | GND |
3.3V | VCC |
2.CubeMX配置
串口配置的关键参数
(1) 波特率(Baud Rate)
-
定义:数据传输速率,单位为波特(Baud),即每秒传输的符号数。
-
常见值:9600、19200、38400、57600、115200 等。
-
注意:通信双方必须设置相同的波特率,否则数据无法正确解析。
(2) 数据位(Data Bits)
-
定义:每个数据帧的有效位数。
-
可选值:5、6、7、8 位(常用 8 位,对应一个字节)。
(3) 停止位(Stop Bits)
-
定义:标志数据帧结束的位数,用于同步。
-
可选值:1、1.5、2 位(常用 1 位)。
(4) 校验位(Parity Bit)
-
定义:用于检测传输错误的校验方式。
-
可选值:
-
None:无校验。
-
Even:偶校验(数据位 + 校验位的 1 的总数为偶数)。
-
Odd:奇校验(数据位 + 校验位的 1 的总数为奇数)。
-
TIM参数
(1)PSC:预分频系数
降低计数频率:将定时器的输入时钟源分频,得到更低的计数频率
扩展定时范围: 通过降低计数频率,定时器计数器(Counter)的每个“步进”时间变长,从而允许在有限的计数器位数(如16位)下实现更长的定时周期。
(2)ARR:自动重装载值
定义计数周期:定时器从 0 开始计数,当达到 ARR 值时触发中断或更新事件,随后自动重置为 0。
控制中断频率:ARR 直接决定了定时器中断的间隔时间。较小的 ARR 值会频繁触发中断,较大的 ARR 值则减少中断频率。
(3)TIM的中断时间T
T=1/F
(4)TIM的中断频率
F=时钟源频率/(预分频系数+1)/(自动重装载值+1)=72 000 000/(PSC+1)/(ARR+1)
时钟源频率:stm32f103c8t6一般默认为72KHz,我们配置的时钟树是多少就是多少(如下图)
使能TIM4
(5) 设置定时时间
需要设置100ms
F=时钟源频率/(预分频系数+1)/(自动重装载值+1)=72 000 000/(PSC+1)/(ARR+1)
=72 000 000 / (720-1)/(10000 -1 )
=10Hz
T=1/F=0.1S=100ms
3.函数发送
printf重定向
添加头文件
#include "stdio.h"
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
使用重定向printf请打开这个
4.函数接收
接收函数封装
//串口封装函数
#define RX_BUF 255 //缓存区的最大容量
uint8_t rxdat[RX_BUF + 1]; //接收数据的存储缓存区
volatile uint16_t rx_count = 0; //记录当前接收到的字节数
uint8_t rxbuff; //临时存储单个接收字节
volatile uint8_t message_processed = 1; //是否接收到正确数据标志位
volatile uint8_t car=0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM4) {
__HAL_TIM_CLEAR_IT(&htim4, TIM_IT_UPDATE);// 清除中断标志
if (!message_processed) {
//错误信息处理区
printf("错误信息:%s\r\n",(char*)rxdat);
//
message_processed = 1;
rx_count = 0;
memset(rxdat, 0, sizeof(rxdat));
}
HAL_TIM_Base_Stop_IT(&htim4);
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART3) {
// 停止定时器并清除中断标志
HAL_TIM_Base_Stop_IT(&htim4);
__HAL_TIM_CLEAR_IT(&htim4, TIM_IT_UPDATE);
HAL_TIM_Base_Start_IT(&htim4);// 重启定时器
if (message_processed) {
rx_count = 0;
memset(rxdat, 0, sizeof(rxdat));
message_processed = 0;
}
if (rx_count >= RX_BUF) {
rx_count = 0;
memset(rxdat, 0, sizeof(rxdat));
printf("[溢出] 缓冲区已满,数据已清空\r\n");
}
rxdat[rx_count++] = rxbuff;
if (rx_count >= 2) {
if (rxdat[rx_count - 2] == '@' && rxdat[rx_count - 1] == '@') {
rxdat[rx_count] = '\0';
//正确信息处理区
printf("接收到的信息:%s\r\n",(char*)rxdat);
if (strcmp((char *)rxdat, "1@@") == 0)
{
car=1;
}
if (strcmp((char *)rxdat, "2@@") == 0)
{
car=2;
}
if (strcmp((char *)rxdat, "3@@") == 0)
{
car=3;
}
if (strcmp((char *)rxdat, "4@@") == 0)
{
car=4;
}
if (strcmp((char *)rxdat, "5@@") == 0)
{
car=5;
}
printf("car:%d\r\n",car);
//
while (HAL_UART_GetState(&huart3) == HAL_UART_STATE_BUSY_TX);// 等待发送完成
// 停止定时器并清除中断标志
HAL_TIM_Base_Stop_IT(&htim4);
__HAL_TIM_CLEAR_IT(&htim4, TIM_IT_UPDATE);
message_processed = 1;
rx_count = 0;
memset(rxdat, 0, sizeof(rxdat));
HAL_UART_Receive_IT(&huart3, &rxbuff, 1);
return;
}
}
HAL_UART_Receive_IT(&huart3, &rxbuff, 1);
}
}
5.main.c主函数初始化
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);//PA0
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);//PA1
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_3);//PA2
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_4);//PA3
HAL_UART_Receive_IT(&huart3,&rxbuff,1);
//确保 UART 中断优先级高于定时器中断,避免 UART 回调被定时器中断抢占:
HAL_NVIC_SetPriority(USART3_IRQn, 0, 0);
HAL_NVIC_SetPriority(TIM4_IRQn, 1, 0);
HAL_TIM_Base_Start_IT(&htim4);
6.手机端操作
(1)下载手机APP:蓝牙调试器(波特率默认9600,配置CubeMX时一定不要搞错)
(2) 连接蓝牙
接通电源,打开蓝牙和APP,点击+号可以进行连接(搜索不到就点击中下进行刷新) ,如需要匹配一般为0000或者1234,连接成功如下图
在设置里面可以更改蓝牙的名称
字符编码格式:GB22312(这里需要和代码里面的编码格式一致,否则会乱码)
切换到按钮控制界面,打开编辑模式,点击按钮
可以选择按下或者松开发送数据,发送的数据自定义,我这里根据代码检测末尾是否是@@作为数据的正确性(可以根据代码自行修改)
编辑完按键之后将编辑模式关闭
点击编辑过的按钮(我这里以上面编辑的两个按钮做示范)
这个返回信息和代码里面是相匹配的
对于历史信息查看可以点击对话模式可以看到发送和接收的全部信息
这样我们就完成蓝牙模块的通信