首页 » Web前端 » php435技巧_基于N32G435的高负载串口通信

php435技巧_基于N32G435的高负载串口通信

访客 2024-11-23 0

扫一扫用手机浏览

文章目录 [+]

一、序言

在单片机中,USART通信是最常用也是最先去打仗的串口外设,在小数据量运用中一样平常不须要考虑USART串口(以下简称为串口)的高负载能力,比如打印一下log,吸收几个其他设备的指令或者发送几个指令掌握其他设备。
但是在高速的大数据量的通信场合,串口可能会承载较高的数据负载,如果不合理地进行单片机的资源利用,则有可能造成各种问题。
比如利用串口吸收中断吸收大量的数据,频繁地进入中断,会占用太多的CPU资源。
这时可能会想到【空闲中断+DMA传输完成中断】的办法吸收大量数据,但是这是一个极具风险的行为,假设一下,DMA数据传输结束之后,此时CPU开始读取DMA缓存中的数据,此时又有新的数据进来,新的数据就会覆盖之前的数据导致非常。

php435技巧_基于N32G435的高负载串口通信

二、如何启用串口的DMA功能

php435技巧_基于N32G435的高负载串口通信
(图片来自网络侵删)

在谈论如何实现串口的高负载通信之前,我们得先明白如何启用串口的DMA通信。

DMA(Direct Memory Access)直接储存器访问,是一个CPU用于数据从一个地址空间到另一个地址空间的搬运组件,该过程无需CPU的干预,不占用CPU的资源,可以使单片机这种单线程CPU实现“伪多线程”。
只需在数据搬运结束后关照CPU即可。

在国民技能的资料中是有串口+DMA的例程的,但是官方为了用户调试方便,例程相对大略,便是实现了两个MCU串口间的DMA通信,在开拓时具有一定借鉴意义,但是不具备高负载能力,同时移植性不是很好,这里我在例程的根本上进行简化,同时例程不具备的功能也会逐一展开。

1.串口+DMA发送

#define TxBufferSize1 (countof(TxBuffer1) - 1)

#define countof(a) (sizeof(a) / sizeof((a)))

USART_InitType USART_InitStructure;

uint8_t TxBuffer1[20] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a};

首先是定义一些干系的变量,数据和构造体啥的,TxBufferSize1 发送数量,TxBuffer1[20]发送的数组。

/

[url=home.php?mod=space&uid=247401]@brief[/url] Configures the different system clocks.

/

void RCC_Configuration(void)

{

/ DMA clock enable /

RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_DMA, ENABLE);

/ Enable GPIO clock /

RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB, ENABLE);

/ Enable USARTy and USARTz Clock /

RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_USART1, ENABLE);

}

/

[url=home.php?mod=space&uid=247401]@brief[/url] Configures the different GPIO ports.

/

void GPIO_Configuration(void)

{

GPIO_InitType GPIO_InitStructure;

/ Initialize GPIO_InitStructure /

GPIO_InitStruct(&GPIO_InitStructure);

/ Configure USARTy Tx as alternate function push-pull /

GPIO_InitStructure.Pin = GPIO_PIN_6;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Alternate = GPIO_AF0_USART1;

GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);

/ Configure USARTy Rx as alternate function push-pull and pull-up /

GPIO_InitStructure.Pin = GPIO_PIN_7;

GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;

GPIO_InitStructure.GPIO_Alternate = GPIO_AF0_USART1;

GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);

}

对干系的时钟和串口的引脚进行初始化,这里是直接用的官方例程,只不过将官方例程的宏定义换成了实际的值,便于看代码,不然还需跳转,但是官方的例程这方面的可移植性会更好。

void DMA_Configuration(void)

{

DMA_InitType DMA_InitStructure;

/ USARTy TX DMA1 Channel (triggered by USARTy Tx event) Config /

DMA_DeInit(DMA_CH4);

DMA_StructInit(&DMA_InitStructure);

DMA_InitStructure.PeriphAddr = (USART1_BASE + 0x04);

DMA_InitStructure.MemAddr = (uint32_t)TxBuffer1;

DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;

DMA_InitStructure.BufSize = TxBufferSize1;

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(DMA_CH4, &DMA_InitStructure);

DMA_RequestRemap(DMA_REMAP_USART1_TX, DMA, DMA_CH4, ENABLE);

}

DMA的初始化采取NORMAL模式,即只发送一次,当计数器为0时便不再搬运数据。

void UART_Init(USART_Module USARTx,uint32_t BaudRate)

{

/ USARTy and USARTz configuration ---------------------------/

USART_StructInit(&USART_InitStructure);

USART_InitStructure.BaudRate = BaudRate;

USART_InitStructure.WordLength = USART_WL_8B;

USART_InitStructure.StopBits = USART_STPB_1;

USART_InitStructure.Parity = USART_PE_NO;

USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;

USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;

/ Configure USARTy and USARTz /

USART_Init(USARTx, &USART_InitStructure);

/ Enable USARTy DMA Rx and TX request /

USART_EnableDMA(USARTx, USART_DMAREQ_RX | USART_DMAREQ_TX, ENABLE);

/ Enable the USARTy and USARTz /

USART_Enable(USARTx, ENABLE);

}

串口的初始化。

void DMA_send(uint8_t pBuffer,uint16_t BufferLength)

{

DMA_EnableChannel(DMA_CH4, DISABLE);

DMA_SetCurrDataCounter(DMA_CH4,BufferLength);

DMA_EnableChannel(DMA_CH4, ENABLE);

while (USART_GetFlagStatus(USART1, USART_FLAG_TXDE) == RESET)

{

}

}

DMA的发送函数,先失落能DMA通道,再重新设置传输长度,再使能DMA通道,这里是检测while是检测串口的发送完成体例位,在官方的demo中检测的是DMA的通道完成标志,这个在这里面是不可以的,由于DMA的搬运速率是远大于串口的通信速率的,如果检测DMA通道完成标志,会导致DMA已经将数据搬运到串口的数据寄存器,但是由于串口的速率不足,导致此时数据还未送出,而由于例程只循环一次,在测试例程时看不出问题,但是这里会出问题。

int main(void)

{

/ System Clocks Configuration /

RCC_Configuration();

/ Configure the GPIO ports /

GPIO_Configuration();

/ Configure the DMA /

DMA_Configuration();

UART_Init(USART1,115200);

while (1)

{

DMA_send(TxBuffer1,20);

Delay(10000000);

}

}

末了在主函数调用各初始化函数,在while (1)中循环发送便可实现最大略的串口+DMA发送。

2.串口+DMA吸收

在上面发送的根本上我们加上DMA的吸收功能,此处须要阐明一下下面的操作:为了对应手册,上面的串口发送DMA通道原来是CH4,我下面全部改成CH1。

uint8_t RxBuffer1[20];

定义一个数组用于吸收串口数据。

USART_ConfigInt(USARTx, USART_INT_IDLEF, ENABLE);

添加串口中断定义。

void NVIC_Configuration(void)

{

NVIC_InitType NVIC_InitStructure;

/ Enable the USARTz Interrupt /

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

}

添加NVIC配置。

void DMA_Configuration(void)

{

DMA_InitType DMA_InitStructure;

/ USARTy TX DMA1 Channel (triggered by USARTy Tx event) Config /

DMA_DeInit(DMA_CH1);

DMA_StructInit(&DMA_InitStructure);

DMA_InitStructure.PeriphAddr = (USART1_BASE + 0x04);

DMA_InitStructure.MemAddr = (uint32_t)TxBuffer1;

DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;

DMA_InitStructure.BufSize = TxBufferSize1;

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(DMA_CH1, &DMA_InitStructure);

DMA_RequestRemap(DMA_REMAP_USART1_TX, DMA, DMA_CH1, ENABLE);

DMA_DeInit(DMA_CH2);

DMA_InitStructure.PeriphAddr = (USART1_BASE + 0x04);

DMA_InitStructure.MemAddr = (uint32_t)RxBuffer1;

DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;

DMA_InitStructure.BufSize = TxBufferSize1;

DMA_Init(DMA_CH2, &DMA_InitStructure);

DMA_RequestRemap(DMA_REMAP_USART1_RX, DMA, DMA_CH2, ENABLE);

}

添加DMA的吸收,并将通道设置为CH2。

void DMA_Revice(uint16_t BufferLength)

{

DMA_EnableChannel(DMA_CH2, DISABLE);

DMA_SetCurrDataCounter(DMA_CH2,BufferLength);

DMA_EnableChannel(DMA_CH2, ENABLE);

}

添加DMA吸收函数

void USART1_IRQHandler(void)

{

if (USART_GetIntStatus(USART1, USART_INT_IDLEF) != RESET)

{

/软件先读 USART_STS,再读 USART_DAT 打消空闲中断标志。
/

USART1->STS;

USART1->DAT;

for(int i=0;i<20;i++)

{

TxBuffer1[i] = RxBuffer1[i];

}

DMA_send(20);

DMA_Revice(20);

}

}

添加串口中断函数,在串口中断函数中将吸收的数据传给DMA发送数组,再通过DMA的办法发送出来用于校验结果。

通过串口助手可不雅观测数据精确。
至此,常见的串口+DMA的发送与吸收完成。
后文将实现高负载的通信。

三、高负载情形下的DMA如何实现

在串口数据量较大时,一样平常利用双BUF,很多单片机有硬件双缓冲,DMA的目标储存区域有两个,当一次完全的数据传输结束后,也便是counter值变为0时,DMA会自动将数据指向另一块区域。
这样用户就有韶光去处理刚存满的buf,而不会被覆盖。
便是“乒乓缓存”。

普通DMA

DMA双缓冲

大致流程如下:

1.串口有数据到来,DMA现将数据储存在内存1,完成后关照CPU过来处理数据。

2.此时DMA一直下,开始将后续的数据搬运到内存2。

3.内存2的数据搬运完成,关照CPU开始处理内存2中的数据。

4.如果数据传输还未结束,此时DMA会将数据储存在内存1。
如此循环,直至没有数据到来。

但是遗憾的是N32G435这块芯片不具备双缓冲模式,那么我们可以主动掌握DMA跳转内存区域。
利用“传输过半中断”来仿照双缓冲模式。

大致流程如下:

1.DMA完成搬运一半的数据时,产生一个传输过半中断,此时我们让CPU来处理上一半数据。

2.DMA数据搬运未停滞,此时连续搬运后一半数据,此操作不会影响前面一半的数据处理。

3.DMA数据搬运完,触发传输完成中断,这时CPU可以处理后半数据。

4.如果数据传输还未结束,DMA连续将数据向前半搬运,如此循环。

代码讲解如下:

以下代码完全流程如下:

1.配置串口波特率2.5M,DMA的BufSize设置为40,开启传输过半中断,传输完成中断,串口空闲中断。

2.启动DMA吸收。

3.通过串口助手发送80个数据到串口。

4.当DMA吸收数组吸收到20个数据触发传输过半中断,跳转中断函数将20个数据存放到数组中。

5.此时DMA仍在运行,但是数据存放在DMA吸收数组的后20个地址空间。

6.当DMA吸收数组填满,触发DMA传输完成中断,跳转中断函数将后20个数据保存,此时DMA一共搬运了40个数据。

7.DMA连续搬运数据到吸收数组里,此时会覆盖之前的前二十个数据,跳转到步骤4.

8.吸收完80个数据,此时触发串口空闲中断,将吸收到的数据打印出来。

在上面代码根本上做如下操作:

1.将DMA CH2通道设置为循环模式,测试阶段将BufSize设置为40,开启传输过半中断和传输完成中断。
同时为了测试高速场景,串口波特率设置为2.5M:

DMA_DeInit(DMA_CH2);

DMA_InitStructure.PeriphAddr = (USART1_BASE + 0x04);

DMA_InitStructure.MemAddr = (uint32_t)buffer;

DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;

DMA_InitStructure.BufSize = 40;

DMA_InitStructure.CircularMode = DMA_MODE_CIRCULAR;

DMA_Init(DMA_CH2, &DMA_InitStructure);

DMA_RequestRemap(DMA_REMAP_USART1_RX, DMA, DMA_CH2, ENABLE);

DMA_ConfigInt(DMA_CH2,DMA_INT_HTX,ENABLE);//半传输中断

DMA_ConfigInt(DMA_CH2,DMA_INT_TXC,ENABLE);//传输完成中断

DMA_ClearFlag(DMA_FLAG_HT2,DMA);//打消标志位,避免第一次传输出错

DMA_ClearFlag(DMA_FLAG_TC2,DMA);

DMA_ClrIntPendingBit(DMA_INT_HTX2,DMA);

DMA_ClrIntPendingBit(DMA_INT_TXC2,DMA);

2.NVIC设置DMA通道中断

void NVIC_Configuration(void)

{

NVIC_InitType NVIC_InitStructure;

/ Enable the USARTz Interrupt /

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);

NVIC_InitStructure.NVIC_IRQChannel = DMA_Channel2_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

}

3.添加DMA的CH2中断函数,num为全局变量,目的是将所有的数据保存进buf数组:

void DMA_Channel2_IRQHandler(void)

{

//传输半满

if(DMA_GetIntStatus(DMA_INT_HTX2,DMA) == SET)

{

DMA_ClrIntPendingBit(DMA_INT_HTX2,DMA);

DMA_ClearFlag(DMA_FLAG_HT2,DMA);

for(int i=0;i<20;i++)

{

buf[num] = buffer[i];

num++;

}

}

//传输满

if(DMA_GetIntStatus(DMA_INT_TXC2,DMA) == SET)

{

DMA_ClrIntPendingBit(DMA_INT_TXC2,DMA);

DMA_ClearFlag(DMA_FLAG_TC2,DMA);

for(int i=20;i<40;i++)

{

buf[num] = buffer[i];

num++;

}

}

}

4.在串口空闲中断中将收到的数据全部打印出来。

void USART1_IRQHandler(void)

{

if (USART_GetIntStatus(USART1, USART_INT_IDLEF) != RESET)

{

/软件先读 USART_STS,再读 USART_DAT 打消空闲中断标志。
/

USART1->STS;

USART1->DAT;

for(int i=0;i<80;i++)

{

TxBuffer1[i] = buf[i];

}

DMA_send(80);

num=0;

}

}

5.测试结果如下,在2.5M波特率的情形下保持数据完全。

写在末了

这次紧张谈论了一种高负载情形下如何缓解CPU压力的方法,所言所写不尽完善,例如不天命据吸收,就可以通过DMA_GetCurrDataCounter(DMA_CH2);函数进行传输数据的统计打算,这点大家可以自由发挥,现实可能碰着的问题是多种多样的,紧张在于关键能力的拓展。
更多的还须要根据实际情形灵巧配置。

阅读原文:https://bbs.21ic.com/icview-3209220-1-1.html

相关文章

QQ伪装黑客代码大全技术与风险警示

网络安全问题日益凸显。QQ作为一种流行的社交工具,成为了黑客攻击的主要目标之一。本文将针对QQ伪装黑客代码大全进行深入剖析,揭示其...

Web前端 2025-03-02 阅读1 评论0