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准确控制
下图是典型伺服三环的控制框图,其中编码器是控制的关键反馈,除了准确读取位置之外,还需要通过不同时刻读取位置计算转速。读取位置的时刻在伺服闭环中占非常关键的作用。
常见位置读取方式是在定时中断中读取位置,确保读取间隔时刻一致。但由于软件响应时间不确定,读取间隔很难保证一致。
本文通过DMA+链表方式实现HPM6200用串口与多摩川编码器定时通讯的方式。多摩川编码器通讯协议见下图:
例程通过PWM定期触发DMA,由DMA启动串口读取动作。DMA动作完成后利用链式传输,可以在处理器不介入的情况下,连续完成多个不同配置的传输任务。
程序中使用了PWM、DMA、UART三个模块
· PWM负责定时输出DMA触发信号;
· DMA接收触发信号后将采样命令写入UART的THR寄存器;
· UART负责收发位置传感器信息,其中接收建议使用硬件idle+FIFO模式。
先楫的UART有硬件收发使能控制,只需要DE设置为有效,485通讯可以自动实现收发方向控制,无须CPU干预。
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,读取多摩川传感器中全部信息。
DMA加速传输
HPM5300、HPM6800、HPM6E00引入了DMAv2,增加了无限循环、DMA传输一半中断,并修改了burst传输长度定义。
下文将列举一个buck-boost电源应用通过DMAv2更新PWM的例子,演示DMA加速传输的方法和效果。
例程选用了两路交错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)。
为了提供单片机内存数据传输效率,众多单片机都支持 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设置来启用和禁用。
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中的内存大小定义:
内存区域定义:
如下原型定义可用于将用户代码/数据放置到TCM中。在启动过程中,.itcm_data和.dtcm_data区域将通过闪存中存储的初始化代码进行数据初始化。.dtcm_bss区域已初始化为零。
FSP中的ITCM段定义:
FSP中的DTCM段定义:
TCM例子分析
下图是一个RA8x1 MCU实际使用TCM的例子,它使用了ITCM和DTCM。图片中的右边为RA8x1 MCU的系统地址空间。
具体分析过程为:
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这些代码或者变量的地址和刚刚分析的结果是相符的。
来源:瑞萨嵌入式小百科
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
Allen 先楫资深 FAE
8年产品研发经验,具有变频器、PLC等工业产品开发经验,也参与过汽车仪表、中控屏等车载产品的研发工作。在产品底层驱动、伺服驱动器、显示仪表等领域有着丰富开发经验。
在嵌入式产品应用开发中,经常需要MCU芯片产生任意的方波信号,从而驱动外设执行相应的操作。比如,驱动模拟量芯片、miniLED屏等。不同于PWM波这种占空比固定的信号,这些驱动信号往往是由等宽的高低电平任意排列的方波。
传统的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; }
串口打印结果如下:
测量GPIO的波形:
因为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};
使用逻辑分析仪测试波形如下:
用示波器测量波形如下图所示,信号质量表现优异。
来源:先楫半导体HPMicro
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
BD网盘链接:
https://pan.baidu.com/s/10WHNgB_cicTP1SbdcI4AkQ?pwd=l1et
提取码:l1et
概述
CW32L052支持DMA(Direct Memory Access),即直接内存访问,无需CPU干预,实现高速数据传输。数据的传输可以发生在:
• 外设和内存之间:例如ADC采集数据到内存,这种传输方式常见于需要将外设采集的数据快速传输到内存进行处理的应用。
• 内存和内存之间:例如在两个不同的数组之间传输数据,或者在不同的内存块之间进行数据拷贝。
• 外设和外设之间:例如从一个SPI主/从机传输数据到另一个SPI从/主机。
使用DMA能够有效减轻CPU的负担,特别是在大量数据需要高效传输的情况下,可以提高系统的整体性能。
DMA功能框图
特性
使用DMA,最核心的就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是连续传输等等。
4 条独立DMA通道:
4个DMA通道的优先级和通道号绑定,通道号越小优先级越高,通道号越大优先级越低。
4种传输模式:
硬件触发BULK传输模式、硬件触发BLOCK传输模式、软件触发BULK传输模式和软件触发BLOCK传输模式。
硬件 | |
BLOCK | |
软件 | BULK |
BLOCK |
适用于小数据块的传输,通常涉及大量的数据点,但每个数据点的大小较小。
与BLOCK模式不同,BULK模式下DMA会更频繁地启动新的传输,因为每个数据点通常被视为单独的传输单元,所以DMA控制器需要在每个数据点传输完成后需要重新配置或者启动DMA。在BULK传输模式下,传输过程不可被打断。
适用于大数据块的高速传输,通常用于需要连续传输大量数据的情况。BLOCK模式下,DMA会将数据分成较大的块,并在传输时以这些块为单位进行操作。DMA控制器在一次配置后,连续传输多个数据块,而无需额外的干预或重新配置。每传输完成1个数据块后就要进行一次传输优先级的仲裁,允许CPU或者更高优先级的DMA通道访问当前DMA通道所占用的外设。
• 硬件触发和软件触发:
要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求。部分外设支持硬件触发启动DMA传输,如FLASH存储器、UART串口、TIM定时器、ADC数模转换器等被配置为DMA通道的触发源时,
可以产生DMA请求(DMA request),硬件触发启动DMA传输,
而不支持硬件DMA的外设,只能配置为软件触发启动DMA传输。
虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
• DMA中断
DMA通道在传输工程中可产生2个中断标志:传输错误中断标志和传输完成中断标志
不同 DMA 通道的中断各自独立,通过中断标志寄存器 DMA_ISR 可以获取各通道的中断标志。标志对应多个可能的产生原因,具体产生原因需查询 DMA_CSRy.STATUS 状态位,如下表所示
• 数据宽度:
数据位宽可以设置为8bit、16bit和32bit,DMA通道的源地址和目的地址的位宽必须完全一致
• 数据块数量:
传输的数据块数量可以设置为1 ~ 65535
• 数据传输优先级
当CPU和DMA访问不同的外设时,数据的传输可以同时进行;
当CPU和DMA同时访问同一个外设时,CPU的优先级高于DMA。
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
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的数据
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)。
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. TIM1 CH1 PWM
2.1.2. GPDMA CH12 配置
选用 GPDMA 通道 12,并配置为循环模式:
图2. GPDMA CH12
2.1.3. GPDMA Linked List 配置
创建 Linked List Queue,并配置为搭配 GPDMA 2D 功能通道使用。创建两个节点,TN1, TN2,并使用循环模式,指定首个循环节点为 TN1。
图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. Linked List Node1
TN2 节点配置,与 TN1 节点类似,用于切换到第二组数据产生第二种波形
图5. Linked List Node2
2.2. 测试代码
GPDMA Linked List 模式执行流程
图6. Linked List 执行过程及期望输
03、PWM滤波输出
在 U575 NUCLEO 板上测试,结果如下:
图7. 实际输出
从实测结果来看,滤波后的正弦波频率,波形持续时长都符合预期。另外,与通过额外 TIM 计时来切换 PWM 输出的方式相比,使用 Linked List repeat 这种方式,正弦波与固定电平输出之间切换更平滑。
图8. 额外 TIM 计时来切换 PWM 输出
04、小结
通过使用 GPDMA Linked List 模式,使用 2D addressing repeat 功能,能方便实现这种多种波形切换的应用场景。如 Node1 与 Node2 使用不同的数据长度和重复次数,则可得到不同时长的两种波形;通过增加更多 Node,则可得到多种不同波形。
来源:STM32单片机
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
01、前言
有客户反馈,使用STM32F4的TIM2结合DMA,产生的PWM波形不符合预期,但是相同的配置使用在TIM3上,得到的PWM波形就是符合预期的。其代码和配置都是从F1移植过来的,在F1上使用TIM2是没有问题的,对于F4的TIM2发生的问题,客户一直没有找到根本原因。
02、实验
根据客户的反馈,我们进行了实验。
硬件:STM32F401RE-NUCLEO
在STM32CubeMX中,将TIM2和TIM3所有参数均做相同的配置,其中配置DMA两端均为halfword长度。
生成代码,并定义两个数组如下图所示:
在主函数中开启Timer。
我们可以发现,实验结果如客户反馈的,TIM2输出的PWM是不正确的,TIM3输出的PWM是正确的。
03、分析
我们的实验中,TIM2和TIM3的配置是完全一样的,即使传输相同的数据,得到的PWM波形也是不同的。为此我们比较了TIM2和TIM3的硬件属性,可以很容易查看出,TIM2的计数器是32bit的,而TIM3的计数器是16bit的。
我想我们已经知道答案了,TIM2的计数器是32bit的,但是我们配置的DMA是halfword长度,这在AHB总线上解析数据时产生了非预期的结果。在调试界面我们也能看到,当问题发生时,TIM2的CCR1竟然比ARR的值要大,或者出现异常值,所以出现异常波形。
根本原因在于,对于大部分STM32系列,主设备基于AHB外设进行寻址是不支持byte/half-word传输的,总线会强制将数据转化为32bit传送到总线上,这就是为什么我们看到CCR1的高半字和低半字的值是相同的原因。
当我们将TIM2的DMA外设端修改为word长度,并将内存数组定义为32bit,再次实验,可以发现PWM的波形就是正常的了:
04、小结
因为F103上没有32bit计数器的Timer,所以客户在F103上并没有出现类似的问题。在使用DMA访问经过AHB转APB的桥接外设时,我们要注意DMA对外设的访问宽度配置问题。
来源:STM32单片机
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
直接存储器访问(DMA)控制器,可以在内存和/或外设之间传输数据,而不需要CPU参与每次传输。合理利用DMA控制器,可以减轻CPU的负担。本文通过介绍DMA结构与工作原理,以及两种模式(兵乓模式与多数据包缓冲传输模式),来看看使用DMA如何提高MCU效率。
DMA结构与工作原理
先进的DMA控制器,如STMicroelectronics的STM32F4系列中包含的控制器,可以通过灵活的数据流分配和传输管理功能进一步减轻CPU的负担。
如图左侧所示,来自8个不同的通道DMA请求,并到仲裁器上,从而建立优先级(编号较低的输入通道,具有较高的优先级)。然后激活最高优先级的传输,传输到图中右侧的两个AHB 主设备(存储器端口和外设接口),提高了外设到存储器传输的效率。这可能是DMA在基于CPU的设计中最常见的情况。
图 1. STM32F4系列DMA控制器(图片来源于STMicroelectronics)
为每个路径分配单独的FIFO,如图1中间所示,允许针对每个外设接口的特性调整FIFO特性。例如,FIFO的阈值级别(请求传输的深度)可以单独设置为FIFO大小的¼,½或¾。这允许低速通道等待,直到FIFO几乎满了才进行传输,以最小化开销。更快的通道会更早地启动传输,可能只有一半大小,以避免FIFO溢出。
我们来通过一个实例,来看看DMA怎么工作的。
实例:“使用 STM32 来控制 NeoPixels LED”
硬件部分采用 STM32 开发板,与 NeoPixel LED、灯带、矩阵等相连接。
RGB NeoPixels实际上是WS2812智能控制LED。下面是WS2812 LED的3字节数据协议的结构,分别代表绿红蓝三个信息。
图 2. WS2812 LED的3字节数据协议的结构
使用计时器来PWM控制波形,然后配置DMA使CPU高效并且易于实施。
在软件中,配置DMA, 选择了“TIM2_CH3/UP”, 将方向改为“内存到外设”, 同时,将优先级改为“非常高”,最后保存.ioc 文件,以生成项目代码。
更多内容请看下面文章:使用 STM32 来控制 NeoPixels
DMA的两种模式
合理使用两种DMA模式(兵乓模式与多数据包缓冲传输模式),可以帮助提高MCU效率。
USB外设是一个很好的外设示例,早期的USB实现的最大吞吐量只有1.5 Mb/秒。随着更高性能的标准版本的出现。比如要接近12 Mbit/s全速USB标准的理论最大值。我们来看看,数据传输方面DMA如何帮助提高MCU效率!
我们以Microchip的ATXMEGA16D4-MH举例。
01、兵乓模式
之前通常使用单个存储器缓冲区进行外设数据传输。如果数据缓冲区已满,MCU将响应NAK(否定确认)消息。接收到NAK后,主机将等待并稍后重试传输。它将继续重试,直到MCU能够成功接收数据。
ATXMEGA16D4-MH使用乒乓模式来消除这个问题。乒乓模式使用两个存储单元(memory banks)进行数据传输。当一个存储单元满时,主机可以将数据传输到另一个存储单元。在两个存储单元之间交替传输可以避免复审,并提高整体数据带宽。
此外,如上图所示,以乒乓模式还使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%。这两个功能的结合,不仅在性能上有所改进,而且还能节省能源。
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
在上一讲单通道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)。