一、分频设置
主频HCLK: 200M
PCLK0: 200M
PCLK1: 100M
PCLK2: 50M<60M
PCLK3: 50M
PCLK4: 100M
EX BUS: 100M
stc_clk_pllh_init_t stcPLLHInit;
/* PCLK0, HCLK Max 240MHz, Set 200MHz */
/* PCLK1, PCLK4 Max 120MHz, Set 100MHz */
/* PCLK2, PCLK3 Max 60MHz, Set 50MHz */
/* EX BUS Max 120MHz, Set 100MHz */
CLK_ClkDiv(CLK_CATE_ALL, \
(CLK_PCLK0_DIV1 | CLK_PCLK1_DIV2 | CLK_PCLK2_DIV4 | \
CLK_PCLK3_DIV4 | CLK_PCLK4_DIV2 | CLK_EXCLK_DIV2 | \
CLK_HCLK_DIV1));
(void)CLK_PLLHStrucInit(&stcPLLHInit);
/* VCO = (8/1)*100 = 800MHz*/
stcPLLHInit.u8PLLState = CLK_PLLH_ON;
stcPLLHInit.PLLCFGR = 0UL;
stcPLLHInit.PLLCFGR_f.PLLM = 1UL - 1UL;
stcPLLHInit.PLLCFGR_f.PLLN = 100UL - 1UL;
stcPLLHInit.PLLCFGR_f.PLLP = 4UL - 1UL;
stcPLLHInit.PLLCFGR_f.PLLQ = 4UL - 1UL;
stcPLLHInit.PLLCFGR_f.PLLR = 4UL - 1UL;
stcPLLHInit.PLLCFGR_f.PLLSRC = CLK_PLLSRC_XTAL;
(void)CLK_PLLHInit(&stcPLLHInit);
二、初始化ADC,并开启时钟
static void AdcInitConfig(void)
{
stc_adc_init_t stcInit;
/* Set a default value. */
(void)ADC_StructInit(&stcInit);
/* 1. Modify the default value depends on the application. */
stcInit.u16AutoClrCmd = ADC_AUTO_CLR_ENABLE;
stcInit.u16DataAlign = ADC_DATA_ALIGN_RIGHT;
stcInit.u16Resolution = ADC_RESOLUTION_12BIT;
stcInit.u16ScanMode = ADC_MODE_SA_CONT;
/* 2. Enable ADC peripheral clock. */
PWC_Fcg3PeriphClockCmd(APP_ADC_PERIP_CLK, Enable);
/* 3. Initializes ADC. */
(void)ADC_Init(APP_ADC_UNIT, &stcInit);
}
- 自动清除,指被DMA读取完转换数据(ADC_DRx)后,ADC_DRx将被自动清除。
- 右对齐。
- 12位采样分辨率。
- 序列A连续扫描。
HC32F4A0每个ADC可以配置两个序列,A和B。每个序列内可以包含若干采样通道,序列B可以打断序列A。详见用户手册ADC章节。本例一共采集8个模拟量,使用8个通道ch0~ch7,全部配置为序列A,连续循环采集。
三、配置采样通道
/*
* Add the channels which were included in sequence A or sequence B to average channel if needed.
* The average channels will be sampled a specified number of times(specified by 'APP_ADC_AVG_CNT'),\
* and the final ADC value is the average of the specified number of samples.
* Define 'APP_ADC_AVG_CH' as 0 to disable average channel.
*/
#define APP_ADC_UNIT (M4_ADC1)
#define APP_ADC_PERIP_CLK (PWC_FCG3_ADC1)
/*
* Specifies the ADC channels according to the application.
* NOTE!!! Sequence A and sequence B CANNOT contain the same channel.
*/
#define APP_ADC_REMAP_CH (ADC_CH0 | ADC_CH1 | ADC_CH2 | \
ADC_CH3 | ADC_CH4 | ADC_CH5 | \
ADC_CH6 | ADC_CH7)
/* Sampling time of ADC channels. */
#define APP_ADC_SA_SAMPLE_TIME { 30,30,30,30,30,30,30,30 }
static void AdcChannelConfig(void)
{
uint8_t au8AdcSASplTime[] = APP_ADC_SA_SAMPLE_TIME;
/* 0. Remap the correspondence between channels and analog input pins. */
ADC_ChannelRemap(APP_ADC_UNIT, ADC_CH0, ADC12_IN4);
ADC_ChannelRemap(APP_ADC_UNIT, ADC_CH1, ADC12_IN5);
ADC_ChannelRemap(APP_ADC_UNIT, ADC_CH2, ADC12_IN6);
ADC_ChannelRemap(APP_ADC_UNIT, ADC_CH3, ADC12_IN7);
ADC_ChannelRemap(APP_ADC_UNIT, ADC_CH4, ADC12_IN8);
ADC_ChannelRemap(APP_ADC_UNIT, ADC_CH5, ADC12_IN9);
ADC_ChannelRemap(APP_ADC_UNIT, ADC_CH6, ADC12_IN14);
ADC_ChannelRemap(APP_ADC_UNIT, ADC_CH7, ADC12_IN15);
/* 1. Set the ADC pin to analog input mode. */
AdcSetChannelPinAnalogMode(APP_ADC_UNIT, APP_ADC_REMAP_CH);
/* 2. Enable the ADC channels. */
(void)ADC_ChannelCmd(APP_ADC_UNIT, ADC_SEQ_A, \
APP_ADC_REMAP_CH, au8AdcSASplTime, \
Enable);
}
采样时间定为30个周期,可以根据实际情况进行设置
本例中的8个模拟量分别使用PA4-7, PB0-1, PC4-5引脚,分别对应ADC的默认通道ADC12_4-9,以及ADC12_14和ADC12_15,通道号不连续。HC32F4A0支持通道重映射,本例使用该功能将8个模拟量输入物理针脚映射成通道ADC12_0-7。(注:ADC12表示只可使用ADC1和ADC2,不可使用ADC3)
使用的函数为:
void ADC_ChannelRemap(M4_ADC_TypeDef *ADCx, uint32_t u32AdcCh, uint8_t u8AdcPinNum)
配置输入引脚的功能为模拟量输入(可以直接使用库函数逐一配置,但是请注意引脚的重映射。官方例程里面提供了实现该功能的通用函数,本例直接使用官方例程):
static void AdcSetChannelPinAnalogMode(const M4_ADC_TypeDef *ADCx, uint32_t u32Channel)
{
uint8_t u8PinNum;
#if (defined APP_ADC_REMAP_CH)
uint8_t u8RemapPinNum;
#endif
u8PinNum = 0U;
while (u32Channel != 0U)
{
if ((u32Channel & 0x1UL) != 0UL)
{
#if (defined APP_ADC_REMAP_CH)
u8RemapPinNum = ADC_GetChannelPinNum(ADCx, (0x1UL << u8PinNum));
AdcSetPinAnalogMode(ADCx, u8RemapPinNum);
#else
AdcSetPinAnalogMode(ADCx, u8PinNum);
#endif
}
u32Channel >>= 1U;
u8PinNum++;
}
}
static void AdcSetPinAnalogMode(const M4_ADC_TypeDef *ADCx, uint8_t u8PinNum)
{
typedef struct
{
uint8_t u8Port;
uint16_t u16Pin;
} stc_adc_pin_t;
stc_gpio_init_t stcGpioInit;
stc_adc_pin_t astcADC12[ADC1_CH_COUNT] = { \
{GPIO_PORT_A, GPIO_PIN_00}, {GPIO_PORT_A, GPIO_PIN_01}, \
{GPIO_PORT_A, GPIO_PIN_02}, {GPIO_PORT_A, GPIO_PIN_03}, \
{GPIO_PORT_A, GPIO_PIN_04}, {GPIO_PORT_A, GPIO_PIN_05}, \
{GPIO_PORT_A, GPIO_PIN_06}, {GPIO_PORT_A, GPIO_PIN_07}, \
{GPIO_PORT_B, GPIO_PIN_00}, {GPIO_PORT_B, GPIO_PIN_01}, \
{GPIO_PORT_C, GPIO_PIN_00}, {GPIO_PORT_C, GPIO_PIN_01}, \
{GPIO_PORT_C, GPIO_PIN_02}, {GPIO_PORT_C, GPIO_PIN_03}, \
{GPIO_PORT_C, GPIO_PIN_04}, {GPIO_PORT_C, GPIO_PIN_05}, \
};
stc_adc_pin_t astcADC3[ADC3_CH_COUNT] = { \
{GPIO_PORT_A, GPIO_PIN_00}, {GPIO_PORT_A, GPIO_PIN_01}, \
{GPIO_PORT_A, GPIO_PIN_02}, {GPIO_PORT_A, GPIO_PIN_03}, \
{GPIO_PORT_F, GPIO_PIN_06}, {GPIO_PORT_F, GPIO_PIN_07}, \
{GPIO_PORT_F, GPIO_PIN_08}, {GPIO_PORT_F, GPIO_PIN_09}, \
{GPIO_PORT_F, GPIO_PIN_10}, {GPIO_PORT_F, GPIO_PIN_03}, \
{GPIO_PORT_C, GPIO_PIN_00}, {GPIO_PORT_C, GPIO_PIN_01}, \
{GPIO_PORT_C, GPIO_PIN_02}, {GPIO_PORT_C, GPIO_PIN_03}, \
{GPIO_PORT_F, GPIO_PIN_04}, {GPIO_PORT_F, GPIO_PIN_05}, \
{GPIO_PORT_H, GPIO_PIN_02}, {GPIO_PORT_H, GPIO_PIN_03}, \
{GPIO_PORT_H, GPIO_PIN_04}, {GPIO_PORT_H, GPIO_PIN_05}, \
};
(void)GPIO_StructInit(&stcGpioInit);
stcGpioInit.u16PinAttr = PIN_ATTR_ANALOG;
if ((ADCx == M4_ADC1) || (ADCx == M4_ADC2))
{
(void)GPIO_Init(astcADC12[u8PinNum].u8Port, astcADC12[u8PinNum].u16Pin, &stcGpioInit);
}
else
{
(void)GPIO_Init(astcADC3[u8PinNum].u8Port, astcADC3[u8PinNum].u16Pin, &stcGpioInit);
}
}
这样,我们把重映射后的8个通道全部放入序列A,每个通道均配置30个周期,初始化完毕。
四、DMA配置
一、开启时钟,设置触发源
华大半导体的HC32F4A0和国内惯用的STM32F4xx相比,其中一个显著的设计区别是HC32F4A0有一个AOS外设,用于硬件之间的联动。详见用户手册第11章及相关章节。
本例涉及到的ADC和DMA的联动功能也集成在该外设中。——而我们知道STM32和DMA联动的外设都设有专门的寄存器,ADC想要使用DMA就得配置ADC外设中与DMA联动的寄存器——由于使用了DMA和AOC两个外设,我们需要把它们的时钟都开启。然后开启“ADC传输完毕->DMA开始传输”联动。
/*
* 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 ADC_DMA_UNIT (M4_DMA2)
#define ADC_DMA_CH (DMA_CH0)
#define ADC_DMA_PERIP_CLK (PWC_FCG0_DMA2)
#define ADC_DMA_BLOCK_SIZE (8U)
#define ADC_DMA_TRANS_COUNT (1U)
#define ADC_DMA_DATA_WIDTH (DMA_DATAWIDTH_16BIT)
#define ADC_DMA_TRIG_SRC (EVT_ADC1_EOCA)
#define ADC_DMA_SRC_ADDR (&M4_ADC1->DR0)
/*******************************************************************************
* Local variable definitions ('static')
******************************************************************************/
static uint16_t m_au16AdcSaVal[ADC_DMA_BLOCK_SIZE];
/* Enable DMA peripheral clock and AOS function. */
PWC_Fcg0PeriphClockCmd((ADC_DMA_PERIP_CLK | PWC_FCG0_AOS), Enable);
DMA_SetTriggerSrc(ADC_DMA_UNIT, ADC_DMA_CH, ADC_DMA_TRIG_SRC);
下面进行DMA初始化配置:
stc_dma_init_t stcDmaInit;
(void)DMA_StructInit(&stcDmaInit);
stcDmaInit.u32IntEn = DMA_INT_DISABLE;
stcDmaInit.u32BlockSize = ADC_DMA_BLOCK_SIZE;
stcDmaInit.u32TransCnt = ADC_DMA_TRANS_COUNT;
stcDmaInit.u32DataWidth = ADC_DMA_DATA_WIDTH;
stcDmaInit.u32DestAddr = (uint32_t)(&m_au16AdcSaVal[0U]);
stcDmaInit.u32SrcAddr = (uint32_t)ADC_DMA_SRC_ADDR;
stcDmaInit.u32SrcInc = DMA_SRC_ADDR_INC;
stcDmaInit.u32DestInc = DMA_DEST_ADDR_INC;
(void)DMA_Init(ADC_DMA_UNIT, ADC_DMA_CH, &stcDmaInit);
- 不开启DMA中断
- 数据块大小为8,表示8个通道即8个模拟量。
- 传输次数为1
- 每个数据位宽为16位
- 目标起始地址
- 源起始地址
- 目标地址自增
- 源目标地址自增
HC32F4A0每个通道都有对应的数据寄存器,且地址连续,而STM32每个ADC就只有一个数据寄存器。所以这里源地址和目标地址同步自增。
这样在前面第三节我们重映射通道的意义就体现了出来。前面映射到了CH0-7,这样数据寄存器的地址就是连续的,配置DMA就方便的多。
按理后面只要开启DMA通道和DMA使能,最后再ADC使能就可以工作了。
DMA_Cmd(ADC_DMA_UNIT, Enable);
DMA_ChannelCmd(ADC_DMA_UNIT, ADC_DMA_CH, Enable);
ADC_Start(APP_ADC_UNIT);
DMA由ADC触发,ADC序列A转换完毕后,触发DMA传输,DMA传输一次后停止,等待下一次触发。但是在实际运行中,DMA传完一次后就不传了。
官方例程使用DMA时,在其初始化后还有以下一段代码
(void)DMA_RepeatStructInit(&stcDmaRptInit);
stcDmaRptInit.u32SrcRptEn = DMA_SRC_RPT_ENABLE;
stcDmaRptInit.u32SrcRptSize = ADC_DMA_BLOCK_SIZE;
stcDmaRptInit.u32DestRptEn = DMA_DEST_RPT_ENABLE;
stcDmaRptInit.u32DestRptSize = ADC_DMA_BLOCK_SIZE;
(void)DMA_RepeatInit(ADC_DMA_UNIT, ADC_DMA_CH, &stcDmaRptInit);
并且
#define ADC_DMA_TRANS_COUNT (0U)
对比用户手册,发现该段代码主要操作了下列寄存器这些位:
且初始化时传输次数被设为0。
测试发现可以连续传输了。用户手册在DMA章节的功能描述和应用举例小节太春秋笔法。
五、将前一篇笔记的最后改为使用DMA读取PWM周期
上一篇笔记中,我们使用了TMR6结合中断读取到了PWM,现在改中断为DMA传输。
华大半导体HC32F4A0笔记(一),PWM输入捕获,使用TIM6
我们可以同一个DMA即DMA2,但是通道需要改一个,ADC使用了通道0,这里我们就使用通道1。
#define TMR6_2_DMA_UNIT (M4_DMA2)
#define TMR6_2_DMA_CH (DMA_CH1)
由于已经启动了DMA2的时钟了,这里就不用再重复开启。我们直接配置开启“TMR6_2捕获完毕->DMA开始传输”联动。
// 激活DMA的EVT_TMR6_2_GCMA 事件号对应激活NVIC的INT_TMR6_2_GCMA
#define TMR6_4_DMA_TRIG_SRC (EVT_TMR6_2_GCMA)
DMA_SetTriggerSrc(TMR6_2_DMA_UNIT, TMR6_2_DMA_CH, TMR6_2_DMA_TRIG_SRC);
然后初始化DMA2的通道1
#define TMR6_4_DMA_DATA_WIDTH (DMA_DATAWIDTH_32BIT)
uint32_t TRM62_GCMA;
(void)DMA_StructInit(&stcDmaInit);
stcDmaInit.u32IntEn = DMA_INT_DISABLE;
stcDmaInit.u32BlockSize = 1;
stcDmaInit.u32TransCnt = 0;
stcDmaInit.u32DataWidth = TMR6_2_DMA_DATA_WIDTH;
stcDmaInit.u32DestAddr = (uint32_t)(&TRM62_GCMA);
stcDmaInit.u32SrcAddr = (uint32_t)TMR6_2_DMA_SRC_ADDR;
stcDmaInit.u32SrcInc = DMA_SRC_ADDR_FIX;
stcDmaInit.u32DestInc = DMA_DEST_ADDR_FIX;
(void)DMA_Init(TMR6_2_DMA_UNIT, TMR6_2_DMA_CH, &stcDmaInit);
- 不开启DMA中断
- BlockSize为1,就一个数据
- 无限次传输
- 32位数据传输,ADC是12位的,但我们地址不增加,所以32位16位都可以
- 目标起始地址
- 源起始地址
- 目标地址不变
- 源目标地址不变
本例不需要配置DMA_RepeatInit()
,因为目标地址和源地址都固定不变。
由于PWM2已经使能,这里只需要使能通道即可。
DMA_ChannelCmd(M4_DMA2, TMR6_2_DMA_CH, Enable);
原文中第八节和第九节的代码均可注释掉。