本帖最后由 聪聪哥哥 于 2025-7-15 21:15 编辑
在CH32单片机中,串口接收数据时的经常使用的方式就是使用CH32的串口中断的方式,即串口每次接收到一个字节时候,就会产生串口中断,将接收到数据放到接收数组内,同时清除标志位。这种接收方式比较简单,之前的学习记录中也尝试过该接收方式,这种接收方式比较浪费CPU的资源。在串口通讯中,经常会遇到接收不定长的数据,这时候没有必要采用上述方式进行数据的接收处理,对串口接收中断的处理中,再到中断处理函数,CPU需要执行很多的程序,频繁的中断为CPU的运行增加了不少的负担,也可能会出现接收出错的情况,而且不确定接收数据长度时,也不确定处理函数应该何时进行处理。
为了解决上述问题,我们尝试使用CH32串口的空闲中断与DMA接收数据,即开始CH32的空闲中断时,告知CPU本次接收数据完成了,可以进行下一步骤的处理;当然串口中断判断的依据,我个人的理解就是:使用串口与DMA接收字节数据时,当串口检测到在1-2个字节通讯时间内,串口没有接收到数据时,就会判定串口空闲了,使用DMA将数据拷贝到其他数据内进行处理即可。
一:串口空闲中断的知识分享 CH32串口知识分享如下:
全双工或半双工的同步或异步通信
NRZ 数据格式
分数波特率发生器,最高 9Mbps
可编程数据长度
可配置的停止位支持 LIN,IrDA 编码器,智能卡
支持 DMA
多种中断源
串口发送数据的基本过程:当 TE(发送使能位)置位时,发送移位寄存器里的数据在 TX 引脚上输出,时钟在 CK 引脚上输出。在发送时,最先移出的是最低有效位,每个数据帧都由一个低电平的起始位开始,然后发送器根据 M(字长)位上的设置发送八位或九位的数据字,最后是数目可配置的停止位。如果配有奇偶检验位,数据字的最后一位为校验位。在 TE 置位后会发送一个空闲帧,空闲帧是 10 位或 11 位高电平,包含停止位。断开帧是 10 位或 11 位低电平,后跟着停止位。
串口的空闲中断:顾名思义,就是当串口在一定的时间内没有接收到数据时,触发控制状态,从而产生空闲中断;一般来说,CH32在数据交互时,传输字节之间的间隔很短,然后再一个字节的通讯时间内,没有收到数据时,意味着程序进入了空闲中断,所以在程序的初始化后,我们只需要开启空闲中断后,然后在串口的回调函数中,进行数据迁移就可以,也可以在空闲中断进行数据处理。
二:DMA知识分享:
直接存储器访问控制器(DMA)提供在外设和存储器之间或存储器和存储器之间的高速数据传输方式,无须 CPU 干预,数据可以通过 DMA 快速地移动,以节省 CPU 的资源来做其他操作。DMA 控制器每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各通道之间的优先级。
主要特征如下:
多个独立可配置通道
每个通道都直接连接专用的硬件 DMA 请求,并支持软件触发
支持循环的缓冲器管理
多个通道之间的请求优先权可以通过软件编程设置(最高、高、中和低),优先权设置相等时由通道号决定(通道号越低优先级越高)
支持外设到存储器、存储器到外设、存储器到存储器之间的传输
闪存、SRAM、外设的 SRAM、PB1、PB2 和 HB 外设均可作为访问的源和目标
可编程的数据传输字 节数目:最大为65535
三:串口2所使用的DMA通道如下:3.1 查看串口2使用的DMA通道
这里看到USART2的RXD和TXD,这里使用的是DMA1的通道6和通道7.当我们使用串口2的DMA发送和接收功能后,我们就不能再使用该通道下的其他复用功能了。3.2 串口2的配置过程:
系统初始化,配置系统时钟;
配置串口2的引脚,波特率等信息;
使能DMA的发送,接收功能;
使能串口空闲中断,开启接收;
四:程序编写过程:
4.1 初始化配置串口2的DMA接收功能:
/*******************************************************************************
* Function Name : DMA_USART2_RX_Configuration
* Description : Configures the DMA To USART2 RX.
* Input : None
* Output : None
* Return : None
* USART2 RX -> DAM1-CH6
*******************************************************************************/
void DMA_USART2_RX_Configuration(void)
{
DMA_InitTypeDef DMA_USART2_RX_InitStructure; //定义USART2的接收DMA结构体
NVIC_InitTypeDef NVIC_InitStructure = {0};
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );
DMA_DeInit(DMA1_Channel6);
DMA_USART2_RX_InitStructure.DMA_PeripheralBaseAddr = ( u32 )( &USART2->DATAR );
DMA_USART2_RX_InitStructure.DMA_MemoryBaseAddr = (u32)RecBuffer2;//定义接收数组
DMA_USART2_RX_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_USART2_RX_InitStructure.DMA_BufferSize = reclength2; //接收数组的最大的长度
DMA_USART2_RX_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_USART2_RX_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_USART2_RX_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_USART2_RX_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_USART2_RX_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式,接收数据循环存入
DMA_USART2_RX_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //最高优先级
DMA_USART2_RX_InitStructure.DMA_M2M = DMA_M2M_Disable;
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DMA_Init(DMA1_Channel6, &DMA_USART2_RX_InitStructure);
}
4.2 初始化配置串口2的DMA发送功能函数
/*******************************************************************************
* Function Name : DMA_USART2_TX_Configuration
* Description : Configures the DMA To USART2 TX.
* Input : None
* Output : None
* Return : None
* USART2 TX -> DAM1-CH7
*******************************************************************************/
void DMA_USART2_TX_Configuration(int length)
{
DMA_InitTypeDef DMA_USART2_TX_InitStructure; //定义USART2的发送DMA结构体,用于在发送的时候,重新初始化DMA通道
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );
/* DMA1 Channel7 (triggered by USART2 Tx event) Config */
DMA_DeInit(DMA1_Channel7);
DMA_USART2_TX_InitStructure.DMA_PeripheralBaseAddr = ( u32 )( &USART2->DATAR );
DMA_USART2_TX_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuffer2;
DMA_USART2_TX_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_USART2_TX_InitStructure.DMA_BufferSize = length; //发送缓冲区USART2
DMA_USART2_TX_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_USART2_TX_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_USART2_TX_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_USART2_TX_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_USART2_TX_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_USART2_TX_InitStructure.DMA_Priority = DMA_Priority_High; //高优先级
DMA_USART2_TX_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel7, &DMA_USART2_TX_InitStructure);
}
初始化串口2的基本信息如下所示:配置串口2 引脚,波特率,使能串口时钟,引脚时钟,配置中断优先级别。
/*******************************************************************************
* Function Name : USART2_Initialize
* Description : 初始化串口2
* Input : baud 波特率
* Output : None
* Return : NOne
*******************************************************************************/
void USART2_Init(int baud)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
USART_InitTypeDef USART_InitStructure = {0};
NVIC_InitTypeDef NVIC_InitStructure = {0};
RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2 , ENABLE );
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA , ENABLE );
/* USART2 TX-->A.2 RX-->A.3 */
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 );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init( GPIOA, &GPIO_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_Tx | USART_Mode_Rx;
/* Enable USART2 Receive and Transmit interrupts */
USART_DMACmd(USART2, USART_DMAReq_Rx | USART_DMAReq_Tx, ENABLE); //使能USART2的收发DMA
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);
USART_Init( USART2, &USART_InitStructure );
DMA_USART2_TX_Configuration(2);//发送2个空字节
DMA_USART2_RX_Configuration();
DMA_Cmd( DMA1_Channel7, ENABLE ); /* USART2 Tx */
DMA_Cmd( DMA1_Channel6, ENABLE ); /* USART2 Rx */
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd( USART2, ENABLE );
}
4.3 配置串口2 DMA发送函数如下:思路如下:
在使用DMA发送数据时候,首先判断一下DMA发送缓冲区内是否存在未发送完成的数据;
待发送缓冲区内数据为空时;
开启DMA发送,并重设发送字节;
/*******************************************************************************
* Function Name : bool SendDataToUSART2(char length)
* Description : Enable Transimt Interrupt and sart Send
* Input : Send data Buf Length
* Output : None
* Return : NOne
*******************************************************************************/
bool SendDataToUSART2(char length)
{
//等待DMA发送完毕后,即可以启动发送
/* Wait until DMA1_Channel 7 Transfer Complete */
volatile u16 uDataCounter;
// while( DMA_GetFlagStatus( DMA1_FLAG_TC7 ) == RESET ) /* Wait until USART2 TX DMA1 Transfer Complete */
// {
// }
uDataCounter = DMA_GetCurrDataCounter(DMA1_Channel7);
while (uDataCounter > 0)
{
uDataCounter = DMA_GetCurrDataCounter(DMA1_Channel7);
}
DMA_USART2_TX_Configuration(length);
DMA_Cmd(DMA1_Channel7, ENABLE); //开启DMA发送
return TRUE;
}
4.4 配置串口2 的DMA 与空闲中断接收函数处理如下:
编写思路如下所示:
1:首先需要清楚空闲中断标志,以便再次进入该串口空闲中断,
2:停止DMA的接收,防止计算长度时候出错
3:计算一下DMA数据中有效数据
4:将有效数据拷贝至其他的定义的数据内,以便程序处理
5:将接收完一包有效数据的标志位置位,同时需要再次开启DMA接收,
/*******************************************************************************
* Function Name : void USART2_IRQHandler(void)
* Description : 串口2的中断服务函数
* Input : 串口2的空闲中断服务函数
* Output : None
* Return : NOne
*******************************************************************************/
void USART2_IRQHandler(void)
{
if (USART_GetITStatus(USART2, USART_IT_IDLE) != RESET) //触发空闲中断
{
USART_ReceiveData(USART2);
uint16_t rxlen = (reclength2 - DMA1_Channel6->CNTR);
// DWINReceiveDeal(RecBuffer2);
memcpy(Usart2_DEAL_RX_Buf,RecBuffer2,rxlen );
memset(RecBuffer2,0,reclength2);//清空接收数组
DMA_Cmd(DMA1_Channel6, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel6, reclength2); //重设接收个数
DMA_Cmd(DMA1_Channel6, ENABLE);
}
}
4.5 测试发送的代码如下:
/***********************************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url] SendTestData(char ID)
* @param 切换图片
* @retval 无
* [url=home.php?mod=space&uid=187600]@author[/url] 聪聪哥哥
* [url=home.php?mod=space&uid=895143]@version[/url] V1.1.0
* [url=home.php?mod=space&uid=212281]@date[/url] 7-10-2025
*************************************************************************************************/
void SendTestData(char ID)
{
SendBuffer2[0] = 0x5A ;
SendBuffer2[1] = 0xA5 ;
SendBuffer2[2] = 0x07 ;
SendBuffer2[3] = 0x82 ;
SendBuffer2[4] = 0x00 ;
SendBuffer2[5] = 0x84 ;
SendBuffer2[6] = 0x5A ;
SendBuffer2[7] = 0x01 ;
SendBuffer2[8] = 0x00 ;
SendBuffer2[9] = ID ;
SendDataToUSART2(10);
}
五:应用层驱动函数如下:/***********************************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url] void WorkPageData(void)
* @param 工作界面数据显示
* @retval 无
* [url=home.php?mod=space&uid=187600]@author[/url] 聪聪哥哥
* [url=home.php?mod=space&uid=895143]@version[/url] V1.1.0
* [url=home.php?mod=space&uid=212281]@date[/url] 7-10-2025
*************************************************************************************************/
int data = 0;
void WorkPageData(void)
{
data++ ;
Display_Text(0x00,"21IC lun tan cong cong ");
Add_Write_Data(0x100 ,data);
}
/***********************************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url] void DataFresh(void)
* @param 各个界面下的数据显示
* @retval 无
* [url=home.php?mod=space&uid=187600]@author[/url] 聪聪哥哥
* [url=home.php?mod=space&uid=895143]@version[/url] V1.1.0
* [url=home.php?mod=space&uid=212281]@date[/url] 7-10-2025
*************************************************************************************************/
void DataFresh(void)
{
switch (cgDisMode)
{
case WorkPage : WorkPageData() ;
break ;
default :
break ;
}
}
六:实物验证:
使用DMA的方式驱动串口屏,也是很方面,而且制作的秒级定时器,实现数据的加减,历时2天,终于调好了DMA的发送和DMA的接收,在调试过程中,开始使用的串口中断的方式,后来为了优化CPU的响应速度,修改为DMA的发送方式,在速度上得到了提升,而且系统也是更加的稳定,
在遇到问题时候,多看看官方的手册还是有帮助的。
在本贴中,对串口的DMA发送时候,判断一下DMA数据种是否正在有数据输出,当数据为空时,在进行放松,防止打断正在发送的数据。
|