声明:以下内容为个人学习心得。

一.基础知识

HC32F460系列MCU内部集成了ADC1和ADC2两个模块,挂载于AHB-APB(APB3)总线,可配置12位、10位和8位分辨率,支持最多16个外部模拟输入通道和1个内部基准电压/8bitDAC输出的检测通道。

HC32F460系列MCU操作起来很灵活,体现在:模拟输入通道可以任意组合一个序列(序列A和序列B),一个序列可以进行单次扫描(包括两个动作:采样和转换),或连续扫描。支持对任意指定通道进行连续多次扫描,并对转换结果进行平均。

特别地,ADC模块还搭载模拟看门狗(以下简称AWD)功能,可对任意指定通道的转换结果进行监视,检测是否超出设定的阈值。

二.实验代码详解

本例程以ADC1的序列A和DMA1的通道0为例,实现了通过DMA读取ADC数据。例程实现原理:PE7的下

降沿触发ADC的序列A扫描,序列A扫描结束后,产生事件“EVT_SRC_ADC1_EOCA”,该事件触发DMA1的通道0

从指定的ADC数据寄存器地址(源地址)读取指定长度的数据到指定的目标地址,如此重复。

说明:

本例程中,DMA的块大小(block size)必须覆盖从最小通道编号到最大通道编号的所有通道,因为

DMA每读取一个数据后,数据指针加1,指向下一个需要读取的数据地址。例程中选用的ADC通道为ADC_CH2、

ADC_CH3和ADC_CH10,那么DMA的块大小须设置为不小于9,同样地,用于保存ADC数据的数组的大小,

也应不小于9。

①ADC、DMA相关寄存器配置参数说明

/* ADC unit instance for this example. */
#define ADC_UNIT                        (CM_ADC1)//ADC1基地址
#define ADC_PERIPH_CLK                  (FCG3_PERIPH_ADC1)//ADC1使能时钟

/* Selects ADC channels that needed. */
#define ADC_CHX                         (ADC_CH2)//选择ADC_CH2通道
#define ADC_CHX_PORT                    (GPIO_PORT_A)//对应PA2口
#define ADC_CHX_PIN                     (GPIO_PIN_02)

#define ADC_CHY                         (ADC_CH3)//选择ADC_CH3通道
#define ADC_CHY_PORT                    (GPIO_PORT_A)//对应PA3口
#define ADC_CHY_PIN                     (GPIO_PIN_03)

#define ADC_CHZ                         (ADC_CH10)//选择ADC_CH10通道
#define ADC_CHZ_PORT                    (GPIO_PORT_C)对应PC0口
#define ADC_CHZ_PIN                     (GPIO_PIN_00)

#define ADC_CH_MIN                      (ADC_CHX)
#define ADC_CH_MAX                      (ADC_CHZ)
#define ADC_DR_START                    ((uint32_t)&ADC_UNIT->DR2)//DMA数据源地址
#define PTTM_VAL_IDX                    (8U)//DMA缓存区大小

/* ADC sequence to be used. */
#define ADC_SEQ                         (ADC_SEQ_A)//使用ADC1的序列A

/* Hard trigger of the specified sequence. */
#define ADC_SEQ_HARDTRIG                (ADC_HARDTRIG_ADTRG_PIN) //硬件触发标志位
#define ADC_SEQ_TRIG_PORT               (GPIO_PORT_E) //PE7下降沿触发
#define ADC_SEQ_TRIG_PIN                (GPIO_PIN_07)
#define ADC_SEQ_TRIG_PIN_FUNC           (GPIO_FUNC_1)//设置PE7功能引脚

/*
 * Definitions of DMA.
 * 'APP_DMA_BLOCK_SIZE': 1~1024, inclusive. 1~16 for ADC1 and ADC2; 1~20 for ADC3.
 * 'APP_DMA_TRANS_COUNT': 0~65535, inclusive. 0: always transmit.
 */
#define DMA_UNIT                        (CM_DMA1)//DMA1基地址
#define DMA_PERIPH_CLK                  (FCG0_PERIPH_DMA1)//DMA1使能时钟
#define DMA_CH                          (DMA_CH0)//通道0地址
#define DMA_AOS_TRIG_SEL                (AOS_DMA1_0)//外设电路之间的联动(AOS)

#define DMA_TRANS_CNT                   (0U)//设置DMA传输次数,0表示无限次数
#define DMA_BLOCK_SIZE                  (ADC_CH_MAX - ADC_CH_MIN + 1U)//设置DMA数据块大小
#define DMA_DATA_WIDTH                  (DMA_DATAWIDTH_16BIT)//设置数据宽度,也决定了源地址和目的地址增加或减少多少
#define DMA_SRC_ADDR                    ADC_DR_START//DMA数据源地址
#define DMA_DEST_ADDR                   ((uint32_t)(&m_au16AdcValue[0U]))//DMA数据目的地址

#define DMA_TRIG_EVT                    (EVT_SRC_ADC1_EOCA)//选择AOS触发源

#define DMA_INT_SRC                     (INT_SRC_DMA1_BTC0)//选择DMA中断类型为:数据块传输完成
#define DMA_INT_IRQn                    (INT038_IRQn)//DMA中断序号为038
#define DMA_INT_PRIO                    (DDL_IRQ_PRIO_03)//DMA中断优先级为03(根据自己需要设定)
#define DMA_INT_FLAG                    (DMA_FLAG_BTC_CH0)//DMA中断完成标志位

②ADC转换电压计算

/* ADC reference voltage. The voltage of pin VREFH. */
#define ADC_VREF                        (3.3F) //参考电压为3.3V

/* ADC accuracy(according to the resolution of ADC). */
#define ADC_ACCURACY                    (1UL << 12U)//设置ADC分辨率为12位,0x10000 ->4096,2^12次方。

/* Calculate the voltage(mV). */
#define ADC_CAL_VOL(adcVal)             (uint16_t)((((float32_t)(adcVal) * ADC_VREF) / ((float32_t)ADC_ACCURACY)) * 1000.F)

③函数定义和DMA接收数据缓存区定义

static void AdcConfig(void);
static void AdcInitConfig(void);
static void AdcSetPinAnalogMode(void);
static void AdcHardTriggerConfig(void);

static void DmaConfig(void);
static void DmaIrqConfig(void);
static void DMA_IrqCallback(void);

static uint16_t m_au16AdcValue[DMA_BLOCK_SIZE];
__IO static uint8_t m_u8AdcValUpdated = 0U; //软件实现下降沿

④ADC初始化配置

static void AdcInitConfig(void)
{
    stc_adc_init_t stcAdcInit;

    /* 1. Enable ADC peripheral clock. */
    FCG_Fcg3PeriphClockCmd(ADC_PERIPH_CLK, ENABLE);

    /* 2. Modify the default value depends on the application. */
    (void)ADC_StructInit(&stcAdcInit);

    /* 3. Initializes ADC. */
    (void)ADC_Init(ADC_UNIT, &stcAdcInit);

    /* 4. ADC channel configuration. */
    /* 4.1 Set the ADC pin to analog input mode. */
    AdcSetPinAnalogMode();
    /* 4.2 Enable ADC channels. */
    ADC_ChCmd(ADC_UNIT, ADC_SEQ, ADC_CHX, ENABLE);
    ADC_ChCmd(ADC_UNIT, ADC_SEQ, ADC_CHY, ENABLE);
    ADC_ChCmd(ADC_UNIT, ADC_SEQ, ADC_CHZ, ENABLE);
}

⑤设置ADC引脚为模拟输入

static void AdcSetPinAnalogMode(void)
{
    stc_gpio_init_t stcGpioInit;

    (void)GPIO_StructInit(&stcGpioInit);
    stcGpioInit.u16PinAttr = PIN_ATTR_ANALOG;
    (void)GPIO_Init(ADC_CHX_PORT, ADC_CHX_PIN, &stcGpioInit);
    (void)GPIO_Init(ADC_CHY_PORT, ADC_CHY_PIN, &stcGpioInit);
    (void)GPIO_Init(ADC_CHZ_PORT, ADC_CHZ_PIN, &stcGpioInit);
}

⑥设置ADC硬件触发

static void AdcHardTriggerConfig(void)
{
    /************** Hard trigger of sequence A ****************/
    GPIO_SetFunc(ADC_SEQ_TRIG_PORT, ADC_SEQ_TRIG_PIN, ADC_SEQ_TRIG_PIN_FUNC);
    ADC_TriggerConfig(ADC_UNIT, ADC_SEQ, ADC_SEQ_HARDTRIG);
    ADC_TriggerCmd(ADC_UNIT, ADC_SEQ, ENABLE);
}

GPIO_SetFunc(ADC_SEQ_TRIG_PORT, ADC_SEQ_TRIG_PIN, ADC_SEQ_TRIG_PIN_FUNC);此函数作用是将GPIO口设定指定引脚功能,这里需要查看数据手册,通过配置GPIO.PFSRxy(功能选择寄存器)b5~b0进行功能选择,参数1、2为GPIO口,参数3 为对应的功能。

ADC_TriggerConfig(ADC_UNIT, ADC_SEQ, ADC_SEQ_HARDTRIG); 设置了ADC1的序列A 对应的端口在下降沿有效,通过配置ADC_TRGSR(A/D转换开始触发寄存器)中b2~b0。

⑦DMA初始化配置

static void DmaConfig(void)
{
    stc_dma_init_t stcDmaInit;
    stc_dma_repeat_init_t stcDmaRptInit;

    (void)DMA_StructInit(&stcDmaInit);
    stcDmaInit.u32IntEn       = DMA_INT_ENABLE;
    stcDmaInit.u32SrcAddr     = DMA_SRC_ADDR;
    stcDmaInit.u32DestAddr    = DMA_DEST_ADDR;
    stcDmaInit.u32DataWidth   = DMA_DATA_WIDTH;
    stcDmaInit.u32BlockSize   = DMA_BLOCK_SIZE;
    stcDmaInit.u32TransCount  = DMA_TRANS_CNT;
    stcDmaInit.u32SrcAddrInc  = DMA_SRC_ADDR_INC;
    stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_INC;

    /* Enable DMA peripheral clock and AOS function. */
    FCG_Fcg0PeriphClockCmd(DMA_PERIPH_CLK, ENABLE);
    (void)DMA_Init(DMA_UNIT, DMA_CH, &stcDmaInit);

    stcDmaRptInit.u32Mode      = DMA_RPT_BOTH;
    stcDmaRptInit.u32SrcCount  = DMA_BLOCK_SIZE;
    stcDmaRptInit.u32DestCount = DMA_BLOCK_SIZE;
    (void)DMA_RepeatInit(DMA_UNIT, DMA_CH, &stcDmaRptInit);

    /* Enable AOS clock */
    FCG_Fcg0PeriphClockCmd(FCG0_PERIPH_AOS, ENABLE);
    /* Set DMA trigger source */
    AOS_SetTriggerEventSrc(DMA_AOS_TRIG_SEL, DMA_TRIG_EVT);

    /* DMA IRQ configuration. */
    DmaIrqConfig();

    DMA_Cmd(DMA_UNIT, ENABLE);
    DMA_ChCmd(DMA_UNIT, DMA_CH, ENABLE);
}

具体DMA结构体成员说明,在本人发表DMA实验中有详细解析。

AOS_SetTriggerEventSrc(DMA_AOS_TRIG_SEL, DMA_TRIG_EVT); 第一个参数是被触发的外设电路动作,第二个参数是外设电路产生的事件。比如这里:ADC转换完成->DMA传输数据。

⑧DMA中断配置

static void DmaIrqConfig(void)
{
    stc_irq_signin_config_t stcIrqSignConfig;

    stcIrqSignConfig.enIntSrc    = DMA_INT_SRC;
    stcIrqSignConfig.enIRQn      = DMA_INT_IRQn;
    stcIrqSignConfig.pfnCallback = &DMA_IrqCallback;

    (void)INTC_IrqSignIn(&stcIrqSignConfig);
    DMA_ClearTransCompleteStatus(DMA_UNIT, DMA_INT_FLAG);

    /* NVIC setting */
    NVIC_ClearPendingIRQ(DMA_INT_IRQn);
    NVIC_SetPriority(DMA_INT_IRQn, DMA_INT_PRIO);
    NVIC_EnableIRQ(DMA_INT_IRQn);
}

中断详解请看本人发的中断实验文章。

⑨DMA中断回调函数

static void DMA_IrqCallback(void)
{
    DMA_ClearTransCompleteStatus(DMA_UNIT, DMA_INT_FLAG);
    m_u8AdcValUpdated = 1U;
}

⑩主函数

int32_t main(void)
{
    /* System clock is MRC@8MHz */

    /* MCU Peripheral registers write unprotected. */
    LL_PERIPH_WE(LL_PERIPH_GPIO | LL_PERIPH_FCG | LL_PERIPH_PWC_CLK_RMU);
    /* Initializes UART for debug printing. Baudrate is 19200. */
    DDL_PrintfInit(BSP_PRINTF_DEVICE, 19200U, BSP_PRINTF_Preinit);
    /* Configures ADC. */
    AdcConfig();
    /* MCU Peripheral registers write protected. */
    LL_PERIPH_WP(LL_PERIPH_GPIO | LL_PERIPH_FCG | LL_PERIPH_PWC_CLK_RMU);

    /***************** Configuration end, application start **************/

    for (;;) {
        /* Make a falling edge on the specified pin ADTRG to start ADC. */
        if (m_u8AdcValUpdated != 0U) {
            m_u8AdcValUpdated = 0U;
            DDL_Printf("Sequence A DMA transmission completed.\r\n");
            /* User code: Use the ADC value of sequence A. */
            DDL_Printf("The ADC value of potentiometer is %u, voltage is %u mV\r\n", \
                       m_au16AdcValue[PTTM_VAL_IDX], ADC_CAL_VOL(m_au16AdcValue[PTTM_VAL_IDX]));
        }
    }
}

特别声明的是:该实验除了通过PE7触发下降沿,还通过一个if函数 去掉按键抖动。if (m_u8AdcValUpdated != 0U){....}。

ADC+DMA实验代码到此结束了,有什么疑问可以私信我~