1. 蓝牙的特点
蓝牙模块采用的 TI 公司设计的 CC2541芯片,主要面向低功耗蓝牙通信方案,该模块的工作频段为 2.4Ghz,这个频段属于国际通用频段注意:蓝牙集成了一个状态指示灯,LED灯如果均匀慢速闪烁,就表示蓝牙未连接,如果LED灯常亮,表示已连接
2. 蓝牙的模式
蓝牙具有两种工作模式,一种是 AT 指令模式,一种是 数据透传模式,两种模式的特点如下
a. AT 指令模式AT指令模式指的是蓝牙未连接的工作模式,在该模式可以获取或配置蓝牙的参数(蓝牙名字、蓝牙密码、蓝牙地址......),需要利用固定的 AT 指令,注意不同公司设计的蓝牙模块的AT 指令大同小异,具体指令参考手册
代码里面:换行字符结尾 (\r\n) 串口调试助手里面:回车结尾
练习:编写程序,利用电脑的串口调试助手发送对应的 AT 指令,设置蓝牙模块的参数信息如果USART1的波特率和USART3不一致,就可能会出现丢包,两种解决方法:
(1) 波特率设置一致
(2) 把接收的数据都接收完,再发送给其他串口
usart.h
#ifndef __USART_H__ #define __USART_H__ #include "stm32f4xx.h" #include "stdio.h" /* 串口1的初始化函数 @baudrate:串口传输的波特率 */ void USART1_Init(int baudrate); /* 串口3的初始化函数 @baudrate:串口传输的波特率 */ void USART3_Init(int baudrate); // 发送一个字节 void USART_SendByte(USART_TypeDef *USARTx, uint16_t data); // 发送一个字符串 void USART_SendDatas(USART_TypeDef *USARTx, const char *str); // 蓝牙的初始化,只需要调用一次 // void BLE_Init(void); // 将printf函数重定向到串口 注意:windows 中换行符为 \r\n int fputc(int c, FILE *stream); #endif
usart.c
#include "usart.h" #include "systick.h" /* 串口1的初始化函数 @baudrate:串口传输的波特率 */ void USART1_Init(int baudrate) { // (1) GPIO 引脚初始化 // a. 使能 GPIO 分组时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // b. 初始化 GPIO // PA9 Tx PA10 Rx GPIO_InitTypeDef g; g.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; g.GPIO_Mode = GPIO_Mode_AF; g.GPIO_Speed = GPIO_Speed_2MHz; g.GPIO_OType = GPIO_OType_PP; // 输出推挽 g.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &g); // c. 配置GPIO复用功能 GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); // (2) USART配置 // a. 使能 USART 分组时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // b. 初始化配置USART USART_InitTypeDef u; // 波特率 u.USART_BaudRate = baudrate; // 指定数据帧数据位,也就是传输字长 = 数据位数 + 校验位数 u.USART_WordLength = USART_WordLength_8b; // 指定停止位长度 u.USART_StopBits = USART_StopBits_1; // 指定校验方式 不要校验 u.USART_Parity = USART_Parity_No; // 指定串口模式 全双工 u.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 指定硬件控制流 不要硬件流控 u.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init(USART1, &u); // (3) 中断配置 // a. 中断源的控制(中断控制位使能) USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // b. 配置NVIC NVIC_InitTypeDef v; v.NVIC_IRQChannel = USART1_IRQn; v.NVIC_IRQChannelPreemptionPriority = 2; v.NVIC_IRQChannelSubPriority = 2; v.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&v); // (4) 使能串口 USART_Cmd(USART1, ENABLE); } /* 串口3的初始化函数 @baudrate:串口传输的波特率 */ void USART3_Init(int baudrate) { // (1) GPIO 引脚初始化 // a. 使能 GPIO 分组时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); // b. 初始化 GPIO // USART3_TX --- PB10 USART3_RX --- PB11 GPIO_InitTypeDef g; g.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; g.GPIO_Mode = GPIO_Mode_AF; g.GPIO_Speed = GPIO_Speed_2MHz; g.GPIO_OType = GPIO_OType_PP; // 输出推挽 g.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOB, &g); // c. 配置GPIO复用功能 GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3); GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3); // (2) USART配置 // a. 使能 USART 分组时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); // b. 初始化配置USART USART_InitTypeDef u; // 波特率 u.USART_BaudRate = baudrate; // 指定数据帧数据位,也就是传输字长 = 数据位数 + 校验位数 u.USART_WordLength = USART_WordLength_8b; // 指定停止位长度 u.USART_StopBits = USART_StopBits_1; // 指定校验方式 不要校验 u.USART_Parity = USART_Parity_No; // 指定串口模式 全双工 u.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 指定硬件控制流 不要硬件流控 u.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init(USART3, &u); // (3) 中断配置 // a. 中断源的控制(中断控制位使能) USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); // b. 配置NVIC NVIC_InitTypeDef v; v.NVIC_IRQChannel = USART3_IRQn; v.NVIC_IRQChannelPreemptionPriority = 2; v.NVIC_IRQChannelSubPriority = 2; v.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&v); // (4) 使能串口 USART_Cmd(USART3, ENABLE); } // 发送一个字节 void USART_SendByte(USART_TypeDef *USARTx, uint16_t data) { // 发送 USART_SendData(USARTx, data); // 等待发送寄存器为空事件产生 while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) != SET); // 清除发送寄存器为空事件标志 USART_ClearFlag(USARTx, USART_FLAG_TXE); } // 发送一个字符串 void USART_SendDatas(USART_TypeDef *USARTx, const char *str) { const char *s = str; while (*s) { USART_SendByte(USARTx, *s); s++; } } /* 串口1 的中断服务函数 */ void USART1_IRQHandler(void) { // 如果产生了接收中断 RXNE 事件产生 if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { uint8_t RecvData = USART_ReceiveData(USART1); USART_SendByte(USART3, RecvData); // 清除中断标志 USART_ClearITPendingBit(USART1, USART_IT_RXNE); } } /* 串口3 的中断服务函数 */ void USART3_IRQHandler(void) { // 如果产生了接收中断 RXNE 事件产生 if (USART_GetITStatus(USART3, USART_IT_RXNE) == SET) { uint8_t RecvData = USART_ReceiveData(USART3); USART_SendByte(USART1, RecvData); // 清除中断标志 USART_ClearITPendingBit(USART3, USART_IT_RXNE); } } // 蓝牙的初始化,只需要调用一次 //void BLE_Init(void) { // // USART_SendDatas(USART3, "AT\r\n"); // 发送测试指令 // delay_ms(500); // // USART_SendDatas(USART3, "AT+NAME666\r\n"); // 设置蓝牙名称 // delay_ms(500); // // USART_SendDatas(USART3, "AT+LADDR\r\n"); // 获取蓝牙地址 // delay_ms(500); // // USART_SendDatas(USART3, "AT+RESET\r\n"); // 复位蓝牙重启 // delay_ms(500); // // printf("ble init success\r\n"); //} // 将printf函数重定向到串口 注意:windows 中换行符为 \r\n int fputc(int c, FILE *stream) { USART_SendData(USART1, c & 0xFF); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 清除发送寄存器为空事件标志 USART_ClearFlag(USART1, USART_FLAG_TXE); return 0; }
main.c
#include "stm32f4xx.h" #include "usart.h" int main(void) { NVIC_PriorityGroupConfig(2); USART1_Init(9600); USART3_Init(9600); while (1); }
b. 数据透传模式
数据透传模式指的是蓝牙已经被手机连接,该模式下蓝牙就相当于一根透明的串口线,蓝牙只负责把数据发送到目的地,不对数据进行处理
练习:编写代码,把超声波获取的距离每隔 2000 ms 发送到手机端进行查看#include "stm32f4xx.h" #include "usart.h" #include "systick.h" #include "HC_SR04.h" #include "systick.h" int main(void) { NVIC_PriorityGroupConfig(2); USART3_Init(9600); HC_SR04_Init(); while (1) { uint32_t distance = HC_SR04_GetDistance(); // 方法一:重定向串口3 printf // 方法二: char res[50] = {0}; sprintf(res, "distance = %d mm", distance); USART_SendDatas(USART3, res); delay_ms(2000); } }
扩展:
(1) 编写代码,可以利用超声波检测障碍物的距离,根据不同的距离进行提示,如果距离小于 30cm 则蜂鸣器叫的声音频率较高,如果距离大于 30cm 并小于 60 cm,则蜂鸣器声音较小,如果距离大于60cm,则蜂鸣器不响。可以利用手机 APP 设置距离的上下限,比如发送"SetDis=15cm\r\n",则距离小于 15cm 时蜂鸣器叫的声音频率较高
思考:如何对接收到的字符串进行处理 "SetDis=15cm"提示:strstr strtok 字符串转整型:atoi 整型转字符串:sprintf
比如利用手机 APP 发送特定的数据包,格式为"beep=on\r\n",就可以让蜂鸣器鸣叫,发送"beep=off\r\n",可以让蜂鸣器不叫
(2) 利用手机APP,控制距离的两个阈值 dis1 + - dis2 + -
如果程序中定义的局部数组太多太大了,需要在启动文件中修改栈空间大小