DMA

直接存储器访问(DMA,Direct Memory Access)的优点

· 提高系统效率:通过绕过CPU,DMA显著减少了数据传输对CPU资源的占用,使得CPU能够专注于其他计算任务,提升了系统整体的响应速度和处理能力。

· 加快数据传输速度:针对多总线高性能MCU,DMA可以避免不同总线同步问题,提供更高的数据传输速率。

· 降低系统延迟:由于减少了CPU参与数据搬运的环节,系统延迟显著降低,这对于实时系统和高性能计算应用至关重要,确保了数据的即时处理和反馈。

· 简化软件设计:DMA控制器的硬件自动化处理降低了软件层面对数据传输的复杂管理,使得软件设计更为简洁,降低了开发难度和维护成本。

先楫产品中,有大量支持DMA的设备,其中USB、Ethernet、ADC、DAC等有自己的内部DMA,AHB和AXI总线上还有公共的DMA设备——HDMA、XDMA。

HDMA、XDMA都是多通道DMA,可以通过DMAMUX实现多通道的数据传输。

HDMA、XDMA分别接入AHB和AXI总线,在总线内部传输效率更高,可以支持8-64bit数据宽度的传输。

本文将通过两个应用案例,说明DMA在如何在实时控制中提高系统的稳定性和实时性。

DMA准确控制

下图是典型伺服三环的控制框图,其中编码器是控制的关键反馈,除了准确读取位置之外,还需要通过不同时刻读取位置计算转速。读取位置的时刻在伺服闭环中占非常关键的作用。

1.png

常见位置读取方式是在定时中断中读取位置,确保读取间隔时刻一致。但由于软件响应时间不确定,读取间隔很难保证一致。

本文通过DMA+链表方式实现HPM6200用串口与多摩川编码器定时通讯的方式。多摩川编码器通讯协议见下图:

2.png

例程通过PWM定期触发DMA,由DMA启动串口读取动作。DMA动作完成后利用链式传输,可以在处理器不介入的情况下,连续完成多个不同配置的传输任务。

程序中使用了PWM、DMA、UART三个模块

· PWM负责定时输出DMA触发信号;

· DMA接收触发信号后将采样命令写入UART的THR寄存器;

· UART负责收发位置传感器信息,其中接收建议使用硬件idle+FIFO模式。

先楫的UART有硬件收发使能控制,只需要DE设置为有效,485通讯可以自动实现收发方向控制,无须CPU干预。

3.png

HPM6280集成了9个UART模块:

除了常规配置之外,还支持硬件空闲中断

支持16 字节的 TXFIFO 和 RXFIFO

硬件收发使能自动控制

通过简单配置即可实现2.5Mbps通讯、RS485自动收发使能控制、硬件空闲中断接收数据等功能。

void config_uart(void) {
    hpm_stat_t stat;
    uart_config_t config = {0};
    
    /* if TEST_UART is same as BOARD_CONSOLE_BASE, it has been initialized in board_init(); */
    uart_default_config(HPM_UART7, &config);
    config.baudrate = 2500000UL;
    config.fifo_enable = true;
    //config.dma_enable = true;
    clock_set_source_divider(clock_uart7, clk_src_pll0_clk0, 5);//80Mhz
    clock_add_to_group(clock_uart7, 0);
    config.src_freq_in_hz = clock_get_frequency(clock_uart7);//clock_get_frequency(clock_uart0);
    config.rx_fifo_level = uart_rx_fifo_trg_gt_three_quarters;/* this config should not change *///uart_rx_fifo_trg_not_empty; 
    config.rxidle_config.detect_enable = true;
    config.rxidle_config.detect_irq_enable = true;
    config.rxidle_config.idle_cond = uart_rxline_idle_cond_rxline_logic_one;
    config.rxidle_config.threshold = 20U; /* 20bit */
    stat = uart_init(HPM_UART7, &config);
    if (stat != status_success) {
        printf("failed to initialize uart\n");
    }
    //uart_enable_irq(HPM_UART0, uart_intr_rx_data_avail_or_timeout);
    intc_m_enable_irq_with_priority(IRQn_UART7, 1);
}

(可滑动查看)

下面DMA配置启用了链式传输,实现DMA循环触发UART读取位置信息。

构建两个相互链接的 DMA 任务描述符列表。DMA控制器会在完成当前任务描述符的相应任务后,从 ChnLLPointer指向地址取下一个任务描述符。下一个任务描述符又关联当前描述符,如此互锁,无限循环。

描述符中DMA配置目标数据为握手模式,UART设备返回接收数据完毕信号。

为确保uart数据可以准确传输,DMA的高优先级标志位要设置为1。

{
    hpm_stat_t stat;
    dma_channel_config_t rx_ch_config = { 0 };
    dmamux_config(HPM_DMAMUX, ch_num, HPM_DMA_SRC_MOT0_0, true);
    /* 1.1 config chain descriptors */
    dma_default_channel_config(HPM_HDMA, &rx_ch_config);
    rx_ch_config.src_addr = src;
    rx_ch_config.src_width = DMA_TRANSFER_WIDTH_BYTE;  /*  In DMA handshake case, source width and destination width must be BYTE. */
    rx_ch_config.src_addr_ctrl = DMA_ADDRESS_CONTROL_INCREMENT;
    rx_ch_config.src_mode = DMA_HANDSHAKE_MODE_NORMAL;
    rx_ch_config.dst_addr = (uint32_t)&uart_ptr->THR;
    rx_ch_config.dst_width = DMA_TRANSFER_WIDTH_BYTE;  /*  In DMA handshake case, source width and destination width must be BYTE. */
    rx_ch_config.dst_addr_ctrl = DMA_ADDRESS_CONTROL_FIXED;
    rx_ch_config.dst_mode = DMA_HANDSHAKE_MODE_HANDSHAKE;
    rx_ch_config.size_in_byte = 1;
    rx_ch_config.priority = 1;
    rx_ch_config.src_burst_size = DMA_NUM_TRANSFER_PER_BURST_1T;  /*  In DMA handshake case, source burst size must be 1 transfer, that is 0. */
    rx_ch_config.linked_ptr = core_local_mem_to_sys_address(HPM_CORE0, (uint32_t)&descriptors[1]);//link to next dma action
    stat = dma_config_linked_descriptor(HPM_HDMA, &descriptors[0], ch_num, &rx_ch_config);
    if (stat != status_success) {
        while (1) {
        };
    }

    rx_ch_config.linked_ptr = core_local_mem_to_sys_address(HPM_CORE0, (uint32_t)&descriptors[0]);
    stat = dma_config_linked_descriptor(HPM_HDMA, &descriptors[1], ch_num, &rx_ch_config);
    if (stat != status_success) {
        while (1) {
        };
    }

(可滑动查看)

在PWM中配置DMA定期触发,配置比较器匹配触发,配置DMA输出使能。

互联管理器TRGM是HPM MCU中非常有特色的外设,可以通过配置实现多个外设的输入输出相互连接,使得多个外设可以相互配合使用。

HPM6200中TRGM支持4个DMA 请求输出,用户可以配置TRGM,从多个DMA请求输入中,选择4个连接到 DMAMUX。本文选择了PWM0的CMP14。


     pwm_set_reload(HPM_PWM0, 0, reload);
    pwm_set_start_count(HPM_PWM0, 0, 0);
    pwm_set_load_counter_shadow_register_trigger(HPM_PWM0,pwm_shadow_register_update_on_modify,0);
    /*
     * config cmp1 and cmp2
     */
    cmp_config[0].mode = pwm_cmp_mode_output_compare;
    cmp_config[0].cmp = reload + 1;
    cmp_config[0].update_trigger = pwm_shadow_register_update_on_hw_event;

    cmp_config[1].mode = pwm_cmp_mode_output_compare;
    cmp_config[1].cmp = reload + 1;
    cmp_config[1].update_trigger = pwm_shadow_register_update_on_hw_event;

    cmp_config[2].mode = pwm_cmp_mode_output_compare;//channel to update compare shadow 
    cmp_config[2].cmp = reload;
    cmp_config[2].update_trigger = pwm_shadow_register_update_on_modify;

    cmp_config[3].mode = pwm_cmp_mode_output_compare;//dma trigger channel
    cmp_config[3].cmp = reload-100;
    cmp_config[3].update_trigger = pwm_shadow_register_update_on_modify;

    pwm_get_default_pwm_pair_config(HPM_PWM0, &pwm_pair_config);
    pwm_pair_config.pwm[0].enable_output = true;
    pwm_pair_config.pwm[0].dead_zone_in_half_cycle = 8000;
    pwm_pair_config.pwm[0].invert_output = false;

    pwm_pair_config.pwm[1].enable_output = true;
    pwm_pair_config.pwm[1].dead_zone_in_half_cycle = 16000;
    pwm_pair_config.pwm[1].invert_output = false;

    /*
     * config pwm
     */
    if (status_success != pwm_setup_waveform_in_pair(HPM_PWM0, 0, &pwm_pair_config, cmp_index, cmp_config, 2)) {
        printf("failed to setup waveform\n");
        while(1);
    }
    //====================set dma trriger from cmp[14]============================
    pwm_config_cmp(HPM_PWM0, 14, &cmp_config[3]);//dma trigger
    pwm_enable_dma_request(HPM_PWM0,1<<14);//enable pwm signal output to dma
    trgm_dma_request_config(HPM_TRGM0,0,14);//connect cmp14 to HPM_DMA_SRC_MOT0_0

(可滑动查看)

下图是DMA以20kHz触发UART定期输出的波形,定期输出0X1A,读取多摩川传感器中全部信息。

4.png

DMA加速传输

HPM5300、HPM6800、HPM6E00引入了DMAv2,增加了无限循环、DMA传输一半中断,并修改了burst传输长度定义。

下文将列举一个buck-boost电源应用通过DMAv2更新PWM的例子,演示DMA加速传输的方法和效果。


5.png

例程选用了两路交错buck-boost电路。

高效电源对功率密度有更高的要求,更高的开关频率可以降低主回路中电感和电容体积,实际应用中,中小功率的电源开关频率可达100khz以上,频繁的调节对CPU的运算能力和读写外设的速度有更高的要求。

HPM5300单次写PWM寄存器至少需要5个AHB时钟(HPM6700、HPM6300时间更长),例程使用了8个PWM比较寄存器,CPU时钟为480Mhz、AHB总线为160Mhz,连续写入时至少0.25us,相当于120条CPU clock。

修改PWM刷新方式后,将PWM比较器寄存器的值放入DLM内存中,更新PWM只是占用了CPU 8个访问高速RAM的时间。

与HPM6200不同,DMAv2直接支持无限循环模式,CHCTRL[CTRL].INFINITELOOP设置为1即可,不需要链表实现无限循环。

将CHCTRL[CTRL].burst_opt配置为1,burst传输个数不再是2的指数次方,可以根据实际需要配置。

PWM配置需要清零SHLK[SHLK],影子寄存器锁定功能。

其它设置与前文配置相同。

void dma_transfer_config(uint8_t DMA_chn, uint8_t PWM_num, uint32_t* CMP0)
{
    //---------------configure dma channel-----------------
    dma_channel_config_t ch_config = {0};
    DMA_chn &= 0x1F;
    dma_disable_channel(HPM_HDMA, DMA_chn);//stop channel
    dmamux_config(HPM_DMAMUX, DMA_chn, HPM_DMA_SRC_MOT_0,  true);//trigger source is from trgms dmacfg0
    //dma_reset(APP_GPTMR_DMA);
    //---------------configure dma chn0-----------------
    dma_default_channel_config(HPM_HDMA, &ch_config);
    ch_config.src_addr = core_local_mem_to_sys_address(HPM_CORE0, (uint32_t)&PWM_DMA_struct);//source address
    //ch_config.dst_addr = (uint32_t)&HPM_PWM0->CMP[0];//destination address
    ch_config.dst_addr = (uint32_t)CMP0;//destination address
    ch_config.src_mode = DMA_HANDSHAKE_MODE_HANDSHAKE;//hand shake mode waiting trigger signal
    ch_config.src_width = DMA_TRANSFER_WIDTH_WORD;// 32bit
    ch_config.src_addr_ctrl = DMA_ADDRESS_CONTROL_INCREMENT;
    ch_config.burst_opt = DMA_SRC_BURST_OPT_CUSTOM_SIZE;//burst size is actural number rather than 2^num
    ch_config.src_burst_size = PWM_num;
    ch_config.dst_width = DMA_TRANSFER_WIDTH_WORD;//32bit
    ch_config.dst_addr_ctrl = DMA_ADDRESS_CONTROL_INCREMENT;
    ch_config.dst_mode = DMA_HANDSHAKE_MODE_NORMAL;//normal
    ch_config.en_infiniteloop = true;//dma will react if transize has been completed
    ch_config.size_in_byte = PWM_num*4;
    ch_config.linked_ptr = 0;//no link
    if (status_success != dma_setup_channel(HPM_HDMA, DMA_chn, &ch_config, true)) {
        printf(" dma setup channel failed\n");
        return;
    }
}

(可滑动查看)

HPM系列MCU包含了强大互联管理器和DMA模块,可以轻松实现外设无限循环的触发DMA,不需要占用CPU时间每次配置DMA触发外设。

DMA直接触发外设动作,将极大提高系统动作的一致性。伺服客户对比之前中断触发读取位置与DMA触发读取位置效果:在2000rpm时,中断触发读取位置得到的计算最大瞬时转速波动为20rpm,改为DMA触发后波动降为2rpm。

微逆应用中,同时变频、变占空比时,通过DMA定时修改PWM比较器和周期寄存器数值消除了同时修改后造成的波形偶发异常问题。

电源应用中,开关频率往往超过100kHz,对CPU的利用率要求更高,且对PWM、ACMP读写频率和内容更多,DMA读写可以有限减轻CPU负担,提高CPU效率。

来源:先楫半导体HPMicro

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 106

为了提供单片机内存数据传输效率,众多单片机都支持 DMA(Direct Memory Access)传输。

但是,还有另外一种可以提高内存传输效率的功能较TCM。今天就结合瑞萨RA8单片机给大家讲讲TCM的优点及应用。

关于TCM

TCM:Tightly-Coupled Memory,紧耦合内存,是通过专用的接口直接连接到处理器的存储器区域,它提供单周期访问,避免其他存储器可能存在的仲裁延时和延迟。
RA8单片机 Cortex-M85内核有2种TCM类型:Instruction TCM (ITCM) 指令TCM和Data TCM (DTCM) 数据TCM。

RA8x1有64KB ITCM和64KB DTCM。

ECC保护(代码生成和纠正逻辑)。

RA8x1支持ITCM和DTCM ECC功能,可以通过OFS2.INITECCEN设置来启用和禁用。

1.png

RA8x1 CPU方框图

TCM具有以下优点:

低延迟访问:TCM与处理器核心之间的直接连接消除了访问延迟,使得数据和指令能够更快速地被处理器访问,从而提高了系统的响应速度。

高带宽:TCM通常具有更高的带宽,可以支持处理器对数据和指令的高速读写,提升了系统的整体性能。

节省功耗:由于TCM与处理器核心直接连接,不需要通过总线进行数据传输,因此可以降低功耗。此外,由于访问延迟较低,处理器可以更快地完成任务并进入休眠模式,进一步降低功耗。

提高实时性能:对于需要实时响应的应用程序,TCM的低延迟和高带宽特性使得处理器能够更快地访问关键数据和指令,从而提高了系统的实时性能。

增强安全性:TCM可以用于存储敏感数据和关键代码,通过与处理器核心的紧密耦合,可以降低数据泄露和恶意攻击的风险,提高系统的安全性。

减少对外部存储器的依赖:TCM可以用于存储频繁访问的数据和指令,减少了对外部存储器的访问次数,降低了总体的访问延迟和功耗。这对于一些资源有限的嵌入式系统尤为重要。

增强可靠性:TCM的直接连接和高速访问特性可以提高系统的可靠性,减少因外部存储器或总线故障而导致的系统性能下降或故障。

因此,TCM适用于许多不同的应用和场景,特别是对于需要高性能、低延迟和实时响应的应用,其优势更加突出。以下是一些适合使用TCM的应用和场景:

实时控制系统:对于需要快速响应的实时控制系统,如工业机器人、航空航天控制系统、医疗设备等,TCM可以提供所需的低延迟和高带宽,确保系统能够及时、准确地响应各种控制指令。

信号处理应用:TCM对于需要大量数据处理和信号处理的应用非常适用,例如无线通信系统、雷达系统、图像处理系统等。通过将频繁访问的数据和指令存储在TCM中,可以提高系统的处理速度和效率。

物联网设备:随着物联网设备的普及,对于需要在资源有限的设备上实现高性能和实时响应的应用,TCM可以帮助提高系统的性能和能效,同时减少对外部存储器和网络带宽的依赖。

高性能计算:在需要进行复杂计算和大规模数据处理的高性能计算应用中,TCM可以提供更快速和可靠的数据访问,从而提高系统的计算性能和吞吐量。

MCU如何将通过FSP将代码/数据放置到TCM中?

瑞萨电子灵活配置软件包(FSP)是用于嵌入式系统设计的高质量增强型软件包,支持瑞萨电子RA产品家族ARM微控制器,提供用户友好的界面且可灵活扩展,确保从入门级到高性能的整个RA微控制器的软件兼容性。FSP包括高性能、低内存占用的业界一流的HAL驱动程序。还包含集成了Azure RTOS和FreeRTOS的中间件协议栈,能够简化通信和安全等复杂模块的实现。e2 studio IDE提供了对图形化配置工具和智能代码生成器的支持,从而使编程和调试变得更加轻松快捷。

  • 瑞萨FSP链接脚本提供TCM内存区域段定义

memory_region.ld中的内存大小定义:

2.png

内存区域定义:

3.png

如下原型定义可用于将用户代码/数据放置到TCM中。在启动过程中,.itcm_data和.dtcm_data区域将通过闪存中存储的初始化代码进行数据初始化。.dtcm_bss区域已初始化为零。

4.png

FSP中的ITCM段定义:

5.png

FSP中的DTCM段定义:

6.png

TCM例子分析

下图是一个RA8x1 MCU实际使用TCM的例子,它使用了ITCM和DTCM。图片中的右边为RA8x1 MCU的系统地址空间。

7.png

具体分析过程为:

1)紫色的代码“uint16_t i;”全局变量,它运行的时候,分配的地址是在从0x2200_0000开始的On-chip SRAM中。

2)红色的代码“BSP_PLACE_IN_SECTION(“.dtcm_data”)uint8x16_t rega_8, regb_8, regc_8, regd_8;”,全局变量,但是由于这些变量前面添加了BSP_PLACE_IN_SECTION(“.dtcm_data”),这表示将这些变量放置到DTCM中,它运行的时候,分配的地址是在从0x2000_0000开始的DTCM中。

3)青色的代码“void hal_entry(void)”,它是函数,运行的时候,分配的地址是在从0x0200_0000开始的On-chip flash中。

4)深绿色的代码“void helium_test(void)”,它是函数,但是由于这些变量前面添加了BSP_PLACE_IN_SECTION(“.itcm_data”),这意味着,该代码运行的时候,分配的地址是从0x0000_0000开始的ITCM中。

下图是上面描述的代码在e2 studio中使用了LLVM工具链编译并仿真运行的截图,可以发现右边表达式窗口中的i,rega_8, regb_8, regc_8, regd_8,helium_test,hal_entry这些代码或者变量的地址和刚刚分析的结果是相符的。

8.png

来源:瑞萨嵌入式小百科

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 141

1718677326108367.jpg

Allen 先楫资深 FAE

8年产品研发经验,具有变频器、PLC等工业产品开发经验,也参与过汽车仪表、中控屏等车载产品的研发工作。在产品底层驱动、伺服驱动器、显示仪表等领域有着丰富开发经验。

在嵌入式产品应用开发中,经常需要MCU芯片产生任意的方波信号,从而驱动外设执行相应的操作。比如,驱动模拟量芯片、miniLED屏等。不同于PWM波这种占空比固定的信号,这些驱动信号往往是由等宽的高低电平任意排列的方波。

1.png

传统的GPIO模拟方波时序,不仅占用CPU资源,而且波形的脉宽较大。在驱动miniLED屏这类外设时,达不到系统的功能要求。先楫半导体的全系列MCU可以将内存中的数据通过DMA来设置GPIO的电平,以TRGM互联管理器和PWM比较器配合使用来设置脉冲宽度,从而产生任意时序的方波信号。该方案基于硬件方式来实现,不会占用CPU处理时间,波形宽度可达到50ns。下面介绍该方案的实现过程,例程基于HPM6360EVK实现。

定义波形数据

定义数组如下所示,其中32位无符号整数可以映射32个GPIO,每个数据位对应一个管脚。数组长度4096对应4096个方波周期。

ATTR_PLACE_AT_NONCACHEABLE_WITH_ALIGNMENT(8) uint32_t g_u32LedBufForGpio32[4096] = {0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0};

初始化GPIO管脚

此处将24个GPIO配置为输出模式。

static void zh_led_gpio_config(void)
{
    uint32_t pad_ctl = IOC_PAD_PAD_CTL_PE_SET(1) | IOC_PAD_PAD_CTL_PS_SET(1);
 
    HPM_IOC->PAD[IOC_PAD_PC00].FUNC_CTL = IOC_PC00_FUNC_CTL_GPIO_C_00;
    HPM_IOC->PAD[IOC_PAD_PC00].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC01].FUNC_CTL = IOC_PC01_FUNC_CTL_GPIO_C_01;
    HPM_IOC->PAD[IOC_PAD_PC01].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC02].FUNC_CTL = IOC_PC02_FUNC_CTL_GPIO_C_02;
    HPM_IOC->PAD[IOC_PAD_PC02].PAD_CTL = pad_ctl;
    
    HPM_IOC->PAD[IOC_PAD_PC03].FUNC_CTL = IOC_PC03_FUNC_CTL_GPIO_C_03;
    HPM_IOC->PAD[IOC_PAD_PC03].PAD_CTL = pad_ctl;
    
    HPM_IOC->PAD[IOC_PAD_PC04].FUNC_CTL = IOC_PC04_FUNC_CTL_GPIO_C_04;
    HPM_IOC->PAD[IOC_PAD_PC04].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC05].FUNC_CTL = IOC_PC05_FUNC_CTL_GPIO_C_05;
    HPM_IOC->PAD[IOC_PAD_PC05].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC06].FUNC_CTL = IOC_PC06_FUNC_CTL_GPIO_C_06;
    HPM_IOC->PAD[IOC_PAD_PC06].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC07].FUNC_CTL = IOC_PC07_FUNC_CTL_GPIO_C_07;
    HPM_IOC->PAD[IOC_PAD_PC07].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC08].FUNC_CTL = IOC_PC08_FUNC_CTL_GPIO_C_08;
    HPM_IOC->PAD[IOC_PAD_PC08].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC09].FUNC_CTL = IOC_PC09_FUNC_CTL_GPIO_C_09;
    HPM_IOC->PAD[IOC_PAD_PC09].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC10].FUNC_CTL = IOC_PC10_FUNC_CTL_GPIO_C_10;
    HPM_IOC->PAD[IOC_PAD_PC10].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC11].FUNC_CTL = IOC_PC11_FUNC_CTL_GPIO_C_11;
    HPM_IOC->PAD[IOC_PAD_PC11].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC12].FUNC_CTL = IOC_PC12_FUNC_CTL_GPIO_C_12;
    HPM_IOC->PAD[IOC_PAD_PC12].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC13].FUNC_CTL = IOC_PC13_FUNC_CTL_GPIO_C_13;
    HPM_IOC->PAD[IOC_PAD_PC13].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC14].FUNC_CTL = IOC_PC14_FUNC_CTL_GPIO_C_14;
    HPM_IOC->PAD[IOC_PAD_PC14].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC15].FUNC_CTL = IOC_PC15_FUNC_CTL_GPIO_C_15;
    HPM_IOC->PAD[IOC_PAD_PC15].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC16].FUNC_CTL = IOC_PC16_FUNC_CTL_GPIO_C_16;
    HPM_IOC->PAD[IOC_PAD_PC16].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC17].FUNC_CTL = IOC_PC17_FUNC_CTL_GPIO_C_17;
    HPM_IOC->PAD[IOC_PAD_PC17].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC18].FUNC_CTL = IOC_PC18_FUNC_CTL_GPIO_C_18;
    HPM_IOC->PAD[IOC_PAD_PC18].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC19].FUNC_CTL = IOC_PC19_FUNC_CTL_GPIO_C_19;
    HPM_IOC->PAD[IOC_PAD_PC19].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC20].FUNC_CTL = IOC_PC20_FUNC_CTL_GPIO_C_20;
    HPM_IOC->PAD[IOC_PAD_PC20].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC21].FUNC_CTL = IOC_PC21_FUNC_CTL_GPIO_C_21;
    HPM_IOC->PAD[IOC_PAD_PC21].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC22].FUNC_CTL = IOC_PC22_FUNC_CTL_GPIO_C_22;
    HPM_IOC->PAD[IOC_PAD_PC22].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC23].FUNC_CTL = IOC_PC23_FUNC_CTL_GPIO_C_23;
    HPM_IOC->PAD[IOC_PAD_PC23].PAD_CTL = pad_ctl;
 
    HPM_IOC->PAD[IOC_PAD_PC24].FUNC_CTL = IOC_PC24_FUNC_CTL_GPIO_C_24;
    HPM_IOC->PAD[IOC_PAD_PC24].PAD_CTL = pad_ctl;
 
    for(int i=0; i<24; i++) {
        gpio_set_pin_output(HPM_GPIO0, GPIO_DO_GPIOC, i);
        gpio_write_pin(HPM_GPIO0, GPIO_DO_GPIOC, i, 0);
    }
}

配置DMA

在每次输出波形之前都要进行一下DMA的设置,用户通过调整波形数组的数据来设置想要的波形。DMA将数组的数据不断搬运到DO[VALUE]寄存器中,因此要注意GPIO管脚和数组数据的对应关系。例程是输出占空比为50%的PWM波。

static void zh_led_dma_config(void)
{
    dma_channel_config_t ch_config = { 0 };
    unsigned int i = 0;
 
    for(i = 0;i<4096;i++)
    {
      g_u32LedBufForGpio32[i] = (i&1)?(~0):(0);
      //g_u32LedBufForGpio32[i] = ~0;
      //if(i%5 == 0)
      //{
      //  g_u32LedBufForGpio32[i] = ~0;
      //}
      //else
      //{
      //  g_u32LedBufForGpio32[i] = 0;
      //}
    }
 
    dma_reset(HPM_HDMA);
 
    intc_m_enable_irq_with_priority(BOARD_APP_HDMA_IRQ, 1);
 
    dma_default_channel_config(HPM_HDMA, &ch_config);
 
    ch_config.src_addr = (uint32_t)&g_u32LedBufForGpio32[0];
 
    ch_config.dst_addr = (uint32_t)&HPM_GPIO0->DO[GPIO_DO_GPIOC].VALUE;
    ch_config.src_width = DMA_TRANSFER_WIDTH_WORD;                                                             // 32位
    ch_config.dst_width = DMA_TRANSFER_WIDTH_WORD;                                                             // 32位
    ch_config.src_addr_ctrl = DMA_ADDRESS_CONTROL_INCREMENT;
    ch_config.dst_addr_ctrl = DMA_ADDRESS_CONTROL_FIXED;
    ch_config.size_in_byte = sizeof(g_u32LedBufForGpio32); //32 * sizeof(uint32_t); 
    ch_config.dst_mode = DMA_HANDSHAKE_MODE_NORMAL;
    ch_config.src_burst_size = 0;
 
    if (status_success != dma_setup_channel(HPM_HDMA, 0, &ch_config, false))
    {
        printf(" dma setup channel failed\n");
        return;
    }
 
    dmamux_config(HPM_DMAMUX, DMAMUX_MUXCFG_HDMA_MUX0, HPM_DMA_SRC_MOT0_0, false);
 
    trgm_dma_request_config(HPM_TRGM0, 0, 18);
 
    pwm_enable_dma_request(HPM_PWM0, PWM_IRQ_CMP(18));
 
    synt_enable_counter(HPM_SYNT, true);
    pwm_start_counter(HPM_PWM0);
    dmamux_enable_channel(HPM_DMAMUX, DMAMUX_MUXCFG_HDMA_MUX0);
    dma_enable_channel(HPM_HDMA, 0);
}

设置DMA中断,在输出波形结束以后会触发中断响应函数。

void isr_dma(void)
{
    uint32_t stat;
 
    stat = dma_check_transfer_status(HPM_HDMA, 0);
 
    if (0 != (stat & DMA_CHANNEL_STATUS_TC)) {
      printf("Transfer done!");
    }
}
SDK_DECLARE_EXT_ISR_M(BOARD_APP_HDMA_IRQ, isr_dma)

配置CLK时钟

以PWM比较器产生需要的波形周期。

/**
 * u8Phase from 1~18, 其中设置1 == 设置18, 设置1时 上升沿的边与数据的开始时刻对齐,随着设置的数字增大,上升沿的边向右移动,设置8时,上升沿的边大约在中间。
 *  */
static int zh_led_clk_config(uint8_t u8Phase)
{
    pwm_cmp_config_t cmp_config_ch0[4] = {0};
    pwm_config_t pwm_config = {0};
    uint32_t u32CmpValue[4];
 
    switch (u8Phase)
    {
        case 1:
            u32CmpValue[0] = 6;
            u32CmpValue[1] = 16;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 18;
            break;
        case 2:
            u32CmpValue[0] = 7;
            u32CmpValue[1] = 18;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 18;
            break;
        case 3:
            u32CmpValue[0] = 17;
            u32CmpValue[1] = 0;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 8;
            break;
        case 4:
            u32CmpValue[0] = 17;
            u32CmpValue[1] = 1;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 9;
            break;
        case 5:
            u32CmpValue[0] = 17;
            u32CmpValue[1] = 2;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 10;
            break;
        case 6:
            u32CmpValue[0] = 17;
            u32CmpValue[1] = 3;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 11;
            break;
        case 7:
            u32CmpValue[0] = 17;
            u32CmpValue[1] = 4;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 12;
            break;
        case 8:
            u32CmpValue[0] = 17;
            u32CmpValue[1] = 5;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 13;
            break;
        case 9:
            u32CmpValue[0] = 17;
            u32CmpValue[1] = 6;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 14;
            break;
        case 10:
            u32CmpValue[0] = 18;
            u32CmpValue[1] = 7;
            u32CmpValue[2] = 17;
            u32CmpValue[3] = 15;
            break;
        case 11:
            u32CmpValue[0] = 18;
            u32CmpValue[1] = 8;
            u32CmpValue[2] = 17;
            u32CmpValue[3] = 16;
            break;
        case 12:
            u32CmpValue[0] = 17;
            u32CmpValue[1] = 9;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 18;
            break;
        case 13:
            u32CmpValue[0] = 0;
            u32CmpValue[1] = 10;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 18;
            break;
        case 14:
            u32CmpValue[0] = 1;
            u32CmpValue[1] = 11;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 18;
            break;
        case 15:
            u32CmpValue[0] = 2;
            u32CmpValue[1] = 12;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 18;
            break;
        case 16:
            u32CmpValue[0] = 3;
            u32CmpValue[1] = 13;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 18;
            break;
        case 17:
            u32CmpValue[0] = 4;
            u32CmpValue[1] = 14;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 18;
            break;
        case 18:
            u32CmpValue[0] = 5;
            u32CmpValue[1] = 15;
            u32CmpValue[2] = 18;
            u32CmpValue[3] = 18;
            break;
            break;
        default:
            break;
    }
 
    pwm_stop_counter(HPM_PWM0);
    pwm_set_reload(HPM_PWM0, 0, zh_led_PWM_FREQ-1);
    pwm_set_start_count(HPM_PWM0, 0, 0);
 
    pwm_config.enable_output = true;
    pwm_config.invert_output = false;
    pwm_config.update_trigger = pwm_shadow_register_update_on_modify;
    pwm_config.fault_mode = pwm_fault_mode_force_output_highz;
    pwm_config.fault_recovery_trigger = pwm_fault_recovery_on_fault_clear;
    pwm_config.force_source = pwm_force_source_software;
    pwm_config.dead_zone_in_half_cycle = 0;
 
    /*cmp0 cmp1 cmp2 cmp3 for pwm ch0*/
    cmp_config_ch0[0].cmp = u32CmpValue[0];
    cmp_config_ch0[0].enable_ex_cmp = false;
    cmp_config_ch0[0].mode = pwm_cmp_mode_output_compare;
    cmp_config_ch0[0].update_trigger = pwm_shadow_register_update_on_hw_event;
    cmp_config_ch0[0].ex_cmp = 0;    
    cmp_config_ch0[0].half_clock_cmp = 0;
    cmp_config_ch0[0].jitter_cmp = 0;   
 
    cmp_config_ch0[1].cmp = u32CmpValue[1];
    cmp_config_ch0[1].enable_ex_cmp = false;
    cmp_config_ch0[1].mode = pwm_cmp_mode_output_compare;
    cmp_config_ch0[1].update_trigger = pwm_shadow_register_update_on_hw_event;
    cmp_config_ch0[1].ex_cmp = 0;    
    cmp_config_ch0[1].half_clock_cmp = 0;
    cmp_config_ch0[1].jitter_cmp = 0;  
 
    cmp_config_ch0[2].cmp = u32CmpValue[2];
    cmp_config_ch0[2].enable_ex_cmp = false;
    cmp_config_ch0[2].mode = pwm_cmp_mode_output_compare;
    cmp_config_ch0[2].update_trigger = pwm_shadow_register_update_on_hw_event;
    cmp_config_ch0[2].ex_cmp = 0;    
    cmp_config_ch0[2].half_clock_cmp = 0;
    cmp_config_ch0[2].jitter_cmp = 0;  
 
    cmp_config_ch0[3].cmp = u32CmpValue[3];
    cmp_config_ch0[3].enable_ex_cmp = false;
    cmp_config_ch0[3].mode = pwm_cmp_mode_output_compare;
    cmp_config_ch0[3].update_trigger = pwm_shadow_register_update_on_hw_event;
    cmp_config_ch0[3].ex_cmp = 0;    
    cmp_config_ch0[3].half_clock_cmp = 0;
    cmp_config_ch0[3].jitter_cmp = 0;  
    if (status_success != pwm_setup_waveform(HPM_PWM0, 1, &pwm_config, 0, cmp_config_ch0, 4)) {
        printf("failed to setup waveform for ch0\n");
        return status_fail;
    }
 
    cmp_config_ch0[0].cmp = zh_led_PWM_FREQ-1;
    cmp_config_ch0[0].update_trigger = pwm_shadow_register_update_on_modify;
    pwm_load_cmp_shadow_on_match(HPM_PWM0, 4, &cmp_config_ch0[0]);
 
    pwm_issue_shadow_register_lock_event(HPM_PWM0);
 
    /* enable pwm fault protect */
    pwm_fault_source_config_t config;
    //config.external_fault_active_low = false;
    config.source_mask = PWM_GCR_FAULTI0EN_MASK;
    config.fault_recover_at_rising_edge = false;
    config.fault_output_recovery_trigger = 0;
    pwm_config_fault_source(HPM_PWM0, &config);
 
 
    return status_success;
}

配置TRGM互联管理器

互联管理器TRGM以PWM的周期来触发DMA进行数据搬运。

static void zh_led_trgm_config(void)
{
    trgm_output_t stTrgmOutput;
 
    stTrgmOutput.invert = false;
    stTrgmOutput.type = trgm_output_same_as_input;
    
    stTrgmOutput.input = 44;
    trgm_output_config(HPM_TRGM0, 14, &stTrgmOutput);
    
    pwm_enable_reload_at_synci(HPM_PWM0);
 
    synt_reset_counter(HPM_SYNT);
    synt_set_reload(HPM_SYNT, zh_led_PWM_FREQ-1);
    synt_set_comparator(HPM_SYNT, 0, zh_led_PWM_FREQ-1);
}

测试程序如下:

int main(void)
{
    unsigned int j=3;
    board_init();
    zh_led_gpio_config();
    zh_led_clk_config(2);
    zh_led_trgm_config();
    zh_led_dma_config();
 
    printf("start\n");
 
    while(j--)
    {
      printf("j = %d\n", j);

      zh_led_dma_config();
      pwm_start_counter(HPM_PWM0);
   
   board_delay_ms(500);
    };

    printf("stop\n");
 
    while(1);

    return 0;
}

串口打印结果如下:

2.png

测量GPIO的波形:

3.png
4.png

因为HDMA访问AHB SRAM速度更快,可以将数据存储到AHB SRAM来提高刷新速度。

 __attribute__ ((section(".ahb_sram"))) uint32_t g_u32LedBufForGpio32[1024]={0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0};

使用逻辑分析仪测试波形如下:

5.png

用示波器测量波形如下图所示,信号质量表现优异。

6.png

来源:先楫半导体HPMicro

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 56

BD网盘链接:

https://pan.baidu.com/s/10WHNgB_cicTP1SbdcI4AkQ?pwd=l1et 
提取码:l1et

概述

CW32L052支持DMA(Direct Memory Access),即直接内存访问,无需CPU干预,实现高速数据传输。数据的传输可以发生在:

• 外设和内存之间:例如ADC采集数据到内存,这种传输方式常见于需要将外设采集的数据快速传输到内存进行处理的应用。

• 内存和内存之间:例如在两个不同的数组之间传输数据,或者在不同的内存块之间进行数据拷贝。

• 外设和外设之间:例如从一个SPI主/从机传输数据到另一个SPI从/主机。

使用DMA能够有效减轻CPU的负担,特别是在大量数据需要高效传输的情况下,可以提高系统的整体性能。

框图
1.png

DMA功能框图

特性

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

2.png

4个DMA通道的优先级和通道号绑定,通道号越小优先级越高,通道号越大优先级越低。

  • 4种传输模式: 

硬件触发BULK传输模式、硬件触发BLOCK传输模式、软件触发BULK传输模式和软件触发BLOCK传输模式。

硬件

 BULK

 BLOCK

软件

 BULK

 BLOCK

BULK传输模式:

适用于小数据块的传输,通常涉及大量的数据点,但每个数据点的大小较小。

与BLOCK模式不同,BULK模式下DMA会更频繁地启动新的传输,因为每个数据点通常被视为单独的传输单元,所以DMA控制器需要在每个数据点传输完成后需要重新配置或者启动DMA。在BULK传输模式下,传输过程不可被打断。

BLOCK传输模式:

适用于大数据块的高速传输,通常用于需要连续传输大量数据的情况。BLOCK模式下,DMA会将数据分成较大的块,并在传输时以这些块为单位进行操作。DMA控制器在一次配置后,连续传输多个数据块,而无需额外的干预或重新配置。每传输完成1个数据块后就要进行一次传输优先级的仲裁,允许CPU或者更高优先级的DMA通道访问当前DMA通道所占用的外设。

• 硬件触发和软件触发:

要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求。部分外设支持硬件触发启动DMA传输,如FLASH存储器、UART串口、TIM定时器、ADC数模转换器等被配置为DMA通道的触发源时,

 3.png

可以产生DMA请求(DMA request),硬件触发启动DMA传输,

 4.png

 而不支持硬件DMA的外设,只能配置为软件触发启动DMA传输。

 5.png

虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

• DMA中断

DMA通道在传输工程中可产生2个中断标志:传输错误中断标志和传输完成中断标志

不同 DMA 通道的中断各自独立,通过中断标志寄存器 DMA_ISR 可以获取各通道的中断标志。标志对应多个可能的产生原因,具体产生原因需查询 DMA_CSRy.STATUS 状态位,如下表所示

6.png

• 数据宽度:

数据位宽可以设置为8bit、16bit和32bit,DMA通道的源地址和目的地址的位宽必须完全一致

• 数据块数量:

传输的数据块数量可以设置为1 ~ 65535

• 数据传输优先级

当CPU和DMA访问不同的外设时,数据的传输可以同时进行;

当CPU和DMA同时访问同一个外设时,CPU的优先级高于DMA。

从外设到内存BD网盘链接:

https://pan.baidu.com/s/1RlVVPt72fJrnBBUEHf3iJQ?pwd=oklw 
提取码:oklw

通过ADC转换完成标志触发(硬件触发)DMA方式实现外设到内存的DMA传输

核心代码:
#include "main.h" 
#include "delay.h" 
#include "gpio.h" 
#include "cw32l052_lcd.h" 
#include "cw32l052_adc.h" 
#include "cw32l052_dma.h" 
 
#define NUM0 0x070d  //段式LCD数字段码 
#define NUM1 0x0600 
#define NUM2 0x030e 
#define NUM3 0x070a 
#define NUM4 0x0603 
#define NUM5 0x050b 
#define NUM6 0x050f 
#define NUM7 0x0700 
#define NUM8 0x070f 
#define NUM9 0x070b  

void ADC_Configuration(void);      //ADC配置函数 
void DMA_Configuration(void);      //DMA配置函数 
void LCD_Configuration(void);      //LCD配置函数 
void LCD_Proc(uint16_t dispdata);  //LCD子程序函数  

/* 
    **功能说明:
    **ADC采集数据触发DMA,将采集到的数据(1.2V内核电压基准源)存储在内存value中,并显示在LCD屏上 
*/ 

int main(void) 
{   
    LED_Init();   
    LCD_Configuration();   
    ADC_Configuration();   
    DMA_Configuration();   
    while (1)   
    {     
        LCD_Proc(value);  //显示采集到的ADC     
        PA15_TOG();     
        Delay_ms(500);   
    } 
}  

void ADC_Configuration(void) 
{   
    ADC_InitTypeDef   ADC_InitStruct = {0};    
    
    __RCC_ADC_CLK_ENABLE();   
    __RCC_GPIOA_CLK_ENABLE();    
    
    PA00_ANALOG_ENABLE(); //PA00 (AIN0)    
    ADC_InitStruct.ADC_OpMode = ADC_SingleChOneMode;   //单通道单次转换模式   
    ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div128;       //PCLK   A
    DC_InitStruct.ADC_SampleTime = ADC_SampTime5Clk; //5个ADC时钟周期   
    ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA;       //VDDA参考电压(3.3V)   
    ADC_InitStruct.ADC_InBufEn = ADC_BufEnable;       //开启跟随器   
    ADC_InitStruct.ADC_TsEn = ADC_TsDisable;           //内置温度传感器失能   
    ADC_InitStruct.ADC_DMASOCEn = ADC_DMASOCEnable;   //ADC转换完成触发DMA传输   
    ADC_InitStruct.ADC_Align = ADC_AlignRight;         //ADC转换结果右对齐   
    ADC_InitStruct.ADC_AccEn = ADC_AccDisable;         //转换结果累加不使能   
    ADC_Init(&ADC_InitStruct);                        //初始化ADC配置      CW_ADC->CR1_f.DISCARD = FALSE;                    //ADC转换结果保存策略配置:新数据覆盖未被读取的旧数据          CW_ADC->CR1_f.CHMUX = ADC_Vref1P2Input;           //待转换通道配置:1.2V内核电压基准源    
    
    ADC_ClearITPendingBit(ADC_IT_EOC);   
    ADC_ITConfig(ADC_IT_EOC, ENABLE);   
    ADC_EnableNvic(ADC_INT_PRIORITY);    
    ADC_Enable();   
    ADC_SoftwareStartConvCmd(ENABLE);                  //开始转换 
}  

void ADC_IRQHandler(void) 
{     
    /* USER CODE BEGIN */   
    if(ADC_GetITStatus(ADC_IT_EOC) != RESET)   
    {       
        ADC_ClearITPendingBit(ADC_IT_EOC);       
        ADC_SoftwareStartConvCmd(ENABLE);              //开始转换   
    }     
    /* USER CODE END */ 
}  

void NVIC_Configuration(void) 
{     
    __disable_irq();      
    NVIC_ClearPendingIRQ(DMACH1_IRQn);      
    NVIC_EnableIRQ(DMACH1_IRQn);      
    __enable_irq(); 
}  

void DMA_Configuration(void) 
{   
    DMA_InitTypeDef DMA_InitStruct = {0};    
    
    __RCC_DMA_CLK_ENABLE();    
    
    DMA_StructInit(&DMA_InitStruct);      
    DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;     //BLOCK模式       
    DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_32BIT; //数据宽度32bit   
    DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix; //源地址固定   
    DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Fix;  //目标地址固定   
    DMA_InitStruct.DMA_TransferCnt = 1;  //数据块数量1   
    DMA_InitStruct.DMA_SrcAddress = (uint32_t)&(CW_ADC->RESULT0);  //数据源地址 (外设)   
    DMA_InitStruct.DMA_DstAddress = (uint32_t)&value;   //传输目标地址   (内存)   
    DMA_InitStruct.TrigMode = DMA_HardTrig;  //硬件触发DMA传输   
    DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_SINGLETRANSCOM;  //硬件触发源:ADC单次转换完成标志   
    DMA_Init(CW_DMACHANNEL1,&DMA_InitStruct); //DMA通道1   
    DMA_ClearITPendingBit(DMA_IT_ALL);  //清除DMA中断标志位   
    DMA_ITConfig(CW_DMACHANNEL1, DMA_IT_TC, ENABLE);   //使能DMA通道1中断   
    NVIC_Configuration();   
    DMA_Cmd(CW_DMACHANNEL1, ENABLE); //启动DMA通道1进行传输 
}  

void DMACH1_IRQHandler(void) 
{    
     /* USER CODE BEGIN */   
     if( DMA_GetITStatus(DMA_IT_TC1) )  //DMA通道1传输完成标志     
     {         
         DMA_ClearITPendingBit(DMA_IT_TC1);          
         CW_DMACHANNEL1->CNT = 0x10001;     //REPEAT写1,传输数量为1                                
         DMA_Cmd(CW_DMACHANNEL1, ENABLE);      
     }     
     /* USER CODE END */ 
}

演示:ADC转换结果为1580左右,换算成电压:1580/4096*3.3=1.27V

7.png

从内存到内存

BD网盘链接:

https://pan.baidu.com/s/1ScCk-UqHBjJA5PxwSqMhkg?pwd=4701 
提取码:4701

通过软件触发DMA方式实现内存(FLASH)到内存(SRAM)的DMA传输

核心代码:
//单片机头文件
#include "main.h"
#include "cw32l052_lcd.h"
#include "cw32l052_dma.h"//硬件外设
#include "delay.h"#include "gpio.h"//C库
#include <string.h>

#define NUM0 0x070d  //段式LCD数字段码
#define NUM1 0x0600#define NUM2 0x030e
#define NUM3 0x070a#define NUM4 0x0603
#define NUM5 0x050b#define NUM6 0x050f
#define NUM7 0x0700#define NUM8 0x070f
#define NUM9 0x070b

#define DATASIZE 10

uint16_t const srcBuf[DATASIZE] =   //源内存(FLASH)数据
{  
    9999,8888,7777,6666,5555,  
    4444,3333,2222,1111,0
};

uint16_t dstBuf[DATASIZE]={0};      //目标内存(SRAM)数据

void DMA_Configuration(void);      //DMA配置函数
void LCD_Configuration(void);      //LCD配置函数
void LCD_Proc(uint16_t dispdata);  //LCD子程序函数

uint32_t value=0;  //ADC数值

/*
    **功能说明:
    **将srcBuf数组中的数据通过DMA传送到dstBuf数组中,
    **srcBuf数组中的数据通过const关键词存储到FLASH中,
    **dstBuf数组存储在SRAM程序运行过程中
    **传输完成后比较两数组内容,相同则打开LED1和LED1,LCD上循环显示dstBuf数据;
    **不同则进入死循环,两指示灯闪烁
*/

int main(void)
{  
    LED_Init();  
    LCD_Configuration();  
    DMA_Configuration();  
    DMA_SWTrigCmd(CW_DMACHANNEL1);   //使能通道1软件触发  
    while(DMA_GetFlagStatus(CW_DMACHANNEL1)!=DMA_CHANNEL_STATUS_TRANSCOMPLETE); //等待传输完成  
    if(memcmp(srcBuf,dstBuf,DATASIZE)==0)  //如果srcBuf和dstBuf相同  
    {      
        LED1_ON(); //指示灯      
        LED2_ON();      
        for(int i=0;i<10;i++)  //LCD屏显示dstBuf数据      
        {        
            LCD_Proc(dstBuf[i]);        
            Delay_ms(500);      
        }  
    }  
    else  //如果不相同  
    {    
        while(1)  //进入while死循环    
        {      
            PA15_TOG();  //指示灯      
            PC10_TOG();      
            Delay_ms(500);    
        }  
    }  
     
    while (1)  
    {   
    }
}

void DMA_Configuration(void)
{  
    DMA_InitTypeDef DMA_InitStruct = {0};   
    
    __RCC_DMA_CLK_ENABLE();   
    
    DMA_StructInit(&DMA_InitStruct);   
    DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;     //BLOCK模式  
    DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_16BIT; //数据宽度16bit  
    DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Increase; //源地址固定  
    DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Increase;  //目标地址递增  
    DMA_InitStruct.DMA_TransferCnt = DATASIZE;  //数据块数量  
    DMA_InitStruct.DMA_SrcAddress = (uint32_t)&srcBuf[0];  //数据源地址 (内存)  
    DMA_InitStruct.DMA_DstAddress = (uint32_t)&dstBuf[0];   //传输目标地址 (内存)  
    DMA_InitStruct.TrigMode = DMA_SWTrig;  //软件触发DMA传输
  
    DMA_Init(CW_DMACHANNEL1,&DMA_InitStruct); //DMA通道1  
    DMA_Cmd(CW_DMACHANNEL1, ENABLE); //启动DMA通道1进行传输
}

演示:LCD屏上显示通过DMA传输的dstBuf的数据

8.png

从外设到外设

BD网盘链接:

https://pan.baidu.com/s/1fSyMPAapft2a_Vy_d3F9vw?pwd=dw6x   
提取码:dw6x

通过硬件触发DMA方式实现外设(SPI)到外设(SPI)的DMA传输

核心代码:
/*单片机头文件*/
#include "main.h"
/*硬件驱动*/
#include "delay.h"
#include "gpio.h"
#include "cw32l052_dma.h"
#include "cw32l052_spi.h"
/*C库*/
#include <string.h>

//硬件连接//SPIY_SCK  (PA10) -- SPIX_SCK  (PB13)
//SPIY_MISO (PA11) -- SPIX_MISO (PB14)
//SPIY_MOSI (PA12) -- SPIX_MOSI (PB15)

//SPI2相关定义(Master)
#define    SPIX                     CW_SPI2               
#define   SPIX_GPIO          CW_GPIOB
#define    SPIX_SCK_PIN             GPIO_PIN_13
#define   SPIX_MISO_PIN            GPIO_PIN_14
#define    SPIX_MOSI_PIN            GPIO_PIN_15
#define    SPIX_AF_SCK              PB13_AFx_SPI2SCK()
#define    SPIX_AF_MISO             PB14_AFx_SPI2MISO()
#define    SPIX_AF_MOSI             PB15_AFx_SPI2MOSI()
#define    SPIX_RX_DMACHANNEL       CW_DMACHANNEL1
#define    SPIX_TX_DMACHANNEL       CW_DMACHANNEL2  
#define     SPIX_DMA_RXTRIGSOURCE    DMA_HardTrig_SPI2_RXBufferNE
#define     SPIX_DMA_TXTRIGSOURCE    DMA_HardTrig_SPI2_TXBufferE

//SPI1相关定义(Slave)
#define    SPIY                     CW_SPI1
#define   SPIY_GPIO          CW_GPIOA
#define    SPIY_SCK_PIN             GPIO_PIN_10
#define    SPIY_MISO_PIN            GPIO_PIN_11
#define    SPIY_MOSI_PIN            GPIO_PIN_12
#define    SPIY_AF_SCK              PA10_AFx_SPI1SCK()
#define    SPIY_AF_MISO             PA11_AFx_SPI1MISO()
#define    SPIY_AF_MOSI             PA12_AFx_SPI1MOSI()

//数组长度
#define   BUFFERSIZE                 ARRAY_SZ(TxBuffer1)

//发送内容1
uint8_t TxBuffer1[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,                       
                       0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,                       
                       0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,                       
                       0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,                       
                       0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23                      
                      };

//发送内容2
uint8_t TxBuffer2[] = {0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,                       
                       0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,                       
                       0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,                       
                       0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C,                       
                       0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83                      
                      }; 
                      
uint8_t RxBuffer1[BUFFERSIZE];  //接收数组1
uint8_t RxBuffer2[BUFFERSIZE];  //接收数组2
uint8_t TxCounter = 0;  //发送计数
uint8_t RxCounter = 0;  //接收计数 

uint8_t TransferStatus1 = 1;  //DMA传输状态标志1
uint8_t TransferStatus2 = 1;  //DMA传输状态标志2 

void DMA_Configuration(void);      //DMA配置函数
void SPI_Configuration(void);      //SPI配置函数
void SPI_GPIO_Configuration(void); //SPI相关GPIO口配置 

/*
    **功能说明:
    **主机SPIY发送TxBuffer1中的数据,从机SPIX通过DMA接收数据并存储到RxBuffer1
  **主机SPIY发送无效数据,启动SPI通信,同时SPIX从机通过DMA发送TxBuffer2中的数据,SIPY接收数据并存储到RxBuffer2
  **单独比较TxBuffer1与RxBuffer1、TxBuffer2与RxBuffer2中的内容,比较结果通过LED灯指示
*/

int main(void)
{  
  LED_Init();               //初始化LED指示灯  
  SPI_GPIO_Configuration(); //配置PI相关GPIO口  
  DMA_Configuration();      //配置DMA传输  
  SPI_Configuration();      //配置SPI传输  
  SPI_DMACmd(SPIX, SPI_DMAReq_Rx, ENABLE); //使能SPIX DMA RX  
  SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Reset);//位选CS选中从机SPIX,起始信号  
  while(TxCounter < BUFFERSIZE)  
  {    
    while(SPI_GetFlagStatus(SPIY,SPI_FLAG_TXE) == RESET);//等待发送缓冲空(为空后硬件自动置1)    
    SPI_SendData(SPIY,TxBuffer1[TxCounter++]); //发送TxBuffer1中的数据,通过数据寄存器DR把数据填充到发送缓冲区中  
  }  
    
    while(DMA_GetFlagStatus(SPIX_RX_DMACHANNEL) != DMA_CHANNEL_STATUS_TRANSCOMPLETE);//等待DMA接收完成
        
    SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Set); //释放从机SPIX,结束信号  
    TransferStatus1 = memcmp(TxBuffer1, RxBuffer1, BUFFERSIZE); //对比两数组数据  
    if(TransferStatus1==0)  //如果数据相同  
    {    
        LED1_ON();  //LED1指示  
    }  
    else  
    {    
       LED1_OFF();  
    }   
        
    TxCounter = 0;  
    SPI_ReceiveData(SPIY);//读DR以清除RXNE(接收非空)标志位  
    SPI_DMACmd(SPIX, SPI_DMAReq_Rx, DISABLE);//失能SPIX DMA RX  
    SPI_FlushSendBuff(SPIX);//清空发送缓冲区和移位寄存器  
    SPI_DMACmd(SPIX, SPI_DMAReq_Tx, ENABLE);//使能SPIX DMA TX  
    SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Reset);  
        
    while(TxCounter < BUFFERSIZE)  
    {          
        while(SPI_GetFlagStatus(SPIY, SPI_FLAG_TXE) == RESET){;} //主机发送数据以启动SPI通信    
        SPI_SendData(SPIY, TxBuffer1[TxCounter++]);     
           
        while(SPI_GetFlagStatus(SPIY, SPI_FLAG_RXNE) == RESET){;}      
        RxBuffer2[RxCounter++] = SPI_ReceiveData(SPIY);  //获取接收缓冲区中的内容    
    }  
        
    while(SPI_GetFlagStatus(SPIY,SPI_FLAG_BUSY) == SET); //检查数据是否已经全部通过SPI发送完毕    
    SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Set); //释放  
    TransferStatus2 = memcmp(TxBuffer2, RxBuffer2, BUFFERSIZE);  //检查  
    if(TransferStatus2 == 0)  
    {    
        LED2_ON();  
    }  
    else  
    {    
        LED2_OFF();  
    }  
    while (1)  
    {   
    }
}

void SPI_GPIO_Configuration(void)
{    
    GPIO_InitTypeDef GPIO_InitStructure = {0};     
    //打开GPIO时钟    
    __RCC_GPIOA_CLK_ENABLE();    
    __RCC_GPIOB_CLK_ENABLE();
    
  //SPI SCK MOSI MISO 复用    
  SPIY_AF_SCK;    
  SPIY_AF_MISO;    
  SPIY_AF_MOSI;    
  SPIX_AF_SCK;    
  SPIX_AF_MISO;    
  SPIX_AF_MOSI;
  
  //推挽输出    
  GPIO_InitStructure.Pins = SPIY_SCK_PIN | SPIY_MOSI_PIN;    
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;    
  GPIO_Init(SPIY_GPIO, &GPIO_InitStructure);
   
   GPIO_InitStructure.Pins = SPIX_MISO_PIN;    
  GPIO_Init(SPIX_GPIO, &GPIO_InitStructure);
  
  //浮空输入    
  GPIO_InitStructure.Pins = SPIX_SCK_PIN | SPIX_MOSI_PIN;    
  GPIO_InitStructure.Mode = GPIO_MODE_INPUT;    
  GPIO_Init(SPIX_GPIO, &GPIO_InitStructure);
  
  GPIO_InitStructure.Pins = SPIY_MISO_PIN;    
  GPIO_Init(SPIY_GPIO, &GPIO_InitStructure);}
void SPI_Configuration(void)
{  
    SPI_InitTypeDef SPI_InitStructure = {0};   
    
    __RCC_SPI1_CLK_ENABLE();  
    __RCC_SPI2_CLK_ENABLE();   
    
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线全双工模式  
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主机模式  
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据位宽8bit  
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;  //时钟极性  
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//时钟相位,奇数边缘采样  
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //配置NSS引脚(片选信号线)的使用模式,软件控制  
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //波特率:PCLK8分频  
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //MSB先行模式  
    SPI_InitStructure.SPI_Speed = SPI_Speed_Low; //低速  
    SPI_Init(SPIY,&SPI_InitStructure);   
    
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; //从机模式  
    SPI_Init(SPIX,&SPI_InitStructure); 
     
    SPI_Cmd(SPIX,ENABLE);  
    SPI_Cmd(SPIY,ENABLE);
}

void DMA_Configuration(void)
{  
    DMA_InitTypeDef DMA_InitStructure = {0};  
     
    __RCC_DMA_CLK_ENABLE();   //DMA TX 
     
    DMA_InitStructure.DMA_Mode = DMA_MODE_BLOCK; //BLOCK模式  
    DMA_InitStructure.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT;  
    DMA_InitStructure.DMA_SrcInc = DMA_SrcAddress_Increase;  
    DMA_InitStructure.DMA_DstInc = DMA_DstAddress_Fix;  
    DMA_InitStructure.TrigMode = DMA_HardTrig;  
    DMA_InitStructure.HardTrigSource = SPIX_DMA_TXTRIGSOURCE;  
    DMA_InitStructure.DMA_TransferCnt = BUFFERSIZE;  
    DMA_InitStructure.DMA_SrcAddress = (uint32_t)&TxBuffer2[0];  
    DMA_InitStructure.DMA_DstAddress = (uint32_t)&SPIX->DR; //数据寄存器  
    DMA_Init(SPIX_TX_DMACHANNEL,&DMA_InitStructure);  
    DMA_Cmd(SPIX_TX_DMACHANNEL,ENABLE);   
    
    //DMA RX      
    DMA_InitStructure.DMA_Mode = DMA_MODE_BLOCK;  
    DMA_InitStructure.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT;  
    DMA_InitStructure.DMA_SrcInc = DMA_SrcAddress_Fix;  
    DMA_InitStructure.DMA_DstInc = DMA_DstAddress_Increase;  
    DMA_InitStructure.TrigMode = DMA_HardTrig;  
    DMA_InitStructure.HardTrigSource = SPIX_DMA_RXTRIGSOURCE;  
    DMA_InitStructure.DMA_TransferCnt = BUFFERSIZE;  
    DMA_InitStructure.DMA_SrcAddress = (uint32_t)&SPIX->DR;  
    DMA_InitStructure.DMA_DstAddress = (uint32_t)&RxBuffer1[0];  
    DMA_Init(SPIX_RX_DMACHANNEL,&DMA_InitStructure);  
    DMA_Cmd(SPIX_RX_DMACHANNEL,ENABLE);
}

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 9

01、问题背景

客户需要使用 MCU 输出正弦波,但受限于 MCU DAC 数量不足,建议尝试使用 PWM加滤波方式产生正弦波。同时要求正弦波与固定电平交替输出。因此可用一个 TIM 输出PWM,同时用另一个 TIM 来定时切换输出正弦波或固定电平。 

使用 TIM 输出 PWM 产生正弦波形时,需要结合 GPDMA 来实现。在 STM32U5 系列中,GPDMA 共有 16 个独立通道,其中 12-15 通道还具有 2D addressing/ repeat 功能。因此也可以使用一个 TIM 加 GPDMA 的一个 2D 通道实现 PWM 波形切换功能。

02、产生PWM

本文按以下配置产生 PWM,在 U575 NUCLEO 板测试: 

(1)MCU 主频:100MHz

(2)PWM 频率 2MHz(周期 500ns),脉宽可调范围 0~50 个计数时钟, 

(3)每个正弦波周期(10us)对应 20 个 PWM 脉冲,各 PWM 脉宽用计数时钟表示分别为:25, 33, 40, 45, 49, 50, 49, 45, 40, 33, 25, 17, 10, 5 , 1 , 0 , 1 , 5 , 10, 17

(4)将步骤 3 中的正弦波重复 1000 次,对应 10ms 的连续正弦波形

2.1. STM32CubeMX 生成测试工程

2.1.1. TIM1 CH1 PWM 配置

1.png

图1. TIM1 CH1 PWM

2.1.2. GPDMA CH12 配置

选用 GPDMA 通道 12,并配置为循环模式:

2.png

图2. GPDMA CH12

2.1.3. GPDMA Linked List 配置

创建 Linked List Queue,并配置为搭配 GPDMA 2D 功能通道使用。创建两个节点,TN1, TN2,并使用循环模式,指定首个循环节点为 TN1。

3.png

图3. Linked List

TN1 节点配置,由此节点结合 TIM 来产生 PWM,并滤波成正弦信号: 

(1)TIM1 更新事件作为 DMA 请求 

(2)使能 2D 功能,一个 block 传输完成后,回退到数组起点,重新传输 

(3)使能 Repeat 功能,重复 block 传输 1000 次

首先使能了 TrustZone 架构,然后将 LPGPIO 映射到了非安全区,并且配置了 DMA 链表功能,使用 LPTimer 作为触发,自动地修改 LPGPIO 的寄存器,从而达到在低功耗模式下,GPIO自动切换的功能。但遇到了 LPDMA 的配置问题,并且程序无法跳转到 Non-Secure 工程。

4.png

图4. Linked List Node1

TN2 节点配置,与 TN1 节点类似,用于切换到第二组数据产生第二种波形

5.png

图5. Linked List Node2

2.2. 测试代码

6.png

GPDMA Linked List 模式执行流程

7.png

图6. Linked List 执行过程及期望输

03、PWM滤波输出

在 U575 NUCLEO 板上测试,结果如下:

8.png

图7. 实际输出

从实测结果来看,滤波后的正弦波频率,波形持续时长都符合预期。另外,与通过额外 TIM 计时来切换 PWM 输出的方式相比,使用 Linked List repeat 这种方式,正弦波与固定电平输出之间切换更平滑。

9.png

图8. 额外 TIM 计时来切换 PWM 输出

04、小结

通过使用 GPDMA Linked List 模式,使用 2D addressing repeat 功能,能方便实现这种多种波形切换的应用场景。如 Node1 与 Node2 使用不同的数据长度和重复次数,则可得到不同时长的两种波形;通过增加更多 Node,则可得到多种不同波形。

来源:STM32单片机

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 24

01、前言

有客户反馈,使用STM32F4的TIM2结合DMA,产生的PWM波形不符合预期,但是相同的配置使用在TIM3上,得到的PWM波形就是符合预期的。其代码和配置都是从F1移植过来的,在F1上使用TIM2是没有问题的,对于F4的TIM2发生的问题,客户一直没有找到根本原因。

02、实验

根据客户的反馈,我们进行了实验。

硬件:STM32F401RE-NUCLEO

在STM32CubeMX中,将TIM2和TIM3所有参数均做相同的配置,其中配置DMA两端均为halfword长度。

1.png

2.png

生成代码,并定义两个数组如下图所示:

3.png

在主函数中开启Timer。

4.png

我们可以发现,实验结果如客户反馈的,TIM2输出的PWM是不正确的,TIM3输出的PWM是正确的。

5.png

03、分析

我们的实验中,TIM2和TIM3的配置是完全一样的,即使传输相同的数据,得到的PWM波形也是不同的。为此我们比较了TIM2和TIM3的硬件属性,可以很容易查看出,TIM2的计数器是32bit的,而TIM3的计数器是16bit的。

我想我们已经知道答案了,TIM2的计数器是32bit的,但是我们配置的DMA是halfword长度,这在AHB总线上解析数据时产生了非预期的结果。在调试界面我们也能看到,当问题发生时,TIM2的CCR1竟然比ARR的值要大,或者出现异常值,所以出现异常波形。

6.png

根本原因在于,对于大部分STM32系列,主设备基于AHB外设进行寻址是不支持byte/half-word传输的,总线会强制将数据转化为32bit传送到总线上,这就是为什么我们看到CCR1的高半字和低半字的值是相同的原因。

当我们将TIM2的DMA外设端修改为word长度,并将内存数组定义为32bit,再次实验,可以发现PWM的波形就是正常的了:

7.png

8.png

9.png

04、小结

因为F103上没有32bit计数器的Timer,所以客户在F103上并没有出现类似的问题。在使用DMA访问经过AHB转APB的桥接外设时,我们要注意DMA对外设的访问宽度配置问题。

来源:STM32单片机

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 203

直接存储器访问(DMA)控制器,可以在内存和/或外设之间传输数据,而不需要CPU参与每次传输。合理利用DMA控制器,可以减轻CPU的负担。本文通过介绍DMA结构与工作原理,以及两种模式(兵乓模式与多数据包缓冲传输模式),来看看使用DMA如何提高MCU效率。

DMA结构与工作原理

先进的DMA控制器,如STMicroelectronicsSTM32F4系列中包含的控制器,可以通过灵活的数据流分配和传输管理功能进一步减轻CPU的负担。

如图左侧所示,来自8个不同的通道DMA请求,并到仲裁器上,从而建立优先级(编号较低的输入通道,具有较高的优先级)。然后激活最高优先级的传输,传输到图中右侧的两个AHB 主设备(存储器端口和外设接口),提高了外设到存储器传输的效率。这可能是DMA在基于CPU的设计中最常见的情况。

1.jpg

图 1.   STM32F4系列DMA控制器(图片来源于STMicroelectronics)

为每个路径分配单独的FIFO,如图1中间所示,允许针对每个外设接口的特性调整FIFO特性。例如,FIFO的阈值级别(请求传输的深度)可以单独设置为FIFO大小的¼,½或¾。这允许低速通道等待,直到FIFO几乎满了才进行传输,以最小化开销。更快的通道会更早地启动传输,可能只有一半大小,以避免FIFO溢出。

我们来通过一个实例,来看看DMA怎么工作的。

实例:“使用 STM32 来控制 NeoPixels LED”

硬件部分采用 STM32 开发板,与 NeoPixel LED灯带矩阵等相连接。

2.png

RGB NeoPixels实际上是WS2812智能控制LED。下面是WS2812 LED的3字节数据协议的结构,分别代表绿红蓝三个信息。

3.jpg

图 2.  WS2812 LED的3字节数据协议的结构

使用计时器来PWM控制波形,然后配置DMA使CPU高效并且易于实施。

4.jpg
图 3.   WS2812 LED的0和1位的计时图

在软件中,配置DMA, 选择了“TIM2_CH3/UP”, 将方向改为“内存到外设”, 同时,将优先级改为“非常高”,最后保存.ioc 文件,以生成项目代码。

5.jpg
图 4.   配置DMA流,以便有效更新PWM信号的占空比

更多内容请看下面文章:使用 STM32 来控制 NeoPixels

DMA的两种模式

合理使用两种DMA模式(兵乓模式与多数据包缓冲传输模式),可以帮助提高MCU效率。

USB外设是一个很好的外设示例,早期的USB实现的最大吞吐量只有1.5 Mb/秒。随着更高性能的标准版本的出现。比如要接近12 Mbit/s全速USB标准的理论最大值。我们来看看,数据传输方面DMA如何帮助提高MCU效率!

我们以MicrochipATXMEGA16D4-MH举例。

01、兵乓模式

之前通常使用单个存储器缓冲区进行外设数据传输。如果数据缓冲区已满,MCU将响应NAK(否定确认)消息。接收到NAK后,主机将等待并稍后重试传输。它将继续重试,直到MCU能够成功接收数据。

ATXMEGA16D4-MH使用乒乓模式来消除这个问题。乒乓模式使用两个存储单元(memory banks)进行数据传输。当一个存储单元满时,主机可以将数据传输到另一个存储单元。在两个存储单元之间交替传输可以避免复审,并提高整体数据带宽。

6.jpg
图 5.  乒乓模式提高了效率(图片来源于Microchip)

此外,如上图所示,以乒乓模式还使MCU有更多时间来处理数据。如图所示,没有乒乓,CPU只能处理传输之间的数据。使用乒乓模式,CPU可以在传输周期的一部分时间内处理数据,并降低NAK被要求“赶上”数据处理要求的可能性。

02、多数据包缓冲传输模式

另一个很有用的模式,可以让MCU的数据传输更高效。这个特性叫做“多数据包缓冲传输模式”。如果你要通过USB端口传送的数据包,超过了全速USB的BULK传输模式所允许的最大值(64字节),那么就可以用上这个模式。以前,你需要在主机上把数据包分成小块,然后在接收端把它们合并,这会增加中央处理器(CPU)的负担。不过现在,多数据包缓冲功能加入了USB设备,它会在数据包超过USB标准大小时自动帮你分割和合并数据。重要的是,这个模式还能减少中断的次数,因为只有在整个传输结束后才需要中断CPU。这意味着,CPU可以处理其他任务,或者进入休眠模式,直到整个传输完成并且准备好处理。

总结

合理利用DMA控制器,可以减轻减轻CPU的负担,事半功倍。结合“乒乓缓冲”和“多传输模式”,你可以把传输的带宽从基准BULK传输模式的5.6 Mb/s提升到8.7 Mbits/s,这是一个不小的提升。更重要的是,在使用这两个功能的情况下,CPU的负担从基准的46%降低到只有9%。这两个功能的结合,不仅在性能上有所改进,而且还能节省能源。

来源:得捷电子DigiKey(作者:Alan Yang)

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 120

在上一讲单通道ADC电压采集的基础上,本节主要介绍CKS32F4xx系列产品基于DMA传输的ADC多通道电压采集转换实现。

DMA传输在ADC中的应用

DMA是直接存储器存取,通常在使用ADC时,需要通过MCU内核不停的读取数据,如果使用DMA,那么读取的过程会绕过MCU,减轻MCU内核的处理压力,这样有利于资源的充分利用,提高ADC数据的处理效率。由于ADC规则通道组只有一个数据寄存器中,当转换多个通道时,使用DMA还可以避免丢失已经存储在ADC_DR寄存器中的数据。在使能DMA模式的情况下,每完成规则通道组中的一个通道转换后,都会生成一个DMA请求,便可将转换的数据从ADC_DR寄存器传输到用指定的目标内存位置。这样取代单通道实验使用中断服务的读取方法,可以实现多通道ADC应用中高速高效的采集。

软件设计要点

跟单通道例程一样,编写两个ADC驱动文件,bsp_adc.h和bsp_adc.c,用来存放ADC所用IO引脚的初始化函数以及ADC和DMA相关配置函数,主要流程为:

(1)初始化配置ADC目标引脚为模拟输入模式;

(2)使能ADC时钟和DMA时钟;

(3)配置DMA从ADC数据寄存器传输数据到指定的存储区;

(4)配置通用ADC为独立模式;

(5)设置ADC为12位分辨率,启动扫描,连续转换,不需要外部触发;

(6)设置ADC转换通道顺序及采样时间;

(7)使能DMA请求,DMA在AD转换完自动传输数据到指定的存储区;

(8)启动ADC模块;

(9)软件使能触发ADC转换。

这里需要注意的是,在使用ADC+DMA功能时,如果在启动ADC转换之后使能DMA,ADC采样数据可能会出现异常。因此建议先配置ADC及DMA相关参数,最后启动ADC转换。

代码实现

受篇幅限制,这里只介绍核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本课程配套的例程。相关核心代码实现如下:

(1)ADC宏定义

#define TEMP_NOFCHANEL      3
/*=====================通道1 IO======================*/
// PB0 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT1    GPIOB
#define TEMP_ADC_GPIO_PIN1     GPIO_Pin_0
#define TEMP_ADC_GPIO_CLK1     RCC_AHB1Periph_GPIOB
#define TEMP_ADC_CHANNEL1      ADC_Channel_8
/*=====================通道2 IO ======================*/
// PB1 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT2    GPIOB
#define TEMP_ADC_GPIO_PIN2     GPIO_Pin_1
#define TEMP_ADC_GPIO_CLK2     RCC_AHB1Periph_GPIOB
#define TEMP_ADC_CHANNEL2      ADC_Channel_9
/*=====================通道3 IO ======================*/
// PA6 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT3    GPIOA
#define TEMP_ADC_GPIO_PIN3     GPIO_Pin_6
#define TEMP_ADC_GPIO_CLK3     RCC_AHB1Periph_GPIOA
#define TEMP_ADC_CHANNEL3     ADC_Channel_6
// ADC 序号宏定义
#define TEMP_ADC              ADC1
#define TEMP_ADC_CLK          RCC_APB2Periph_ADC1
// ADC DR寄存器宏定义,ADC转换后的数字值则存放在这里
#define TEMP_ADC_DR_ADDR    ((u32)ADC1+0x4c)
// ADC DMA 通道宏定义,使用DMA传输
#define TEMP_ADC_DMA_CLK      RCC_AHB1Periph_DMA2
#define TEMP_ADC_DMA_CHANNEL  DMA_Channel_0
#define TEMP_ADC_DMA_STREAM   DMA2_Stream0

定义多个通道进行多通道ADC实验,并且定义DMA相关配置。

(2)ADC GPIO初始化

static void Temp_ADC_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    /*=====================通道1======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK1,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN1; // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;     //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT1, &GPIO_InitStructure);
    /*=====================通道2======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK2,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN2; // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;     //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT2, &GPIO_InitStructure);
    /*=====================通道3=======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK3,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN3;  // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;   //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT3, &GPIO_InitStructure);
}

使用到GPIO时候都必须开启对应的GPIO时钟,GPIO用于AD转换功能必须配置为模拟输入模式。

(3)配置ADC工作模式

static void Temp_ADC_Mode_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;     
    // --------------DMA Init 结构体参数初始化-------------     
    // ADC1使用DMA2,数据流0,通道0,
    RCC_AHB1PeriphClockCmd(TEMP_ADC_DMA_CLK, ENABLE);   // 开启DMA时钟
    DMA_InitStructure.DMA_PeripheralBaseAddr = TEMP_ADC_DR_ADDR; // 外设基址为:ADC 数据寄存器地址
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)ADC_ConvertedValue;  // AD值存储地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;   // 数据传输方向为外设到存储器
    DMA_InitStructure.DMA_BufferSize = TEMP_NOFCHANEL; // 缓冲区大小,指一次传输的数据量
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;// 外设寄存器只有一个,地址不递增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   // 存储器地址固定
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  // 外设数据大小为半字
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;   // 存储器数据大小也为半字
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环传输模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;// DMA传输通道优先级为高
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;    // 禁止DMA FIFO ,使用直连模式
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; // FIFO大小,FIFO禁止时不用配置
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
    DMA_InitStructure.DMA_Channel = TEMP_ADC_DMA_CHANNEL; // 选择 DMA 通道,通道存在于流中
    DMA_Init(TEMP_ADC_DMA_STREAM, &DMA_InitStructure);//初始化DMA流,
    DMA_Cmd(TEMP_ADC_DMA_STREAM, ENABLE); // 使能DMA流

    RCC_APB2PeriphClockCmd(TEMP_ADC_CLK , ENABLE);// 开启ADC时钟     
    // -------------ADC Common 结构体 参数初始化----------------
    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;// 独立ADC模式
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;// 时钟为fpclk x分频
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // 禁止DMA直接访问模式     
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;  // 采样时间间隔
    ADC_CommonInit(&ADC_CommonInitStructure);     
    // -------------------ADC Init 结构体 参数初始化--------------------------
    ADC_StructInit(&ADC_InitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;// ADC 分辨率
    ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式,多通道采集需要
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // 连续转换
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止外部边沿触发
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;  //外部触发通道,使用软件触发时此值随便赋值即可
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐ADC_InitStructure.ADC_NbrOfConversion = TEMP_NOFCHANEL;      //转换通道3个                             
    ADC_Init(TEMP_ADC, &ADC_InitStructure);

    
  // 配置 ADC 通道转换顺序和采样时间周期
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL1, 1, ADC_SampleTime_3Cycles);
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL2, 2, ADC_SampleTime_3Cycles);
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL3, 3, ADC_SampleTime_3Cycles);

    ADC_DMARequestAfterLastTransferCmd(TEMP_ADC, ENABLE); // 使能DMA请求      
    ADC_DMACmd(TEMP_ADC, ENABLE);// 使能ADC DMA
    ADC_Cmd(TEMP_ADC, ENABLE);  // 使能ADC
    ADC_SoftwareStartConv(TEMP_ADC);//开始ADC转换,软件触发
}

首先,使用DMA_InitTypeDef定义了DMA初始化类型变量,另外使用ADC_InitTypeDef和ADC_CommonInitTypeDef结构体分别定义一个ADC初始化和ADC通用类型变量。

调用RCC_APB2PeriphClockCmd()开启ADC时钟以及RCC_AHB1PeriphClockCmd()开启DMA时钟。

对DMA进行必要的配置。首先设置外设基地址就是ADC的规则数据寄存器地址;存储器的地址就是指定的数据存储区空间,ADC_ConvertedValue是我们定义的一个全局数组名,它是一个无符号16位含有3个元素的整数数组;ADC规则转换对应只有一个数据寄存器,所以地址不能递增,而定义的存储区是专门用来存放不同通道数据的,所以需要自动地址递增。ADC的规则数据寄存器只有低16位有效,实际存放的数据只有12位而已,所以设置数据大小为半字大小。ADC配置为连续转换模式,DMA也设置为循环传输模式。设置好DMA相关参数后就使能DMA的ADC通道。

接下来使用ADC_CommonInitTypeDef结构体变量ADC_CommonInitStructure来配置ADC为独立模式、分频系数4、20个周期的采样延迟,并调用ADC_CommonInit函数完成ADC通用工作环境配置。

使用ADC_InitTypeDef结构体变量ADC_InitStructure来配置ADC1为12位分辨率、使能扫描模式、启动连续转换、使用内部软件触发无需外部触发事件、使用右对齐数据格式、转换通道为3,并调用ADC_Init函数完成ADC1工作环境配置。

ADC_RegularChannelConfifig函数用来绑定ADC通道转换顺序和采样时间。分别绑定3个ADC通道引脚并设置相应的转换顺序。

ADC_DMARequestAfterLastTransferCmd函数控制是否使能ADC的DMA请求,如果使能请求,并调用ADC_DMACmd函数使能DMA,则在ADC转换完成后就请求DMA实现数据传输。ADC_Cmd函数控制ADC转换启动和停止。

最后使用软件触发调用ADC_SoftwareStartConvCmd函数进行使能配置。

(4)Main程序

/**  主函数  */
int main(void)
{
    Debug_USART_Config();
    Temp_Init();
    while (1)
    {
        ADC_ConvertedValueLocal[0] =(float) ADC_ConvertedValue[0]/4096*(float)3.3;
        ADC_ConvertedValueLocal[1] =(float) ADC_ConvertedValue[1]/4096*(float)3.3;
        ADC_ConvertedValueLocal[2] =(float) ADC_ConvertedValue[2]/4096*(float)3.3;
        printf("\r\n PB0 value = %f V \r\n",ADC_ConvertedValueLocal[0]);
        printf("\r\n PB1 value = %f V \r\n",ADC_ConvertedValueLocal[1]);
        printf("\r\n PA6 value = %f V \r\n",ADC_ConvertedValueLocal[2]);
        Delay(0xffffff);  
    }
}

主函数先调用Debug_USART_Config函数配置调试串口相关参数,接下来调用Temp_Init函数进行ADC初始化配置并启动ADC。配置了DMA数据传输,它会自动把ADC转换完成后数据保存到数组ADC_ConvertedValue内,我们只要使用数组就可以了。经过简单地计算就可以得到每个通道对应的实际电压。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 15

页面

订阅 RSS - DMA