HPM6E00 硬件周期触发SPI收发数据,使用DMA
使用先楫半导体MCU,型号HPM6E00,实现使用PWM周期触发SPI自动收发数据。
·
一、需求描述
在数据采集的应用中,使用外置ADC,通信接口为SPI,要求MCU硬件自动触发SPI发送命令以及读取ADC转换结果,触发周期可调。
二、代码配置
1.测试环境
测试硬件:HPM6E00EVK_RevB
SDK版本:hpm_sdk 1.10
SPI引脚:SPI_CS1:PF25
SPI_SCK:PF26
SPI_MOSI:PF29
SPI_MISO:PF28
1.触发源初始化
触发源初始化。选用 PWM 作为硬件触发源,使用PWM周期重载事件作为触发信号输出,用以触发DMA。
void trigger_source_init(uint32_t t_ms)
{
uint32_t reload = 0;
uint32_t fclk = 0;
fclk = clock_get_frequency(clock_pwm0);
reload = fclk / (1000) * t_ms - 1; // 计算周期重载值
pwmv2_disable_counter(HPM_PWM0, pwm_counter_0); // 关闭 PWM0 模块计数器
pwmv2_set_reload_update_time(HPM_PWM0, pwm_counter_0, pwm_reload_update_on_reload); // 重载值更新时机为计数器重载时
pwmv2_counter_select_data_offset_from_shadow_value(HPM_PWM0, pwm_counter_0, PWMV2_SHADOW_INDEX(0));
pwmv2_shadow_register_unlock(HPM_PWM0); // 解锁影子寄存器
pwmv2_set_shadow_val(HPM_PWM0, PWMV2_SHADOW_INDEX(0), reload, 0, false); // 设置影子寄存器 0 的值,重载值
pwmv2_shadow_register_lock(HPM_PWM0); // 锁定影子寄存器
pwmv2_enable_dma_at_reload_point(HPM_PWM0, pwm_dma_0, pwm_counter_0); // 使能计数器 0 重载事件触发 DMA
pwmv2_enable_counter(HPM_PWM0, pwm_counter_0); // 启动计数器 0 计数
}
2.DMA初始化
DMA 通道 0 用来启动 SPI 传输,
DMA 通道 1 用来搬运 SPI Tx 数据,
DMA 通道 2 用来搬运 SPI Rx 数据,SPI Rx 接收完数据后产生 DMA 传输完成中断。
void dma_config(void)
{
dma_channel_config_t cfg_ch_config = { 0 }; // 定义 DMA 启动 SPI 传输的通道配置结构体
dma_channel_config_t tx_ch_config = { 0 }; // 定义 DMA SPI Tx 的通道配置结构体
dma_channel_config_t rx_ch_config = { 0 }; // 定义 DMA SPI Rx 的通道配置结构体
dma_reset(HPM_HDMA); // 复位 DMA 外设
trgm_dma_request_config(HPM_TRGM0, TRGM_DMACFG_0, HPM_TRGM0_DMA_SRC_PWM0_REQ0); // 配置 TRGM,PWM0 触发输出作为 DMACFG0 触发信号
dmamux_config(HPM_DMAMUX, DMA_SOC_CHN_TO_DMAMUX_CHN(HPM_HDMA, 0), HPM_DMA_SRC_MOT_0, true); // 配置 DMA 通道 0 触发源为 DMACFG0
dma_default_channel_config(HPM_HDMA,&cfg_ch_config); // 获取 DMA 通道默认配置
cfg_ch_config.src_addr = core_local_mem_to_sys_address(HPM_CORE0, (uint32_t)&spi_setup_data); // DMA 通道源地址,SPI 非命令模式下,向 SPI CMD 寄存器写入 0xFF 可以启动 SPI 传输,spi_setup_data 变量的值为 0xFF
cfg_ch_config.dst_addr = (uint32_t)&HPM_SPI7->CMD; // DMA 通道目的地址,SPI CMD寄存器
cfg_ch_config.src_width = DMA_TRANSFER_WIDTH_BYTE; // 源数据地址数据位宽为 1 BYTE
cfg_ch_config.dst_width = DMA_TRANSFER_WIDTH_BYTE; // 目的地址数据位宽为 1 BYTE
cfg_ch_config.size_in_byte = 1; // 传输的数据总字节数为 1
cfg_ch_config.src_addr_ctrl = DMA_ADDRESS_CONTROL_FIXED; // 源地址固定
cfg_ch_config.dst_addr_ctrl = DMA_ADDRESS_CONTROL_FIXED; // 目的地址固定
cfg_ch_config.src_mode = DMA_HANDSHAKE_MODE_HANDSHAKE; // 源数据 DMA 为硬件握手模式(需等待硬件触发信号才会启动传输)
cfg_ch_config.dst_mode = DMA_HANDSHAKE_MODE_NORMAL; // 目的地址 DMA 为普通模式
cfg_ch_config.src_burst_size = DMA_NUM_TRANSFER_PER_BURST_1T; // 源数据 DMA 搬运次数,1 次搬运所有数据
cfg_ch_config.en_infiniteloop = true; // 使能当前配置无限循环,即下一次触发信号来时 DMA 配置会自动重载,不需要再重新配置 DMA 通道
if(status_success != dma_setup_channel(HPM_HDMA, 0, &cfg_ch_config, true)) { // 配置 DMA 通道 0 并启动通道,等待触发信号
printf("dma ch0 setup failed \n");
}
dmamux_config(HPM_DMAMUX, DMA_SOC_CHN_TO_DMAMUX_CHN(HPM_HDMA, 1), HPM_DMA_SRC_SPI7_TX, true);// 配置 DMA 通道 1 触发源为 SPI Tx
dma_default_channel_config(HPM_HDMA,&tx_ch_config); // 获取 DMA 通道默认配置
tx_ch_config.src_addr = core_local_mem_to_sys_address(HPM_CORE0, (uint32_t)&sent_buff);// DMA 通道源地址
tx_ch_config.dst_addr = (uint32_t)&HPM_SPI7->DATA; // DMA 通道目的地址,SPI Tx 数据寄存器
tx_ch_config.src_width = DMA_TRANSFER_WIDTH_BYTE; // 源数据地址数据位宽为 1 BYTE
tx_ch_config.dst_width = DMA_TRANSFER_WIDTH_BYTE; // 目的地址数据位宽为 1 BYTE
tx_ch_config.size_in_byte = sizeof(sent_buff); // 传输的数据总字节数
tx_ch_config.src_addr_ctrl = DMA_ADDRESS_CONTROL_INCREMENT; // 源数据地址自增
tx_ch_config.dst_addr_ctrl = DMA_ADDRESS_CONTROL_FIXED; // 目的地址固定
tx_ch_config.src_mode = DMA_HANDSHAKE_MODE_HANDSHAKE ; // 源数据 DMA 为硬件握手模式
tx_ch_config.dst_mode = DMA_HANDSHAKE_MODE_NORMAL; // 目的地址 DMA 为普通模式
tx_ch_config.src_burst_size = DMA_NUM_TRANSFER_PER_BURST_1T;// 源数据 DMA 搬运次数,1 次搬运所有数据
tx_ch_config.en_infiniteloop = true; // 使能当前配置无限循环
if(status_success != dma_setup_channel(HPM_HDMA, 1, &tx_ch_config, true)) { // 配置 DMA 通道 1 并启动通道
printf("dma setup failed \n");
}
dmamux_config(HPM_DMAMUX, DMA_SOC_CHN_TO_DMAMUX_CHN(HPM_HDMA, 2), HPM_DMA_SRC_SPI7_RX, true);// 配置 DMA 通道 1 触发源为 SPI Rx
dma_default_channel_config(HPM_HDMA,&rx_ch_config); // 获取 DMA 通道默认配置
rx_ch_config.src_addr = (uint32_t)&HPM_SPI7->DATA; // DMA 通道源数据地址,SPI Rx 数据寄存器
rx_ch_config.dst_addr = core_local_mem_to_sys_address(HPM_CORE0, (uint32_t)&receive_buff); // DMA 通道目的地址
rx_ch_config.src_width = DMA_TRANSFER_WIDTH_BYTE; // 源数据地址数据位宽为 1 BYTE
rx_ch_config.dst_width = DMA_TRANSFER_WIDTH_BYTE; // 目的地址数据位宽为 1 BYTE
rx_ch_config.size_in_byte = sizeof(sent_buff); // 传输的数据总字节数
rx_ch_config.src_addr_ctrl = DMA_ADDRESS_CONTROL_FIXED; // 源数据地址固定
rx_ch_config.dst_addr_ctrl = DMA_ADDRESS_CONTROL_INCREMENT; // 目的地址自增
rx_ch_config.src_mode = DMA_HANDSHAKE_MODE_HANDSHAKE; // 源数据 DMA 为硬件握手模式
rx_ch_config.dst_mode = DMA_HANDSHAKE_MODE_NORMAL; // 目的地址 DMA 为普通模式
rx_ch_config.src_burst_size = DMA_NUM_TRANSFER_PER_BURST_1T;// 源数据 DMA 搬运次数,1 次搬运所有数据
rx_ch_config.en_infiniteloop = true; // 使能当前配置无限循环
if(status_success != dma_setup_channel(HPM_HDMA, 2, &rx_ch_config, true)) { // 配置 DMA 通道 2 并启动通道
printf("dma setup failed \n");
}
dma_disable_channel_interrupt(HPM_HDMA, 0, DMA_INTERRUPT_MASK_ALL); // 禁能 DMA 通道 0 所有中断
dma_disable_channel_interrupt(HPM_HDMA, 1, DMA_INTERRUPT_MASK_ALL); // 禁能 DMA 通道 1 所有中断
dma_disable_channel_interrupt(HPM_HDMA, 2, DMA_INTERRUPT_MASK_ALL); // 禁能 DMA 通道 2 所有中断
dma_enable_channel_interrupt(HPM_HDMA, 2, DMA_INTERRUPT_MASK_TERMINAL_COUNT); // 使能 DMA 通道 2 传输完成中断
intc_m_enable_irq_with_priority(IRQn_HDMA, 2); // 使能 DMA 中断,中断优先级为 2
}
DMA 传输完成中断服务函
SDK_DECLARE_EXT_ISR_M(IRQn_HDMA, isr_dma)
void isr_dma(void)
{
hpm_stat_t stat;
stat = dma_check_transfer_status(HPM_HDMA, 2);
if (stat & DMA_CHANNEL_STATUS_TC) {
printf("dma ch2\n");
}
}
3.SPI初始化
SPI 速率设置为 2MHz,数据宽度为8Bit,同时使用发送和接收DMA
void spi_init(void)
{
spi_timing_config_t timing_config = {0};
spi_format_config_t format_config = {0};
spi_control_config_t control_config = {0};
uint32_t spi_tx_trans_count;
hpm_stat_t stat;
board_init_spi_pins(HPM_SPI7); // 初始化 SPI 外设引脚
spi_master_get_default_timing_config(&timing_config); // 获取 SPI 时钟默认配置
timing_config.master_config.clk_src_freq_in_hz = board_init_spi_clock(HPM_SPI7); // 获取 SPI 时钟源大小
timing_config.master_config.sclk_freq_in_hz = 2000000UL; // 要设置的 SCLK 时钟大小
if (status_success != spi_master_timing_init(HPM_SPI7, &timing_config)) { // 配置 SPI 时钟大小,即 SPI 传输速率
printf("SPI master timing init failed\n");
}
/* SPI 传输数据格式 */
spi_master_get_default_format_config(&format_config); // 获取传输格式默认配置
format_config.master_config.addr_len_in_bytes = 1U; // 地址长度 8bit(禁能地址段模式下,该配置无效)
format_config.common_config.data_len_in_bits = 8; // 数据长度 8bit
format_config.common_config.data_merge = false; // 数据不合并
format_config.common_config.mosi_bidir = false; // MOSI 单向信号传输
format_config.common_config.lsb = false; // 数据传输高位先行,MSB
format_config.common_config.mode = spi_master_mode; // Master 模式
format_config.common_config.cpol = spi_sclk_high_idle; // 时钟极性,空闲时为高电平
format_config.common_config.cpha = spi_sclk_sampling_even_clk_edges; // 时钟相位,时钟上升沿采样
spi_format_init(HPM_SPI7, &format_config);
/* SPI 传输控制 */
spi_master_get_default_control_config(&control_config);
control_config.master_config.cmd_enable = false; // 禁能命令段
control_config.master_config.addr_enable = false; // 禁能地址段
control_config.master_config.addr_phase_fmt = spi_address_phase_format_single_io_mode;
control_config.common_config.tx_dma_enable = true; // 使能 DMA 发送
control_config.common_config.rx_dma_enable = true; // 使能 DMA 接收
control_config.common_config.trans_mode = spi_trans_write_read_together; // 传输模式为同时读写
control_config.common_config.data_phase_fmt = spi_single_io_mode; // 数据段格式为单线模式
control_config.common_config.dummy_cnt = spi_dummy_count_1; // 写、读模式下无效
control_config.common_config.cs_index = spi_cs_1; // 选择 CS_1 片选1信号
spi_tx_trans_count = sizeof(sent_buff); // 传输字节数大小
stat = spi_control_init(HPM_SPI7, &control_config, spi_tx_trans_count, spi_tx_trans_count);
if (stat != status_success) {
printf("spi setup dma transfer failed\n");
}
spi_enable_tx_dma(HPM_SPI7); // 使能 SPI Tx DMA
spi_enable_rx_dma(HPM_SPI7); // 使能 SPI Rx DMA
}
4.测试结果
将如上配置代码烧录板子后,用逻辑分析仪抓SPI接口的数据,如果MOSI和MISO引脚短接的话,可以看到SPI总线上会循环收发如下数据。

总结
如上介绍完整测试代码如附件所示。
更多推荐




所有评论(0)