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
};
使用逻辑分析仪测试波形如下:
用示波器测量波形如下图所示,信号质量表现优异。