STMF4之UART使用
一、串口简介
串口通讯基本是所有MCU的标配,在实际的项目中也经常使用,在项目中常常使用串口打印调试信息以及使用RS485、RS232和外部设备的通讯。
二、什么是重定向?
所谓重定向就是复写标准库函数,在使用printf时,printf最终会调用fputc函数将信息输出,所以需要重写fputc函数,在该函数中实现串口的输出,该函数重写后不需要声明。
三、串口发送和接收的几种方式
1、轮询方式:在主函数中轮询串口接收或者发送标准位,该种发送占用资源较多,使用较少。
2、中断方式:使能串口的中断后,当串口发送或者接收完成后会产生中断,该种方式消耗较少的资源,最为常用。
3、DMA发送:该种方式需要有芯片有DMA支持才可以,消耗资源最少,不是本篇讨论重点。
四、代码解析
1、初始化串口
/* 库版本: V1.8.0
* 串口1初始化
* 使用PA9和PA10
*/
static void UART1Init(void)
{
USART_DeInit(USART1);
/* 1.使能PA口时钟 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
/* 2.使能串口1时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
/* 3.管脚映射 */
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); /* GPIOA9复用为USART1 */
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); /* GPIOA10复用为USART1 */
/* 4.配置串口1的PA9和PA10管脚 */
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure); /* TXIO */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure); /* RXIO */
/* 5.配置串口工作模式 */
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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(USART1, &USART_InitStructure);
/* 6.设置中断 */
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 7.使能串口中断 */
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
#ifdef USE_TC_SEND_DATA
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
#else
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
#endif
USART_Cmd(USART1, ENABLE);
}
2、重定向
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t) ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
3、中断函数
/* 串口收发中断处理函数 */
void UART1RxOverInterrupt(void)
{
#ifdef USE_TC_SEND_DATA
/* TX */
if(USART_GetITStatus(USART1, USART_IT_TC) == SET)
{
/* 清零标志 */
USART_ClearITPendingBit(USART1, USART_IT_TC);
TX1BusyFlag = 0;
}
#else
/* TX */
if( USART_GetITStatus(USART1, USART_IT_TXE) == SET)
{
if(UART1RxdWriteCnt == UART1RxdReadCnt)
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);/* 发送完数据必须关掉中断 */
else
{
USART_SendData(USART1, UART1RxdBuf[UART1RxdReadCnt++]);
if(UART1RxdReadCnt == MAXBUFLENG)
{
UART1RxdReadCnt = 0;
}
}
}
#endif
/* RX */
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
UART1RxdBuf[UART1RxdWriteCnt++] = (u8)USART_ReceiveData(USART1);
if(UART1RxdWriteCnt == MAXBUFLENG)
{
UART1RxdWriteCnt = 0;
}
}
}
五、额外提示:
1、发送标识USART_IT_TC
和USART_IT_TXE
区别
USART_IT_TC:串口8051方式,在发送完一个字节后产生中断,在中断中清除标准位。
USART_IT_TXE:发送寄存器空中断,即发送寄存器没有数据时,会产生中断,因此在没有数据发送时需要关闭中断,不然会一直进入中断,在需要发送数据时打开中断。
2、printf打印问题
在所有函数都正确设置后,任然没有打印信息,可以是由于编译器设置不对,不同的编译器有不同的设置方法,如MDK需要勾选Use MicroLIB
3、在使用串口时,所有串口配置都正确,但是数据输出不正确
可能是由于使用的晶振和代码中设置的晶振频率不一致导致,在stm32f4xx.h中默认外部晶振频率为25MHz
#if !defined (HSE_VALUE)
#define HSE_VALUE ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */
需要根据所使用的外部晶振定义重新定义 HSE_VALUE,由于stm32f4xx.h是库文件最后不要改动,可在stm32f4xx_conf.h中添加如下代码:
#if defined(HSE_VALUE)
#undef HSE_VALUE
#define HSE_VALUE ((uint32_t)8000000)
#endif
4、串口中断标识有多种,不同的标识有不同的清零方式
具体参考《STM32F4xx中文参考手册.pdf》中关系USART_SR寄存器的详细介绍。