这一篇文章与 上一篇文章相基于 stm32f103c8t6的串口非中断蓝牙通讯上一篇文章相http://t.csdnimg.cn/7j0Ec
相比,硬件部分是相同的。在原有的旧初上,要在stm32cube加入中断,同时代码中也要引入中断函数以及中断回调函数。到后面我谁说说我遇到的坑。
一、硬件部分
名称 | 作用 | 图片 |
stm32f103c8t6板子 | —— | —— |
HC08蓝牙模块 | 连接单片机通讯TX接单片机RX1,RX接单片机TX1。 | |
usb转ttl模块 | 模拟调试蓝牙通讯,同时在串口助手里修改蓝牙的波特率,让蓝牙的波特率和单片机相同 | |
二、stm32cube新增加的部分
把USART1的中断打开,其余的不变
三、代码部分(黑色软件生成,蓝色自己书写)
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
#define UART1_REC_LEN 200
uint8_t buf=0;
uint8_t UART1_RX_Buffer[UART1_REC_LEN];
uint16_t UART1_RX_STA=0;
void SystemClock_Config(void);
/* USER CODE BEGIN 0 */
unsigned char ch[20] = {0};
int fputc(int ch, FILE *f)//重映射使用
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//中断接收回调函数
{
// 判断中断是由哪个串口触发的
if(huart->Instance == USART1)
{
// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
if((UART1_RX_STA & 0x8000) == 0)
{
// 如果已经收到了 0x0d (回车),
if(UART1_RX_STA & 0x4000)
{
// 则接着判断是否收到 0x0a (换行)
if(buf == 0x0a)
// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
UART1_RX_STA |= 0x8000;
else
// 否则认为接收错误,重新开始
UART1_RX_STA = 0;
}
else // 如果没有收到了 0x0d (回车)
{
//则先判断收到的这个字符是否是 0x0d (回车)
if(buf == 0x0d)
{
// 是的话则将 bit14 位置为1
UART1_RX_STA |= 0x4000;
}
else
{
// 否则将接收到的数据保存在缓存数组里
UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;
// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART1_REC_LEN - 1)
UART1_RX_STA = 0;
}
}
}
// 重新开启中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
/* USER CODE END 0 */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, &buf, 1);
/* USER CODE END 2 */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//判断判断串口是否接收完成
if(UART1_RX_STA & 0x8000)
{
printf("收到数据:");
if (!strcmp((const char *)UART1_RX_Buffer, "open"))
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET)
printf("LED1已经打开\r\n");
memset(UART1_RX_Buffer, 0, strlen((const char *)UART1_RX_Buffer));
}
else if(!strcmp((const char *)UART1_RX_Buffer, "close"))
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET)
printf("LED1已关闭\r\n");
memset(UART1_RX_Buffer, 0, strlen((const char *)UART1_RX_Buffer));
}
else
{
if(UART1_RX_Buffer[0] != '\0')
printf("指令发送错误:%s", UART1_RX_Buffer);
memset(UART1_RX_Buffer, 0, strlen((const char *)UART1_RX_Buffer));
}
printf("\r\n");
// 重新开始下一次接收
UART1_RX_STA = 0;
}
HAL_Delay(40);
}
/* USER CODE END 3 */
}
四、其它补充
我不知道大家看完中断回调函数有没有啥疑问?
我来提两个问题
1.为什么在中断回调函数里还要在此调用中断接收函数?
我们打开HAL_UART_Receive_IT(&huart1, &buf, 1)的解释,可以看到下图所示
从图中我们可以看到,当我们开启中断的时候,串口就已经READY了,第一个if满足;接着因为我们的输入非空或者大小不为0U,所以第二个if跳过。接着就来到了一个结构体指针的赋值语句,这程序的意思就是表示UART串口的接收模式为标准模式。在标准模式下,UART串口以字节为单位接收数据,当接收到一个字节后,将触发接收完成事件,并将数据保存在接收缓冲区中。所以这句话过后,就相当于中断关闭了,所以我们每次接受一个字节之后,都需要重新调用中断函数。
还有就是要注意这里的逻辑,回调函数里是来一个字节触发一次中断,而不是把识别一个字符串触发中断,回调函数没有写循环,却可以循环。从第一个字符开始,到标志位(UART1_RX_STA=1结束,好好想想这个循。
2.我不知道有没有人关心程序中出现的变量buf,再回回调函数中,buf并没有任何人给他赋值,buf仅仅是出现在HAL_UART_Receive_IT(&huart1, &buf, 1),那么问题来了。既然没有给buf赋值,那程序中有关buf值的判断是怎么进行的呢?
比如if(buf == 0x0a)之类的语句,后来我跳转了HAL_UART_Receive_IT(&huart1, &buf, 1)解释,
我们看到串口接收变为标准模式之后,紧接着return另一个都函数里面。我们进入这个函数,大家看到了吗,这里是 huart->pRxBuffPtr一个结构体指针,而且pRxBuffPtr与pData都是指针,把指针赋值给指针,它们指向的是同一个内存位置。这意味着它们可以用来访问相同的数据,这不就意味着uart1的接收传输缓冲区与buf共享数据了嘛,所以看似buf没有赋值,但是HAL库已经帮我们赋值了。
3.memset(UART1_RX_Buffer, 0, strlen((const char *)UART1_RX_Buffer))
关于这句话,我建议你加上,我在手机蓝牙上试过,如果不加的话,当你输入的不是open或者close而是别的字符串,比如asdajd,它会报错,没问题。但是当你再次输入正确的指令的open或者close的时候,他还是会报错。
这是回调函数里的一个bug
UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;
// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART1_REC_LEN - 1)
UART1_RX_STA = 0;
}
printf("指令发送错误:%s\r\n", UART1_RX_Buffer);
个人觉得可能是printf的存在,导致只要不超过200个字节,他会一直给你累加字节
想要解决这个问题,要么你重启单片机,要么你加上memset(UART1_RX_Buffer, 0, strlen((const char *)UART1_RX_Buffer)),位置我已经用红色标记了。