本文基于笔者之前的博客STM32F103串口通信USART小试牛刀,在此基础上,加上中断函数,并补充HAL函数实现。
一、实验原理
实验原理可以参考笔者之前的两篇博客
STM32F103串口通信USART小试牛刀_江南烟浓雨的博客-CSDN博客
STM32F103开关控制LED灯_江南烟浓雨的博客-CSDN博客
本次串口实验的中断名称为USART1,和之前一样,初始化GPIO,初始化NVIC,初始化串口USART。这里主要介绍一下中断函数
/* USART1中断函数 */
void USART1_IRQHandler(void)
{
unsigned char ucTemp; //接收数据
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET) //检查
{
ucTemp = USART_ReceiveData(USART1);
USART_SendData(USART1,ucTemp);
}
}
中断时,调用此函数,将发送的内容通过串口发送给PC机。这里和上次实验有所不同的是,没有添加清除中断位的操作。在STM32中文参考手册中有所说明
串口接收到数据,接收中断标志位为1,但当你读USART_DR寄存器的时候,硬件会自动清除串口接收中断标志,所以我们不需要添加清除中断位地操作。
如果想使用重定向的printf函数,可以在usart.h头文件中添加stdio.h头文件。然后在usart.c中添加重定向函数
//重定向C库函数printf到串口,重定向后可使用printf函数
int fputc(int ch,FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(USART1,(unsigned char) ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
return (ch);
}
//重定向C库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
return (int)USART_ReceiveData(USART1);
}
然后在keil中勾选Use MicroLIB
注意:在中断函数中不能添加printf,malloc等函数,printf放在中断里,相当于中断中嵌入中断,这样的函数可能被阻塞。
二、实验代码
1.库函数
usart.h
#ifndef __USART_H
#define __USART_H
#include "stm32f10x.h"
#include<stdio.h>
void MyUSART_Init(void);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);
#endif
usart.c
#include "usart.h"
//重定向C库函数printf到串口,重定向后可使用printf函数
int fputc(int ch,FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(USART1,(uint8_t) ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
return (ch);
}
//重定向C库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
return (int)USART_ReceiveData(USART1);
}
void MyUSART_Init()
{
/* 定义GPIO、NVIC和USART初始化的结构体 */
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* 使能GPIO和USART的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
/* 将USART TX(A9)的GPIO设置为推挽复用模式 */
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/* 将USART RX(A10)的GPIO设置为浮空输入模式 */
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/* 配置串口 */
USART_InitStructure.USART_BaudRate=115200; //波特率了设置为115200
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流控制
USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //使能接收和发送
USART_InitStructure.USART_Parity=USART_Parity_No; //不使用奇偶校验位
USART_InitStructure.USART_StopBits=USART_StopBits_1; //1位停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b; //字长设置为8位
USART_Init(USART1, &USART_InitStructure);
/* Usart1 NVIC配置 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStructure);
/*初始化串口,开启串口接收中断 */
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
/* 使能串口1 */
USART_Cmd(USART1,ENABLE);
}
/* USART1中断函数 */
void USART1_IRQHandler(void)
{
uint8_t ucTemp; //接收数据
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
{
ucTemp = USART_ReceiveData(USART1);
USART_SendData(USART1,ucTemp);
}
}
/* 发送一个字节 */
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 发送一个字节数据到USART */
USART_SendData(pUSARTx,ch);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/* 发送字符串 */
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
{}
}
main.c
#include "stm32f10x.h"
#include "usart.h"
int main(void)
{
MyUSART_Init();
while(1)
{}
}
2.HAL库函数
和之前一样,这里仅介绍不同的操作。首先配置串口1(USART1),将模式设置为异步,NVIC设置为Enabled。
然后设置中断分组
如果需要设置USART1的其它参数,可以在Parameter Settings中设置,笔者这里选择了默认设置
设置好后修改代码,和上面的库函数类似,主要是修改中断函数。首先在main.c中添加数组和相关参数的定义
char RxBuffer[256]; //接收数据
uint8_t aRxBuffer; //接收中断缓冲
uint8_t Uart1_Rx_Cnt = 0; //接收缓冲计数
然后在下面添加中断回调函数
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_TxCpltCallback could be implemented in the user file
*/
RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer; //接收数据转存
if((RxBuffer[Uart1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
{
HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去
while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX); //检测UART发送结束
Uart1_Rx_Cnt = 0;
memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //再开启接收中断
}
因为在stm32f1xx_hal_uart.c文件中,对于中断函数中的回调函数是弱定义,所以这里的函数定义覆盖了之前的定义。对于HAL库来说,提供了一系列的函数可以操作。如果需要使用printf等函数可以参考库函数的方法,重定向函数来实现。
三、实验结果
四、总结
本次实验用到了中断,串口通信。对于HAL库来说,其结构和相关函数可以参考生成的代码中的资料和网络上的相关资料。
五、参考文献
为何stm32 中断处理函数里不能调用printf?_dingpan119的博客-CSDN博客
【STM32】HAL库 STM32CubeMX教程四—UART串口通信详解_Z小旋-CSDN博客_hal_uart_transmit