打印
[综合信息]

ADC测量模拟量,DMA传输

[复制链接]
3905|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
aoyi|  楼主 | 2021-9-1 08:58 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一、分频设置



主频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);


使用特权

评论回复
沙发
aoyi|  楼主 | 2021-9-1 09:00 | 只看该作者
二、初始化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,连续循环采集。


使用特权

评论回复
板凳
aoyi|  楼主 | 2021-9-1 09:01 | 只看该作者
三、配置采样通道

/*
* 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个周期,初始化完毕。


使用特权

评论回复
地板
aoyi|  楼主 | 2021-9-1 09:04 | 只看该作者
四、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章节的功能描述和应用举例小节太春秋笔法。


使用特权

评论回复
5
aoyi|  楼主 | 2021-9-1 09:07 | 只看该作者
本帖最后由 aoyi 于 2021-9-1 09:13 编辑

五、将前一篇笔记的最后改为使用DMA读取PWM周期

在笔记   

https://bbs.21ic.com/icview-3160662-1-1.html?fromuid=1777418

中,我们使用了TMR6结合中断读取到了PWM,现在改中断为DMA传输。

我们可以同一个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);


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



使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

100

主题

3306

帖子

3

粉丝