HPMicro MCU片上ADC使用指南
HPMicro MCU片上ADC使用指南
1. 引言
ADC,全称为Analog-to-Digital Converter,即模数转换器,是一种将模拟信号转换为数字信号的电子设备。ADC的主要作用是在存在外部和内部噪声的情况下,将未知的模拟信号转换为数字信号,以便于计算机或其他数字系统进行处理和分析。
2. MCU片上ADC具有的主要特点
- 采样时间设置
- 分辨率设置
- 内部通道采样(温度传感器)
- 外部通道采样
- 软件触发/硬件触发
- 看门狗监测
- 中断机制
- 寄存器、DMA存储转换结果
- 多样的转换模式
3. ADC转换模式的介绍
为了能够提供给应用逻辑在使用上的便利性,一般MCU片上ADC都定义了单次模式(one-shot mode)、循环模式(cycle mode)、单次扫描模式(one-shot scan mode)、循环扫描模式(cycle scan mode)、单次间断模式(one-shot interrupted mode)、循环间断模式(cycle interrupted mode):
单通道:
- 单次模式:ADC执行一个指定通道的一次转换,转换完成后,ADC自动停止工作
- 循环模式:ADC执行一个指定通道的循环转换,完成一次转换后,ADC自动进行下一次转换
多通道:
- 单次扫描模式:ADC执行一个序列的一次转换,转换完成后,ADC自动停止工作
- 循环扫描模式:ADC执行一个序列的循环转换,完成一次转换后,ADC自动进行下一次序列转换
- 单次间断模式:ADC执行一个序列的一次转换,每个通道转换完成后,ADC会等待下次触发到达后,再启动转换,直到整个序列的通道转换全部完成, ADC自动停止工作
- 循环间断模式:ADC执行一个序列的循环转换,每个通道转换完成后,ADC会等待下次触发到达后,再启动转换,整个序列的通道转换全部完成后,ADC自动进行下一次序列转换
4. HPMicro MCU片上ADC具有的特点
-
16位逐次逼近型ADC
-
16位分辨率下最大采样率为2MSPS
-
输入信号类型
- 单端输入
- 差分输入(早期型号不支持)
-
外部通道采样
- 最多支持16个外部通道输入
-
分辨率设置
- 支持设置为16位、12位、10位、8位
-
采样时间设置
- 支持设置灵活的采样时间
-
读取转换模式(oneshot mode)
- 读取直接触发转换
-
周期转换模式 (period mode)
- 按照定时器周期转换
-
序列转换模式 (sequence mode)
- 由软件或硬件触发启动序列
- 仅支持一组序列且序列长度可达16
- 支持序列单次转换或序列循环转换
- 支持序列内间断转换
-
抢占转换模式 (preemption mode)
- 由软件或硬件触发启动序列
- 支持十二组序列且每组序列长度可达4
-
寄存器、DMA存储转换结果
- 读取模式、周期模式只能通过寄存器读取转换结果
- 序列模式、抢占模式只能通过从DMA写入的memory中读取转换结果
-
支持多个ADC同步工作
- 通过互联管理器(trigmux)输入同源触发信号,可以同步触发多个ADC同时启动转换
-
中断机制
- 支持看门狗阈值超限中断
- 支持序列转换模式中任意通道转换完成中断
- 支持序列转换模式中所有通道转换完成中断
- 支持抢占转换模式中任意通道转换完成中断
-
看门狗监测
- 每个通道都可设置独立的阈值以及超限中断
注:
-
HPMicro的早期产品也有12位ADC,除了分辨率的差异,其余主要特性与16位ADC相同(请查阅手册了解具体信息)
-
HPMicro的早期型号中的16位ADC仅支持单端输入(请查阅手册了解具体信息)
5. 如何在转换模式上将HPMicro MCU片上ADC与其他MCU片上ADC进行对应?
根据HPMicro MCU片上ADC的特性与其他MCU片上ADC的主要特点进行对比,可以发现主要特点基本都已涵盖,下面关于ADC的转换模式,做进一步地介绍:
-
ADC单通道单次模式:可以使用HPMicro MCU片上ADC的读取转换模式、序列转换模式、抢占转换模式
-
ADC单通道循环模式:可以使用HPMicro MCU片上ADC的序列转换模式、抢占转换模式
-
ADC多通道单次扫描模式:可以使用HPMicro MCU片上ADC的序列转换模式、抢占转换模式
-
ADC多通道循环扫描模式:可使使用HPMicro MCU片上ADC的序列转换模式、抢占转换模式
-
ADC多通道单次间断模式:可以使用HPMicro MCU片上ADC的序列转换模式
-
ADC多通道循环间断模式:可使使用HPMicro MCU片上ADC的序列转换模式
注:
- 由此看出HPMicro MCU片上的ADC的序列转换模可以覆盖其他MCU片上ADC的所有转换模式
6. 如何通过HPM SDK来使用HPMicro MCU片上的ADC的序列模式?
通过上述介绍,我们已经了解了其他MCU以及HPMicro MCU片上的ADC的的特点后,接下来以序列模式为例,做进一步介绍:
-
ADC管脚初始化
-
设置指定管脚IO PAD的FUNC_CTL寄存器的BIT8(IOC_PAD_FUNC_CTL_ANALOG_MASK),用于隔离指定管脚所复用的数字功能
以HPM6E00为例,PF07既有数字功能的复用, 也有模拟功能的复用:
因此对于此管脚需要使用模拟功能时,就需要隔离数字功能,程序设置如下:
void init_adc16_pins(void) { HPM_IOC->PAD[IOC_PAD_PF07].FUNC_CTL = IOC_PAD_FUNC_CTL_ANALOG_MASK; }
-
-
ADC时钟初始化
-
ADC时钟结构,HPMicro MCU的所有系列的用户手册上的功能时钟章节中都有时钟结构框图,如下图所示:
注:-
目前HPMicro MCU片上ADC的时钟结构都具有两级,第一级为BUS时钟或者ANAn时钟,第二级为ADCn时钟
-
BUS为AHB或AXI,由MCU的设计决定,具体可通过查阅SYSCTL寄存器中的ADCCLK的MUX选择位来确认
-
-
设置ADC的输入时钟, 用于配置BUS时钟或者ANAn时钟
示例:以HPM6E00为例,选择200MHz的BUS时钟作为ADC0的输入时钟
a) 设置PLL1输出400MHz, 再设置CLK_TOP_AHB时钟来源于PLL1CLK0的2分频输出, 因此得到200MHz的时钟
pllctlv2_init_pll_with_freq(HPM_PLLCTLV2, pllctlv2_pll1, 400000000); /* PLL1 400MHz */ pllctlv2_set_postdiv(HPM_PLLCTLV2, pllctlv2_pll1, pllctlv2_clk0, pllctlv2_div_1p0); /* PLL1CLK0 400MHz */ clock_set_source_divider(clock_ahb, clk_src_pll1_clk0, 2); /* AHB 200MHz */
b) 选择AHB时钟作为ADC0的输入时钟
clock_set_adc_source(clock_adc0, clk_adc_src_ahb0);
-
-
ADC初始化
ADC初始化分为两部分,模拟部分和数字部分, 其中模拟部分为通用属性,而数字部分分为四种模式:读取模式、周期模式、序列模式、抢占模式
-
设置ADC通用属性
a) 通用设置结构体定义
/** @brief ADC16 common configuration struct. */ typedef struct { uint8_t res; /* 用于设置分辨率,可以选择16位,12位,10位,8位 */ uint8_t conv_mode; /* 用于设置工作模式,可以选择读取模式、周期模式、序列模式、抢占模式 */ uint32_t adc_clk_div; /* 用于设置ADC输入时钟分频系数, 最大16分频 */ bool port3_realtime; /* 用于设置多模式下的抢占使能 */ bool wait_dis; /* 用于在使用读取模式时,设置是否禁用阻塞方式 */ bool sel_sync_ahb; /* 用于标记ADC的输入时钟与BUS时钟是否相同 */ bool adc_ahb_en; /* 用于设置DMA访问AHB总线的使能,仅对序列模式和抢占模式中DMA需要访问memory时有效 */ } adc16_config_t;
注:
-
多模式指的是对于同一个ADC,可以同时请求ADC进行读取转换、周期转换、序列转换、抢占转换
-
多模式抢占中,抢占模式抢占其余模式(读取转换、周期转换、序列转换),而其他模式不能抢占抢占模式
-
多模式抢占中,抢占只能发生在ADC的采样阶段,当ADC进入转换阶段,则不能发生抢占
b) 示例:设置ADC分辨率为16位,设置序列模式进行转换,标记输入时钟与BUS相同,使能DMA访问AHB总线
adc16_config_t cfg; /* initialize an ADC instance */ adc16_get_default_config(&cfg); cfg.res = adc16_res_16_bits; /* 设置分辨率为16位 */ cfg.conv_mode = adc16_conv_mode_sequence; /* 设置位序列转换模式 */ cfg.adc_clk_div = adc16_clock_divider_4; /* 设置分频为4 */ cfg.sel_sync_ahb = true; /* 标记ADC输入时钟为BUS时钟相同 */ cfg.adc_ahb_en = true; /* 使能DMA访问AHB总线 */ /* adc16 initialization */ if (adc16_init(BOARD_APP_ADC16_BASE, &cfg) == status_success) { /* enable irq */ intc_m_enable_irq_with_priority(BOARD_APP_ADC16_IRQn, 1); return status_success; } else { printf("%s initialization failed!\n", BOARD_APP_ADC16_NAME); return status_fail; }
-
-
设置ADC序列转换属性
a) 定义序列转换模式所使用的通道,例如:
uint8_t seq_adc_channel[] = {BOARD_APP_ADC16_CH_1};
注:
- 如果需要实现多通道,可在数组中添加通道号对应的宏定义
b) 定义序列转换模式存储转换结果所使用的buffer,例如:
ATTR_PLACE_AT_NONCACHEABLE_WITH_ALIGNMENT(ADC_SOC_DMA_ADDR_ALIGNMENT) uint32_t seq_buff[APP_ADC16_SEQ_DMA_BUFF_LEN_IN_4BYTES];
注:
-
定义序列转换模式的memory时,必须声明以ADC_SOC_DMA_ADDR_ALIGNMENT所定义的长度对齐
-
定义序列buffer的长度时,不得超过最大长度ADC_SOC_SEQ_MAX_DMA_BUFF_LEN_IN_4BYTES
c) 通道属性
通道设置结构体定义,如下:/** @brief ADC16 channel configuration struct. */ typedef struct { uint8_t ch; /* 用于设置通道号 */ uint16_t thshdh; /* 用于设置看门狗监测阈值上限 */ uint16_t thshdl; /* 用于设置看门狗监测阈值下限 */ bool wdog_int_en; /* 用于设置看门狗监测中断使能 */ uint8_t sample_cycle_shift; /* 用于设置采样时间左移位数 */ uint32_t sample_cycle; /* 用于设置采样时间,以ADC输入时钟分频后的时钟周期位单位 */ } adc16_channel_config_t;
示例:不启用看门狗阈值监测,仅设置通道号和采样周期
adc16_channel_config_t ch_cfg; /* get a default channel config */ adc16_get_channel_default_config(&ch_cfg); /* initialize an ADC channel */ ch_cfg.sample_cycle = APP_ADC16_CH_SAMPLE_CYCLE; for (uint32_t i = 0; i < sizeof(seq_adc_channel); i++) { ch_cfg.ch = seq_adc_channel[i]; adc16_init_channel(BOARD_APP_ADC16_BASE, &ch_cfg); }
注:
- 通过adc16_get_channel_default_config(&ch_cfg) 获取默认的配置,默认配置中禁用了看门狗监测功能
示例: 启用看门狗阈值监测, 设置监测通道电压超过VREF/2时,产生中断
adc16_channel_config_t ch_cfg; /* get a default channel config */ adc16_get_channel_default_config(&ch_cfg); /* initialize an ADC channel */ ch_cfg.sample_cycle = APP_ADC16_CH_SAMPLE_CYCLE; ch_cfg.thshdh = 0x7ffff; ch_cfg.thshdl = 0; ch_cfg.wdog_int_en = true; for (uint32_t i = 0; i < sizeof(seq_adc_channel); i++) { ch_cfg.ch = seq_adc_channel[i]; adc16_init_channel(BOARD_APP_ADC16_BASE, &ch_cfg); }
注:
- 根据Datasheet, VREF为ADC外部参考电压,取值为2.0~3.6V
d) 序列属性
序列模式队列设置结构体定义,如下:/** @brief ADC16 queue configuration struct for the sequence mode. */ typedef struct { bool seq_int_en; /* 用于设置序列模式指定队列成员的通道完成转换后是否产生中断 */ uint8_t ch; /* 用于指定序列模式队列成员的通道号 */ } adc16_seq_queue_config_t;
序列设置结构体定义,如下:
/** @brief ADC16 configuration struct for the sequence mode. */ typedef struct { adc16_seq_queue_config_t queue[ADC_SOC_SEQ_MAX_LEN]; /* 用于设置序列模式队列成员的通道号和中断使能 */ bool restart_en; /* 用于设置序列模式完成一次序列转换后是否自动重新开始 */ bool cont_en; /* 用于设置是否使能多通道的循环转换 */ bool sw_trig_en; /* 用于设置是否使能软件触发 */ bool hw_trig_en; /* 用于设置是否使能硬件触发 */ uint8_t seq_len; /* 用于设置序列长度,最大长度为16 */ } adc16_seq_config_t;
e) 序列模式中的DMA
DMA设置结构体定义,如下:/** @brief ADC16 DMA configuration struct. */ typedef struct { uint32_t *start_addr; /* 用于设置序列buffer的起始地址 */ uint32_t buff_len_in_4bytes; /* 用于设置序列buffer的长度,以4字节为单位 */ uint32_t stop_pos; /* 用于设置DMA停止写入位置,DMA写入指针指向设置的offset时,暂停工作(此位置不写入) */ bool stop_en; /* 用于设置是否使能DMA在停止位置处停止写入 */ } adc16_dma_config_t;
注:
- 当使用HPM6750时,如果序列buffer的地址在DLM范围内,则需要通过将地址转换为AXI地址,具体请参考SDK中ADC相关sample
- 当设置stop_en为true时,stop_pos的设置才有效
- DMA写入数据时,会按照所设置的序列长度顺序循环写入序列buffer中
f) 序列模式中DMA所使用的数据结构
数据结构定义,如下:/** @brief ADC16 DMA configuration struct for the sequence mode. */ #if defined(ADC_SOC_IP_VERSION) && (ADC_SOC_IP_VERSION < 2) typedef struct { uint32_t result :16; /* 用于存放16位ADC转换结果 */ uint32_t seq_num :4; /* 用于表示在序列中的编号,一个序列最大长度为16,因此编号取值为0~15 */ uint32_t :4; uint32_t adc_ch :5; /* 用于表示ADC的通道号 */ uint32_t :2; uint32_t cycle_bit :1; /* 用于表示序列每轮完成后的翻转指示位(0和1之间翻转变化) */ } adc16_seq_dma_data_t; #else typedef struct { uint32_t result :16; uint32_t seq_num :4; uint32_t adc_ch :5; uint32_t :6; uint32_t cycle_bit :1; } adc16_seq_dma_data_t; #endif
g)触发源(硬件或软件)
-
硬件触发:通过硬件触发源,经过互联管理器(trigmux)到达ADC,触发ADC启动序列模式转换
-
软件触发:通过调用如下API, 即可完成一次软件触发ADC转换
hpm_stat_t adc16_trigger_seq_by_sw(ADC16_Type *ptr);
注:
- 在无法确保相邻两次触发的间隔大于ADC采样/转换的周期时,应用逻辑需要判断API的返回值,以此来确认是否产生了software conflict
-
设置互联管理器
通过调用如下API来设置互联管理器/* uint8_t input: 用于设置互联管理器的输入 */ /* uint8_t output: 用于设置互联管理器的输出 */ void init_trigger_mux(TRGM_Type *ptr, uint8_t input, uint8_t output);
示例: 以HPM6750为例,设置以PWM0的通道8的信号作为输入,ADC3的序列触发作为输出
init_trigger_mux(HPM_TRGM0, HPM_TRGM0_INPUT_SRC_PWM0_CH8REF, TRGM_TRGOCFG_ADC3_STRGI);
注:
- 互联管理器的输入源可以选择来自于PWM、GPTMR、GPIO等,而输出需要根据具体ADC实例来选择TRGM_TRGOCFG_ADCn_STRGI
-
设置硬件触发源
具体指的是对于所选择的互联管理器的外设进行初始化(此处不做详解) -
中断事件
ADC中断事件枚举类型定义如下:/** @brief Define ADC16 irq events. */ typedef enum { /** This mask indicates that a trigger conversion is complete. */ adc16_event_trig_complete = ADC16_INT_STS_TRIG_CMPT_MASK, /** This mask indicates that a conflict caused by software-triggered conversions. */ adc16_event_trig_sw_conflict = ADC16_INT_STS_TRIG_SW_CFLCT_MASK, /** This mask indicates that a conflict caused by hardware-triggered conversions. */ adc16_event_trig_hw_conflict = ADC16_INT_STS_TRIG_HW_CFLCT_MASK, /** This mask indicates that a conflict caused when bus reading from different channels. */ adc16_event_read_conflict = ADC16_INT_STS_READ_CFLCT_MASK, /** This mask indicates that a conflict caused by sequence-triggered conversions. */ adc16_event_seq_sw_conflict = ADC16_INT_STS_SEQ_SW_CFLCT_MASK, /** This mask indicates that a conflict caused by hardware-triggered conversions. */ adc16_event_seq_hw_conflict = ADC16_INT_STS_SEQ_HW_CFLCT_MASK, /** This mask indicates that DMA is stopped currently. */ adc16_event_seq_dma_abort = ADC16_INT_STS_SEQ_DMAABT_MASK, /** This mask indicates that all of the configured conversion(s) in a queue is(are) complete. */ adc16_event_seq_full_complete = ADC16_INT_STS_SEQ_CMPT_MASK, /** This mask indicates that one of the configured conversion(s) in a queue is complete. */ adc16_event_seq_single_complete = ADC16_INT_STS_SEQ_CVC_MASK, /** This mask indicates that DMA FIFO is full currently. */ adc16_event_dma_fifo_full = ADC16_INT_STS_DMA_FIFO_FULL_MASK } adc16_irq_event_t;
此处,仅讨论与序列模式相关的中断事件,根据定义:
- 当需要在序列模式的单个通道转换完成时产生中断,使能adc16_event_seq_single_complete事件中断即可
- 当需要在序列模式的多个(所有)通道转换完成时产生中断,可以使能adc16_event_seq_full_complete事件中断,也可以仅使能最后一个通道的转换完成事件中断
-
中断服务
- 判断序列模式任意通道转换完成中断是否产生: 通过读取中断状态寄存器中的SEQ_CVC位来确定
- 判断通道看门狗监测中断是否产生: 通过读取中断状态寄存器中(一个或多个)通道所对应的掩码来确定
- 看门狗监测中断产生后,需要关闭中断,由应用逻辑处理后(或异常消除后),再确定是否开启相关中断,否则中断会持续产生
示例: 中断服务
SDK_DECLARE_EXT_ISR_M(BOARD_APP_ADC16_IRQn, isr_adc16) void isr_adc16(void) { uint32_t status; status = adc16_get_status_flags(BOARD_APP_ADC16_BASE); /* Clear status */ adc16_clear_status_flags(BOARD_APP_ADC16_BASE, status); if (ADC16_INT_STS_SEQ_CVC_GET(status)) { /* Set flag to read memory data */ seq_complete_flag = 1; } if (ADC16_INT_STS_TRIG_CMPT_GET(status)) { /* Set flag to read memory data */ trig_complete_flag = 1; } if (ADC16_INT_STS_WDOG_GET(status) & APP_ADC16_CH_WDOG_EVENT) { adc16_disable_interrupts(BOARD_APP_ADC16_BASE, APP_ADC16_CH_WDOG_EVENT); res_out_of_thr_flag = ADC16_INT_STS_WDOG_GET(status) & APP_ADC16_CH_WDOG_EVENT; } }
注:
- 上述代码中,APP_ADC16_CH_WDOG_EVENT为初始化时,所定义的(一个或多个)通道对应的掩码(具体可查阅手册中相关寄存器描述)
-
序列转换结果和看门狗监测结果处理
a) 示例:序列模式中,对转换结果进行处理hpm_stat_t process_seq_data(uint32_t *buff, int32_t start_pos, uint32_t len) { adc16_seq_dma_data_t *dma_data = (adc16_seq_dma_data_t *)buff; if (ADC16_IS_SEQ_DMA_BUFF_LEN_INVLAID(len)) { return status_invalid_argument; } current_cycle_bit = !current_cycle_bit; for (uint32_t i = start_pos; i < start_pos + len; i++) { printf("Sequence Mode - %s - ", BOARD_APP_ADC16_NAME); printf("Cycle Bit: %02d - ", dma_data[i].cycle_bit); printf("Sequence Number:%02d - ", dma_data[i].seq_num); printf("ADC Channel: %02d - ", dma_data[i].adc_ch); printf("Result: 0x%04x\n", dma_data[i].result); if (dma_data[i].cycle_bit != current_cycle_bit) { printf("Error: Cycle bit is not expected value[%d]!\n", current_cycle_bit); while (1) { } } } return status_success; }
b) 示例: 看门狗监测处理
void channel_result_out_of_threshold_handler(void) { adc16_channel_threshold_t threshold; if (res_out_of_thr_flag & APP_ADC16_CH_WDOG_EVENT) { adc16_get_channel_threshold(BOARD_APP_ADC16_BASE, ADC16_SOC_TEMP_CH_NUM, &threshold); printf(“Warning - %s [channel %02d] - Sample voltage is out of the thresholds between 0x%04x and 0x%04x !\n”, BOARD_APP_ADC16_NAME, ADC16_SOC_TEMP_CH_NUM, threshold.thshdl, threshold.thshdh); res_out_of_thr_flag = 0; adc16_enable_interrupts(BOARD_APP_ADC16_BASE, APP_ADC16_CH_WDOG_EVENT); } }
-
7. 如何设置HPMicro MCU片上ADC的序列模式的相关属性来对应到其他MCU所具有的转换模式?
-
定义ADC序列数组中定义通道
uint8_t seq_adc_channel[] = {BOARD_APP_ADC16_CH_1, BOARD_APP_ADC16_CH_2, BOARD_APP_ADC16_CH_3};
-
各种模式对应序列设置的差异
模式 seq_len设置值 restart_en设置值 cont_en设置值 单通道单次模式 1 false x 单通道连续模式 1 true x 多通道单次扫描模式 sizeof(seq_adc_channel) false true 多通道连续扫描模式 sizeof(seq_adc_channel) true true 多通道单次间断模式 sizeof(seq_adc_channel) false false 多通道连续间断模式 sizeof(seq_adc_channel) true false 注:
-
通道定义可以是一个或多个,但是不能超过序列所支持的最大长度
-
x表示不用设置(设置无效)
示例:多通道循环扫描模式
需要设置序列长度, 且需要使能序列自动重新开始,还需要使能自动循环转换,如下:
/* Set a sequence config */ seq_cfg.seq_len = sizeof(seq_adc_channel); seq_cfg.restart_en = true; seq_cfg.cont_en = true;
-
8. 关于HPMicro MCU片上ADC的性能评估
请查阅《HPM系列MCU 高精度ADC之性能评估与测试指南》, 相关连接如下: