一.串口理论
串口作为MCU的重要外部接口,同时也是软件开发重要的调试手段,其重要性不言而喻。现在基本上所有的MCU都会带有串口,STM32自然也不例外。
STM32F407VGT6内嵌四个通用同步/异步接收器(USART1,USART2,USART3和USART6)
和两个通用异步收发器(UART4和UART5),如图:
另外,usart和uart的差异在于usart是同步串口(亦可用于异步串口),uart是异步串口,差异如下:
同步通信:带时钟同步信号传输。
异步通信:不带时钟同步信号。
按照数据传送方向,分为:
单工:数据传输只支持数据在一个方向上传输
半双工:允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;
全双工:允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。
如图:
二.串口库函数应用
对于USART复用IO的uart功能,我们首先要使能GPIO时钟,然后使能相应的外设时钟,同时要把GPIO模式设置为复用。这些准备工作做完之后,剩下的当然是串口参数的初始化设置,包括波特率,停止位等等参数。在设置完成只能接下来就是使能串口,这很容易理解。同时,如果我们开启了串口的中断,当然要初始化NVIC设置中断优先级别,最后编写中断服务函数。
串口设置的一般步骤可以总结为如下几个步骤:
1) 串口时钟使能,GPIO时钟使能。
2) 设置引脚复用器映射:调用GPIO_PinAFConfig函数。
3) GPIO初始化设置:要设置模式为复用功能。
4) 串口参数初始化:设置波特率,字长,奇偶校验等参数。
5) 开启中断并且初始化NVIC,使能中断(如果需要开启中断才需要这个步骤)。
6) 使能串口。
7) 编写中断处理函数:函数名格式为USARTxIRQHandler(x对应串口号)。
具体步骤:
1) 串口时钟和GPIO时钟使能。
串口USART2是挂载在APB1下面的外设,所以使能函数为:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟
GPIO时钟使能,就非常简单,因为我们使用的是串口2,串口2对应着芯片引脚PA2,PA3
所以这里我们只需要使能GPIOA时钟即可:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//使能GPIOA时钟
2) 设置引脚复用器映射
引脚复用器映射配置方法在我们4.4小节讲解非常清晰,调用函数为:
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);//PA2复用为USART2
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);//PA3复用为USART2
因为串口使用到PA2,PA3,所以我们要把PA2和PA3都映射到串口2。所以这里我们要调用两次函数。
对于GPIO_PinAFConfig函数的第一个和第二个参数很好理解,就是设置对应的IO口,如
果是PA2那么第一个参数是GPIOA,第二个参数就是GPIO_PinSource2。第二个参数串口2为GPIO_AF_USART2。
3) GPIO端口模式设置:P29和PA3要设置为复用功能。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 |GPIO_Pin_3; //GPIOA2与GPIOA3
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType =GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化PA2,PA3
4) 串口参数初始化:设置波特率,字长,奇偶校验等参数
串口初始化是调用函数USART_Init来实现的,具体设置方法如下:
USART_InitStructure.USART_BaudRate =bound;//一般设置为9600;
USART_InitStructure.USART_WordLength =USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits =USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity =USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl= USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode =USART_Mode_Rx | USART_Mode_Tx;//收发模式
USART_Init(USART2,&USART_InitStructure); //初始化串口
5) 使能串口
使能串口调用函数USART_Cmd来实现,具体使能串口2方法如下:
USART_Cmd(USART3, ENABLE); //使能串口
6) 串口数据发送与接收。
STM32F4的发送与接收是通过数据寄存器USART_DR来实现的,这是一个双寄存器,包
含了TDR和RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。
STM32库函数操作USART_DR寄存器发送数据的函数是:
void USART_SendData(USART_TypeDef* USARTx,uint16_t Data);
通过该函数向串口寄存器USART_DR写入一个数据。
STM32库函数操作USART_DR寄存器读取串口接收到的数据的函数是:
uint16_t USART_ReceiveData(USART_TypeDef*USARTx);
通过该函数可以读取串口接受到的数据。
7) 串口状态
串口的状态可以通过状态寄存器USART_SR读取。USART_SR的各位描述如图所示:
这里我们关注一下两个位,第5、6位RXNE和TC。
RXNE(读数据寄存器非空),当该位被置1的时候,就是提示已经有数据被接收到了,并
且可以读出来了。这时候我们要做的就是尽快去读取USART_DR,通过读USART_DR可以将
该位清零,也可以向该位写0,直接清除。
TC(发送完成),当该位被置位的时候,表示USART_DR内的数据已经被发送完成了。如
果设置了这个位的中断,则会产生中断。该位也有两种清零方式:1)读USART_SR,写
USART_DR。2)直接向该位写0。
状态寄存器的其他位我们这里就不做过多讲解,大家需要可以查看中文参考手册。
在我们固件库函数里面,读取串口状态的函数是:
FlagStatusUSART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
这个函数的第二个入口参数非常关键,它是标示我们要查看串口的哪种状态,比如上面讲解的RXNE(读数据寄存器非空)以及TC(发送完成)。例如我们要判断读寄存器是否非空(RXNE),操作库函数的方法是:
USART_GetFlagStatus(USART1,USART_FLAG_RXNE);
我们要判断发送是否完成(TC),操作库函数的方法是:
USART_GetFlagStatus(USART1, USART_FLAG_TC);
这些标识号在MDK里面是通过宏定义定义的:
#define USART_IT_PE ((uint16_t)0x0028)
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_IT_RXNE ((uint16_t)0x0525)
……//(省略部分代码)
#define USART_IT_NE ((uint16_t)0x0260)
#define USART_IT_FE ((uint16_t)0x0160)
8) 开启中断并且初始化NVIC,使能相应中断
这一步如果我们要开启串口中断才需要配置NVIC中断优先级分组。通过调用函数
NVIC_Init来设置。
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3; //响应优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd =ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
同时,我们还需要使能相应中断,使能串口中断的函数是:
void USART_ITConfig(USART_TypeDef* USARTx,uint16_t USART_IT,
FunctionalState NewState)
这个函数的第二个入口参数是标示使能串口的类型,也就是使能哪种中断,因为串口的中断类型有很多种。比如在接收到数据的时候(RXNE读数据寄存器非空),我们要产生中断,那么我们开启中断的方法是:
USART_ITConfig(USART1, USART_IT_RXNE,ENABLE);//开启中断,接收到数据中断
我们在发送数据结束的时候(TC,发送完成)要产生中断,那么方法是:
USART_ITConfig(USART1,USART_IT_TC,ENABLE);
这里还要特别提醒,因为我们实验开启了串口中断,所以我们在系统初始化的时候需要先设置系统的中断优先级分组,我们是在我们main函数开头设置的,代码如下:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
我们设置分组为2,也就是2位抢占优先级,2位响应优先级
9) 获取相应中断状态
当我们使能了某个中断的时候,当该中断发生了,就会设置状态寄存器中的某个标志位。
经常我们在中断处理函数中,要判断该中断是哪种中断,使用的函数是:
ITStatus USART_GetITStatus(USART_TypeDef*USARTx, uint16_t USART_IT)
比如我们使能了串口发送完成中断,那么当中断发生了, 我们便可以在中断处理函数中调用这个函数来判断到底是否是串口发送完成中断,方法是:
USART_GetITStatus(USART1, USART_IT_TC)
返回值是SET,说明是串口发送完成中断发生。
10) 中断服务函数
串口2中断服务函数为:
void USART2_IRQHandler(void) ;
当发生中断的时候,程序就会执行中断服务函数。然后我们在中断服务函数中编写我们相应的逻辑代码即可。
三.串口实例一
此串口实例为:通过PC串口助手向stm32发送数据,stm32收到后,进行loopback,重新回送回来,代码如下:
voidMy_USART2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA ,ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 |GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType =GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate=115200; USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; USART_InitStructure.USART_Parity=USART_Parity_No; USART_InitStructure.USART_StopBits=USART_StopBits_1; USART_InitStructure.USART_WordLength=USART_WordLength_8b; USART_Init(USART2,&USART_InitStructure); USART_Cmd(USART2 ,ENABLE); USART_ITConfig(USART2,USART_IT_RXNE,ENABLE); NVIC_InitStructure.NVIC_IRQChannel=USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_Init(&NVIC_InitStructure); } voidUSART2_IRQHandler(void) { u8 res; if(USART_GetITStatus(USART2,USART_IT_RXNE)) { res=USART_ReceiveData(USART2); USART_SendData(USART2,res); } }
四.串口实例二
此实例是在上面的基础上进行增加printf的功能
主要是重定向fputc函数,如下:
intfputc(int ch, FILE *f) { USART_SendData(USART2, (unsigned char) ch); while (!(USART2->SR &USART_FLAG_TXE)); return (ch); }
此处需要注意一点:
我在编译的时候会报错误:
Error[Pe020]: identifier "FILE" is undefinedD:\stm32_f407\project\wifi_project\STM32F4-Discovery_FW_V1.1.0_GPIO\Project\Audio_playback_and_record\src\uart.c50
解决方法如图:
把library由Normal替换成Full即可
Main.c
intmain(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); My_USART2_Init(); printf("http://blog.csdn.net/XiaoXiaoPengBo\r\n"); while(1); }
接线图:
通过杜邦线连接
测试结果如下: