模数转换器(ADC)

ADC将引脚上的模拟电压转换为内存中的数字变量
当然也有从数字信号转换为模拟信号的 DAC ;同时之前的PWM也有类似的效果
stm32的ADC 又设置规则组和注入组两个转换单元,一次可启动一个组连续转换多个值.

简单看一个ADC的结构

首先先进行输入信号选择,将选择的外部信号,输入到比较器和DAC输出的已知的编码电压输入到比较器进行大小判断,如果DAC输出的电压小则增大DAC数据,电压大则减小DAC数据,使其逼近外部信号

在这里插入图片描述

ADC基本结构图

AD转换器

AD数据寄存器

规则组结果 1

注入组结果 4

规则组

注入组

GPIO

比较器

温度

REFINT

触发控制

RCC

模拟看门狗

中断输出控制

NVIC

转换模式

  1. 单次转换非扫描
  2. 连续转换非扫描
  3. 单次转换扫描
  4. 连续转换扫描

为了理解上述转换模式,需要先理解 CONT与SCAN
首先CONT位决定是单次转换还是连续转换

若CONT = 0 则为单次转换      
触发一次,转换一轮(一个或一组通道),然后 自动停止
若CONT = 1,则为连续转换
触发一次后,转换完一轮 自动开始下一轮,循环往复,直到软件关闭或停止

SCAN为决定是否扫描

若SCAN = 0 非扫描
只转换 一个通道
若SCAN = 1 扫描模式
按通道顺序 依次转换多个通道

给出表格

模式 CONT SCAN 通道数 转换次数(一次触发) 行为特点
单次转换非扫描 0 0 单通道 1 次 每次触发只转换一个通道一次,然后停止
连续转换非扫描 1 0 单通道 无限次 在同一通道上不停循环转换
单次转换扫描 0 1 多通道 一轮(N 个通道各一次) 扫完一轮后停止
连续转换扫描 1 1 多通道 无限轮 触发一次后不停重复扫描多通道

简单来个例子

直接上代码

void ADC_Init (void) {
    // ADC 初始化代码
    __HAL_RCC_ADC1_CLK_ENABLE ();  // 使能 ADC1 时钟
    __HAL_RCC_GPIOA_CLK_ENABLE ();  //使能 GPIOA 时钟

    RCC_ClkInitTypeDef PCLK2_DIV = { 0 };
    PCLK2_DIV.APB2CLKDivider = RCC_HCLK_DIV8;  // APB2 时钟 = HCLK / 8

    HAL_RCC_ClockConfig (&PCLK2_DIV, HAL_RCC_GetPCLK2Freq ());  // 配置时钟

    //配置GPIO
    GPIO_InitTypeDef GPIO_InitStruct = { 0 };
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init (GPIOA, &GPIO_InitStruct);

    hadc.Instance = ADC1;
    hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;  // 数据对齐右对齐
    hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;  // 外部触发源选择 软件触发
    hadc.Init.ContinuousConvMode = DISABLE;  // 单次转换模式
    hadc.Init.ScanConvMode = ADC_SCAN_DISABLE;  // (非扫描)单通道模式 (扫描)多通道
    hadc.Init.DiscontinuousConvMode = DISABLE;  //不连续(分段)模式。 把完整序列拆成多个“小段”,每次触发只转换一小段
    hadc.Init.NbrOfDiscConversion = 1;  //   不连续转换子组大小为 1(每次触发转换一个通道)
    hadc.Init.NbrOfConversion = 1;  // 规则组转换总数 为 1
    HAL_ADC_Init (&hadc);  // 初始化 ADC

    ADC_ChannelConfTypeDef sConfig = { 0 };
    sConfig.Channel = ADC_CHANNEL_0;  // 配置通道 0
    sConfig.Rank = ADC_REGULAR_RANK_1;  // 排序为 1
    sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;  // 采样时间为 55.5 周期

    HAL_ADC_ConfigChannel (&hadc, &sConfig);  // 配置 ADC 通道

    // ADC 校准
    HAL_ADCEx_Calibration_Start (&hadc);  // 启动 ADC 校准
}

这里简单配置一下APB2的时钟处于8分频 但是似乎并不建议在配置好系统时钟后继续配置APB2时钟

  RCC_ClkInitTypeDef PCLK2_DIV = { 0 };
    PCLK2_DIV.APB2CLKDivider = RCC_HCLK_DIV8;  // APB2 时钟 = HCLK / 8

下面是adc配置的主要参数,简单查看源码不难发现其中对于上述的扫描转换多了一种不连续转换模式,在这里 DiscontinuousConvModeNbrOfDiscConversion参数其实无论如何配置都没有效果,因为在单通道模式下其队列参数为1 只是为了方便展示

    hadc.Instance = ADC1;
    hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;  // 数据对齐右对齐
    hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;  // 外部触发源选择 软件触发
    hadc.Init.ContinuousConvMode = DISABLE;  // 单次转换模式
    hadc.Init.ScanConvMode = ADC_SCAN_DISABLE;  // (非扫描)单通道模式 (扫描)多通道
    hadc.Init.DiscontinuousConvMode = DISABLE;  //不连续(分段)模式。 把完整序列拆成多个“小段”,每次触发只转换一小段
    hadc.Init.NbrOfDiscConversion = 1;  //   不连续转换子组大小为 1(每次触发转换一个通道)
    hadc.Init.NbrOfConversion = 1;  // 规则组转换总数 为 1
    HAL_ADC_Init (&hadc);  // 初始化 ADC

简单解释一下不连续模式,首先其是在多通道模式下才有效
而开启不连续模式 其会将多通道的一个长序列进行划分一次只扫描一个切片。

uint32_t ADC_GetValue (void) {
    // 启动转换 (单次模式需要每次启动)
    HAL_ADC_Start (&hadc);

    // 等待转换完成 (超时设为 1ms,足够了)
    if (HAL_ADC_PollForConversion (&hadc, 1) == HAL_OK) {
        return HAL_ADC_GetValue (&hadc);
    }
}

而在这里对于HAL库而言 应先启动ADC校准然后开始start ADC转换 读取数据
值得注意的是上面因为选择采用单次转换模式,所以需要在进行读取时启动一下ADC

int main (void) {

  HAL_Init ();
  OLED_Init ();
  MX_GPIO_Init ();
  OLED_ShowString (1, 1, "ADC Value:");
  ADC_Init ();


  while (1) {
    adc_value = ADC_GetValue ();
    OLED_ShowNum (1, 11, adc_value, 5);
  }
}

主函数依旧是一个简单读取输出逻辑。

而聪明的你,此时或许发现,在我们这样的一通操作下,其实是一种连续转换的软件实现方式,对于单次转换其实在该并没得到一个很好的体现,你可以将Star放入Init函数再去 更改模式体会其中区别。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐