打印
[APM32F1]

《APM32 ADC多路采集+DMA传输应用实例》part2

[复制链接]
1289|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
JunKook|  楼主 | 2022-4-28 15:53 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
#技术资源# 4. DMA初始化
void DMA_Init(void)
{
DMA_Config_T DMA_ConfigStruct;

DMA_Reset(DMA1_Channel1); /* 复位DMA1通道1 */

DMA_ConfigStruct.peripheralBaseAddr = ADC1_DR_Address; /* DMA1通道1外设基地址 */
DMA_ConfigStruct.memoryBaseAddr = (uint32_t)dma_buffer; /* DMA1通道1 ADC1数据存储器 */
DMA_ConfigStruct.dir = DMA_DIR_PERIPHERAL_SRC; /* 指定外设为源地址 */
DMA_ConfigStruct.bufferSize = 4; /* DMA1通道1缓冲区大小(根据ADC1采集通道数量修改) */
DMA_ConfigStruct.peripheralInc = DMA_PERIPHERAL_INC_DISABLE; /* 当前外设寄存器地址不变(即不自增) */
DMA_ConfigStruct.memoryInc = DMA_MEMORY_INC_ENABLE; /* 当前存储器地址:Disable不变,Enable递增(用于多通道采集) */
DMA_ConfigStruct.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD; /* 外设数据宽度16位 */
DMA_ConfigStruct.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD; /* 存储器数据宽度16位 */
DMA_ConfigStruct.loopMode = DMA_MODE_CIRCULAR; /* DMA1通道1操作模式位环形缓冲模式 */
DMA_ConfigStruct.priority = DMA_PRIORITY_HIGH; /* DMA1通道1优先级高 */
DMA_ConfigStruct.M2M = DMA_M2MEN_DISABLE; /* 禁止DMA1通道1存储器到存储器传输 */
DMA_Config(DMA1_Channel1, &DMA_ConfigStruct);

//DMA_EnableInterrupt(DMA1_Channel1, DMA_INT_TC);
DMA_Enable(DMA1_Channel1);
}
        通过查阅用户手册可知,ADC1的采集数据是通过DMA1的通道1来搬运,所以这里对DMA1的通道1进行初始化配置。
        DMA1通道1配置参数说明:
        第1个参数(peripheralBaseAddr):用来设置DMA传输的起始地址,这里是ADC1外设数据寄存器的基地址;
        第2个参数(memoryBaseAddr):为内存基地址,也就是我们存放DMA传输数据的内存地址,为自定义的存储buffer;
        第3个参数(dir):设置数据传输方向,决定是从外设读取数据到内存,还是从内存读取数据发送到外设,这里是从外设读取数据到内存;
        第4个参数(bufferSize):设置一次传输数据量的大小,可以理解成buffer的大小;
        第5个参数(peripheralInc):设置传输数据的时候外设地址是不变还是递增,这里设置为不变,因为都是从ADC1的数据寄存器中搬运;
        第6个参数(memoryInc):设置传输数据时候内存地址是否递增,意思同上,这里设置为递增,数组地址依次递增保存ADC的数据;
        第7个参数(peripheralDataSize):设置外设的数据长度是为字节传输(8bits),半字传输 (16bits) 还是字传输 (32bits);
        第8个参数(memoryDataSize)设置内存的数据长度,外设与内存数据宽度应为一样;
        第9个参数(loopMode):设置DMA模式是否循环采集,DMA传输模式为循环传输,因为ADC配置的是连续循环模式,所以DMA也是循环模式;
        第10个参数(priority):设置 DMA 通道的优先级,如果不使能DMA中断,随便设置一个即可;
        第11个参数(M2M):设置是否是存储器到存储器模式传输,由于这里是从外设读取数据到内存,所以选择DMA_M2M_Disable。


5. ADC初始化
ADC_Config_T ADC_configStruct;
ADC_Reset(ADC1); /* 复位ADC1 */
/** ADC1 Configuration */
ADC_configStruct.mode = ADC_MODE_INDEPENDENT; /* ADC1工作在独立模式 */
ADC_configStruct.scanConvMode = ENABLE; /* 使能扫描 */
ADC_configStruct.continuosConvMode = ENABLE; /* 使能ADC连续转换模式 轮询方式使用*/
// ADC_configStruct.continuosConvMode = DISABLE; /* 不使能ADC连续转换模式 中断方式使用*/
ADC_configStruct.externalTrigConv = ADC_EXT_TRIG_CONV_None; /* 软件控制转换 */
ADC_configStruct.dataAlign = ADC_DATA_ALIGN_RIGHT; /* 转换数据右对齐 */
ADC_configStruct.nbrOfChannel = 4; /* 顺序进行规则转换的ADC通道的数目 */
ADC_Config(ADC1, &ADC_configStruct); /* 初始化ADC1寄存器 */
        ADC配置参数说明:
        第1个参数(mode):设置ADC的模式,ADC的模式非常多,包括独立模式,注入同步模式等等,这里我们选择独立模式;
  第2个参数 (scanConvMode):设置是否使能扫描模式,这里使能扫描,因为是多通道采集,采集完一个通道后自动转换到下一个通道采集;
  第3个参数 (continuosConvMode ):设置是否使能连续转换模式,即最后一个通道转换完后自动又从第一个通道开始转换,建议在中断模式下不使能;
  第4个参数 (externalTrigConv ):设置启动规则转换组转换的外部事件,这里我们选择软件触发;
  第5个参数 (dataAlign):设置 ADC 数据对齐方式是左对齐还是右对齐,这里我们选择右对齐方式;
  第6个参数 (nbrOfChannel ):用来设置规则序列的长度,也就是使能了几个采集通道,本例使能了4个。

/* 设置指定ADC的规则组通道,设置它们的转化顺序和采样时间 */
ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_10, 1, ADC_SAMPLETIME_239CYCLES5); /* ADC1选择通道10 采样顺序1 采样时间13.5个周期 */
ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_11, 2, ADC_SAMPLETIME_239CYCLES5); /* ADC1选择通道11 采样顺序2 采样时间13.5个周期 */
ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_12, 3, ADC_SAMPLETIME_239CYCLES5); /* ADC1选择通道12 采样顺序3 采样时间13.5个周期 */
ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_13, 4, ADC_SAMPLETIME_239CYCLES5); /* ADC1选择通道13 采样顺序4 采样时间13.5个周期 */

// ADC_EnableInterrupt(ADC1, ADC_INT_EOC); /* 使能ADC转换完成中断 */
ADC_EnableDMA(ADC1); /* 使能ADC的DMA支持 */
ADC_Enable(ADC1); /* 使能ADC1 */

ADC_ResetCalibration(ADC1); /* 复位ADC1的校准寄存器 */
while(ADC_ReadResetCalibrationStatus(ADC1)); /* 等待ADC1复位校准完成 */
ADC_StartCalibration(ADC1); /* 开始ADC1校准 */
while(ADC_ReadCalibrationStartFlag(ADC1)); /* 等待ADC1校准完成 */

ADC_EnableSoftwareStartConv(ADC1); /* 启动ADC1转换 */
        下面介绍几个API函数的应用:
        1、ADC_ConfigRegularChannel()用于设置采样顺序和采样周期的,本例我们配置ADC1通道10,11,12,13的采样顺序依次为1,2,3,4;
        2、ADC_EnableDMA()用于使能ADC使能DMA传输,这里必须使能;
        3、ADC_Enable()用于使能ADC;
        4、ADC_ResetCalibration()和ADC_StartCalibration()用于复位ADC校准和启动ADC校准,在配置好ADC后,必须校准ADC,否则ADC采样数据可能会不准确;
        5、ADC_EnableSoftwareStartConv()用于触发ADC采样转换,前面我们配置了由软件触发转换。

        至此,ADC多路采集并使能DMA传输的配置完成,附件提供完整的配置代码,可直接复制到我司SDK的ADC例程中使用。


main.rar

2.83 KB

使用特权

评论回复
沙发
nnqtdf| | 2022-11-19 15:37 | 只看该作者
本帖最后由 nnqtdf 于 2022-11-19 15:38 编辑
#define ADC1_DR_Address ((uint32_t)0x40012400+0x4c) /* ADC1数据寄存器地址(ADC基地址+偏移) */

__IO uint16_t ADC_ConvertedValue[ADC_CH_SIZE];
extern SemaphoreHandle_t  adc1_dma_Semaphore;        //二值信号量句柄

/*!
* [url=home.php?mod=space&uid=247401]@brief[/url]       DMA Init
*
* @param       None
*
* @retval      None
*/
void ADC_DMA_Config(void)
{
    /** DMA Configure */
    DMA_Config_T dmaConfig;

    /** Enable DMA clock */
    RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1);

        DMA_Disable(DMA1_Channel1);//关DMA通道
        DMA_Reset(DMA1_Channel1);//恢复缺省值
    /** size of buffer*/
    dmaConfig.bufferSize = ADC_CH_SIZE;
    /** set memory Data Size*/
    dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
    /** Set peripheral Data Size*/
    dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
    /** Enable Memory Address increase*/
    dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE;
    /** Disable Peripheral Address increase*/
    dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
    /** Reset Circular Mode*/
    dmaConfig.loopMode = DMA_MODE_NORMAL;
    /** set priority*/
    dmaConfig.priority = DMA_PRIORITY_HIGH;
    /** read from peripheral*/
    dmaConfig.dir = DMA_DIR_PERIPHERAL_SRC;
    /** Set memory Address*/
    dmaConfig.memoryBaseAddr = (uint32_t)ADC_ConvertedValue;
    /** Set Peripheral Address*/
    dmaConfig.peripheralBaseAddr = ADC1_DR_Address;
        dmaConfig.M2M = DMA_M2MEN_DISABLE;
    DMA_Config(DMA1_Channel1, &dmaConfig);
        
        NVIC_EnableIRQRequest(DMA1_Channel1_IRQn, 4, 0);
        
        DMA_ClearStatusFlag(DMA1_FLAG_TC1);
        DMA_EnableInterrupt(DMA1_Channel1, DMA_INT_TC);
    DMA_Enable(DMA1_Channel1);
}

/*
这里默认APB2的CLK PCLK2为60MHz。那么当设置ADC分辨率为12bits,ADCCLK = PCLK2 / 2 = 30MHz时。结合前述“转换时间”章节中讲到的计算方式,可以得知单次
ADC转换时间 = 采样周期 + 转换周期
          = 3 x ADCCLK + 12 x ADCCLK
          = 15 ADC CLK
          = 15 / 30MHz
          = 0.5 us
每次ADC转换间隔为20 x ADCCLK。
*/
/*!
* [url=home.php?mod=space&uid=247401]@brief[/url]       ADC Init
*
* @param       None
*
* @retval      None
*/
void ADC_ADC_Config(void)
{
    GPIO_Config_T gpioConfig;
    ADC_Config_T  ADC_configStruct;

    /** RCM Enable*/
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);
        RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);
        RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOC);
        
        RCM_ConfigADCCLK(RCM_PCLK2_DIV_6);
        
    /** GPIO Configuration */
    GPIO_ConfigStructInit(&gpioConfig);
    gpioConfig.pin = GPIO_PIN_1;
    gpioConfig.mode = GPIO_MODE_ANALOG;        
    GPIO_Config(GPIOA, &gpioConfig);

    gpioConfig.pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
    gpioConfig.mode = GPIO_MODE_ANALOG;
    GPIO_Config(GPIOC, &gpioConfig);        
        
        
        ADC_Reset(ADC1);
        ADC_EnableTempSensorVrefint(ADC1);//开启内部温度传感器
        
    /* ADC1 Configuration */
    ADC_configStruct.mode = ADC_MODE_INDEPENDENT;
    ADC_configStruct.scanConvMode = ENABLE;
    ADC_configStruct.continuosConvMode = DISABLE;
    ADC_configStruct.externalTrigConv = ADC_EXT_TRIG_CONV_None;
    ADC_configStruct.dataAlign = ADC_DATA_ALIGN_RIGHT;
    ADC_configStruct.nbrOfChannel = ADC_CH_SIZE;
    ADC_Config(ADC1, &ADC_configStruct);
        
        ADC_EnableAutoInjectedConv(ADC1);
        
    /* ADC1 regular channel14 configuration */
    ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_10, 1, ADC_SAMPLETIME_239CYCLES5);//
    ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_11, 2, ADC_SAMPLETIME_239CYCLES5);//
    ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_12, 3, ADC_SAMPLETIME_239CYCLES5);//
        ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_13, 4, ADC_SAMPLETIME_239CYCLES5);//
        ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_1, 5, ADC_SAMPLETIME_239CYCLES5);//
        ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_16, 6, ADC_SAMPLETIME_239CYCLES5);//
        
        ADC_EnableDMA(ADC1);
    /* Enable ADC1 */
    ADC_Enable(ADC1);

    /* Enable ADC1 reset calibration register */
    ADC_ResetCalibration(ADC1);
    /* Check the end of ADC1 reset calibration register */
    while(ADC_ReadResetCalibrationStatus(ADC1));

    /* Start ADC1 calibration */
    ADC_StartCalibration(ADC1);
    /* Check the end of ADC1 calibration */
    while(ADC_ReadCalibrationStartFlag(ADC1));
        
        ADC_EnableSoftwareStartConv(ADC1); /* 启动ADC1转换 */

}

void  DMA1_Channel1_IRQHandler(void)// 使用DMA中断采集数据,不会容易丢失数据
{
        BaseType_t xHigherPriorityTaskWoken;

        printf("DMA1_Channel1_IRQHandler!\r\n");
        
        if(DMA_ReadIntFlag(DMA1_INT_FLAG_TC1)!=RESET)
        {
                DMA_ClearIntFlag(DMA1_INT_FLAG_TC1);
                DMA_Disable(DMA1_Channel1);
                if(adc1_dma_Semaphore!=NULL)//二值信号量有效,在这里向ADC计算任务释放二值信号量
                {
                        xSemaphoreGiveFromISR(adc1_dma_Semaphore,&xHigherPriorityTaskWoken);        //释放二值信号量
                        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
                }
        }
}

/*启动ADC转换*/
void ADC_Start(void)
{
        DMA_Enable(DMA1_Channel1);
    /* Start ADC1 Software Conversion */
    ADC_EnableSoftwareStartConv(ADC1);
}

/*ADc初始化*/
void ADC_init(void)
{
        ADC_DMA_Config();
        ADC_ADC_Config();        
        
}
这段代码,进不去dma中断,请问是哪里问题?

使用特权

评论回复
板凳
nnqtdf| | 2022-11-19 15:56 | 只看该作者
找到原因了,dmaConfig.loopMode = DMA_MODE_CIRCULAR;

使用特权

评论回复
地板
1988020566| | 2022-12-1 16:55 | 只看该作者
这个多路ADC之间存在串扰吗              

使用特权

评论回复
5
JunKook|  楼主 | 2022-12-1 18:40 | 只看该作者
1988020566 发表于 2022-12-1 16:55
这个多路ADC之间存在串扰吗

不会的,多路ADC通道分配好采样顺序后,采样时就会按设置的顺序依次采样,不会相互产生干扰

使用特权

评论回复
6
earlmax| | 2022-12-1 19:33 | 只看该作者
使用DMA采样还是比较便利。              

使用特权

评论回复
7
caigang13| | 2022-12-1 19:45 | 只看该作者
DMA用好了,能够大大提升程序效率。

使用特权

评论回复
8
tifmill| | 2022-12-1 20:03 | 只看该作者
怎么实现ADC用定时器启动转换呢、

使用特权

评论回复
9
JunKook|  楼主 | 2022-12-3 15:07 | 只看该作者
tifmill 发表于 2022-12-1 20:03
怎么实现ADC用定时器启动转换呢、

请参考这篇推文哈,https://bbs.21ic.com/icview-3264710-1-1.html

使用特权

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

本版积分规则

认证:极海半导体
简介:珠海极海半导体有限公司是一家致力于开发工业级/车规级微控制器、模拟与混合信号IC及系统级芯片的集成电路设计型企业。极海团队拥有20年集成电路设计经验和嵌入式系统开发能力,可为客户提供核心可靠的芯片产品及方案,实现准确感应、安全传输和实时控制,助力客户在智慧家居、高端消费电子、工业控制、汽车电子、智慧能源以及通信设施等领域的拓展创新。

15

主题

55

帖子

1

粉丝