因为项目原因需要使用stm32l0xx芯片,原本在f103上使用的是标准库开发,改用后发现stm32l0由于比较新,不支持标准库开发,移植很麻烦,只能重新学Hal库开发。
在开发上Hal库还是比较完善的,基本在STM32CubeMX的帮助下可以生成所有所需要的初始化函数,只是需要尽量提前想好需要哪些配置,否则中间再添加main函数内写的代码会被清除。目前把串口收发做好了,查了很多资料,参考了很多博客,因为比较杂一时想不起哪些,无法放全链接,需要可以联系我补上。
Hal库里中断收发主要用到的函数是
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);//串口中断发送,以中断方式发送指定长度的数据。
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);//串口中断接收,以中断方式接收指定长度数据。
正常情况下开发流程是(一些问题和改进后面会说):
直接调用
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
这段代码由MX直接生成,不需要改动什么。
在USART_Init中
if(Hal_UART_Init(&huart2) ==OK)
这是使能了USART2以及配置一些硬件设备
在初始化后,进行中断处理函数,在stm32l0xx_it.c中找到
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
这写都是生成好的,其中 HAL_UART_IRQHandler(&huart2);是Hal库处理UART中断请求,这个函数是一个总的中断处理函数,也就是很多串口的中断处理最后都汇集到这个地方来处理go to definition,会发现其内是一系列包括清中断、读取寄存器等操作,我们也不需要管,但也不能删掉。
void USART2_IRQHandler(void)作为中断处理函数是每次中断触发都会运行的,所以如果有什么自己写的中断服务函数,可以在这里面添加。
回调函数我是写在main.c里的
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART2)//判断,如果是串口2
{
//写入自己要实现的代码
}
}
每次中断后会进入这段函数,go to definition一下会发现
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_RxHalfCpltCallback can be implemented in the user file.
*/
}
出现在stn32l0xx_hal_uart.c里,这里__weak表示可以在用户文件中实现,实现后只会调用用户文件里的函数。
我在这个函数里调用了HAL_UART_Transmit();这样就能打印出接收的数据。
中断使能是在初始化后添加HAL_UART_Receive_IT();
实际使用
__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);
也可以实现。
在这之后,中断收发基本都都好了,整个过程除了添加收发数组,使能函数和回调函数,基本不用做什么改动。
这里要记得 HAL_UART_IRQHandler(&huart2);后还要再用HAL_UART_Receive_IT();使能中断,不然只能收一次。
最后代码是这样的:
main.c
stm32l0xx_it.c
然后编译执行时会发现,发送不满100不打印,发送超过100只打印前100(这里的100是由于我使能的时候填的100)。
后面我考虑可能是因为只有到设定的数据量后才触发回调函数,然后我把HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);的Size改为了1
然后就是这样子了,对,它坏了。
于是开始翻各种博客,最后找到了通过直接对寄存器操作实现不限字数中断收发,顺便实现了printf重定向。
代码实现如下:
添加接收状态标记:
uint16_t USART2_RX_STA=0;
在usart2初始化函数里添加中断使能
__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);
void USART2_IRQHandler(void) //串口2中断服务程序
{
uint8_t Res;
HAL_UART_IRQHandler(&huart2);
if(__HAL_UART_GET_IT(&huart2, UART_IT_RXNE) != RESET) //产生中断
{
USART2->RQR |= 0x08;
Res =USART2->RDR;//USARTx_RX_Data(&UartHandle);
if((USART2_RX_STA&0x8000)==0)//
{
if(USART2_RX_STA&0x4000)//
{
if(Res!=0x0a)
USART2_RX_STA=0;//
else
USART2_RX_STA|=0x8000; //
}
else
{
if(Res==0x0d)
USART2_RX_STA|=0x4000;
else
{
USART2_RX_BUF[USART2_RX_STA&0X3FFF]=Res ;
USART2_RX_STA++;
if(USART2_RX_STA>(USART_REC_LEN-1))
USART2_RX_STA=0;
}
}
}
}
HAL_NVIC_ClearPendingIRQ(USART2_IRQn);
}
当有数据时产生中断,标记位为1,数据存入USART2_RX_BUF。
在主函数while(1)里添加
if(USART2_RX_STA&0x8000)//接收到数据
{
//写入自己要实现的代码
USART2_RX_STA = 0;
}
发送时需要发送数据长度,可以由下面操作得到
len=USART2_RX_STA&0x3f;
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART2->ISR&0X40)==0)
{
};//循环发送,直到发送完毕
USART2->TDR = (uint8_t) ch;
return ch;
}
#endif
由衷感谢提供了帮助的博主。
在实现上面中断收发后,就想把usart1用DMA来实现收发,与之前一样DMA用到的函数是
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);//串口DMA发送,以DMA方式发送指定长度的数据。
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);//串口DMA接收,以DMA方式接收指定长度的数据
与中断一样串口发送很简单,其他的不多说,这里注意一个就是DMA发送需要打开串口的中断并使能,即
否则只能发送一次,不能再发送。
直接使用DMA接收与中断接收基本类似,会有类似的数据长度固定的问题,下面直接给出解决方案:
定义接收标志
volatile uint8_t RX_flag=0; //IDLE 接收标志
在初始化函数里使能中断和DMA
HAL_UART_Receive_DMA(&huart1, usartDMA_rxBuf, RECEIVELEN); /*使能DMA接收*/
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); /*使能串口空闲中断*/
void USART1_IRQHandler(void)
{
UsartReceive_IDLE(&huart1);
HAL_UART_IRQHandler(&huart1);
}
void UsartReceive_IDLE(UART_HandleTypeDef *huart)
{
uint32_t temp;
if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
HAL_UART_DMAStop(&huart1);
temp = huart1.hdmarx->Instance->CNDTR;
rx_len = RECEIVELEN - temp;
HAL_UART_Receive_DMA(&huart1,usartDMA_rxBuf,RECEIVELEN);
RX_flag=1;
}
}
在主函数while(1)里添加
if(RX_flag == 1)
{
//写入自己要实现的代码
rx_len = 0;
RX_flag = 0;
}
这里需要注意的就是DMA接收需要配置为循环模式,DMA发送配置为普通模式
实现效果如下
以上为开发过程中自己的理解以及参考别的博客实现的串口功能,存在不足与误导感谢批评指正,有问题也可以留言讨论。
再次感谢提供帮助的博主博客。
附参考了以下链接的部分内容:
http://www.stmcu.org.cn/module/forum/thread-609561-1-1.html
https://blog.csdn.net/youmeichifan/article/details/51750435