MCU:stm32f407vet6
外设:LD3320,其他同理
一、基本流程
思路:先配置好USART1,再配置中断,然后对串口发送的数据进行处理
接线:外设的TXD、RXD分别接MCU的RXD、TXD,交叉相连
流程:外设向串口发送数据,MCU接收缓冲区非空,从而触发中断接收数据,接收数据后进入中断回调函数,在中断回调函数里进行数据处理或是发送数据处理的标志由主循环的函数执行
二、代码
重点是先理解原理,然后就可以自由发挥
1,配置
配置USART1的模式,以及TXD、RXD的GPIO引脚,重点是配置波特率和GPIO引脚
uint8_t usartBuffer[USART_RX_BUFFER_SIZE];// 定义接收缓冲区,里面是宏定义
uint16_t bufferIndex = 0;// 缓冲区索引
UART_HandleTypeDef huart1;
void LD_USART1_UART_Init()
{
__HAL_RCC_USART1_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
huart1.Instance = USART1;
huart1.Init.BaudRate = 9600;//波特率
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = TXD | RXD;//引脚已经宏定义过了
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);//我这里是中断分组4,你可以按照你自己的中断分组来配,比如2,2,
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
配置ISR(这里没有把它放在中断步骤里,是为了让代码的配置流程更顺畅一点)
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
2,初始化
在合适的位置放上初始化函数。下面那个接收中断的函数HAL_UART_Receive_IT其实就是激活接收非空中断,简单来说就是外设向串口发送数据,接收缓冲区就非空了,于是触发中断,进入中断回调函数了。
不过这个函数有个特点,就是你调用这个函数后,它会一直等待接收缓冲区非空,直至非空触发中断,然后就没了,不会再接收数据了。也就是说如果你想一直接收数据的话,那么必须在中断回调函数里也调用这个函数。
这个函数里的形参,第一个不陌生,第二个是自己定义的缓冲区数组,在前面的【配置】里定义过了。第三个参数是传递的字节长度n,当接收缓冲区非空,这个函数会把缓冲区的n个字节长度的数据复制到你填的缓冲区数组里。
字节长度填1是因为接收的数据是不定长的并且中英数字混合,同时还有结束标志,所以需要一个一个字节地传输以便判断接收是否完成。一般都填1,除非你确定数据是定长的
LD_USART1_UART_Init();
if (HAL_UART_Receive_IT(&huart1, usartBuffer, 1) != HAL_OK) /*激活传输*/
Error_Handler();
3,中断
配置中断回调函数(我这里开启了预编译指令,不用在意),正如前面所言,我需要接收含结束标志的不定长数据,这里的结束标志为“\r\n”。
所以第①步就是,先检验一下数据的末尾是否是结束标志,从而决定是否处理数据。这里使用if、else if 、else的三个分支流程,分别对应【传输完成】、【传输错误】、【正在传输】
需要说明的是最后一句bufferIndex++放在前面是因为
bufferIndex++;
HAL_UART_Receive_IT(&huart1, usartBuffer + bufferIndex, 1);
在初始化步骤时,就已经开启了接收中断,所以接收完之后的,缓冲区数组的第0个位置已经有了数据,为了不被覆盖,需要在接收下一个之前先让索引自增
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
#if LD_USART1_USE
if (huart->Instance == USART1)
{
// 检查是否收到了完整的 "\r\n"
if (bufferIndex >= 2 && usartBuffer[bufferIndex - 1] == '\r' && usartBuffer[bufferIndex] == '\n')
{
bufferIndex -= 2;// 移除结束标志
/**解锁处理任务,你可以换成自己的数据处理代码,或者是把处理数据的标志置1,在主循环里识别标志并处理*/
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(usartBinarySemHandle, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE)
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);// 请求上下文切换
}
/**上面是解锁处理任务*/
}
else if (bufferIndex == USART_RX_BUFFER_SIZE - 1)// 缓冲区满但未找到结束符,处理错误
{
bufferIndex = 0;// 或者其他适当的错误处理
memset(usartBuffer, 0, USART_RX_BUFFER_SIZE);
}
else// 继续接收下一个字符
{
bufferIndex++;
HAL_UART_Receive_IT(&huart1, usartBuffer + bufferIndex, 1);
}
}
#endif
}
4,数据处理
函数名或者任务名我就不写了,这里只写大概的执行过程。这一步的前提就是先判断是否需要进行数据处理,如果需要的话再执行下面代码。
先是处理相关数据,再是重置缓冲区,防止数据不断追加。最后是再启动接收数据中断,等待接收缓冲区非空,即等待外设向串口发送数据。
/**处理相关数据*/
// 重置缓冲区索引,准备接收下一条数据
bufferIndex = 0;
memset(usartBuffer, 0, USART_RX_BUFFER_SIZE);
// 继续接收新的数据
HAL_UART_Receive_IT(&huart1, usartBuffer, 1);