STM32中使用ADC的方法

这篇具有很好参考价值的文章主要介绍了STM32中使用ADC的方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

ADC简介

ADC(Analog-Digital Converter)即 模拟-数字转换器。
它的作用是将引脚上连续变化的模拟电压,转换为内存中存储的数字量。

  • STM32中的ADC是12位逐次逼近型ADC,最快转换速度大约1us。

  • 它有多达18个通道,可测量16个外部和2个内部信号源。 各通道的A/D转换可以单次、连续、扫描或间断模式执行。

  • ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。 包含规则组与注入组两个转换单元。

  • ADC输入范围:VREF- ≤ VIN ≤ VREF+;

  • 对于64脚以上的封装,可以外接独立参考电压到 VREF+与 VREF-引脚上来获得更高的精度,其中
    VREF+的电压范围为2.4V~VDDA。

  • 对于64脚及以下的封装形式,是没有 VREF+与 VREF-引脚的,他们在芯片内部与ADC的电源(VDDA)和地(VSSA)相联。

  • 通常输入范围为0~3.3V,因为他是12位逐次逼近型ADC,所有转换结果的范围是0~4095
    (0~1111 1111 1111)。

还可以设置模拟看门狗来监测想要的值,达到后会产生中断,这样就不需要一直进中断去判断,在一定程度上节省了软件资源。

何为逐次逼近

将输入的电压与DAC输出的电压进行比较,通过不断的修改DAC输出的值,找到相同的电压值,将其数字量输出。

一般会使用二分法以最快的找到目标值,首先比较2048,也就是二进制下的1000 0000 0000,然后根据结果依次移位,这也是逐次逼近名称的来源。
stm32 adc,stm32,嵌入式硬件,单片机,c语言
三角形表示比较器,从图中可以看出DAC输出的比较值会不断迭代,直到结果符合才输出数字量。

关于通道与转换单元

先看一下文档中的框图
stm32 adc,stm32,嵌入式硬件,单片机,c语言
一共18个通道,包含可测量的16个外部通道(ADCx_IN0~ADCx_IN15),两个内部信号源,一个温度传感器,与一个内部参考电压(V REFINT)。

其中温度传感器和通道ADC1_IN16相连接,内部参考电压VREFINT和ADC1_IN17相连接。可以按注入或规则通道对这两个内部通道进行转换。需要注意的是,这两个通道只在ADC1中有。

模拟的电压通过我们选择的通道经过多路选择开关,到达我们选择的转换单元,之后经过逐次比较,得到的数字量最终被存放到各个转换单元所对应的寄存器中。

  • 注入组最多可以设置 4 个通道,其寄存器可以同时存储四个转换结果。
  • 而规则组可以设置 16 个通道,但其寄存器只能存储一个转换结果,也就是说,当转换一次后需要立即读取数据,否则就会被下一个数据覆盖。一般会使用DMA配合进行快速转移数据。

ADC的触发方式

可以使用软件触发,将ADCx_CR2寄存器中的ADON位置1即可。
stm32 adc,stm32,嵌入式硬件,单片机,c语言
也可直接调用库函数

ADC_SoftwareStartConvCmd(ADCx, ENABLE);

也可使用定时器作为他的外部触发源,使用库函数的配置方法如下:

ADC_InitTypeDef adc_initStructure;
...
adc_initStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 不使用外部触发方法
...

可选的参数有:

#define ADC_ExternalTrigConv_T1_CC1                ((uint32_t)0x00000000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T1_CC2                ((uint32_t)0x00020000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T2_CC2                ((uint32_t)0x00060000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T3_TRGO               ((uint32_t)0x00080000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T4_CC4                ((uint32_t)0x000A0000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO    ((uint32_t)0x000C0000) /*!< For ADC1 and ADC2 */

#define ADC_ExternalTrigConv_T1_CC3                ((uint32_t)0x00040000) /*!< For ADC1, ADC2 and ADC3 */
#define ADC_ExternalTrigConv_None                  ((uint32_t)0x000E0000) /*!< For ADC1, ADC2 and ADC3 */

#define ADC_ExternalTrigConv_T3_CC1                ((uint32_t)0x00000000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T2_CC3                ((uint32_t)0x00020000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T8_CC1                ((uint32_t)0x00060000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T8_TRGO               ((uint32_t)0x00080000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T5_CC1                ((uint32_t)0x000A0000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T5_CC3                ((uint32_t)0x000C0000) /*!< For ADC3 only */

ADC时钟

ADC时钟的最大支持为14Mhz,并且其时钟来源于RCC的预分频。
stm32 adc,stm32,嵌入式硬件,单片机,c语言
可以看到RCC的最大值为72Mhz,可选的预分频值为2,4,6,8,当选择4分频时,得到的频率为18Mhz,大于最大允许的值,故只有6和8是实际可供选择的。

库函数配置方法如下:

RCC_ADCCLKConfig(RCC_PCLK2_Div8); // 配置ADC时钟,为PCLK2的8分频,即9MHz,ADC频率最高不能超过14MHz

ADC转换模式的选择

首先对于转换通道数量可以选择 非扫描模式(Single)或扫描模式(Scan),由ADCx_CCR1上的SCAN位控制。

  • 非扫描模式只对一个通道进行转换,转换完成后产生EOC标志
  • 扫描模式是在完成所有指定通道的转换后才产生EOC标志

stm32 adc,stm32,嵌入式硬件,单片机,c语言

对于转换方式则有 单次转换模式(Single)与 连续转换模式(Continuous),由ADCx_CCR2上的CONT位控制。
stm32 adc,stm32,嵌入式硬件,单片机,c语言

  • 单次转换模式在一次转换完成后,ADC转换停止,直到下一次开启
  • 连续转换模式则会在这一次转换完成后立即进入下一次转换

另外,还有一种间断模式,这个模式可以让我们设置每次进入ADC后转换的通道数量,这里的通道数量是指将选择的通道截短成多个小节,每个小节的通道个数为n,每次转换时,只转换一个小节,并且在转换完最后一个小节时,将EOC置位。
stm32 adc,stm32,嵌入式硬件,单片机,c语言

adc_initStructure.ADC_ContinuousConvMode = ENABLE;                  // 设置为连续转换
adc_initStructure.ADC_ScanConvMode = ENABLE;                        // 设置为扫描模式

寄存器中的介绍为:

  FunctionalState ADC_ScanConvMode;       /*!< Specifies whether the conversion is performed in
                                               Scan (multichannels) or Single (one channel) mode.
                                               This parameter can be set to ENABLE or DISABLE */

  FunctionalState ADC_ContinuousConvMode; /*!< Specifies whether the conversion is performed in
                                               Continuous or Single mode.
                                               This parameter can be set to ENABLE or DISABLE. */

数据对齐

由于32芯片上转换出的是12位的数字量,而寄存器为16位寄存器,故存在左对齐与右对齐之分。
stm32 adc,stm32,嵌入式硬件,单片机,c语言
一般都会选择右对齐,因为其结果是与实际值相等的。
若选择左对齐,则其结果比实际值要大,可以使用左对齐来裁剪转换精度,如只提取前八位,则获得到一个8位ADC。

关于ADC校准

ADC内置有一个校准模式,通过记录内部电容电阻的变化,计算出一个修正码,会在之后的转换中使用这个修正码消除误差,实际上就是消除零漂。

通常会选择在上电后进行校准操作。

校准过程如下:

	ADC_Cmd(ADC1, ENABLE);		//使能ADC1
    ADC_ResetCalibration(ADC1);		//校准复位
    while (ADC_GetResetCalibrationStatus(ADC1) == SET)		//等待复位完成
        ;
    ADC_StartCalibration(ADC1);		//开始校准
    while (ADC_GetCalibrationStatus(ADC1) == SET)		//等待校准完成
        ;

配置方法

首先将想要作为输入通道的管脚设置为 模拟输入模式(GPIO_MODE_AIN),并开启时钟:

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef gpio_initStructure;
    
    gpio_initStructure.GPIO_Mode = GPIO_Mode_AIN;
    gpio_initStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_Init(GPIOA, &gpio_initStructure);

也可以使用寄存器快速便捷的配置IO口:

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);

	GPIOA->CRL |= 0x00000000;

这里给一个GPIO管脚对应转换通道的表格:
stm32 adc,stm32,嵌入式硬件,单片机,c语言

其次是ADC的有关配置:

    adc_initStructure.ADC_Mode = ADC_Mode_Independent;                  // ADC模式为独立模式,不是双ADC就选这个
    adc_initStructure.ADC_DataAlign = ADC_DataAlign_Right;              // 数据对齐方式,右对齐
    adc_initStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 外部触发方法
    adc_initStructure.ADC_ContinuousConvMode = DISABLE;                  // 设置为单次转换
    adc_initStructure.ADC_ScanConvMode = DISABLE;                        // 设置为非扫描模式
    adc_initStructure.ADC_NbrOfChannel = 1;                             // 扫描模式通道数量设置

    ADC_Init(ADC1, &adc_initStructure);

    RCC_ADCCLKConfig(RCC_PCLK2_Div8); // 配置ADC时钟,为PCLK2的8分频,即9MHz,ADC频率最高不能超过14MHz

	//校准的过程
    ADC_Cmd(ADC1, ENABLE);
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1) == SET)
        ;
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1) == SET)
        ;

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//软件启动ADC

可以使用以下函数读取ADC的值,也可自己另写其他的:

u16 adc_get_value(u8 ch)
{
    u32 temp_val = 0;

    // ADC1,ADC 通道,转换结果存放的次序,55.5 个周期
    ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5);

    ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能指定的 ADC1 的软件转换启动功能

    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)) // 等待转换结束
        ;
    temp_val = ADC_GetConversionValue(ADC1);
    delay_ms(5);

    return temp_val;
}

最后展示一个用ADC配合DMA转换读取摇杆的值的例程:文章来源地址https://www.toymoban.com/news/detail-764618.html

#define ADC1_DR_Address ((u32)0x40012400 + 0x4c)

__IO u16 adc_buffer[2];

void adc_gpio_config(void)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);

    GPIOA->CRL |= 0x00000000;

    // GPIO_InitTypeDef gpio_initStructure;
    // gpio_initStructure.GPIO_Mode = GPIO_Mode_AIN;
    // gpio_initStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    // GPIO_Init(GPIOA, &gpio_initStructure);
}

void adc_mode_config(void)
{
    DMA_InitTypeDef dma_initStructure;
    ADC_InitTypeDef adc_initStructure;

    DMA_DeInit(DMA1_Channel1);
    dma_initStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
    dma_initStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    dma_initStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    dma_initStructure.DMA_MemoryBaseAddr = (u32)&adc_buffer;
    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;
    dma_initStructure.DMA_Mode = DMA_Mode_Circular;
    dma_initStructure.DMA_M2M = DMA_M2M_Disable;
    dma_initStructure.DMA_Priority = DMA_Priority_High;
    DMA_Init(DMA1_Channel1, &dma_initStructure);

    DMA_Cmd(DMA1_Channel1, ENABLE);

    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 = ENABLE;                  // 是否连续扫描
    adc_initStructure.ADC_ScanConvMode = ENABLE;                        // 设置转换方式为扫描模式
    adc_initStructure.ADC_NbrOfChannel = 2;                             // 扫描模式通道数量设置

    ADC_Init(ADC1, &adc_initStructure);

    RCC_ADCCLKConfig(RCC_PCLK2_Div8); // 配置ADC时钟,为PCLK2的8分频,即9MHz,ADC频率最高不能超过14MHz

    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);

    ADC_DMACmd(ADC1, ENABLE);

    ADC_Cmd(ADC1, ENABLE);
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1) == SET)
        ;
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1) == SET)
        ;

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

void adc_init(void)
{
    adc_gpio_config();
    adc_mode_config();
}

void adc_data_handle(u16 *data_buffer)
{
    u32 temp_buffer[2] = {0, 0};

    for (u8 i = 0; i < 15; i++)
    {
        temp_buffer[0] += adc_buffer[0];
        temp_buffer[1] += adc_buffer[1];
    }

    temp_buffer[0] = temp_buffer[0] / 15;
    temp_buffer[1] = temp_buffer[1] / 15;

    // 消除抖动与存在误差的部分,个位与十位
    temp_buffer[0] -= temp_buffer[0] % 100;
    temp_buffer[1] -= temp_buffer[1] % 100;

    // data_buffer[0] = (float)temp_buffer[0] / 4096 * 500;
    // data_buffer[1] = (float)temp_buffer[1] / 4096 * 500;
    data_buffer[0] = (float)temp_buffer[0] / 4000 * 500;
    data_buffer[1] = (float)temp_buffer[1] / 4000 * 500;
    // data_buffer[0] = temp_buffer[0];
    // data_buffer[1] = temp_buffer[1];
}

到了这里,关于STM32中使用ADC的方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【嵌入式学习笔记】嵌入式基础9——STM32启动过程

    程序段交叉引用关系(Section Cross References):描述各文件之间函数调用关系 删除映像未使用的程序段(Removing Unused input sections from the image):描述工程中未用到被删除的冗余程序段(函数/数据) 映像符号表(Image Symbol Table):描述各符号(程序段/数据)在存储器中的地址、类

    2024年02月15日
    浏览(87)
  • stm32嵌入式实验考核

    STM32 实验考核题目 1. 利用 STM32 小板实现:控制外接 LED 灯每隔 3 秒钟亮暗变换,同 时在 PC 机上显示 MCU 的计时时间,MCU 的初始时间由 PC 机 方设置。 2. 利用 STM32 小板实现:利用导线外接 GPIO 口模拟 2 个按键输入, 根据输入组合的四种情况,分别控制三色灯四种流水灯效果

    2024年02月03日
    浏览(53)
  • 从零开始教你使用Clion优雅开发STM32(三)Clion嵌入式开发必备插件

    (一)软件安装与环境配置 (二)移植工程文件到其他芯片 (三)Clion嵌入式开发必备插件 文章目录 前言 1)Chinese(simplified) 2)CodeGlance Pro 3)File Watchers  4)Key Promoter X 5)CodeGlance Pro 6)Monokai Pro Theme 7)Rainbow Brackets Lite 8)Serial Port  Monitor 总结 前段时间,稚晖君用 Cl

    2024年02月06日
    浏览(62)
  • 【Mac+CLion+STM32+ST-Link】Mac(apple sillicon)上使用STM32CubeMX和CLion搭建嵌入式开发环境

    Clion 官网安装或者brew安装,我用的是2023.2版本。 stlink server https://www.st.com/en/development-tools/st-link-server.html 不安装的话检测不到stlink。 STM32CubeMX https://www.st.com/en/development-tools/stm32cubeide.html#overviewsecondary=st-get-software 用来快速搭建一个工程。 Arm-toolchain 用来编译和debug的组件。

    2024年02月08日
    浏览(45)
  • 嵌入式 STM32 通讯协议--MODBUS

    目录 一、自定义通信协议 1、协议介绍 2、网络协议 3、自定义的通信协议  二、MODBUS通信协议 1、概述 2、MODBUS帧结构  协议描述 3、MODBUS数据模型   4、MODBUS事务处理的定义 5、MODBUS功能码  6、功能码定义   7、MODBUS数据链路层 8、MODBUS地址规则  9、MODBUS帧描述 10、MODBUS两种

    2024年02月11日
    浏览(66)
  • STM32的时钟系统(嵌入式学习)

    时钟是指用于计量和同步时间的装置或系统。时钟是嵌入式系统的脉搏,处理器内核在时钟驱动下完成指令执行,状态变换等动作,外设部件在时钟的驱动下完成各种工作,例如:串口数据的发送、AD转换、定时器计数等。因此时钟对于计算机系统是至关重要的,通常时钟系

    2024年02月16日
    浏览(50)
  • 嵌入式——新建STM32工程(标准库)

    目录 一、初识标准库 1.CMSIS标准及库层级关系 2.库文件介绍 (1)Libraries文件夹 ①CMSIS文件夹 ②STM32F10x_Std_Periph_Driver文件夹 ③ 在用库建立一个完整的工程时,还需要添加stm32f10x_it.c、 stm32f10x_conf.h 和 system_stm32f10x.c文件 (2)Project文件夹 (3)Utilities文件夹 3.库各文件之间的关

    2024年01月23日
    浏览(59)
  • STM32串口通信详解(嵌入式学习)

    时钟信号在电子领域中是指用于同步和定时电路操作的周期性信号。它在数字系统和通信系统中起着至关重要的作用,用于协调各个组件之间的数据传输和操作。 时钟信号有以下几个重要的方面: 频率:时钟信号的频率是指单位时间内信号周期的数量。它通常以赫兹(Hz)为

    2024年02月09日
    浏览(69)
  • 嵌入式C语言基础(STM32)

    前言:一条混迹嵌入式3年的老咸鱼,想到自己第一次接触到stm32的库函数时,c语言稀碎,痛不欲生的场景,该文章为萌新指条明路。 位操作在嵌入式中常用于直接对芯片的寄存器进行操作,当时作为初学者的我看着一脸懵逼,至于为什么这样修改,下面好好分析一下。  一

    2024年02月02日
    浏览(59)
  • 嵌入式学习笔记——STM32的时钟树

    在之前的所有代码编程的过程中,似乎每次都绕不开一个叫做时钟使能的东西,当时我们是在数据手册上直接看其挂接在那条时钟线上的,那么STM32内部的时钟到底是怎么一个构型呢,本文来对此做一个介绍。 老规矩,一个新的名词出现,首先需要搞清楚它是个啥,下图中对

    2024年02月02日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包