STM32-UART串口通信
一、UART数据传输过程
1.字符发送
首先在初始化完USART的时候,但我们要发送一个字节的数据,那么先把这个数据写进USART_DR,这个时候TXE (Transmit data register empty)位由硬件清零,表示发送数据寄存器不为空。如果移位寄存器为空,表示此时USART正在发送数据,则要等待
前传输结束时(也就是移位寄存器为空)把TDR中的数据复制进移位寄存器,如果此时USART没有在发送数据,处于空闲状态,对USART_DR寄存器的写操作直接把数据放
进移位寄存器,数据传输开始, TXE位立即被置起。当移位寄存器里面的数据也被完全发送时,移位寄存器为空,则TC位被置起,表示数据传输完成。
2.字符接收
当接收的数据到来的时候,先存放在移位寄存器中,然后再将移位寄存器中的数据转移到RDR中,RXNE位被置位。它表明移位寄存器的内容被转移到RDR。换句话说,数据已经被接收并且可以被读出(包括与之有关的错误标志)。如果RXNEIE位被设置,产生中断。
3.几个重要的标志位
TXE: (Transmit data register empty)表示发送数据寄存器为空,也就是如果当前TDR中的数据没有被放进移位寄存器,就不为空,TXE被置起,如果TDR中的数据被放进了移位寄存器,那么TXE被清零。如果TXEIE位被设置,此标志将产生一个中断,也就是TXE被置起的时候,就会产生中断。
TC: (Transmission complete)表示当前数据发送完成,也就是移位寄存器里面的数据为空,则表示数据发送完成,TC为就被置起,如果USART_CR1中的TCIE为’1’,则产生中断。
RXNE: 它表明移位寄存器的内容被转移到RDR。换句话说,数据已经被接收并且可以被读出(包括与之有关的错误标志)。如果RXNEIE位被设置,产生中断。
二、UART初始化配置
本工程可以参考官方固件库中的例程修改\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\USART\Interrupt和Printf。
本示例使用的是USART2,可以根据情况修改为自己需要的。
1.初始化配置
- 使能时钟
- 配置中断优先级
- 初始化GPIO
- 初始化USART
- 使能USART的发送中断
- 打开USART
/**
* @brief Configures the different system clocks.
* @param None
* @retval None
*/
void UART_RCC_Configuration(void)
{
/* Enable GPIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
}
/**
* @brief Configures the different GPIO ports.
* @param None
* @retval None
*/
void UART_GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure USART2 Rx as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART2 Tx as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/**
* @brief Configures the nested vectored interrupt controller.
* @param None
* @retval None
*/
void UART_NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the USARTy Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void UART_INIT(void)
{
USART_InitTypeDef USART_InitStructure;
/* System Clocks Configuration */
UART_RCC_Configuration();
/* NVIC configuration */
UART_NVIC_Configuration();
/* Configure the GPIO ports */
UART_GPIO_Configuration();
/* USART2 configuration ------------------------------------------------------*/
/* USARTy and USARTz configured as follow:
- BaudRate = 9600 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled
*/
USART_InitStructure.USART_BaudRate = 9600;
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;
/* nRTS:请求以发送(Request To Send), n 表示低电平有效。如果使能 RTS 流控制,当
USART 接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时,
nRTS 将被设置为高电平。该引脚只适用于硬件流控制。
nCTS:清除以发送(Clear To Send), n 表示低电平有效。如果使能 CTS 流控制,发送
器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为
高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。*/
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* Configure USART2 */
USART_Init(USART2, &USART_InitStructure);
/* Enable USART2 Receive and Transmit interrupts */
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
// USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
/* Enable the USART2 */
USART_Cmd(USART2, ENABLE);
}
2.如何使用printf函数向串口打印数据
如果要使用printf打印串口数据,需要包含头文件stdio.h,在官方的参考例程Printf中可以找到这样一段代码复制下来:
该代码就是对printf进行重定向,具体不必深究。
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
USART_SendData(USART2, (uint8_t) ch);
/* Loop until the end of transmission */
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET)
{
}
return ch;
}
例程:串口接收消息
要求:通过电脑,就像聊天一样,每次可以发送不定长的消息,每次点击一次发送,就把当前的消息也就是字符串打印到LCD上。
要做到以上的要求,我们可以对收到的消息加一个时间来约束。首先每次我通过电脑点击一次发送,那么在开发板接收到该数据的第一个字节的时候,就设置一个标志位,表示正在接收消息,然后打开定时器,设置50ms的超时时长,也就是从接收数据的第一个字节开始,计时50ms,50ms内所接收到的数据就认为是当前接收到的消息。当然超时的时间,可以具体调节,比如说我们要传输的数据很大的时候,可以把超时时间设定得长一些,如果对我20字节以内得数据,50ms简直是绰绰有余。
下面是主要部分的代码:
#include "uart.h"
#include <stdio.h>
#include <string.h>
uint8_t RxBuffer[20];
uint8_t RxCounter = 0x00;
_Bool Rx_flag = 0;
uint16_t uart_cnt = 0;
/**
* @brief Configures the different system clocks.
* @param None
* @retval None
*/
void UART_RCC_Configuration(void)
{
/* Enable GPIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
}
/**
* @brief Configures the different GPIO ports.
* @param None
* @retval None
*/
void UART_GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure USART2 Rx as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART2 Tx as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/**
* @brief Configures the nested vectored interrupt controller.
* @param None
* @retval None
*/
void UART_NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the USARTy Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void UART_INIT(void)
{
USART_InitTypeDef USART_InitStructure;
/* System Clocks Configuration */
UART_RCC_Configuration();
/* NVIC configuration */
UART_NVIC_Configuration();
/* Configure the GPIO ports */
UART_GPIO_Configuration();
/* USART2 configuration ------------------------------------------------------*/
/* USARTy and USARTz configured as follow:
- BaudRate = 9600 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled
*/
USART_InitStructure.USART_BaudRate = 9600;
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;
/* nRTS:请求以发送(Request To Send), n 表示低电平有效。如果使能 RTS 流控制,当
USART 接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时,
nRTS 将被设置为高电平。该引脚只适用于硬件流控制。
nCTS:清除以发送(Clear To Send), n 表示低电平有效。如果使能 CTS 流控制,发送
器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为
高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。*/
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* Configure USART2 */
USART_Init(USART2, &USART_InitStructure);
/* Enable USART2 Receive and Transmit interrupts */
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
// USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
/* Enable the USART2 */
USART_Cmd(USART2, ENABLE);
}
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
USART_SendData(USART2, (uint8_t) ch);
/* Loop until the end of transmission */
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET)
{
}
return ch;
}
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
Rx_flag = 1;
uart_cnt = 0;
/* Read one byte from the receive data register */
RxBuffer[RxCounter++] = USART_ReceiveData(USART2);
if(RxCounter > 20)
{
RxCounter = 0;
memset(RxBuffer,0,sizeof(RxBuffer));
}
}
}
void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
if(Rx_flag && ++uart_cnt >= 50)
{
uart_display_flag = 1;
uart_cnt = 0;
RxCounter = 0;
Rx_flag = 0;
strcpy((char*)lcd_buf,(char*)RxBuffer);
memset(RxBuffer,0,sizeof(RxBuffer));
}
}
}
main函数里面包含的相关代码:
void main(void)
{
UART_INIT();
if(uart_display_flag)
{
uart_display_flag = 0;
memset(lcd_buf2,0,sizeof(lcd_buf2));
sprintf((char*)lcd_buf2,"%-20.20s",lcd_buf);
LCD_DisplayStringLine(Line4,lcd_buf2);
printf("%s\r\n",lcd_buf2);
}
}