一、需求描述

在数据采集的应用中,使用外置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总线上会循环收发如下数据。在这里插入图片描述

在这里插入图片描述

总结

如上介绍完整测试代码如附件所示。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐