华大半导体HC32F4A0笔记(二),ADC测量模拟量,DMA传输

本文详细介绍了HC32F4A0单片机的ADC与DMA配置过程,包括分频设置、ADC初始化、采样通道配置及DMA配置等步骤。通过实例演示如何使用DMA进行ADC数据读取。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、分频设置

在这里插入图片描述
主频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);

原文中第八节和第九节的代码均可注释掉。

### HC32F4A0开发板概述 #### 硬件规格 HC32F4A0是一款由华大半导体有限公司设计的高性能微控制器,采用ARM Cortex-M4内核。该系列MCU集成了丰富的外设资源和高速处理能力,在工业控制、消费电子等领域有广泛应用。 具体到硬件配置方面: - **处理器核心**: ARM®Cortex®-M4, 支持浮点运算单元(FPU),最高工作频率可达180MHz[^1]。 - **闪存容量**: 内置高达512KB Flash程序存储空间;外部扩展支持最大至8MB SPI NOR Flash (如W25Q64)[^2]。 - **RAM大小**: 配备192KB SRAM用于数据缓存与运行时变量保存。 - **通信接口**: 提供多种串行通讯方式包括USART/SPI/IIC/CAN等标准协议模块。 - **定时器/计数器**: 多通道通用PWM发生器以及高级矢量电机控制系统专用TIMers。 - **ADC/DAC性能**: 12位精度模拟输入转换电路(最多可选配多达21路) 和两个独立DAC输出通道。 - **其他特性**: 还拥有低功耗模式切换机制、温度传感器集成等功能特色。 对于外部Flash W25Q64而言,其主要参数如下: - 存储密度:8 Mbit(即1 MB) - ID标识:“EF16” - 组织结构:每页256字节,每扇区4 KB,每块64 KB #### 使用教程 针对初学者来说,《F4A0手把手教程》提供了一套完整的指南来帮助理解和掌握HC32F4A0 MCU的应用开发流程。以下是创建新工程项目的关键步骤摘要: 1. 下载并安装官方IDE环境; 2. 解压缩`HC32F4A0_SDK`软件包内的DDL库(`hc32f4a0_ddl_Rev1.4.0.zip`); 3. 将解压后的`driver`目录下的全部文件拷贝至指定路径`\Lib\hc32f4a0_ddl_Rev1.4.0`下以便后续调用API函数进行编程操作。 此外,为了更好地利用内置或外接组件完成特定任务(比如读取/写入SPI Flash),可以参考具体的案例研究文档进一步学习实际应用场景中的编码技巧。 ```python import spi_flash as sf def read_id(): id_data = bytearray([0x9F]) response = sf.spi_transfer(id_data) manufacturer_id = hex(response[1]) + hex(response[2])[2:] + hex(response[3])[2:] print(f"The Manufacturer ID is {manufacturer_id.upper()}") ``` 此段Python伪代码展示了如何通过SPI总线发送命令序列给目标设备以获取制造商唯一识别码的方法之一。 #### 购买渠道 目前市场上可以通过多个平台采购HC32F4A0相关产品和服务: - 官方网站直接下单订购评估套件或者批量生产所需物料清单; - 合作分销商处查询库存状态并提交订单请求; - 第三方电商平台搜索对应型号的商品链接完成在线支付过程。 建议优先考虑授权代理机构所提供的正品保障服务,确保所购得的产品质量可靠且售后服务完善。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值