声明:以下内容为个人学习心得。
一.基础知识
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实验代码到此结束了,有什么疑问可以私信我~