本篇文章进行串口发信息的讲解,我们接上文来看
上一章介绍了串口通信,讲到了中断服务函数部分。简单说下什么是串口中断服函数,串口中断服务函数是用于处理串口接收和发送数据的相关操作。在STM32中,串口中断服务函数的名称为USARTX_IRQHandler
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
// 处理接收数据
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET)
{
// 处理接收缓冲区的数据
}
}
if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
{
// 处理发送数据
USART_SendData(USART1, 0x0);
}
}
做一个解释:在上面的代码中,我们首先通过USART_GetITStatus()
函数检查是否接收到新的数据,如果接收到新的数据,则通过USART_GetFlagStatus()
函数检查接收缓冲区中是否有数据待处理。如果有数据待处理,则可以对接收缓冲区中的数据进行处理。如果发送缓冲区中有数据待发送,则可以通过USART_SendData()
函数发送数据。(我一般使用printf函数去发送信息),在C语言库函数里,已经有了封装好的printf 函数,print函数的在多串口使用的注意点在上一篇文章中进行了介绍,这里再次进行一个总结,
先描述一个多串口环境,一般在进行物联网开发的时候,都是多串口环境:串口1用作调试串口,串口2用作MQTT通信串口,串口3用作下位机通信串口
若是三个串口都使用printf函数,会出现什么情况呢?如果有尝试过的读者,可以知道,这种情况下,串口就不能正常工作了,那么该怎么合理的去使用printf资源呢?我这边总结了一种解决STM32多串口同时收发的方法:即使用串口指针和printf函数重定向来解决
我来解释一下思路:
先定义串口指针(初学者会用就行,暂时不必深究)
其次,进行重定向:重定向c库函数printf到串口
然后对使用到的串口进行初始化,串口初始化在上一章已经讲过,此处一笔带过
再根据所需进行串口中断服务函数的编写
代码分块讲解:
第一部分:定义串口指针
USART_TypeDef * DEBUG_USARTx = USART1; //定义串口指针,
//通过指针指到使用串口的各个寄存器,(SR状态寄存器,DR数据寄存器)
第二部分:重定向
//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
这里读者可能不太理解,什么是重定向:
printf重定向是指将printf函数的输出从默认输出改为其他设备,如串口或LCD显示屏等。实现printf重定向可以通过重定义printf函数调用的方式来实现。例如,在使用串口时,可以将USART_SendData函数替换为fputc函数,从而使printf函数的输出发送到串口。需要注意的是:要使用printf重定向功能,需要导入stdio.h头文件。
第三部分:串口初始化,此处以串口一三为例:
void uart1_init(u32 bt)
{
GPIO_InitTypeDef GPIO_InitStructure; //声明一个结构体变量,用来初始化GPIO
NVIC_InitTypeDef NVIC_InitStructure; //中断结构体定义
USART_InitTypeDef USART_InitStructure; //串口结构体定义
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1|RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//TX
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//RX
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init(GPIOA,&GPIO_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_InitStructure.USART_BaudRate=bt; //波特率设置为bt
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);
USART_Cmd(USART1, ENABLE);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//使能或者失能指定的USART中断 接收中断
USART_ClearFlag(USART1,USART_FLAG_TC);//清除USARTx的待处理标志位
}
void uart3_init(u32 bt)
{
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure; //声明一个结构体变量,用来初始化GPIO
//使能串口的RCC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE); //使能UART3所在GPIOB的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
USART_DeInit(USART3); //复位串口3
//串口使用的GPIO口配置
// Configure USART3 Rx (PB.11) as input floating
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//配置串口
USART_InitStructure.USART_BaudRate = bt;
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(USART3, &USART_InitStructure);//配置串口3
USART_Cmd(USART3, ENABLE);//使能串口3
//串口中断配置
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启中断
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
上述代码中,我们只需要按照流程来配置参数即可。主要就是串口对应GPIO配置,串口基础配置,串口中断配置三部分。
定义完成以后,就可以按需添加了,在使用的时候,只需要按照以下格式即可:
//串口1:
DEBUG_USARTx = USART1;
printf(); //自定义即可
//串口2:
DEBUG_USARTx = USART2;
printf(); //自定义即可
//串口3:
DEBUG_USARTx = USART3;
printf(); //自定义即可
以上便是串口发信息的方法,下面再介绍一种办法:
除了printf之外还有什么办法发信息,答案之一是DMA收发,什么是DMA收发呢,专业的说法是:DMA收发是指直接存储器访问(Direct Memory Access,DMA)。是CPU 用于数据从一个地址空间到另一地址空间“搬运”的组件,数据拷贝过程不需CPU干预,数据拷贝结束则通知CPU处理。在大量数据拷贝时,使用DMA可以释放CPU资源。DMA数据拷贝过程包括:内存—>内存,内存间拷贝,外设—>内存,如uart、spi、i2c等总线接收数据过程,内存—>外设,如uart、spi、i2c等总线发送数据过程。简单些就是不占用CPU进行数据搬运的过程,可以把数据从一个地方搬运到另一个地方。
使用DMA收发也需要去编写DMA驱动函数,在下一篇进行总结。