目录
1.认识串口
2.stm32串口介绍
2.1 查询方式
2.1 中断方式
2.2 DMA方式
3.使用stm32串口实现printf
串口作为嵌入式设备最常用的外设之一,被广泛的应用。本文介绍STM32串口的如何使用。从以下几个方面介绍:
常用串口的引脚主要由TX,RX,RTS,CTS,GND组成。RTS与CTS用于硬件流控,TX与RX为数据通讯引脚。两个串口通信连接示意图如下(GND未体现):
RTS与CTS主要是为了进行通讯流控制的,在实际应用中可以选择开启或关闭流控功能,如果流控打开,当usart1需要发送数据时,会先判断CTS的电平信号,当CTS为低电平时,表示usart2可以的FIFO未满,可以继续接收,usart1就可以发送数据,当usart2的FIFO中数据未被及时读走,即FIFO满了,usart2就会其RTS引脚将usart1的CTS引脚拉高,此时usart1就不再发送数据。虽然流控可以控制硬件的发送,保证接收的FIFO不溢出,但是实际使用中,很少会用到,由软件来保证FIFO数据被及时取走。
串口通讯通常需要设置的参数有:起始位,停止位,数据位,校验位,波特率,有无流控。根据需要配置成什么样的参数需要两个通讯设备保持一致才可正常通讯。
以STM32F103为例介绍,STM32F103支持多路串口,串口支持硬件校验,硬件控制,帧错误检测,空闲状态检测等功能,支持查询方式收发,中断方式,以及DMA方式传输。空闲帧检测功能为应用开发提供了许多方便之处。串口特征如下:
STM32F103同一串口可以配置使用不同的GPIO。如UART1即可使用PA9,PA10也可以使用PB6与PB7。具体根据硬件选择。
串口通讯常见的有3种方式,查询方式,中断方式,DMA方式。以下分别介绍这三种通讯方式。为使用串口功能,我们需要将stm32官方提供的usart相关SDK添加进工程中,添加方法可参照STM32入门教程——点亮一个LED_单片机的码农的博客-CSDN博客一文中的方法。进入manage run-time enviorenment对话框,选中usart即可。当然串口需要用到GPIO,后面也会用介绍中断传输与DMA传输,所以可以一并把GPIO,EXTI,DMA一并加入到工程中。
无论使用何种方式通讯,GPIO的配置及串口参数配置都一样,这里以STM32F103使用USART1为例,REMPA为0时,PA9与PA10分别作为USART1的发送与接受,则串口的GPIO初始化如下:
#define USART1_TX_GPIO_PORT GPIOA
#define USART1_TX_GPIO_PIN GPIO_Pin_9
#define USART1_RX_GPIO_PORT GPIOA
#define USART1_RX_GPIO_PIN GPIO_Pin_10
void uart_gpio_init(void){
GPIO_InitTypeDef GPIO_InitStructure;
// 打开串口GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = USART1_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = USART1_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStructure);
}
串口参数配置如下:
#define USART1_BAUDRATE 115200
void usart_param_init(void)
{
USART_InitTypeDef USART_InitStructure;
// 配置串口的工作参数
// 配置波特率
USART_InitStructure.USART_BaudRate = USART1_BAUDRATE;
// 配置针数据长度为8位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置停止位为1位停止位
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);
// 使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
此时,串口1的配置已经完成。
查询方式使用最为简单,只需要配置串口的基本属性,通过上述配置后直接写串口寄存器即可。串口发送函数如下:
/***************** 查询方式发送数据 **********************/
void usart1_send(char *buff,int32_t len)
{
unsigned int k=0;
for(int32_t k = 0;i < len;k++){
/* 发送一个字节数据到USART */
USART_SendData(USART1,buff[k]);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
}
uint32_t usart1_read(uint8_t *buff,uint32_t len,uint32_t time_out)
{
uint32_t tick = 0;
tick = get_system_tick();
for(uint32_t i = 0;i < len;i++){
while((RESET == USART_GetFlagStatus(usart1,USART_FLAG_RXNE))&&(get_system_tick() < (tick + time_out)));
buff[i] = USART_ReceiveData(usart1);
}
}
stm32串口支持的中断类型有很多种,具体如下:
* @arg USART_IT_CTS: CTS change interrupt (not available for UART4 and UART5)
* @arg USART_IT_LBD: LIN Break detection interrupt
* @arg USART_IT_TXE: Transmit Data Register empty interrupt
* @arg USART_IT_TC: Transmission complete interrupt
* @arg USART_IT_RXNE: Receive Data register not empty interrupt
* @arg USART_IT_IDLE: Idle line detection interrupt
* @arg USART_IT_PE: Parity Error interrupt
* @arg USART_IT_ERR: Error interrupt(Frame error, noise error, overrun error)
使用比较多的是TC中断与RXNE中断,即发送完成中断与接收中断。为使用中断方式通讯,在完成串口基本参数配置后,还需要进行中断相关的初始化。在需要发送数据时开启串口TC中断,需要接收时开启RXNE中断,当然,实际应用过程中,MCU可能是要随时接收数据,所以RXNE中断可以在初始化后一直开启。开启中断与关闭中断的代码如下:
USART_ITConfig(USART1, USART_IT_TC, ENABLE);//开启TC中断
USART_ITConfig(USART1, USART_IT_TC, DISABLE);//关闭TC中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启RXNE中断
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);//开启TC中断
以上操作的中断仅是控制串口外设是否会产生中断请求,那么外设的中断请求是否要被MCU响应,还需要开启串口的中断。串口中断初始化如下:
void usart1_irq_init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
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);
}
当程序中开启了中断且有中断产生时,程序需要定义中断处理函数,否则中断产生后,会导致程序死机。串口中断也一样。中断函数函数的定义中startup.s中有定义。如下图:
因此,我们需要定义usart1的中断处理函数,中断进行数据发送参考代码如下:
#define USART1_TX_BUFF_SIZE 100
static uint8_t usart1_tx_buff[USART1_TX_BUFF_SIZE] = {0};
static uint32_t usart1_tx_buff_write_idx = 0;
static uint32_t usart1_tx_buff_read_idx = 0;
void usart1_sendbytes_irq(uint8_t *buff,uint32_t len)
{
uint8_t flag = 0;
if(usart1_tx_buff_write_idx == usart1_tx_buff_read_idx){
flag = 1;
}
for(uint32_t i = 0;i < len;i++){
usart1_tx_buff[usart1_tx_buff_write_idx] = buff[i];
usart1_tx_buff_write_idx ++;
if(usart1_tx_buff_write_idx >= USART1_TX_BUFF_SIZE){
usart1_tx_buff_write_idx = 0;
}
}
if(flag){
USART_SendData(USART1,usart1_tx_buff[usart1_tx_buff_read_idx]);
USART_ITConfig(USART1, USART_IT_TC, ENABLE);//开启TC中断
}
}
void USART1_IRQHandler(void)
{
uint8_t temp = 0;
if(SET == USART_GetITStatus(USART1, USART_IT_TC)){
usart1_tx_buff_read_idx++;
if(usart1_tx_buff_read_idx >= USART1_TX_BUFF_SIZE){
usart1_tx_buff_read_idx = 0;
}
if(usart1_tx_buff_read_idx== usart1_tx_buff_write_idx ){//all data send
USART_ITConfig(USART1, USART_IT_TC, DISABLE);
}else{
temp = usart1_tx_buff[usart1_tx_buff_read_idx];
USART_SendData(USART1, temp); //send one byte
}
}
if(SET == USART_GetITStatus(USART1, USART_IT_RXNE)){
temp = USART_ReceiveData(usart1);//receice one byte
}
}
stm32串口支持DMA方式进行数据收发。不同的串口使用的DMA通道不同,具体使用哪一个DMA通道需要根据芯片定义确定,如下图所示:
由上图知,usart1的发送使用的是DMA1通道4,接收使用DMA1的通道5,因此初始化时要选对正确的通道。DMA的初始化主要是设置数据方向(外设到MEMORY,MEMORY到外设,外设到外设等),起始地址,数据传输大小,地址是否递增等。串口发送,DMA方向为memory到外设,接收时方向为外设到memory,以下是串口1使用DMA发送的初始化代码:
#define USART1_DMA_TX_SIZE 100
static uint8_t gUartDmaTxBuffer[USART1_DMA_TX_SIZE] = {0};
static DMA_InitType DMA_InitStructure;
void usart1_dma_init(void)
{
USART_InitType USART_InitStructure;
DMA_DeInit(DMA1_Channel4);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.PeriphAddr = USART1_DR_Base;
DMA_InitStructure.MemAddr = (uint32_t)gUartDmaTxBuffer;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_InitStructure.BufSize = USART1_DMA_TX_SIZE;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE;
DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;
DMA_InitStructure.MemDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL;
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH;
DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
USART_EnableDMA(USART1, USART_DMAREQ_TX, ENABLE);
}
通过上述DMA初始化后,即可通过DMA进行数据发送,DMA发送数据代码如下:
void usart1_send_by_dma(uint8_t *buff,uint31_t size)
{
memcpy(gUartDmaTxBuffer,buff,size);
DMA_InitStructure.MemAddr = (UINT32)gUartDmaTxBuffer;
DMA_InitStructure.BufSize = size;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_EnableChannel(DMA1_Channel4, ENABLE);
}
至此,串口3种通讯方式已经介绍完毕。
在使用STM32做项目时,经常需要使用串口打印日志,那么如何快速使用stm32串口实现日志打印呢?只需要实现fputc函数即可。
int fputc(int ch, FILE *f)
{
uint16_t tmp = ch;
USART_SendData(USART1,tmp);
while(RESET == USART_GetFlagStatus(USART1,USART_FLAG_TXC));
return (ch);
}
只需要将函数串口初始化,并实现上述fputc函数就可以实现串口日志打印,和window中的printf使用方法完全一样,是不是很方便呢。