STM32学习笔记13:ADC扫描模式+DMA

芯片型号:STM32F103RC

软件开发包:标准外设库

一、 任务分析

ADC扫描模式+DMA

如图,左边是 ADC 扫描模式的执行流程,有 7 个通道,触发一次, 7 个通道依次进行 AD 转换,然后转换结果都放到 ADC_DR 数据寄存器中。在这里,DMA 的任务是在每个单独的通道转换完成后,进行数据转运,并且目的地址进行自增,这样数据就不会被覆盖了。

所以,这里 DMA 的配置是:

  1. 外设地址写入 ADC_DR 的地址,存储器的地址可以在 SRAM 中定义一个数组 ADValue,然后把 ADValue 的地址作为存储器的地址;
  2. 对于数据宽度,因为 ADC_DR 和 ADValue 存储的数据都是 uint16_t 型的,所以数据的宽度就是 16 位的半字;
  3. 对于地址是否自增,如图,外设地址不自增,存储器地址自增;
  4. 传输方向是外设站点到存储器站点;
  5. 传输计数器写入 7,因为这里通道有 7 个,要计数 7 次。计数器是否自动重装,这里可以看 ADC 的配置,ADC 如果是单次扫描,可以不自动重装,转换一轮就停止;如果是扫描模式,就可以使用自动重装,在 ADC 启动下一轮转换的时候,DMA 也启动下一轮转运,同步工作;
  6. 这里的 ADC_DR 的值在 ADC 单个通道转换完成后才会有效,DMA 转运的时机需要和 ADC 单个通道转换完成同步,所以 DMA 的触发要选择 ADC 的硬件触发。

说明: ADC 的扫描模式,在每个单独通道转换完成后并没有任何标志位,也不会触发中断,所以在程序中不太好判断某一个通道转换完成的时机。虽然单个通道转换完成后,不产生任何标志位和中断,但它会产生 DMA 请求,触发 DMA 转运。

二、 软件设计(ADC 单次扫描+ DMA 单次转运)

上述图片中展示了 7 个通道的使用,在这里以 2 个通道作为例子。

  1. 初始化 GPIO,为了方便,使用 pc0 和 pc1
void AD_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 设置为模拟输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // 初始化pc0和pc1
    GPIO_Init(GPIOC, &GPIO_InitStructure);
}
  1. 配置 DMA,传输方向为外设到存储器,传输数据数目为 2,使用单次模式,硬件触发
uint16_t ad_value[2]; // 用于存储ADC的转换结果

void AD_DMA_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能 DMA1 时钟

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;    // 设置外设基地址为ADC1的DR寄存器的地址
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据宽度为半字
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;            // 失能外设地址自增
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ad_value;          // 设置存储器基地址为ad_value的首地址
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         // 存储器数据宽度为半字
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                     // 使能存储器地址自增
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                     // 设置数据传输方向为外设到存储器
    DMA_InitStructure.DMA_BufferSize = 2;                                       // 设置传输数据数目为2
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                               // 设置为单次模式
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                                // 失能存储器到存储器模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;                       // 设置优先级为中等
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);                                // 初始化 DMA1 的通道1
}

注意:这里没有加DMA_Cmd(DMA1_Channel1, ENABLE);,使能 DMA 通道的任务交给了后面创建的函数 AD_GetValue

  1. 配置 ADC,配置两个规则通道,并分配序列号,使用单次转换、扫描模式,使能 ADC 的DMA触发信号
void AD_Config(void)
{
    ADC_InitTypeDef ADC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 使能ADC1时钟
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);                    // 配置ADC时钟为PCLK2的六分频(72M/6 = 12MHz)

     // 配置ADC1的规则通道0,序列1,采样时间为55.5个周期
    ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5);
    // 配置ADC1的规则通道1,序列2,采样时间为55.5个周期
    ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_55Cycles5); 

    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;                  // 设置ADC工作在独立模式
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;              // 数据右对齐
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 禁用外部触发转换
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;                 // 使用单次转换模式
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;                        // 使能扫描转换模式
    ADC_InitStructure.ADC_NbrOfChannel = 2;                             // 设置转换通道数量为2
    ADC_Init(ADC1, &ADC_InitStructure);
    
    ADC_DMACmd(ADC1, ENABLE); // 使能ADC1的DMA触发信号
    
    ADC_Cmd(ADC1, ENABLE); // 使能ADC

    ADC_ResetCalibration(ADC1);                                     // 复位ADC1的校准寄存器
    while (ADC_GetResetCalibrationStatus(ADC1) == SET);             // 等待复位校准结束
    ADC_StartCalibration(ADC1);                                     // 启动ADC1的校准
    while (ADC_GetCalibrationStatus(ADC1) == SET);                  // 等待校准完成
}

注意: ADC_DMACmd(ADC1, ENABLE) 必须放在RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE) 之后。

  1. 使用 ADC 的扫描模式,多通道,所以就不需要手动更改通道了;转换结果通过 DMA 转运,所以也不需要手动调用函数取值了;ADC每次转换完成后会有 DMA 请求,所以也不需要调用等待 ADC 转换完成的函数。但因为还是单次模式,所以还是需要软件触发一下 ADC 开始转换。
void AD_GetValue(void)
{
    DMA_Cmd(DMA1_Channel1, ENABLE);                         // 使能 DMA1 的通道1
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);                 // 启动ADC1的软件转换
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);      // 等待传输完成标志位置位
    DMA_ClearFlag(DMA1_FLAG_TC1);                           // 清除传输完成标志位

    DMA_Cmd(DMA1_Channel1, DISABLE);          // 失能 DMA1 的通道1
    DMA_SetCurrDataCounter(DMA1_Channel1, 2); // 设置当前数据传输数目为2
}

三、 改用 ADC 连续扫描 + DMA 循环转运

上述是 ADC 单次扫描+ DMA 单次转运的模式,也可以配置成为 ADC 连续扫描 + DMA 循环转运的模式,程序如下改动:

  1. AD_Config 中将
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;       // 使用单次转换模式

改为

ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;        // 使用连续转换模式
  1. AD_DMA_Config 中将
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;    // 设置为单次模式

改为

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;       // 设置为循环模式
  1. 在这里,可以直接将 ADC_SoftwareStartConvCmd(ADC1, ENABLE); 放在 AD_Config 的最后一行,同时将 DMA_Cmd(DMA1_Channel1, ENABLE); 放在 AD_DMA_Config 的最后一行。因为在 ADC 触发之后,ADC 连续转换,DMA 循环转运,两者一直在工作,始终把最新的转换结果刷新到 SRAM 数组里,想要数据直接到数组里取就行了 。因此,在该模式下,也就不需要函数 AD_GetValue 了。

参考视频源于B站up主: 野火科技、江协科技
参考文档:《STM32库开发实战指南——基于野火MINI开发板》

  • 32
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值