RTU使用DMA通信,MCU DMA驱动,STM32 DMA驱动

网站导航

开发相关(SCJ)

当前位置: 首页 > 开发相关(SCJ)
STM32H7 DMA驱动(STM32H743 DMA驱动)
时间:2025-03-31 13:25:51 点击次数:

STM32H7 DMA驱动(STM32H743 DMA驱动)


STM32H7 拥有多个DMA,此处只讲解最常用的DMA1,DMA2;与之前的STM32不同的是,STM32H7的DMA支持不同的触发源,不用像以前一样,每个通道与对应的硬件触发源固定死,通过DMAMUX进项选择

使用上会更加灵活。

image.png



在使用DMA过程中,需要注意,STM32H743的内存分区很多,ITCM与DTCM是无法通过DMA1,DMA2进行访问的,使用的时候需要注意内存位置,同时使用了DMA后会存在一致性问题,

就是DMA传输的数据通常都在cache中更新,通过CPU去访问会发现数据并没有同步到实际内存区域。

直接存储器访问 (DMA) 用于在外设与存储器之间以及存储器与存储器之间提供高速数据传
输。可以在无需任何
CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可
供其它操作使用。
DMA 控制器基于复杂的总线矩阵架构,将功能强大的双 AHB 主总线架构与独立的 FIFO
合在一起,优化了系统带宽。
两个
DMA 控制器总共有 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理
一个或多个外设的存储器访问请求。每个数据流总共可以有多达
8 个通道(或称请求)。每
个通道都有一个仲裁器,用于处理
DMA 请求间的优先级。


DMA 主要特性
DMA 主要特性是:
AHB 主总线架构,一个用于存储器访问,另一个用于外设访问
仅支持 32 位访问的 AHB 从编程接口D
每个 DMA 控制器有 8 个数据流,每个数据流有多达 115 个通道(或称请求)
每个数据流有四个字深的 32 位先进先出存储器缓冲区 (FIFO),可用于 FIFO 模式或直接
模式:
– FIFO 模式:可通过软件将阈值级别选取为 FIFO 大小的 1/41/2 3/4
直接模式
每个
DMA 请求会立即启动对存储器的传输。当在直接模式(禁止 FIFO)下将
DMA 请求配置为以存储器到外设模式传输数据时, DMA 仅会将一个数据从存储器
预加载到内部
FIFO,从而确保一旦外设触发 DMA 请求时则立即传输数据。
通过硬件可以将每个数据流配置为:
支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道
在存储器端支持双缓冲的双缓冲区通道
8 个数据流中的每一个都连接到专用硬件 DMA 通道(请求)
DMA 数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软
件优先级相同的情况下可以通过硬件决定优先级(例如,请求
0 的优先级高于请求 1
每个数据流还支持通过软件触发的存储器间的传输
可通过 DMAMux1 在多达 115 个可能的通道请求中选择每个数据流请求。此选择可由软
件配置,允许多个外设发起
DMA 请求
要传输的数据项的数目可以由 DMA 控制器或外设管理:
– DMA 流控制器:要传输的数据项的数目可用软件编程,从 1 65535
外设流控制器:要传输的数据项的数目未知并由源或目标外设控制,这些外设通过
硬件发出传输结束的信号
独立的源和目标传输宽度(字节、半字、 字):源和目标的数据宽度不相等时, DMA
自动封装/解封必要的传输数据来优化带宽。这个特性仅在 FIFO 模式下可用
对源和目标的增量或非增量寻址
支持 4 个、 8 个和 16 个节拍的增量突发传输。突发增量的大小可由软件配置,通常等
于外设
FIFO 大小的一


image.png



//DMA驱动代码

/*************************************************************************************************************

 * 文件名 : dma.c

 * 功能 : STM32H7 DMA驱动

 * 作者 : www.scj-water.com

 * 创建时间 : 2025-03-07

 * 最后修改时间 : 2025-03-11

 * 详细: 需要提前使能DMA1 DMA2时钟

 * DMA12无法访问ITCM与DTCM内存

*************************************************************************************************************/

#include "dma.h"

#include "system.h"


//DMA外设结构

static const  DMA_TypeDef * const sg_DMA_TYPE_BUFF[2] = {DMA1, DMA2};

static const DMA_Stream_TypeDef * sg_DMA1_Stream[8] = {DMA1_Stream0, DMA1_Stream1, DMA1_Stream2, DMA1_Stream3, DMA1_Stream4, DMA1_Stream5, DMA1_Stream6, DMA1_Stream7};

static const DMA_Stream_TypeDef *sg_DMA2_Stream[8] = {DMA2_Stream0, DMA2_Stream1, DMA2_Stream2, DMA2_Stream3, DMA2_Stream4, DMA2_Stream5, DMA2_Stream6, DMA2_Stream7};

static const DMAMUX_Channel_TypeDef* sg_DMAMUX_Channel[16] = {

DMAMUX1_Channel0, DMAMUX1_Channel1, DMAMUX1_Channel2, DMAMUX1_Channel3, DMAMUX1_Channel4, DMAMUX1_Channel5, DMAMUX1_Channel6, DMAMUX1_Channel7,

DMAMUX1_Channel8, DMAMUX1_Channel9, DMAMUX1_Channel10, DMAMUX1_Channel11, DMAMUX1_Channel12, DMAMUX1_Channel13, DMAMUX1_Channel14, DMAMUX1_Channel15

};


#define DMA12_MUX1_REQ_MAX 115 //请求号最大值




/*************************************************************************************************************************

* 函数 : void DMA_Config(DMAxSx_CH_TYPE DMAxSx_Ch, DAM_CONFIG *pConfig)

* 功能 : DMA配置

* 参数 : DMAxSx_Ch:DMA通道选择;pConfig:配置,见DAM_CONFIG

* 返回 : 无

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-11

* 最后修改时间 : 2025-03-11

* 说明 :

*************************************************************************************************************************/

void DMA_Config(DMAxSx_CH_TYPE DMAxSx_Ch, DAM_CONFIG *pConfig)

{

u8 Stream,Req;

DMA_Stream_TypeDef *DMA_Stream;

DMAMUX_Channel_TypeDef* DMAMUX_Channel;

DMA_TypeDef *DMAx;

DMAx = (DMA_TypeDef *)sg_DMA_TYPE_BUFF[(DMAxSx_Ch >> 8)&0x01]; //获取DMA

Stream = (DMAxSx_Ch)&0xff; //获取流通道

Req = DMAxSx_Ch&0xff;

if((Req > DMA12_MUX1_REQ_MAX) || (Stream > 7)) //通道或流超出了范围

{

return;

}

if((DMAxSx_Ch >> 8)&0x01) //DMA2

{

DMA_Stream = (DMA_Stream_TypeDef *)sg_DMA2_Stream[Stream]; //获取数据流 x 配置寄存器

DMAMUX_Channel = (DMAMUX_Channel_TypeDef*)sg_DMAMUX_Channel[8+ Stream];

}

else //DMA1

{

DMA_Stream = (DMA_Stream_TypeDef *)sg_DMA1_Stream[Stream]; //获取数据流 x 配置寄存器

DMAMUX_Channel = (DMAMUX_Channel_TypeDef*)sg_DMAMUX_Channel[Stream];

}

//开始配置

DMA_Stream->CR = 0; //禁止数据流,才能进行后面的设置

DMAMUX_Channel->CCR = pConfig->DMA_Req; //设置DMA请求,同时禁止同步模式,禁止中断,禁止事件


DMA_Stream->CR |= (pConfig->Mem_BurstSize & 0x03)<<23; //存储器突发传输配置

DMA_Stream->CR |= (pConfig->Per_BurstSize & 0x03)<<21; //外设突发传输配置

if(pConfig->isDoubleBuffer) //使能双缓冲模式

{

DMA_Stream->CR |=  1<<18;

}

DMA_Stream->CR |= (pConfig->PrioLevel & 0x03)<<16; //优先级设置

if(!pConfig->isPerIncPsizeAuto) //外设地址的偏移量与 PSIZE 相关

{

DMA_Stream->CR |= 1<<15; //用于计算外设地址的偏移量固定为 4(32 位对齐)。

}

DMA_Stream->CR |= (pConfig->Mem_Size & 0x03)<<13; //存储器数据大小设置

DMA_Stream->CR |= (pConfig->Pre_Size & 0x03)<<11; //外设数据大小设置

if(pConfig->isMemInc)

{

DMA_Stream->CR |= 1<<10; //存储器地址递增模式

}

if(pConfig->isPerInc)

{

DMA_Stream->CR |= 1<<9; //外设地址递增模式

}

if(pConfig->isCircMode)

{

DMA_Stream->CR |= 1<<8; //循环模式

}

DMA_Stream->CR |= (pConfig->DataTranDir & 0x03)<<6; //数据传输方向

if(pConfig->isPreFlowCtrl)

{

DMA_Stream->CR |= 1<<5; //外设是流控设备

}

if(pConfig->isEnableTranComplInt)

{

DMA_Stream->CR |= 1<<4; //使能完成中断

}

DMA_Stream->NDTR = pConfig->TranDataCount; //传输的数据项数-外设到存储器是流控无需设置长度

DMA_Stream->PAR = pConfig->PerAddr; //外设地址

DMA_Stream->M0AR = pConfig->Mem0Addr; //存储器0地址

DMA_Stream->M1AR = pConfig->Mem0Addr; //存储器1地址

DMA_Stream->FCR = 0;

if(!pConfig->isDirectMode)

{

DMA_Stream->FCR |= 1 << 2; //禁止直接模式

}

DMA_Stream->FCR |= (pConfig->FIFO_Thres & 0x03)<<0; //FIFO阈值选择

//必须清除中断标记,否则DMA可能会停止工作

DMA_ClearIntStatus(DMAxSx_Ch, DMA_INT_ALL);

if(pConfig->isEnableStream)

{

DMA_Stream->CR |= 1<<0; //使能流,开始传输

}

//INFO_S("DMA_Stream->NDTR:%X\r\n",DMA_Stream->NDTR);

//INFO_S("DMA_Stream->NDTR:%X\r\n",DMA_Stream->NDTR);

//INFO_S("DMA_Stream->NDTR:%X\r\n",DMA_Stream->NDTR);

}




/*************************************************************************************************************************

* 函数 : void DMA_StartTrans(DMAxSx_CH_TYPE DMAxSx_Ch, u32 Mem0Addr, u32 Mem1Addr, u16 TranDataCount)

* 功能 : 启动DMA 传输

* 参数 : DMAxSx_Ch:DMA通道选择;Mem0Addr:内存地址0,Mem1Addr:内存地址1(双缓冲模式下有效);TranDataCount:传输数据数量

* 返回 : 无

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-11

* 最后修改时间 : 2025-03-11

* 说明 : 启动传输,请先调用DMA_Config对当前通道进行配置

*************************************************************************************************************************/  

void DMA_StartTrans(DMAxSx_CH_TYPE DMAxSx_Ch, u32 Mem0Addr, u32 Mem1Addr, u16 TranDataCount)

{

u8 Stream;

DMA_Stream_TypeDef *DMA_Stream;


Stream = (DMAxSx_Ch) & 0xff; //获取流通道

if (Stream > 7) //通道或流超出了范围

{

return;

}

if((DMAxSx_Ch >> 8)&0x01) //DMA2

{

DMA_Stream = (DMA_Stream_TypeDef *)sg_DMA2_Stream[Stream]; //获取数据流 x 配置寄存器

}

else //DMA1

{

DMA_Stream = (DMA_Stream_TypeDef *)sg_DMA1_Stream[Stream]; //获取数据流 x 配置寄存器

}

DMA_Stream->CR &= ~BIT0; //关闭数据流

DMA_Stream->NDTR = TranDataCount; //传输的数据项数

DMA_Stream->M0AR = Mem0Addr; //存储器0地址

DMA_Stream->M1AR = Mem1Addr; //存储器1地址

//必须清除中断标记,否则DMA可能会停止工作

DMA_ClearIntStatus(DMAxSx_Ch, DMA_INT_ALL);

DMA_Stream->CR |= 1<<0; //使能流,开始传输

}



/*************************************************************************************************************************

* 函数 : void DMA_StartTrans_Mem(DMAxSx_CH_TYPE DMAxSx_Ch, u32 DestAddr, u32 SourceAddr, u16 TranDataCount)

* 功能 : 启动DMA 传输(内存到内存传输)

* 参数 : DMAxSx_Ch:DMA通道选择;DestAddr:目标内存地址,SourceAddr:源内存地址;TranDataCount:传输数据数量

* 返回 : 无

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-11

* 最后修改时间 : 2025-03-11

* 说明 : 启动传输,请先调用DMA_Config对当前通道进行配置

*************************************************************************************************************************/  

void DMA_StartTrans_Mem(DMAxSx_CH_TYPE DMAxSx_Ch, u32 DestAddr, u32 SourceAddr, u16 TranDataCount)

{

u8 Stream;

DMA_Stream_TypeDef* DMA_Stream;


Stream = (DMAxSx_Ch) & 0xff; //获取流通道

if (Stream > 7) //通道或流超出了范围

{

return;

}

if((DMAxSx_Ch >> 8)&0x01) //DMA2

{

DMA_Stream = (DMA_Stream_TypeDef *)sg_DMA2_Stream[Stream]; //获取数据流 x 配置寄存器

}

else //DMA1

{

DMA_Stream = (DMA_Stream_TypeDef *)sg_DMA1_Stream[Stream]; //获取数据流 x 配置寄存器

}

DMA_Stream->CR &= ~BIT0; //关闭数据流

DMA_Stream->NDTR = TranDataCount; //传输的数据项数

DMA_Stream->M0AR = DestAddr; //存储器0地址-目标地址

DMA_Stream->PAR = SourceAddr; //外设-源地址

//必须清除中断标记,否则DMA可能会停止工作

DMA_ClearIntStatus(DMAxSx_Ch, DMA_INT_ALL);

DMA_Stream->CR |= 1<<0; //使能流,开始传输

}



/*************************************************************************************************************************

* 函数 : void DMA_StopTrans(DMAxSx_CH_TYPE DMAxSx_Ch)

* 功能 : 关闭 DMA 传输

* 参数 : DMAxSx_Ch:DMA通道选择

* 返回 : 无

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-11

* 最后修改时间 : 2025-03-11

* 说明 :

*************************************************************************************************************************/  

void DMA_StopTrans(DMAxSx_CH_TYPE DMAxSx_Ch)

{

u8 Stream;

DMA_Stream_TypeDef *DMA_Stream;

u32 retry = 100000;

Stream = (DMAxSx_Ch) & 0xff; //获取流通道

if (Stream > 7) //通道或流超出了范围

{

return;

}

if((DMAxSx_Ch >> 8)&0x01) //DMA2

{

DMA_Stream = (DMA_Stream_TypeDef *)sg_DMA2_Stream[Stream]; //获取数据流 x 配置寄存器

}

else //DMA1

{

DMA_Stream = (DMA_Stream_TypeDef *)sg_DMA1_Stream[Stream]; //获取数据流 x 配置寄存器

}

DMA_Stream->CR &= ~BIT0; //关闭数据流

while((DMA_Stream->CR & BIT0) && retry) //等待数据流关闭成功

{

retry --;

Delay_US(1);

}

if (0 == retry)

{

ERROR_S("DMA CH%d停止超时\r\n", DMAxSx_Ch);

}

//必须清除中断标记,否则DMA可能会停止工作

DMA_ClearIntStatus(DMAxSx_Ch, DMA_INT_ALL);

}




/*************************************************************************************************************************

* 函数 : u32 DMA_GetIntStatus(DMAxSx_CH_TYPE DMAxSx_Ch)

* 功能 : 获取DMA中断状态

* 参数 : DMAxSx_Ch:DMA通道选择

* 返回 : 中断状态,见DMA中断定义

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-11

* 最后修改时间 : 2025-03-11

* 说明 :

*************************************************************************************************************************/  

u32 DMA_GetIntStatus(DMAxSx_CH_TYPE DMAxSx_Ch)

{

u8 Stream;

DMA_TypeDef *DMAx;

u32 temp = 0;


DMAx = (DMA_TypeDef *)sg_DMA_TYPE_BUFF[(DMAxSx_Ch >> 8)&0x01]; //获取DMA

Stream = (DMAxSx_Ch)&0xff; //获取流通道

if (Stream > 7) //通道或流超出了范围

{

return 0;

}

switch(Stream)

{

case 0: //Stream 0

{

temp = (DMAx->LISR >> 0)&0x3F;

}break;

case 1: //Stream 1

{

temp = (DMAx->LISR >> 6)&0x3F;

}break;

case 2: //Stream 2

{

temp = (DMAx->LISR >> (16+0))&0x3F;

}break;

case 3: //Stream 3

{

temp = (DMAx->LISR >> (16+6))&0x3F;

}break;

case 4: //Stream 4

{

temp = (DMAx->HISR >> 0)&0x3F;

}break;

case 5: //Stream 5

{

temp = (DMAx->HISR >> 6)&0x3F;

}break;

case 6: //Stream 6

{

temp = (DMAx->HISR >> (16+0))&0x3F;

}break;

case 7: //Stream 7

{

temp = (DMAx->HISR >> (16+6))&0x3F;

}break;

default: 

{

temp = 0; 

}break;

}

return temp;

}



/*************************************************************************************************************************

* 函数 : void DMA_ClearIntStatus(DMAxSx_CH_TYPE DMAxSx_Ch, u32 IntStatus)

* 功能 : 清除DMA中断状态

* 参数 : DMAxSx_Ch:DMA通道选择;IntStatus:要清除的中断,见DMA中断定义

* 返回 : 无

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-11

* 最后修改时间 : 2025-03-11

* 说明 :

*************************************************************************************************************************/  

void DMA_ClearIntStatus(DMAxSx_CH_TYPE DMAxSx_Ch, u32 IntStatus)

{

u8 Stream;

DMA_TypeDef *DMAx;

u32 temp;

DMAx = (DMA_TypeDef *)sg_DMA_TYPE_BUFF[(DMAxSx_Ch >> 8)&0x01]; //获取DMA

Stream = (DMAxSx_Ch) & 0xff; //获取流通道

if (Stream > 7) //通道或流超出了范围

{

return;

}

switch(Stream)

{

case 0: //Stream 0

{

temp = IntStatus & 0x3F;

DMAx->LIFCR |= temp << 0;

}break;

case 1: //Stream 1

{

temp = IntStatus & 0x3F;

DMAx->LIFCR |= temp << 6;

}break;

case 2: //Stream 2

{

temp = IntStatus & 0x3F;

DMAx->LIFCR |= temp << (16 + 0);

}break;

case 3: //Stream 3

{

temp = IntStatus & 0x3F;

DMAx->LIFCR |= temp << (16 + 6);

}break;

case 4: //Stream 4

{

temp = IntStatus & 0x3F;

DMAx->HIFCR |= temp << 0;

}break;

case 5: //Stream 5

{

temp = IntStatus & 0x3F;

DMAx->HIFCR |= temp << 6;

}break;

case 6: //Stream 6

{

temp = IntStatus & 0x3F;

DMAx->HIFCR |= temp << (16 + 0);

}break;

case 7: //Stream 7

{

temp = IntStatus & 0x3F;

DMAx->HIFCR |= temp << (16 + 6);

}break;

default : break;

}

}




/*************************************************************************************************************************

* 函数 : u8 DMA_GetCurrentTargetBuffIndex(DMAxSx_CH_TYPE DMAxSx_Ch)

* 功能 : 获取双缓冲模式下当前使用的存储器缓冲区索引(0,1)

* 参数 : DMAxSx_Ch:DMA通道选择;

* 返回 : 0:存储器0正在被使用;1:存储器1正在被使用

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-11

* 最后修改时间 : 2025-03-11

* 说明 : 仅在双缓冲模式下有效

*************************************************************************************************************************/  

u8 DMA_GetCurrentTargetBuffIndex(DMAxSx_CH_TYPE DMAxSx_Ch)

{

u8 Stream;

DMA_Stream_TypeDef *DMA_Stream;

// DMA_TypeDef *DMAx;

// u32 temp;

// DMAx = (DMA_TypeDef *)DMA_TYPE_BUFF[(DMAxSx_Ch >> 16)&0x01]; //获取DMA

Stream = (DMAxSx_Ch)&0xff; //获取流通道

if(Stream > 7) return 0;

if((DMAxSx_Ch >> 8)&0x01) //DMA2

{

DMA_Stream = (DMA_Stream_TypeDef *)sg_DMA2_Stream[Stream]; //获取数据流 x 配置寄存器

}

else //DMA1

{

DMA_Stream = (DMA_Stream_TypeDef *)sg_DMA1_Stream[Stream]; //获取数据流 x 配置寄存器

}

if(DMA_Stream->CR & BIT19) return 1;

else return 0;

}




/*************************************************************************************************************************

* 函数 : u16 DMA_GetCompleteResidualCnt(DAM1Sx_CH_TYPE DMA1Sx_Ch)

* 功能 : 获取DMA传输的剩余数据量

* 参数 : DMAxSx_Ch:DMA通道选择;

* 返回 : 剩余数据数量

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-23

* 最后修改时间 : 2025-03-23

* 说明 :

*************************************************************************************************************************/

u16 DMA_GetCompleteResidualCnt(DMAxSx_CH_TYPE DMAxSx_Ch)

{

u8 Stream;

DMA_Stream_TypeDef* DMA_Stream;


Stream = (DMAxSx_Ch) & 0xff; //获取流通道

if (Stream > 7) return 0;

if ((DMAxSx_Ch >> 8) & 0x01) //DMA2

{

DMA_Stream = (DMA_Stream_TypeDef*)sg_DMA2_Stream[Stream]; //获取数据流 x 配置寄存器

}

else //DMA1

{

DMA_Stream = (DMA_Stream_TypeDef*)sg_DMA1_Stream[Stream]; //获取数据流 x 配置寄存器

}


return (u16)(DMA_Stream->NDTR); //获取传输的剩余数据量

}



/*************************************************************************************************************************

* 函数 : bool DMA_WaitComplete(DMAxSx_CH_TYPE DMAxSx_Ch, u16 TimeOutMs)

* 功能 : 等待DMA传输完成

* 参数 : DMAxSx_Ch:DMA通道选择;TimeOutMs:等待超时时间,单位ms

* 返回 : TRUE:传输完成,FALSE:传输超时

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-23

* 最后修改时间 : 2025-03-23

* 说明 : 无

*************************************************************************************************************************/

bool DMA_WaitComplete(DMAxSx_CH_TYPE DMAxSx_Ch, u16 TimeOutMs)

{

u32 DelayCount = TimeOutMs + 1;

u8 Stream;

DMA_Stream_TypeDef* DMA_Stream;


Stream = (DMAxSx_Ch) & 0xff; //获取流通道

if (Stream > 7) return 0;

if ((DMAxSx_Ch >> 8) & 0x01) //DMA2

{

DMA_Stream = (DMA_Stream_TypeDef*)sg_DMA2_Stream[Stream]; //获取数据流 x 配置寄存器

}

else //DMA1

{

DMA_Stream = (DMA_Stream_TypeDef*)sg_DMA1_Stream[Stream]; //获取数据流 x 配置寄存器

}


while (DelayCount)

{

if ((DMA_Stream->NDTR) == 0) return TRUE;

SYS_DelayMS(1);

DelayCount--;

}

return FALSE;

}








/*************************************************************************************************************************

* 函数 : void DMA_MemoryToPeripheralConfig(DMAxSx_CH_TYPE DMAxSx_Ch, DMA12_MUX1_REQ DMA_Req, u32 PeriAddr, DMA_SIZE_TYPE SIZE_xbit, bool isMemoryInc, bool isCircularMode)

* 功能 : DMA存储器到外设传输配置(快捷设置)-不会启动传输

* 参数 : DMAxSx_Ch:DMA通道选择,DMA_Req:DMA请求选择,,PeriAddr:外设地址;SIZE_xbit:传输位宽;isMemoryInc:内存地址自增;isCircularMode:循环模式

* 返回 : 无

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-23

* 最后修改时间 : 2025-03-23

* 说明 : 不会启动传输

*************************************************************************************************************************/

void DMA_MemoryToPeripheralConfig(DMAxSx_CH_TYPE DMAxSx_Ch, DMA12_MUX1_REQ DMA_Req,u32 PeriAddr, DMA_SIZE_TYPE SIZE_xbit, bool isMemoryInc, bool isCircularMode)

{

u8 Stream, Req;

DMA_Stream_TypeDef* DMA_Stream;

DMAMUX_Channel_TypeDef* DMAMUX_Channel;

DMA_TypeDef* DMAx;


DMAx = (DMA_TypeDef*)sg_DMA_TYPE_BUFF[(DMAxSx_Ch >> 8) & 0x01]; //获取DMA

Stream = (DMAxSx_Ch) & 0xff; //获取流通道

Req = DMAxSx_Ch & 0xff;

if ((Req > DMA12_MUX1_REQ_MAX) || (Stream > 7)) //通道或流超出了范围

{

return;

}

if ((DMAxSx_Ch >> 8) & 0x01) //DMA2

{

DMA_Stream = (DMA_Stream_TypeDef*)sg_DMA2_Stream[Stream]; //获取数据流 x 配置寄存器

DMAMUX_Channel = (DMAMUX_Channel_TypeDef*)sg_DMAMUX_Channel[8 + Stream];

}

else //DMA1

{

DMA_Stream = (DMA_Stream_TypeDef*)sg_DMA1_Stream[Stream]; //获取数据流 x 配置寄存器

DMAMUX_Channel = (DMAMUX_Channel_TypeDef*)sg_DMAMUX_Channel[Stream];

}


//开始配置

DMA_Stream->CR = 0; //禁止数据流,才能进行后面的设置

DMAMUX_Channel->CCR = DMA_Req; //设置DMA请求,同时禁止同步模式,禁止中断,禁止事件

DMA_Stream->CR |= 1 << 16; //优先级中等

DMA_Stream->CR |= SIZE_xbit << 13; //存储器数据大小设置

DMA_Stream->CR |= SIZE_xbit << 11; //外设数据大小设置

if (isMemoryInc) DMA_Stream->CR |= 1 << 10; //存储器地址递增模式

//DMA_Stream->CR |= 1<<9; //外设地址递增模式

if (isCircularMode) DMA_Stream->CR |= 1 << 8; //循环模式开启

DMA_Stream->CR |= 1 << 6; //数据传输方向->存储器到外设

//DMA_Stream->CR |= 1<<5; //外设是流控设备

DMA_Stream->FCR = 0; //使能直接模式

DMA_Stream->PAR = PeriAddr; //外设地址

//DMA_Stream->M0AR = MemAddr; //存储器0地址

//DMA_Stream->NDTR = DataCnt; //传输的数据项数


//DMA_Stream->CR |= 1 << 0; //使能流,开始传输

//uart_printf("DMA_Stream->NDTR:%X\r\n",DMA_Stream->NDTR);

//uart_printf("DMA_Stream->NDTR:%X\r\n",DMA_Stream->NDTR);

//uart_printf("DMA_Stream->NDTR:%X\r\n",DMA_Stream->NDTR);

}





/*************************************************************************************************************************

* 函数 : void DMA_PeripheralToMemoryConfig(DMAxSx_CH_TYPE DMAxSx_Ch, DMA12_MUX1_REQ DMA_Req, u32 MemAddr,  u32 PeriAddr, DMA_SIZE_TYPE SIZE_xbit,  u16 DataCnt, bool isMemoryInc)

* 功能 : DMA外设到存储器传输配置(快捷设置)

* 参数 : DMAxSx_Ch:DMA通道选择,DMA_Req:DMA请求选择,MemAddr:存储器地址;PeriAddr:外设地址;SIZE_xbit:传输位宽;DataCnt:传输数量;isMemoryInc:内存地址自增

* 返回 : 无

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-23

* 最后修改时间 : 2025-03-23

* 说明 : 不会启动传输

*************************************************************************************************************************/

void DMA_PeripheralToMemoryConfig(DMAxSx_CH_TYPE DMAxSx_Ch, DMA12_MUX1_REQ DMA_Req, u32 MemAddr, u32 PeriAddr, DMA_SIZE_TYPE SIZE_xbit, u16 DataCnt, bool isMemoryInc)

{

u8 Stream, Req;

DMA_Stream_TypeDef* DMA_Stream;

DMAMUX_Channel_TypeDef* DMAMUX_Channel;

DMA_TypeDef* DMAx;


DMAx = (DMA_TypeDef*)sg_DMA_TYPE_BUFF[(DMAxSx_Ch >> 8) & 0x01]; //获取DMA

Stream = (DMAxSx_Ch) & 0xff; //获取流通道

Req = DMAxSx_Ch & 0xff;

if ((Req > DMA12_MUX1_REQ_MAX) || (Stream > 7)) //通道或流超出了范围

{

return;

}

if ((DMAxSx_Ch >> 8) & 0x01) //DMA2

{

DMA_Stream = (DMA_Stream_TypeDef*)sg_DMA2_Stream[Stream]; //获取数据流 x 配置寄存器

DMAMUX_Channel = (DMAMUX_Channel_TypeDef*)sg_DMAMUX_Channel[8 + Stream];

}

else //DMA1

{

DMA_Stream = (DMA_Stream_TypeDef*)sg_DMA1_Stream[Stream]; //获取数据流 x 配置寄存器

DMAMUX_Channel = (DMAMUX_Channel_TypeDef*)sg_DMAMUX_Channel[Stream];

}


//开始配置

DMA_Stream->CR = 0; //禁止数据流,才能进行后面的设置

DMAMUX_Channel->CCR = DMA_Req; //设置DMA请求,同时禁止同步模式,禁止中断,禁止事件


DMA_Stream->CR |= 3 << 16; //优先高

DMA_Stream->CR |= SIZE_xbit << 13; //存储器数据大小设置

DMA_Stream->CR |= SIZE_xbit << 11; //外设数据大小设置

if (isMemoryInc) DMA_Stream->CR |= 1 << 10; //存储器地址递增模式

//DMA_Stream->CR |= 1<<9; //外设地址递增模式

DMA_Stream->CR |= 0 << 6; //数据传输方向->外设到存储器

//DMA_Stream->CR |= 1<<5; //外设是流控设备

DMA_Stream->FCR = 0; //使能直接模式

DMA_Stream->PAR = PeriAddr; //外设地址

DMA_Stream->M0AR = MemAddr; //存储器地址


//必须清除中断标记,否则DMA可能会停止工作

if (Stream < 4)

{

DMA2->LIFCR = DMA2->LISR; //写1清除中断标志

}

else

{

DMA2->HIFCR = DMA2->HISR; //写1清除中断标志

}



DMA_Stream->NDTR = DataCnt; //传输的数据项数


//uart_printf("DMA_Stream->NDTR:%X\r\n",DMA_Stream->NDTR);

//uart_printf("DMA_Stream->NDTR:%X\r\n",DMA_Stream->NDTR);

//uart_printf("DMA_Stream->NDTR:%X\r\n",DMA_Stream->NDTR);

}











//==动态申请DMA相关==

static u16 sg_DMA_ChannelStatus = 0; //DMA通道占用状态 DAM1的8个通道灵活申请


/*************************************************************************************************************************

* 函数 : int DMA_GetIdleChannel(void)

* 功能 : 获取一个DMA空闲通道(用完后需要释放)

* 参数 : 无

* 返回 : -1:无效;0-7:DMA1的通道1-通道8

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-30

* 最后修改时间 : 2025-03-30

* 说明 : 只能动态获取DMA1的8个通道

*************************************************************************************************************************/

int DMA_GetIdleChannel(void)

{

OS_CRITICAL_SR_VAL;

u8 i;

int ch = -1;


OS_EnterCriticalSection(); //关闭系统中断,进入临界状态

for (i = 0; i < 8; i++)

{

if ((sg_DMA_ChannelStatus & (1 << i)) == 0) //当前通道空闲

{

ch = i;

sg_DMA_ChannelStatus |= 1 << i; //记录当前通道,并进行占用

break;

}

}

OS_LeaveCriticalSection(); //退出临界状态


return ch;

}




/*************************************************************************************************************************

* 函数 : void DMA_ReleaseChannel(int ch)

* 功能 : 释放当前使用的通道

* 参数 : ch:0-7:DMA1的通道1-通道8

* 返回 : 无

* 依赖 : 底层宏定义

* 作者 : www.scj-water.com

* 时间 : 2025-03-30

* 最后修改时间 : 2025-03-30

* 说明 : 只能动态获取DMA1的7个通道

*************************************************************************************************************************/

void DMA_ReleaseChannel(int ch)

{

OS_CRITICAL_SR_VAL;


if (ch < 0 || ch > 7) return;

OS_EnterCriticalSection(); //关闭系统中断,进入临界状态

sg_DMA_ChannelStatus &= ~(1 << ch); //释放占用状态

OS_LeaveCriticalSection(); //退出临界状态

DMA_StopTrans((DMAxSx_CH_TYPE)ch); //关闭DMA

}



//DMA.h


/*************************************************************************************************************

 * 文件名 : dma.h

 * 功能 : STM32H7 DMA驱动

 * 作者 : www.scj-water.com

 * 创建时间 : 2025-03-07

 * 最后修改时间 : 2025-03-07

 * 详细:

*************************************************************************************************************/

#ifndef __DMA_H_

#define __DMA_H_    

#include "system.h"


//传输数据位宽定义

typedef enum

{

DMA_SIZE_8BIT = 0,

DMA_SIZE_16BIT = 1,

DMA_SIZE_32BIT = 2,

}DMA_SIZE_TYPE;




//DMA1,2请求映射(使用的DMAMUX1通道)

typedef enum

{

dmamux1_req_gen0     =1,

dmamux1_req_gen1     =2,

dmamux1_req_gen2     =3,

dmamux1_req_gen3     =4,

dmamux1_req_gen4     =5,

dmamux1_req_gen5     =6,

dmamux1_req_gen6     =7,

dmamux1_req_gen7     =8,

DMA12_ADC1 =9,

DMA12_ADC2 =10,

DMA12_TIM1_CH1 =11,

DMA12_TIM1_CH2 =12,

DMA12_TIM1_CH3 =13,

DMA12_TIM1_CH4 =14,

DMA12_TIM1_UP =15,

DMA12_TIM1_TRIG     =16,

DMA12_TIM1_COM =17,

DMA12_TIM2_CH1 =18,

DMA12_TIM2_CH2 =19,

DMA12_TIM2_CH3 =20,

DMA12_TIM2_CH4 =21,

DMA12_TIM2_UP =22,

DMA12_TIM3_CH1 =23,

DMA12_TIM3_CH2 =24,

DMA12_TIM3_CH3 =25,

DMA12_TIM3_CH4 =26,

DMA12_TIM3_UP =27,

DMA12_TIM3_TRIG     =28,

DMA12_TIM4_CH1 =29,

DMA12_TIM4_CH2 =30,

DMA12_TIM4_CH3 =31,

DMA12_TIM4_UP =32,

DMA12_I2C1_RX =33,

DMA12_I2C1_TX =34,

DMA12_I2C2_RX =35,

DMA12_I2C2_TX =36,

DMA12_SPI1_RX =37,

DMA12_SPI1_TX =38,

DMA12_SPI2_RX =39,

DMA12_SPI2_TX =40,

DMA12_USART1_RX     =41,

DMA12_USART1_TX     =42,

DMA12_USART2_RX     =43,

DMA12_USART2_TX     =44,

DMA12_USART3_RX     =45,

DMA12_USART3_TX     =46,

DMA12_TIM8_CH1 =47,

DMA12_TIM8_CH2 =48,

DMA12_TIM8_CH3 =49,

DMA12_TIM8_CH4 =50,

DMA12_TIM8_UP =51,

DMA12_TIM8_TRIG     =52,

DMA12_TIM8_COM =53,

//DMA12_保留 =54,

DMA12_TIM5_CH1 =55,

DMA12_TIM5_CH2 =56,

DMA12_TIM5_CH3 =57,

DMA12_TIM5_CH4 =58,

DMA12_TIM5_UP =59,

DMA12_TIM5_TRIG     =60,

DMA12_SPI3_RX =61,

DMA12_SPI3_TX =62,

DMA12_UART4_RX =63,

DMA12_UART4_TX =64,

DMA12_UART5_RX     =65,

DMA12_UART5_TX =66,

DMA12_DAC1 =67,

DMA12_DAC2 =68,

DMA12_TIM6_UP =69,

DMA12_TIM7_UP =70,

DMA12_USART6_RX     =71,

DMA12_USART6_TX     =72,

DMA12_I2C3_RX =73,

DMA12_I2C3_TX =74,

DMA12_DCMI =75,

DMA12_CRYP_IN =76,

DMA12_CRYP_OUT =77,

DMA12_HASH_IN =78,

DMA12_UART7_RX =79,

DMA12_UART7_TX =80,

DMA12_UART8_RX =81,

DMA12_UART8_TX =82,

DMA12_SPI4_RX =83,

DMA12_SPI4_TX =84,

DMA12_SPI5_RX =85,

DMA12_SPI5_TX =86,

DMA12_SAI1_A =87,

DMA12_SAI1_B =88,

DMA12_SAI2_A =89,

DMA12_SAI2_B =90,

DMA12_SWPMI_RX =91,

DMA12_SWPMI_TX =92,

DMA12_SPDIFRX_DT =93,

DMA12_SPDIFRX_CS =94,

DMA12_HR_REQ_1     =95,

DMA12_HR_REQ_2     =96,

DMA12_HR_REQ_3     =97,

DMA12_HR_REQ_4     =98,

DMA12_HR_REQ_5     =99,

DMA12_HR_REQ_6     =100,

dfsdm1_dma0     =101,

dfsdm1_dma1     =102,

dfsdm1_dma2     =103,

dfsdm1_dma3     =104,

DMA12_TIM15_CH1     =105,

DMA12_TIM15_UP     =106,

DMA12_TIM15_TRIG =107,

DMA12_TIM15_COM     =108,

DMA12_TIM16_CH1     =109,

DMA12_TIM16_UP     =110,

DMA12_TIM17_CH1     =111,

DMA12_TIM17_UP     =112,

DMA12_SAI3_A =113,

DMA12_SAI3_B =114,

DMA12_ADC3     =115

}DMA12_MUX1_REQ;


//DMA通道选择

typedef enum

{

DMA1_CH0 = 0x0000,

DMA1_CH1 = 0x0001,

DMA1_CH2 = 0x0002,

DMA1_CH3 = 0x0003,

DMA1_CH4 = 0x0004,

DMA1_CH5 = 0x0005,

DMA1_CH6 = 0x0006,

DMA1_CH7 = 0x0007,


DMA2_CH0 = 0x0100,

DMA2_CH1 = 0x0101,

DMA2_CH2 = 0x0102,

DMA2_CH3 = 0x0103,

DMA2_CH4 = 0x0104,

DMA2_CH5 = 0x0105,

DMA2_CH6 = 0x0106,

DMA2_CH7 = 0x0107,

}DMAxSx_CH_TYPE;


//存储器到存储器通道定义

typedef enum

{

//内存到内存通道,使用一些不常用的通道用内存到内存的通道

DAM2S0_MEM = 0x010001,

DAM2S1_MEM = 0x010103,

DAM2S2_MEM = 0x010202,

DAM2S3_MEM = 0x010302,

DAM2S4_MEM = 0x010404,

DAM2S5_MEM = 0x010505,

DAM2S6_MEM = 0x010606,

DAM2S7_MEM = 0x010706,

}DMA_MEM_CH;




//突发传输长度配置(在直接模式中,当位EN =1时这些位由硬件强制置为 0x0。)

typedef enum

{

DMA_BURST_INCR1 = 0, //单次传输

DMA_BURST_INCR4 = 1, //4个节拍传输

DMA_BURST_INCR8 = 2, //8个节拍传输

DMA_BURST_INCR16 = 3, //16个节拍传输

}DMA_BURST_TRAN_SIZE;


//优先级

typedef enum

{

DAM_PRIO_LEVEL_0 = 0, //优先级低

DAM_PRIO_LEVEL_1 = 1, //优先级中

DAM_PRIO_LEVEL_2 = 3, //优先级高

DAM_PRIO_LEVEL_3 = 4, //优先级最高

}DAM_PRIO_LEVEL;



//数据传输方向定义

typedef enum

{

DMA_PRE_TO_MEM = 0, //外设到存储器

DMA_MEM_TO_PRE = 1, //存储器到外设

DMA_MEM_TO_MEM = 2, //存储器到存储器

}DMA_TRAN_DIR;


//FIFO阈值选择

typedef enum

{

DMA_FIFO_THRES_1_4 = 0, //FIFO阈值为1/4

DMA_FIFO_THRES_1_2 = 1, //FIFO阈值为1/2

DMA_FIFO_THRES_3_4 = 2, //FIFO阈值为3/4

DMA_FIFO_THRES_FULL = 3, //FIFO阈值为完整容量

}DMA_FIFO_THRES;


//DMA配置

typedef struct

{

DMA12_MUX1_REQ DMA_Req; //DMA请求定义

DMA_BURST_TRAN_SIZE Mem_BurstSize; //存储器的突发传输长度配置

DMA_BURST_TRAN_SIZE Per_BurstSize; //外设的突发传输长度配置

DAM_PRIO_LEVEL PrioLevel; //传输优先级

DMA_SIZE_TYPE Mem_Size; //存储器数据大小

DMA_SIZE_TYPE Pre_Size; //外设数据大小

DMA_TRAN_DIR DataTranDir; //数据传输方向

u32 PerAddr; //外设地址

u32 Mem0Addr; //存储器0地址

u32 Mem1Addr; //存储器1地址

DMA_FIFO_THRES FIFO_Thres; //非直接模式下,FIFO阈值容量选择

u16 TranDataCount; //传输数据长度-外设到存储器是流控无需设置长度

bool isDoubleBuffer; //使能双缓冲模式

bool isPerIncPsizeAuto; //外设偏移量自动根据PSIZE设置(TRUE:偏移量与 PSIZE 相关;否则4字节对齐,如果位 PINC =“0”,则此位没有意义)

bool isMemInc; //存储器地址自增

bool isPerInc; //外设地址自增

bool isCircMode; //循环模式

bool isPreFlowCtrl; //外设是流控制设备

bool isEnableTranComplInt; //传输完成中断使能

bool isDirectMode; //直接模式

bool isEnableStream; //是否使能数据流-开始传输

}DAM_CONFIG;



//DMA中断定义

#define DAM_INT_FIFO_ERROR BIT0 //数据流FIFO错误中断

#define DMA_INT_DIRECT_ERROR BIT2 //直接模式错误中断

#define DMA_INT_TRANS_ERROR BIT3 //传输错误

#define DMA_INT_TRANS_HALF BIT4 //传输过半

#define DMA_INT_TRANS_COMPL BIT5 //传输完成中断

#define DMA_INT_ALL (0x3D) //所有中断



void DMA_Config(DMAxSx_CH_TYPE DMAxSx_Ch, DAM_CONFIG *pConfig); //DMA配置

void DMA_StartTrans(DMAxSx_CH_TYPE DMAxSx_Ch, u32 Mem0Addr, u32 Mem1Addr, u16 TranDataCount); //启动DMA 传输

void DMA_StartTrans_Mem(DMAxSx_CH_TYPE DMAxSx_Ch, u32 DestAddr, u32 SourceAddr, u16 TranDataCount); //启动DMA 传输(内存到内存传输)

void DMA_StopTrans(DMAxSx_CH_TYPE DMAxSx_Ch); //关闭 DMA 传输

u32 DMA_GetIntStatus(DMAxSx_CH_TYPE DMAxSx_Ch); //获取DMA中断状态

void DMA_ClearIntStatus(DMAxSx_CH_TYPE DMAxSx_Ch, u32 IntStatus); //清除DMA中断状态

u8 DMA_GetCurrentTargetBuffIndex(DMAxSx_CH_TYPE DMAxSx_Ch); //获取双缓冲模式下当前使用的存储器缓冲区索引(0,1)

u16 DMA_GetCompleteResidualCnt(DMAxSx_CH_TYPE DMAxSx_Ch); //获取DMA传输的剩余数据量

bool DMA_WaitComplete(DMAxSx_CH_TYPE DMAxSx_Ch, u16 TimeOutMs); //等待DMA传输完成



//============快捷配置接口============

//DMA存储器到外设传输配置(快捷设置)-不会启动传输依赖DMA_StartTrans()开始传输

void DMA_MemoryToPeripheralConfig(DMAxSx_CH_TYPE DMAxSx_Ch, DMA12_MUX1_REQ DMA_Req, u32 PeriAddr, DMA_SIZE_TYPE SIZE_xbit, bool isMemoryInc, bool isCircularMode);

//DMA外设到存储器传输配置(快捷设置)-不会启动传输依赖DMA_StartTrans()开始传输

void DMA_PeripheralToMemoryConfig(DMAxSx_CH_TYPE DMAxSx_Ch, DMA12_MUX1_REQ DMA_Req, u32 MemAddr, u32 PeriAddr, DMA_SIZE_TYPE SIZE_xbit, u16 DataCnt, bool isMemoryInc);



int DMA_GetIdleChannel(void); //获取一个DMA空闲通道(用完后需要释放)

void DMA_ReleaseChannel(int ch); //释放当前使用的通道


#endif //__DMA_H_


//测试

/*************************************************************************************************************

 * 文件名: dma_test.c

 * 功能: DMA测试

 * 作者: www.scj-water.com

 * 创建时间: 2025-03-14

 * 最后修改时间: 2025-03-14

 * 详细:

*************************************************************************************************************/

#include "system.h"

#include "test.h"

#include "dma.h"

#include "string.h"


char g_buff1[33];

static char g_buff2[33];



//DMA 内存到内存的传输测试

void DMA_MemToMem_Test(void)

{

DAM_CONFIG mConfig;

u16 cnt;

u16 i;



INFO_S("DMA 内存到内存拷贝测试\r\n");

mConfig.DMA_Req = 0; //DMA请求定义-内存到内存,软件触发

mConfig.Mem_BurstSize = DMA_BURST_INCR4; //存储器的突发传输长度配置

mConfig.Per_BurstSize = DMA_BURST_INCR4; //外设的突发传输长度配置

mConfig.PrioLevel = DAM_PRIO_LEVEL_1; //传输优先级

mConfig.Mem_Size = DMA_SIZE_8BIT; //存储器数据大小

mConfig.Pre_Size = DMA_SIZE_8BIT; //外设数据大小

mConfig.DataTranDir = DMA_MEM_TO_MEM; //数据传输方向

mConfig.PerAddr = (u32)g_buff2; //外设地址

mConfig.Mem0Addr = (u32)g_buff1; //存储器0地址

mConfig.Mem1Addr = NULL; //存储器1地址

mConfig.FIFO_Thres = DMA_FIFO_THRES_1_2; //非直接模式下,FIFO阈值容量选择

mConfig.TranDataCount = sizeof(g_buff1); //传输数据长度-外设到存储器是流控无需设置长度

mConfig.isDoubleBuffer = FALSE; //使能双缓冲模式

mConfig.isPerIncPsizeAuto = TRUE; //外设偏移量自动根据PSIZE设置(TRUE:偏移量与 PSIZE 相关;否则4字节对齐,如果位 PINC =“0”,则此位没有意义)

mConfig.isMemInc = TRUE; //存储器地址自增

mConfig.isPerInc = TRUE; //外设地址自增

mConfig.isCircMode = FALSE; //循环模式

mConfig.isPreFlowCtrl = FALSE; //外设是流控制设备

mConfig.isEnableTranComplInt = FALSE; //传输完成中断使能

mConfig.isDirectMode = TRUE; //直接模式

mConfig.isEnableStream = TRUE; //是否使能数据流-开始传输




memset(g_buff1, 0, sizeof(g_buff1));

for (i = 0; i < sizeof(g_buff1); i++)

{

g_buff2[i] = '0' + i;

}


DMA_Config(DMA1_CH1, &mConfig);


SYS_DelayMS(2);

cnt = 0;


g_buff1[sizeof(g_buff1) - 1] = 0;

uart_printf("buff1:%s\r\n", g_buff1);


g_buff2[sizeof(g_buff2) - 1] = 0;

uart_printf("buff2:%s\r\n", g_buff2);

while(1)

{

SYS_DelayMS(1000);

}

}






































如果您有任何问题,请跟我们联系!

欢迎来电:18571629282

Copyright © 2024 武汉水测家科技有限公司 版权所有  鄂ICP备2022002065号-1   --WTRExpert English website--

XML地图

QQ在线咨询
销售(微信同号)
18571629282
技术(微信同号)
13871206075