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

[复制链接]
2100|8
 楼主| JunKook 发表于 2022-4-28 15:53 | 显示全部楼层 |阅读模式
#技术资源# 4. DMA初始化
  1. void DMA_Init(void)
  2. {
  3. DMA_Config_T DMA_ConfigStruct;

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

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

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

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

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

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

  13. 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, 下载次数: 28

nnqtdf 发表于 2022-11-19 15:37 | 显示全部楼层
本帖最后由 nnqtdf 于 2022-11-19 15:38 编辑
  1. #define ADC1_DR_Address ((uint32_t)0x40012400+0x4c) /* ADC1数据寄存器地址(ADC基地址+偏移) */

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

  4. /*!
  5. * [url=home.php?mod=space&uid=247401]@brief[/url]       DMA Init
  6. *
  7. * @param       None
  8. *
  9. * @retval      None
  10. */
  11. void ADC_DMA_Config(void)
  12. {
  13.     /** DMA Configure */
  14.     DMA_Config_T dmaConfig;

  15.     /** Enable DMA clock */
  16.     RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA1);

  17.         DMA_Disable(DMA1_Channel1);//关DMA通道
  18.         DMA_Reset(DMA1_Channel1);//恢复缺省值
  19.     /** size of buffer*/
  20.     dmaConfig.bufferSize = ADC_CH_SIZE;
  21.     /** set memory Data Size*/
  22.     dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
  23.     /** Set peripheral Data Size*/
  24.     dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
  25.     /** Enable Memory Address increase*/
  26.     dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE;
  27.     /** Disable Peripheral Address increase*/
  28.     dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
  29.     /** Reset Circular Mode*/
  30.     dmaConfig.loopMode = DMA_MODE_NORMAL;
  31.     /** set priority*/
  32.     dmaConfig.priority = DMA_PRIORITY_HIGH;
  33.     /** read from peripheral*/
  34.     dmaConfig.dir = DMA_DIR_PERIPHERAL_SRC;
  35.     /** Set memory Address*/
  36.     dmaConfig.memoryBaseAddr = (uint32_t)ADC_ConvertedValue;
  37.     /** Set Peripheral Address*/
  38.     dmaConfig.peripheralBaseAddr = ADC1_DR_Address;
  39.         dmaConfig.M2M = DMA_M2MEN_DISABLE;
  40.     DMA_Config(DMA1_Channel1, &dmaConfig);
  41.         
  42.         NVIC_EnableIRQRequest(DMA1_Channel1_IRQn, 4, 0);
  43.         
  44.         DMA_ClearStatusFlag(DMA1_FLAG_TC1);
  45.         DMA_EnableInterrupt(DMA1_Channel1, DMA_INT_TC);
  46.     DMA_Enable(DMA1_Channel1);
  47. }

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

  68.     /** RCM Enable*/
  69.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);
  70.         RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);
  71.         RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOC);
  72.         
  73.         RCM_ConfigADCCLK(RCM_PCLK2_DIV_6);
  74.         
  75.     /** GPIO Configuration */
  76.     GPIO_ConfigStructInit(&gpioConfig);
  77.     gpioConfig.pin = GPIO_PIN_1;
  78.     gpioConfig.mode = GPIO_MODE_ANALOG;        
  79.     GPIO_Config(GPIOA, &gpioConfig);

  80.     gpioConfig.pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
  81.     gpioConfig.mode = GPIO_MODE_ANALOG;
  82.     GPIO_Config(GPIOC, &gpioConfig);        
  83.         
  84.         
  85.         ADC_Reset(ADC1);
  86.         ADC_EnableTempSensorVrefint(ADC1);//开启内部温度传感器
  87.         
  88.     /* ADC1 Configuration */
  89.     ADC_configStruct.mode = ADC_MODE_INDEPENDENT;
  90.     ADC_configStruct.scanConvMode = ENABLE;
  91.     ADC_configStruct.continuosConvMode = DISABLE;
  92.     ADC_configStruct.externalTrigConv = ADC_EXT_TRIG_CONV_None;
  93.     ADC_configStruct.dataAlign = ADC_DATA_ALIGN_RIGHT;
  94.     ADC_configStruct.nbrOfChannel = ADC_CH_SIZE;
  95.     ADC_Config(ADC1, &ADC_configStruct);
  96.         
  97.         ADC_EnableAutoInjectedConv(ADC1);
  98.         
  99.     /* ADC1 regular channel14 configuration */
  100.     ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_10, 1, ADC_SAMPLETIME_239CYCLES5);//
  101.     ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_11, 2, ADC_SAMPLETIME_239CYCLES5);//
  102.     ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_12, 3, ADC_SAMPLETIME_239CYCLES5);//
  103.         ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_13, 4, ADC_SAMPLETIME_239CYCLES5);//
  104.         ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_1, 5, ADC_SAMPLETIME_239CYCLES5);//
  105.         ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_16, 6, ADC_SAMPLETIME_239CYCLES5);//
  106.         
  107.         ADC_EnableDMA(ADC1);
  108.     /* Enable ADC1 */
  109.     ADC_Enable(ADC1);

  110.     /* Enable ADC1 reset calibration register */
  111.     ADC_ResetCalibration(ADC1);
  112.     /* Check the end of ADC1 reset calibration register */
  113.     while(ADC_ReadResetCalibrationStatus(ADC1));

  114.     /* Start ADC1 calibration */
  115.     ADC_StartCalibration(ADC1);
  116.     /* Check the end of ADC1 calibration */
  117.     while(ADC_ReadCalibrationStartFlag(ADC1));
  118.         
  119.         ADC_EnableSoftwareStartConv(ADC1); /* 启动ADC1转换 */

  120. }

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

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

  137. /*启动ADC转换*/
  138. void ADC_Start(void)
  139. {
  140.         DMA_Enable(DMA1_Channel1);
  141.     /* Start ADC1 Software Conversion */
  142.     ADC_EnableSoftwareStartConv(ADC1);
  143. }

  144. /*ADc初始化*/
  145. void ADC_init(void)
  146. {
  147.         ADC_DMA_Config();
  148.         ADC_ADC_Config();        
  149.         
  150. }
这段代码,进不去dma中断,请问是哪里问题?
nnqtdf 发表于 2022-11-19 15:56 | 显示全部楼层
找到原因了,dmaConfig.loopMode = DMA_MODE_CIRCULAR;
1988020566 发表于 2022-12-1 16:55 | 显示全部楼层
这个多路ADC之间存在串扰吗              
 楼主| JunKook 发表于 2022-12-1 18:40 | 显示全部楼层
1988020566 发表于 2022-12-1 16:55
这个多路ADC之间存在串扰吗

不会的,多路ADC通道分配好采样顺序后,采样时就会按设置的顺序依次采样,不会相互产生干扰
earlmax 发表于 2022-12-1 19:33 | 显示全部楼层
使用DMA采样还是比较便利。              
caigang13 发表于 2022-12-1 19:45 来自手机 | 显示全部楼层
DMA用好了,能够大大提升程序效率。
tifmill 发表于 2022-12-1 20:03 | 显示全部楼层
怎么实现ADC用定时器启动转换呢、
 楼主| 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

粉丝
快速回复 在线客服 返回列表 返回顶部