第十五章 DMA

频道:消费点评 日期: 浏览:33851

单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机

W55MH32是WIZnet重磅推出的高性能以太网单片机,它为用户带来前所未有的集成化体验。这颗芯片将强大的组件集于一身,具体来说,一颗W55MH32内置高性能Arm® Cortex-M3核心,其主频最高可达216MHz;配备1024KB FLASH与96KB SRAM,满足存储与数据处理需求;集成TOE引擎,包含WIZnet全硬件TCP/IP协议栈、内置MAC以及PHY,拥有独立的32KB以太网收发缓存,可供8个独立硬件socket使用。如此配置,真正实现了All-in-One解决方案,为开发者提供极大便利。

在封装规格上,W55MH32 提供了两种选择:QFN100和QFN68。

W55MH32L采用QFN100封装版本,尺寸为12x12mm,其资源丰富,专为各种复杂工控场景设计。它拥有66个GPIO、3个ADC、12通道DMA、17个定时器、2个I2C、5个串口、2个SPI接口(其中1个带I2S接口复用)、1个CAN、1个USB2.0以及1个SDIO接口。如此丰富的外设资源,能够轻松应对工业控制中多样化的连接需求,无论是与各类传感器、执行器的通信,还是对复杂工业协议的支持,都能游刃有余,成为复杂工控领域的理想选择。 同系列还有QFN68封装的W55MH32Q版本,该版本体积更小,仅为8x8mm,成本低,适合集成度高的网关模组等场景,软件使用方法一致。更多信息和资料请进入http://www.w5500.com/网站或者私信获取。

此外,本W55MH32支持硬件加密算法单元,WIZnet还推出TOE+SSL应用,涵盖TCP SSL、HTTP SSL以及 MQTT SSL等,为网络通信安全再添保障。

为助力开发者快速上手与深入开发,基于W55MH32L这颗芯片,WIZnet精心打造了配套开发板。开发板集成WIZ-Link芯片,借助一根USB C口数据线,就能轻松实现调试、下载以及串口打印日志等功能。开发板将所有外设全部引出,拓展功能也大幅提升,便于开发者全面评估芯片性能。

若您想获取芯片和开发板的更多详细信息,包括产品特性、技术参数以及价格等,欢迎访问官方网页:http://www.w5500.com/,我们期待与您共同探索W55MH32的无限可能。

wKgZPGgbOfaANhwzACodXd3sVzg463.png

第十五章 DMA

本章参考资料:《W55MH32中文参考手册》DMA控制器章节。

学习本章时,配合《W55MH32中文参考手册》DMA控制器章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。

1 DMA简介

DMA(Direct Memory Access)—直接存储器存取,是单片机的一个外设,它的主要功能是用来搬数据,但是不需要占用CPU, 即在传输数据的时候,CPU可以干其他的事情,好像是多线程一样。数据传输支持从外设到存储器或者存储器到存储器, 这里的存储器可以是SRAM或者是FLASH。DMA控制器包含了DMA1和DMA2,其中DMA1有7个通道,DMA2有5个通道, 这里的通道可以理解为传输数据的一种管道。要注意的是DMA2只存在于大容量产品和互联型产品中。

2 DMA功能框图

DMA控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌握功能框图中的三部分内容即可, 具体见下图,DMA框图 :DMA控制器的框图:

2.1 DMA请求

如果外设要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号, 当外设应答后且DMA控制器收到应答信号之后,就会启动DMA的传输,直到传输完毕。

DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同的DMA控制器的通道对应着不同的外设请求, 这决定了我们在软件编程上该怎么设置,具体见DMA请求映像表:

外设 通道 1 通道 2 通道 3 通道 4 通道 5 通道 6 通道 7
ADC1 ADC1
SPI/I²S SPI1_RX SPI1_TX SPI/I2S2_RX SPI/I2S2_TX
USART USART3_TX USART3_RX USART1_TX USART1_RX USART2_RX USART2_TX
I²C I2C2_TX I2C2_RX I2C1_TX I2C1_RX
TIM1 TIM1_CH1 TIM1_CH2 TIM1_TX4

TIM1_TRIG

TIM1_COM

TIM1_UP TIM1_CH3
TIM2 TIM2_CH3 TIM2_UP TIM2_CH1 TIM2_CH2

TIM2_CH4

TIM3 TIM3_CH3 TIM3_CH4

TIM3_UP

TIM3_CH1

TIM3_TRIG

TIM4 TIM4_CH1 TIM4_CH2 TIM4_CH3 TIM4_UP
外设 通道 1 通道 2 通道 3 通道 4 通道 5
ADC3⁽¹⁾ ADC3
SPI/I²S3 SPI/I²S3_RX SPI/I²S3_TX
UART4 UART4_RX UART4_TX
SDIO⁽¹⁾ SDIO
TIM5 TIM5_CH4

TIM5_TRIG

TIM5_CH3

TIM5_UP

TIM5_CH2 TIM5_CH1
TIM6/DAC 通道 1 TIM6_UP/

DAC 通道 1

TIM7/DAC 通道 2 TIM7_UP/

DAC 通道 2

TIM8⁽¹⁾ TIM8_CH3

TIM8_UP

TIM8_CH4

TIM8_TRIG

TIM8_COM

TIM8_CH1 TIM8_CH2

其中ADC3、SDIO和TIM8的DMA请求只在大容量产品中存在,这个在具体项目时要注意。

2.2 通道

DMA具有12个独立可编程的通道,其中DMA1有7个通道,DMA2有5个通道,每个通道对应不同的外设的DMA请求。 虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

2.3 仲裁器

当发生多个DMA通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理DMA通道请求分为两个阶段。 第一阶段属于软件阶段,可以在DMA_CCRx寄存器中设置,有4个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段, 如果两个或以上的DMA通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道0高于通道1。 在大容量产品和互联型产品中, DMA1控制器拥有高于DMA2控制器的优先级。

3 DMA数据配置

使用DMA,最核心就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是循环传输等等。

3.1DMA传输方向

我们知道DMA传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。 具体的方向DMA_CCR位4 DIR配置:0表示从外设到存储器,1表示从存储器到外设。 这里面涉及到的外设地址由DMA_CPAR配置,存储器地址由DMA_CMAR配置。

外设到存储器

当我们使用从外设到存储器传输时,以ADC采集为例。DMA外设寄存器的地址对应的就是ADC数据寄存器的地址, DMA存储器的地址就是我们自定义的变量(用来接收存储AD采集的数据)的地址。方向我们设置外设为源地址。

存储器到外设

当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA外设寄存器的地址对应的就是串口数据寄存器的地址, DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。

存储器到存储器

当我们使用从存储器到存储器传输时,以内部FLASH向内部SRAM复制数据为例。 DMA外设寄存器的地址对应的就是内部FLASH(我们这里把内部FALSH当作一个外设来看)的地址, DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH的数据)的地址。 方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要把DMA_CCR位14:MEM2MEM:存储器到存储器模式配置为1,启动M2M模式。

3.2传输大小及单位

当我们配置好数据要从哪里来到哪里去之后,我们还需要知道我们要传输的数据是多少,数据的单位是什么。

以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR配置, 这是一个32位的寄存器,一次最多只能传输65535个数据。

要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是8位的, 所以我们定义的要发送的数据也必须是8位。外设的数据宽度由DMA_CCRx的PSIZE[1:0]配置, 可以是8/16/32位,存储器的数据宽度由DMA_CCRx的MSIZE[1:0]配置,可以是8/16/32位。

在DMA控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。 外设的地址指针由DMA_CCRx的PINC配置,存储器的地址指针由MINC配置。以串口向电脑发送数据为例,要发送的数据很多, 每发送完一个,那么存储器的地址指针就应该加1,而串口数据寄存器只有一个, 那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。

3.3 传输完成时间

数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个DMA通道在DMA传输过半、 传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。有关各个标志位的详细描述请参考DMA中断状态寄存器DMA_ISR的详细描述。

传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话, 必须关断DMA使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输, 不断的重复。具体的由DMA_CCRx寄存器的CIRC 循环模式位控制。

4 DMA初始化结构体详解

标准库函数对每个外设都建立了一个初始化结构体xxx_InitTypeDef(xxx为外设名称),结构体成员用于设置外设工作参数, 并由标准库函数xxx_Init()调用这些设定参数进入设置外设相应的寄存器,达到配置外设工作环境的目的。

结构体xxx_InitTypeDef和库函数xxx_Init配合使用是标准库精髓所在,理解了结构体xxx_InitTypeDef每个成员意义基本上就可以对该外设运用自如。 结构体xxx_InitTypeDef定义在W55MH32_xxx.h(后面xxx为外设名称)文件中,库函数xxx_Init定义在W55MH32_xxx.c文件中, 编程时我们可以结合这两个文件内注释使用。

DMA_ InitTypeDef初始化结构体

typedef struct
{
    uint32_t DMA_PeripheralBaseAddr;   // 外设地址
    uint32_t DMA_MemoryBaseAddr;       // 存储器地址
    uint32_t DMA_DIR;                  // 传输方向
    uint32_t DMA_BufferSize;           // 传输数目
    uint32_t DMA_PeripheralInc;        // 外设地址增量模式
    uint32_t DMA_MemoryInc;            // 存储器地址增量模式
    uint32_t DMA_PeripheralDataSize;   // 外设数据宽度
    uint32_t DMA_MemoryDataSize;       // 存储器数据宽度
    uint32_t DMA_Mode;                 // 模式选择
    uint32_t DMA_Priority;             // 通道优先级
    uint32_t DMA_M2M;                  // 存储器到存储器模式
} DMA_InitTypeDef;

DMA_PeripheralBaseAddr: 外设地址,设定DMA_CPAR寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。

DMA_Memory0BaseAddr: 存储器地址,设定DMA_CMAR寄存器值;一般设置为我们自定义存储区的首地址。

DMA_DIR: 传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR寄存器的DIR[1:0]位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。

DMA_BufferSize: 设定待传输数据数目,初始化设定DMA_CNDTR寄存器的值。

DMA_PeripheralInc: 如果配置为DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定DMA_CCR寄存器的PINC位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。

DMA_MemoryInc: 如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定DMA_CCR寄存器的MINC位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。

DMA_PeripheralDataSize: 外设数据宽度,可选字节(8位)、半字(16位)和字(32位),它设定DMA_CCR寄存器的PSIZE[1:0]位的值。

DMA_MemoryDataSize: 存储器数据宽度,可选字节(8位)、半字(16位)和字(32位),它设定DMA_CCR寄存器的MSIZE[1:0]位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。

DMA_Mode:DMA传输模式选择,可选一次传输或者循环传输,它设定DMA_CCR寄存器的CIRC位的值。例程我们的ADC采集是持续循环进行的,所以使用循环传输模式。

DMA_Priority: 软件设置通道的优先级,有4个可选优先级分别为非常高、高、中和低,它设定DMA_CCR寄存器的PL[1:0]位的值。DMA通道优先级只有在多个DMA通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。

DMA_M2M: 存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR的位14 MEN2MEN即可启动存储器到存储器模式。

5 DMA存储器到外设模式实验

5.1 代码分析

USART初始化

代码清单:DMA-5 USART初始化

void UART_Configuration(uint32_t bound)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    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;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate            = bound;
    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(USART_TEST, &USART_InitStructure);
    USART_Cmd(USART_TEST, ENABLE);
}

这段代码定义了UART_Configuration函数用于配置 UART,它接收波特率作为参数。函数先初始化 GPIO 和 USART 配置结构体,接着使能 USART1 和 GPIOA 的时钟。然后将 PA9 配置为复用推挽输出作为 TX 引脚,PA10 配置为浮空输入作为 RX 引脚。之后设置 USART 的波特率、数据位、停止位、校验位、硬件流控制等参数,并使能接收和发送模式。最后初始化并使能 USART

串口DMA传输配置

代码清单:DMA-6 USART1 发送请求DMA设置

void DMA_RecvConfiguration(void)
{
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    //USART1_RX DMA Config
    DMA_DeInit(DMA1_Channel5);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t)Buff;
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize         = BUFFSIZE;
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M                = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel5, &DMA_InitStructure);

    DMA_Cmd(DMA1_Channel5, ENABLE);
}

void DMA_SendConfiguration(void)
{
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    //USART1_TX DMA Config
    DMA_DeInit(DMA1_Channel4);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t)Buff;
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize         = BUFFSIZE;
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M                = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel4, &DMA_InitStructure);

    DMA_Cmd(DMA1_Channel4, ENABLE);
}

代码定义了两个函数用于配置 DMA(直接内存访问)的接收和发送功能。DMA_RecvConfiguration 函数配置了 USART1 的 DMA 接收功能,开启了 DMA1 的时钟,将 DMA1 通道 5 初始化为从 USART1 的数据寄存器接收数据到内存缓冲区 Buff,并设置了数据方向、缓冲区大小、数据增量模式、数据大小、工作模式、优先级等参数,最后使能该通道。DMA_SendConfiguration 函数则配置了 USART1 的 DMA 发送功能,同样开启 DMA1 时钟,将 DMA1 通道 4 初始化为从内存缓冲区 Buff 发送数据到 USART1 的数据寄存器,设置了相似的参数后使能该通道。

主函数

代码清单:DMA-7 存储器到外设模式主函数

int main(void)
{
    uint32_t          i;
    RCC_ClocksTypeDef clocks;

    delay_init();
    UART_Configuration(115200);
    RCC_GetClocksFreq(&clocks);

    printf("n");
    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhzn",
           (float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);

    printf("USART Asyn DMA Test.n");

    while (1)
    {
        DMA_Cmd(DMA1_Channel4, DISABLE);
        USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
        DMA_RecvConfiguration();
        while (DMA_GetFlagStatus(DMA1_FLAG_TC5) == RESET);
        printf("USART Asyn DMA Recv Completen");
        for (i = 0; i < BUFFSIZE; i++)
        {
            printf("%c  ,", Buff[i]);
        }


        DMA_Cmd(DMA1_Channel5, DISABLE);
        USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
        DMA_SendConfiguration();
        while (DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET);
        printf("USART Asyn DMA Send Completen");
    }
}

这段代码是一个 C 语言的 main 函数,主要实现了 USART(通用同步异步收发器)的异步 DMA(直接内存访问)测试功能。具体步骤如下:

定义变量 i 和 clocks,i 用于循环计数,clocks 用于存储时钟频率信息。

初始化延时函数,以 115200 的波特率配置 UART,获取系统时钟频率并存储在 clocks 中。

打印系统各时钟频率信息,包括 SYSCLK、HCLK、PCLK1、PCLK2 和 ADCCLK,同时输出测试提示信息。

进入无限循环,在循环中交替进行接收和发送操作:

接收操作:先禁用 DMA1 通道 4,使能 USART1 的接收 DMA 请求,调用 DMA_RecvConfiguration 函数配置 DMA 接收,等待接收完成标志置位,打印接收完成信息并逐个输出接收到的数据。

发送操作:禁用 DMA1 通道 5,使能 USART1 的发送 DMA 请求,调用DMA_SendConfiguration 函数配置 DMA 发送,等待发送完成标志置位,打印发送完成信息。

通过这种方式,不断进行数据的接收和发送测试。

5.2 下载验证

保证开发板相关硬件连接正确,在电脑端打开串口调试助手,把编译好的程序下载到开发板。

wKgZO2gu79yAM_RBAAB3jQGzg8w806.png

WIZnet 是一家无晶圆厂半导体公司,成立于 1998 年。产品包括互联网处理器 iMCU™,它采用 TOE(TCP/IP 卸载引擎)技术,基于独特的专利全硬连线 TCP/IP。iMCU™ 面向各种应用中的嵌入式互联网设备。

WIZnet 在全球拥有 70 多家分销商,在香港、韩国、美国设有办事处,提供技术支持和产品营销。

香港办事处管理的区域包括:澳大利亚、印度、土耳其、亚洲(韩国和日本除外)。

推荐阅读:

【图解】国家统计局:上半年天然气生产创历史新高

“五一”餐饮市场火热 外卖成餐饮企业营收上涨重要渠道

首届全国林草行业森林消防员职业技能竞赛举办