经验分享 | HPMicro定制化波形--DMA推GPIO指南

2024-06-18
浏览量:
498

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

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

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


-END-