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 RxGPIO_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. 初始化配置USARTUSART_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. 配置NVICNVIC_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 --- PB11GPIO_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. 初始化配置USARTUSART_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. 配置NVICNVIC_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 + -
如果程序中定义的局部数组太多太大了,需要在启动文件中修改栈空间大小