STM32-16-ADC
STM32-01-认识单片机
STM32-02-基础知识
STM32-03-HAL库
STM32-04-时钟树
STM32-05-SYSTEM文件夹
STM32-06-GPIO
STM32-07-外部中断
STM32-08-串口
STM32-09-IWDG和WWDG
STM32-10-定时器
STM32-11-电容触摸按键
STM32-12-OLED模块
STM32-13-MPU
STM32-14-FSMC_LCD
STM32-15-DMA
文章目录
- STM32-16-ADC
- 1. ADC简介
- 2. ADC工作原理
- 3. 单通道ADC采集实验
- 1. 相关寄存器
- 2. 配置步骤
- 3. 代码实现
- 4. 单通道ADC采集(DMA读取)实验
- 5. 多通道ADC采集(DMA读取)实验
- 6. 单通道ADC过采样实验
- 7. 内部温度传感器实验
- 8. 光敏传感器实验
STM32-16-ADC
1. ADC简介
-
什么是ADC:
ADC,Analog-to-digital converter,即模拟/数字转换器,可以将外部的模拟信号转换为数字信号。STM32F103系列芯片拥有3个ADC(C8T6只有2个),这些ADC可以独立使用,其中 ADC1和ADC2还可以组成双重模式(提高采样率)。STM32的ADC是12位逐次逼近型的模拟数字转换器。它有18个通道,可测量16个外部和2个内部信号源,其中ADC3根据CPU引脚的不同其通道数也不同,一般有8个外部通道。ADC中的各个通道的AD转换可以单次、连续、扫描或间断模式执行。ADC的结果可以以左对齐或者右对齐存储在16位数据寄存器中。

在嵌入式系统中,ADC通常用于采集传感器的数据,例如压力、温度、图像传感器等参数,被转换成电压后,通过ADC转换为数字量,供单片机处理,单片机根据处理结果进行相关控制和显示。
-
主要特性:
- 12位分辨率
- 转换结束、注入转换结束和发生模拟看门狗时产生中断
- 单次和连续转换模式
- 自校准
- 带内嵌数据一致性的数据对齐
- 采样间隔可以按通道分别编程
- 规则转换和注入转换均有外部触发选项
- 间断模式
- 双重模式
- ADC转换时间:时钟为72MHz为1.17us
- ADC供电要求:2.4V到3.6V
- ADC输入范围:Vref-<=Vin<=Vref+
- 规则通道转换期间有DMA请求产生
-
常见的ADC类型:
ADC电路类型 优点 缺点 并联比较型 转换速度最快 成本高、功耗高,分辨率低 逐次逼近型 结构简单,功耗低 转换速度较慢 并联比较型(Flash型)和逐次逼近型(SAR型)是两种常见的模数转换器(ADC)架构,它们在工作原理、速度、功耗、精度等方面有明显的区别。
并联比较型ADC(Flash ADC)

工作原理:
- 并联比较型ADC使用一组并行的比较器,将输入模拟信号同时与参考电压分压网络进行比较。
- 每个比较器对应一个特定的电压范围,输出一个二进制码。
- 比较器的输出直接决定ADC的数字输出,无需逐步逼近。
特点:
- 速度快:由于所有比较器同时工作,转换时间非常短,适合高频信号采样。
- 高功耗:大量并行比较器和电阻网络消耗较多电能。
- 高复杂度:电路复杂度随分辨率成指数增长,如8位ADC需要255个比较器。
- 应用场景:高速数据采集、视频处理、通信系统等。
逐次逼近型ADC(SAR ADC)

工作原理:
- 逐次逼近型ADC通过一个逐次逼近寄存器(SAR)和一个比较器工作。
- 它逐步逼近输入模拟信号,从最高位到最低位,逐位进行比较。
- 每次比较后,根据比较结果决定下一个比较的方向,直到所有位都确定。
特点:
- 速度适中:转换速度适中,比Flash ADC慢,但比积分型ADC快。
- 低功耗:由于只使用一个比较器,功耗较低。
- 高分辨率:适合高分辨率应用,电路复杂度与分辨率成线性关系。
- 应用场景:低功耗应用、音频采样、精密测量仪器等。
特点 并联比较型ADC 逐次逼近型ADC 转换速度 非常快(纳秒级) 适中(微秒级) 功耗 高 低 电路复杂度 高(比较器数量多) 适中(逐次逼近寄存器) 分辨率 适中(通常8位或以下) 高(可达16位或以上) 典型应用 高速数据采集、视频处理 音频采样、精密测量 并联比较型ADC适合需要极高转换速度的应用,而逐次逼近型ADC则在功耗和分辨率之间提供了良好的平衡,适合多种精密测量和中速数据采集的应用。
-
ADC的特性参数
- 分辨率: 表示ADC能辨别的最小模拟量,用二进制位表示
- 转换时间: 表示完成一次A/D转换所需要的时间,转换时间越短,采样率越高
- 精度: 最小刻度基础上叠加各种误差的参数,精度受ADC性能、温度和气压等影响
- 量化误差: 用数字量近似表示模拟量,采用四舍五入原则,此过程产生的误差为量化误差
2. ADC工作原理
ADC框图

-
①输入电压

-
②输入通道
确定好ADC输入电压后,通过ADC输入通道把外部电压输送到ADC转换器中,下面是ADC1~3对应的通道。

-
③转换顺序
在STM32的ADC转换过程中,规则组和注入组是两种不同的ADC转换序列,它们在配置、使用场景和优先级等方面存在显著区别。
规则组(Regular Group)
定义和特点:
- 规则组是常规的ADC转换序列,通常用于连续的、周期性的ADC采样。
- 可以包含多个通道,这些通道按照配置的顺序依次进行转换。
- 规则组的转换可以由多种触发源启动,如软件触发、定时器触发等。
应用场景:
- 常用于采集传感器数据、定期采样信号等场景。
- 适用于实时性要求较高的应用,但相对注入组来说,优先级较低。
触发源:
- 支持多种触发源,包括软件触发和硬件触发(如定时器事件)。
- 可设置为连续转换模式或单次转换模式。
注入组(Injected Group)
定义和特点:
- 注入组是为了在规则转换之外进行临时或优先转换的一种机制。
- 通常包含少量通道,用于处理突发性或优先级更高的ADC采样需求。
- 注入组转换有独立的触发源,与规则组转换互不干扰。
应用场景:
- 常用于优先级更高的采样需求,如监测关键参数的变化、快速响应事件等。
- 注入组的转换结果可以通过中断或DMA方式快速处理,适用于突发事件的处理。
触发源:
- 注入组有独立的触发源,可以是外部事件、定时器触发等。
- 通常由特定的事件触发,不会像规则组那样进行连续转换。
总结:
特点 规则组(Regular Group) 注入组(Injected Group) 转换触发 多种触发源(软件、定时器等) 独立触发源(外部事件、定时器等) 转换顺序 配置的通道顺序 配置的少量优先通道 优先级 相对较低 相对较高 应用场景 定期采样、传感器数据采集 关键参数监测、突发事件处理 转换模式 连续或单次转换 通常为单次转换 结果处理 常规方式处理 可通过中断或DMA快速处理

规则序列
规则组最多允许16个输入通道进行转换,那么就需要设置通道转换的顺序,即规则序列。
规则序列寄存器控制关系如下:

注入序列
注入组最大允许4个通道输入,它的注入序列由JSQR寄存器配置。
注入序列寄存器控制关系如下:

-
④触发源

规则组外部触发使用方法是将
EXTTRIG位置 1,并且通过EXTSET[2:0]位选择规则组启动转换的触发源。如果EXTSET[2:0]位设置为 111,那么可以通过SWSTART为启动 ADC 转换, 相当于软件触发。注入组外部触发使用方法是将
JEXTTRIG位置 1,并且通过JEXTSET[2:0]位选择注入组启动转换的触发源。如果JEXTSET[2:0]位设置为 111,那么可以通过JSWSTART为启动 ADC 转换,相当于软件触发。

-
⑤转换时间
ADC的输入时钟是由PCLK2经过分频产生的,分频系数是由
RCC_CFGR寄存器的ADCPRE[1:0]位设置的,可选择 2/4/8/16 分频。需要注意的是,ADC 的输入时钟频率最大值是14MHz,如果超过这个值将会导致 ADC 的转换结果准确度下降。 一般我们设置PCLK2为 72MHz。为了不超过 ADC 的最大输入时钟频率 14MHz,我们设置 ADC 的预分频器分频系数为 6,就可以得到 ADC 的输入时钟频率为 72MHz/6,即 12MHz。


-
⑥数据寄存器

ADC 规则数据寄存器(ADC_DR)
ADC规则组数据寄存器ADC_DR是一个32位的寄存器,独立模式时只使用到该寄存器低16位保存ADC1/2/3的规则转换数据。在双ADC模式下,高16位用于保存
ADC2转换的数据,低16位用于保存ADC1转换的数据。因为ADC的精度是12位的,ADC_DR寄存器无论高16位还是低16w位,存放数据的位宽都是16位的,所以允许选择数据对齐方式。由ADC_CR2寄存器的ALIGN位设置数据对齐方式,可选择:右对齐或者左对齐。
规则组16个输入通道只对应一个数据寄存器,所以通道转换完成后要及时取出数据,比较常用的方法是使用DMA模式。当规则组的转换结束后,就会产生DMA请求,这样就可以把数据及时搬运到用户指定的目的地址存放。只有ADC1和ADC3可以产生DMA请求,而ADC2转换的数据可以通过双ADC模式,利用ADC1的DMA功能传输。
ADC 注入数据寄存器x(ADC_JDRx)(x=1~4)
ADC注入数据寄存器有4个,注入组最多有4个输入通道,刚好每个通道都有自己对应的数据寄存器。
ADC_JDRx寄存器是32位的,低16位有效,高16位保留,数据也同样需要选择对齐方式。也是由ADC _CR2寄存器的ALIGN位设置数据对齐方式,可选择:右对齐或者左对齐。 -
⑦中断
中断事件 事件标志 使能控制位 规则通道转换结束 EOC EOCIE 注入通道转换结束 JEOC JEOCIE 设置了模拟看门狗状态位 AWD AWDIE 规则组和注入组的转换结束后,除了可以产生中断外,还可以产生 DMA 请求,我们利用DMA 及时把转换好的数据传输到指定的内存里,防止数据被覆盖。
-
⑧单次转换模式和连续转换模式

-
⑨扫描模式

3. 单通道ADC采集实验
1. 相关寄存器
-
ADC控制寄存器1(ADC_CR1)

-
ADC控制寄存器2(ADC_CR2)

-
ADC采样事件寄存器1(ADC_SMPR1)

-
ADC采集事件寄存器2(ADC_SMPR2)

ADC 采样时间设置需要由两个寄存器设置,
ADC_SMPR1和ADC_SMPR2,分别设置通道 10~17 和通道 0~9 的采样时间,每个通道用 3 个位设置。 -
ADC规则序列寄存器1(ADC_SQR1)

ADC_SQR2

ADC_SQR3

L[3:0]用于设置规则组序列的长度,取值范围:015,表示规则组的长度是116。本实验只用了1个输入通道,所以L[3:0]位设置为0000即可。
SQ13[4:0]~SQ16[4:0]位设置规则组序列的第 13~16 个转换编号,第 1~12 个转换编号的设置请查看 ADC_SQR2 和 ADC_SQR3 寄存器。
-
ADC规则数据寄存器(ADC_DR)

-
ADC状态寄存器(ADC_SR)

2. 配置步骤
-
配置ADC工作参数、ADC校准
HAL_ADC_Init() HAL_ADCEx_Calibration_Start() -
ADC MSP初始化
HAL_ADC_MspInit() -
配置ADC相应通道相关参数
HAL_ADC_ConfigChannel() -
启动A/D转换
HAL_ADC_Start() -
等待规则通道转换完成
HAL_ADC_PollForConversion() -
获取规则通道A/D转换结果
HAL_ADC_GetValue()
-
具体函数功能
函数 主要寄存器 主要功能 HAL_ADC_Init() CR1、CR2 配置ADC工作参数 HAL_ADCEx_Calibration_Start() CR2 ADC校准 HAL_ADC_MspInit() 无 存放NVIC、CLOCK、GPIO初始化代码 HAL_RCCEx_PeriphCLKConfig() RCC_CFGR 设置扩展外设时钟,如:ADC、RTC等 HAL_ADC_ConfigChannel() SQRx、SMPRx 配置ADC相应通道的相关参数 HAL_ADC_Start() CR2 启动A/D转换 HAL_ADC_PollForConversion() SR 等待规则通道转换完成 HAL_ADC_GetValue() DR 获取规则通道A/D转换结果
3. 代码实现
-
功能: 采集ADC1通道1(PA1)上面的电压,并在LCD模块上面显示ADC规则数据寄存器12位的转换值以及将该值换算成电压后的电压值。使用杜邦线将ADC和RV1排针连接,使得PA1连接到电位器上,然后将ADC采集到的数据和转换后的电压值在TFTLCD屏中显示。用户可以通过调节电位器的旋钮改变电压值。LED0闪烁,提示程序运行。
-
ADC单通道初始化函数
void adc_init(void) {ADC_ChannelConfTypeDef adc_ch_conf;g_adc_handle.Instance = ADC1; //选择ADC1g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; //数据对齐方式为右对齐g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; //非扫描模式,仅使用一个通道g_adc_handle.Init.ContinuousConvMode = DISABLE; //关闭连续转换模式g_adc_handle.Init.NbrOfConversion = 1; //通道数选择1个g_adc_handle.Init.DiscontinuousConvMode = DISABLE; //禁止规则通道组间断模式 g_adc_handle.Init.NbrOfDiscConversion = 0; //配置间断模式的规则通道个数g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //软件触发方式HAL_ADC_Init(&g_adc_handle); //进行初始化HAL_ADCEx_Calibration_Start(&g_adc_handle); //校准ADCadc_ch_conf.Channel = ADC_CHANNEL_1; //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_1; //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_handle, &adc_ch_conf); //通道配置 }g_adc_handle.Instance = ADC1;: 选择ADC1作为转换的ADC模块。g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;: 数据右对齐。g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;: 禁用扫描模式,仅使用一个通道。g_adc_handle.Init.ContinuousConvMode = DISABLE;: 禁用连续转换模式。g_adc_handle.Init.NbrOfConversion = 1;: 仅使用一个转换通道。g_adc_handle.Init.DiscontinuousConvMode = DISABLE;: 禁用间断模式。g_adc_handle.Init.NbrOfDiscConversion = 0;: 设置间断模式的规则通道数为0。g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;: 设置软件触发转换。adc_ch_conf.Channel = ADC_CHANNEL_1;: 选择ADC通道1。adc_ch_conf.Rank = ADC_REGULAR_RANK_1;: 设置通道1为规则组的第一个转换通道。adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;: 采样时间为239.5个ADC时钟周期。STM32的ADC1模块进行初始化和通道配置,为后续的ADC数据采集做好准备。具体来说,初始化配置包括设置数据对齐方式、转换模式和触发方式等。通过校准确保ADC转换的精度,然后配置一个规则组通道,使得ADC可以在指定通道上进行采样并转换。
-
ADC MSP初始化函数
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc) {if(hadc->Instance == ADC1){GPIO_InitTypeDef gpio_init_struct;RCC_PeriphCLKInitTypeDef adc_clk_init = {0};//使能GPIOA和ADC1的时钟__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_ADC1_CLK_ENABLE();// 配置GPIOA的引脚1为模拟输入模式gpio_init_struct.Pin = GPIO_PIN_1;gpio_init_struct.Mode = GPIO_MODE_ANALOG; //模拟输入HAL_GPIO_Init(GPIOA, &gpio_init_struct);//配置ADC的时钟源和分频系数adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC; //ADC外设时钟adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;//分频因子6,时钟为72M/6=12MHz HAL_RCCEx_PeriphCLKConfig(&adc_clk_init); //设置ADC时钟} }配置ADC1的时钟源和分频系数。这里选择了RCC的ADC外设时钟,并将ADC的时钟频率设置为系统时钟(72MHz)除以6,即12MHz。这有助于确保ADC1以适当的频率运行,从而保证其性能和精度。
-
启动ADC并获取转换结果函数
uint32_t adc_get_result(void) {// 启动A/D转换HAL_ADC_Start(&g_adc_handle); // 开启ADC// 等待规则通道转换完成HAL_ADC_PollForConversion(&g_adc_handle, 10); // 轮询转换,等待完成// 获取规则通道A/D转换结果return (uint16_t)HAL_ADC_GetValue(&g_adc_handle); // 返回最近一次规则组的转换结果 }-
启动A/D转换:
HAL_ADC_Start(&g_adc_handle);g_adc_handle是先前定义的并初始化好的 ADC 句柄。此操作会开始对选定的ADC通道进行模数转换。 -
等待规则通道转换完成:
HAL_ADC_PollForConversion(&g_adc_handle, 10);调用
HAL_ADC_PollForConversion函数进行轮询,等待转换完成。这里设置了一个超时值为10个时钟周期。如果在超时时间内转换完成,函数会返回并继续执行;否则,它会阻塞直到超时或者转换完成。 -
获取规则通道A/D转换结果:
return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);调用
HAL_ADC_GetValue函数获取ADC转换结果。返回的值是最近一次转换的结果,类型为uint16_t,因为一般ADC的结果是16位。
-
-
主函数
int main(void) {uint16_t adcx; //ADC换算后的数字量float temp; //转换后的电压值HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */delay_init(72); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */lcd_init(); /* 初始化LCD */oled_init(); //OLED初始化adc_init();lcd_show_string(110, 50, 240, 16, 32, "STM32", GREEN);lcd_show_string(105, 90, 240, 16, 24, "ADC TEST", GREEN);lcd_show_string(30, 150, 240, 16, 24, "CH1_VAL:", BLUE);lcd_show_string(30, 180, 240, 16, 24, "CH1_VOL:0.000V", BLUE);oled_show_string(10,0,"ADC TEST",24);oled_show_string(0, 25, "CH1_VAL:", 16);oled_show_string(0, 41, "CH1_VOL:0.000V", 16);oled_refresh_gram();while (1){adcx = adc_get_result(); //获取ADC转换后的结果lcd_show_xnum(126, 150, adcx, 5, 24, 0, BLUE);oled_show_num(65, 25, adcx, 5, 16);//在LCD和OLED上显示电压值整数部分temp = (float)adcx * (3.3 / 4096);adcx = temp;lcd_show_xnum(126, 180, adcx, 1, 24, 0, BLUE);oled_show_num(64, 41, adcx, 1, 16);//在LCD和OLED上显示电压值小数部分temp -= adcx;temp *= 1000;lcd_show_xnum(150, 180, temp, 3, 24, 0x80, BLUE);oled_show_num(80, 41, temp, 3, 16);LED0_TOGGLE(); delay_ms(100);oled_refresh_gram();} }实验结果:
作用:
- 实现ADC转换,并将结果显示在LCD和OLED上,实时显示ADC通道的数值和对应的电压值。
影响:
- 可以实时监控ADC通道的输入电压,适用于需要监测模拟信号的应用,如传感器数据读取。
- 通过LED的切换,可以直观地知道系统是否在运行。
- 延时和轮询会占用CPU资源,对实时性有一定影响,如果有更高的实时性要求,可以考虑使用中断或DMA。
4. 单通道ADC采集(DMA读取)实验
-
功能: 使用 ADC 采集(DMA 读取)通道 1(PA1)上面的电压,在 LCD 和OLED模块上面显示 ADC 转换值以及换算成电压后的电压值。使用短路帽将 ADC 和 RV1 排针连接,使得 PA1 连接到电位器上,然后将 ADC 采集到的数据和转换后的电压值在 TFTLCD 屏中显示。用户可以通过调节电位器的旋钮改变电压值。LED0 闪烁,提示程序运行。
-
ADC_DMA初始化函数
void adc_dma_init(uint32_t mar) {ADC_ChannelConfTypeDef adc_ch_conf = {0};__HAL_RCC_DMA1_CLK_ENABLE();g_dma_adc_handle.Instance = DMA1_Channel1;g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE;g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;g_dma_adc_handle.Init.Mode = DMA_NORMAL;g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; HAL_DMA_Init(&g_dma_adc_handle);//2.将DMA和ADC句柄连接起来__HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);g_adc_dma_handle.Instance = ADC1; //选择ADC1g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; //数据对齐方式为右对齐g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; //非扫描模式,仅使用一个通道g_adc_dma_handle.Init.ContinuousConvMode = ENABLE; //打开连续转换模式g_adc_dma_handle.Init.NbrOfConversion = 1; //通道数选择1个g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE; //禁止规则通道组间断模式 g_adc_dma_handle.Init.NbrOfDiscConversion = 0; //配置间断模式的规则通道个数g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //软件触发方式HAL_ADC_Init(&g_adc_dma_handle); //进行初始化HAL_ADCEx_Calibration_Start(&g_adc_dma_handle); //校准ADCadc_ch_conf.Channel = ADC_CHANNEL_1; //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_1; //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf); //通道配置HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar, 0); }总结:
- 启用DMA1时钟
__HAL_RCC_DMA1_CLK_ENABLE();- 使能DMA1的时钟,确保DMA1能够正常工作。
- 配置DMA
g_dma_adc_handle.Instance = DMA1_Channel1; g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; // 数据从外设到内存 g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不递增 g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增 g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // 外设数据对齐为16位 g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; // 内存数据对齐为16位 g_dma_adc_handle.Init.Mode = DMA_NORMAL; // 普通模式 g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; // 优先级为中等 HAL_DMA_Init(&g_dma_adc_handle);- 初始化
DMA1的通道1,配置DMA的数据传输方向、地址递增模式、数据对齐方式、传输模式和优先级。
- 将DMA和ADC句柄连接起来
__HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);- 将DMA句柄与ADC句柄连接,使ADC能够使用DMA传输数据。
- 初始化ADC
g_adc_dma_handle.Instance = ADC1; // 选择ADC1 g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 数据右对齐 g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; // 非扫描模式 g_adc_dma_handle.Init.ContinuousConvMode = ENABLE; // 连续转换模式 g_adc_dma_handle.Init.NbrOfConversion = 1; // 单通道 g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE; // 禁止间断模式 g_adc_dma_handle.Init.NbrOfDiscConversion = 0; // 间断模式转换数 g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发 HAL_ADC_Init(&g_adc_dma_handle);- 配置并初始化
ADC1,包括数据对齐方式、扫描模式、连续转换模式、转换通道数、间断模式和触发方式。
- 校准ADC
HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);- 启动ADC的校准,以提高转换精度。
- 配置ADC通道
adc_ch_conf.Channel = ADC_CHANNEL_1; // 通道1 adc_ch_conf.Rank = ADC_REGULAR_RANK_1; // 规则组第1个转换 adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 采样时间 HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);- 配置ADC的输入通道、转换序列和采样时间。
- 配置DMA中断优先级并启用中断
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3); HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);- 设置DMA1通道1中断的优先级,并启用中断。
- 启动DMA传输和ADC DMA模式
HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0); HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar, 0);- 启动DMA传输,将ADC数据寄存器的地址作为源地址,
mar作为目标地址,并指定传输数据量。 - 启动ADC的DMA模式,使得ADC转换完成后通过DMA传输数据。
代码的功能:
- 使能DMA和ADC时钟:确保DMA和ADC正常工作。
- 配置DMA:设置DMA传输的方向、地址递增模式、数据对齐方式、传输模式和优先级。
- 初始化ADC:配置ADC的工作模式、转换方式和触发方式。
- 校准ADC:提高ADC转换的精度。
- 配置ADC通道:指定ADC的输入通道和采样时间。
- 配置中断:设置DMA中断的优先级并启用中断,以便在数据传输完成后处理中断。
- 启动DMA传输和ADC DMA模式:实现ADC转换结果通过DMA自动传输到指定内存地址,提高数据传输效率和系统性能。
对程序的影响:
- 提高数据传输效率:使用DMA自动传输数据,减少CPU的负担。
- 自动化数据采集:通过DMA实现ADC数据的自动传输,无需手动处理数据搬运。
- 实时数据处理:配置中断处理机制,可以在数据传输完成后立即处理,提高系统的实时性和响应速度。
-
ADC MSP初始化函数
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc) {if(hadc->Instance == ADC1){GPIO_InitTypeDef gpio_init_struct;RCC_PeriphCLKInitTypeDef adc_clk_init = {0};//使能时钟__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_ADC1_CLK_ENABLE();//配置工作模式gpio_init_struct.Pin = GPIO_PIN_1;gpio_init_struct.Mode = GPIO_MODE_ANALOG; //模拟输入HAL_GPIO_Init(GPIOA, &gpio_init_struct);adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC; //ADC外设时钟adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6; //分频因子6,时钟为72M/6=12MHz HAL_RCCEx_PeriphCLKConfig(&adc_clk_init); //设置ADC时钟} } -
使能一次ADC DMA传输函数
void adc_dma_enable(uint16_t cndtr) {ADC1->CR2 &= ~(1 << 0); //CONT位置1,关闭连续转换模式DMA1_Channel1->CCR &= ~(1 << 0); //禁用DMA通道1while(DMA1_Channel1->CCR & (1 << 0)); //等待DMA通道1禁用完成DMA1_Channel1->CNDTR = cndtr; //设置DMA传输数据量DMA1_Channel1->CCR |= 1 << 0; //启用DMA通道1ADC1->CR2 |= 1 << 0; //开启ADC1ADC1->CR2 |= 1 << 22; //启用ADC1的DMA请求 }总结:
-
关闭ADC连续转换模式:
ADC1->CR2 &= ~(1 << 0);
- 清除ADC1控制寄存器2(CR2)的第0位,关闭连续转换模式。
-
禁用DMA通道1:
DMA1_Channel1->CCR &= ~(1 << 0);- 清除DMA1通道1控制寄存器(CCR)的第0位,禁用DMA通道1。
-
等待DMA通道1禁用完成:
while(DMA1_Channel1->CCR & (1 << 0));- 使用while循环等待,直到DMA1通道1控制寄存器的第0位被清除,确保DMA通道1已完全禁用。
-
设置DMA传输数据量:
DMA1_Channel1->CNDTR = cndtr;- 将传入的参数
cndtr赋值给DMA1通道1的数据传输数量寄存器(CNDTR),指定DMA传输的数据量。
- 将传入的参数
-
启用DMA通道1:
DMA1_Channel1->CCR |= 1 << 0;- 设置DMA1通道1控制寄存器的第0位,启用DMA通道1。
-
开启ADC1:
ADC1->CR2 |= 1 << 0;
- 设置ADC1控制寄存器2(CR2)的第0位,开启ADC1。
- 启用ADC1的DMA请求:
ADC1->CR2 |= 1 << 22;- 设置ADC1控制寄存器2(CR2)的第22位,启用ADC1的DMA请求。这样,ADC转换完成后将自动通过DMA将数据传输到指定的存储位置。
作用:
- 启用DMA:通过DMA自动传输ADC转换的数据,减少CPU的负担,提高数据传输效率。
- 启用ADC连续转换模式:让ADC能够连续进行多次转换,而无需每次都启动转换。-
-
-
ADC DMA采集中断服务函数
void DMA1_Channel1_IRQHandler(void) {if(DMA1->ISR & (1<<1)){g_adc_dma_sta = 1;//清除中断标志位DMA1->IFCR |= (1 << 1);}}总结:
-
中断处理:
- 该函数是DMA1通道1的中断处理函数。当DMA1通道1触发中断时,系统会调用这个函数。
-
检查中断标志:
if(DMA1->ISR & (1<<1)):检查DMA1的中断状态寄存器(ISR)的第1位是否被置位。这一位表示DMA1通道1的传输完成中断。如果该位被置位,说明DMA1通道1完成了数据传输。
-
设置状态标志:
g_adc_dma_sta = 1;:设置一个全局标志位g_adc_dma_sta,表示DMA传输已完成。这个标志位可以在主程序或者其他地方用来判断DMA传输状态。
-
清除中断标志:
DMA1->IFCR |= (1 << 1);:清除DMA1通道1的传输完成中断标志位。这一步是必须的,否则中断处理程序会再次触发。
作用总结:
- 响应DMA传输完成中断:当DMA1通道1完成数据传输时,会触发中断。
- 设置传输完成标志:通过设置
g_adc_dma_sta,主程序或其他代码可以检测到DMA传输已完成,并进行后续处理。 - 清除中断标志位:清除中断标志位,以便系统能处理下一次DMA传输完成中断。
-
-
主函数
int main(void) {uint16_t i;uint16_t adcx;uint32_t sum;float temp;HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */delay_init(72); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */lcd_init(); /* 初始化LCD */oled_init();adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */oled_show_string(10,0,"ADC TEST",24);oled_show_string(0, 25, "CH1_VAL:", 16);oled_show_string(0, 41, "CH1_VOL:0.000V", 16);oled_refresh_gram();lcd_show_string(110, 50, 240, 16, 32, "STM32", GREEN);lcd_show_string(105, 90, 240, 16, 24, "ADC TEST", GREEN);lcd_show_string(30, 150, 240, 16, 24, "CH1_VAL:", BLUE);lcd_show_string(30, 180, 240, 16, 24, "CH1_VOL:0.000V", BLUE);adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */while (1){if (g_adc_dma_sta == 1){/* 计算DMA 采集到的ADC数据的平均值 */sum = 0;for (i = 0; i < ADC_DMA_BUF_SIZE; i++) /* 累加 */{sum += g_adc_dma_buf[i];printf("%d\r\n", sum);}adcx = sum / ADC_DMA_BUF_SIZE; /* 取平均值 *//* 显示结果 */lcd_show_xnum(126, 150, adcx, 5, 24, 0, BLUE); /* 显示ADCC采样后的原始值 */oled_show_num(65, 25, adcx, 5, 16);temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */lcd_show_xnum(126, 180, adcx, 1, 24, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */oled_show_num(64, 41, adcx, 1, 16);temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */lcd_show_xnum(150, 180, temp, 3, 24, 0x80, BLUE); /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */oled_show_num(80, 41, temp, 3, 16);g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */}LED0_TOGGLE();delay_ms(100);oled_refresh_gram();} }总结:
adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */初始化ADC和DMA,使得ADC可以通过DMA方式进行数据采集,采集的数据存储在
g_adc_dma_buf缓冲区中。adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */启动ADC DMA采集,使得ADC开始连续采集数据并通过DMA传输到缓冲区
g_adc_dma_buf。代码通过初始化和配置STM32的各种外设,实现了ADC数据的DMA采集,并在LCD和OLED上显示采集到的ADC数据和转换后的电压值。同时,通过中断和DMA机制实现了高效的数据采集和处理。
什么时候采集,什么时候传输
ADC采集的控制:
- ADC的连续转换模式被启用(
g_adc_dma_handle.Init.ContinuousConvMode = ENABLE;)。这意味着ADC会连续地进行转换。 - 但是,ADC的转换触发和数据读取是通过DMA控制的。当你调用
adc_dma_enable函数时,ADC和DMA都被启动,并开始进行数据采集和传输。
DMA传输的控制:
- 当你停止DMA传输(例如,通过禁用DMA通道),ADC可能会继续进行转换,但是这些转换结果不会通过DMA传输到内存。这是因为DMA传输是负责将ADC转换结果传输到指定的内存缓冲区(
g_adc_dma_buf)。 - 如果DMA被禁用,但ADC继续进行转换,结果仍会存储在ADC的数据寄存器(DR)中,但不会被传输到内存。
- ADC的连续转换模式被启用(
5. 多通道ADC采集(DMA读取)实验
-
功能:使用ADC1采集(DMA读取)通道1/2/3/4/5/6的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA0/PA1/PA2/PA3/PA4/PA5到你想测量的电压源(0~3.3V),然后通过TFTLCD显示的电压值。LED0闪烁,提示程序运行。
-
多通道ADC初始化函数
void adc_nch_dma_init(uint32_t mar) {ADC_ChannelConfTypeDef adc_ch_conf = {0};__HAL_RCC_DMA1_CLK_ENABLE(); //使能DMA1时钟g_dma_adc_handle.Instance = DMA1_Channel1; //初始化DMA1的通道1g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; //数据传输方向设置为外设到内存g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; //外设地址不递增g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE; //内存地址递增g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //数据对齐使用半字g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; //数据对齐使用半字g_dma_adc_handle.Init.Mode = DMA_NORMAL; //传输模式配置为正常模式g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; //优先级设置为中等HAL_DMA_Init(&g_dma_adc_handle);//2.将DMA和ADC句柄连接起来__HAL_LINKDMA(&g_adc_nch_dma_handle, DMA_Handle, g_dma_adc_handle);g_adc_nch_dma_handle.Instance = ADC1; //选择ADC1g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; //数据对齐方式为右对齐g_adc_nch_dma_handle.Init.ScanConvMode = ADC_SCAN_ENABLE; //扫描模式,多个通道g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE; //打开连续转换模式g_adc_nch_dma_handle.Init.NbrOfConversion = 6; //通道数选择1个g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE; //禁止规则通道组间断模式 g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0; //配置间断模式的规则通道个数g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //软件触发方式HAL_ADC_Init(&g_adc_nch_dma_handle); //进行初始化HAL_ADCEx_Calibration_Start(&g_adc_nch_dma_handle); //校准ADCadc_ch_conf.Channel = ADC_CHANNEL_0; //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_1; //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置adc_ch_conf.Channel = ADC_CHANNEL_1; //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_2; //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置adc_ch_conf.Channel = ADC_CHANNEL_2; //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_3; //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置adc_ch_conf.Channel = ADC_CHANNEL_3; //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_4; //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置adc_ch_conf.Channel = ADC_CHANNEL_4; //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_5; //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置adc_ch_conf.Channel = ADC_CHANNEL_5; //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_6; //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar, 0); }多通道ADC转换与单通道ADC转换的区别:
DMA的相关配置不需要改动;
ADC的配置要使能扫描模式,通道数设置为6个
g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE; //打开连续转换模式 g_adc_nch_dma_handle.Init.NbrOfConversion = 6; //通道数选择6个设置6个通道序列:
adc_ch_conf.Channel = ADC_CHANNEL_0; //输入通道 adc_ch_conf.Rank = ADC_REGULAR_RANK_1; //转换序列 adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置adc_ch_conf.Channel = ADC_CHANNEL_1; //输入通道 adc_ch_conf.Rank = ADC_REGULAR_RANK_2; //转换序列 adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置adc_ch_conf.Channel = ADC_CHANNEL_2; //输入通道 adc_ch_conf.Rank = ADC_REGULAR_RANK_3; //转换序列 adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置adc_ch_conf.Channel = ADC_CHANNEL_3; //输入通道 adc_ch_conf.Rank = ADC_REGULAR_RANK_4; //转换序列 adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置adc_ch_conf.Channel = ADC_CHANNEL_4; //输入通道 adc_ch_conf.Rank = ADC_REGULAR_RANK_5; //转换序列 adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置adc_ch_conf.Channel = ADC_CHANNEL_5; //输入通道 adc_ch_conf.Rank = ADC_REGULAR_RANK_6; //转换序列 adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); //通道配置 -
ADC MSP初始化函数
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc) {if(hadc->Instance == ADC1){GPIO_InitTypeDef gpio_init_struct;RCC_PeriphCLKInitTypeDef adc_clk_init = {0};//使能时钟__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_ADC1_CLK_ENABLE();//配置工作模式gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;gpio_init_struct.Mode = GPIO_MODE_ANALOG; //模拟输入HAL_GPIO_Init(GPIOA, &gpio_init_struct);adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC; //ADC外设时钟adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6; //分频因子6,时钟为72M/6=12MHz HAL_RCCEx_PeriphCLKConfig(&adc_clk_init); //设置ADC时钟} } -
使能一次ADC DMA传输函数
void adc_dma_enable(uint16_t cndtr) {ADC1->CR2 &= ~(1 << 0); //CONT位置1,关闭连续转换模式DMA1_Channel1->CCR &= ~(1 << 0); //禁用DMA通道1while(DMA1_Channel1->CCR & (1 << 0)); //等待DMA通道1禁用完成DMA1_Channel1->CNDTR = cndtr; //设置DMA传输数据量DMA1_Channel1->CCR |= 1 << 0; //启用DMA通道1ADC1->CR2 |= 1 << 0; //开启ADC1ADC1->CR2 |= 1 << 22; //启用ADC1的DMA请求 } -
ADC DMA采集中断服务函数
void DMA1_Channel1_IRQHandler(void) {if(DMA1->ISR & (1<<1)){g_adc_dma_sta = 1;//清除中断标志位DMA1->IFCR |= (1 << 1);} } -
主函数
int main(void) {uint16_t i,j;uint16_t adcx;uint32_t sum;float temp;HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */delay_init(72); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */lcd_init(); /* 初始化LCD */adc_nch_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);lcd_show_string(30, 70, 200, 16, 16, "ADC 6CH DMA TEST", RED);lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);lcd_show_string(30, 110, 200, 12, 12, "ADC1_CH0_VAL:", BLUE);lcd_show_string(30, 122, 200, 12, 12, "ADC1_CH0_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */lcd_show_string(30, 140, 200, 12, 12, "ADC1_CH1_VAL:", BLUE);lcd_show_string(30, 152, 200, 12, 12, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */lcd_show_string(30, 170, 200, 12, 12, "ADC1_CH2_VAL:", BLUE);lcd_show_string(30, 182, 200, 12, 12, "ADC1_CH2_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */lcd_show_string(30, 200, 200, 12, 12, "ADC1_CH3_VAL:", BLUE);lcd_show_string(30, 212, 200, 12, 12, "ADC1_CH3_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */lcd_show_string(30, 230, 200, 12, 12, "ADC1_CH4_VAL:", BLUE);lcd_show_string(30, 242, 200, 12, 12, "ADC1_CH4_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */lcd_show_string(30, 260, 200, 12, 12, "ADC1_CH5_VAL:", BLUE);lcd_show_string(30, 272, 200, 12, 12, "ADC1_CH5_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */while (1){if (g_adc_dma_sta == 1){/* 循环显示通道0~通道5的结果 */for(j = 0; j < 6; j++) /* 遍历6个通道 */{sum = 0; /* 清零 */for (i = 0; i < ADC_DMA_BUF_SIZE / 6; i++) /* 每个通道采集了50次数据,进行50次累加 */{sum += g_adc_dma_buf[(6 * i) + j]; /* 相同通道的转换数据累加 */}adcx = sum / (ADC_DMA_BUF_SIZE / 6); /* 取平均值 *//* 显示结果 */lcd_show_xnum(108, 110 + (j * 30), adcx, 4, 12, 0, BLUE); /* 显示ADC采样后的原始值 */temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */lcd_show_xnum(108, 122 + (j * 30), adcx, 1, 12, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */lcd_show_xnum(120, 122 + (j * 30), temp, 3, 12, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */}g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */}LED0_TOGGLE();delay_ms(100);} }
6. 单通道ADC过采样实验
-
功能:使用ADC1通道1(PA1),通过软件方式实现16位分辨率采集外部电压,并在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA1到你想测量的电压源(0~3.3V),然后通过TFTLCD显示的电压值。LED0闪烁,提示程序运行。
-
单通道ADC初始化函数
void adc_dma_init(uint32_t mar) {ADC_ChannelConfTypeDef adc_ch_conf = {0};__HAL_RCC_DMA1_CLK_ENABLE(); //使能DMA1时钟g_dma_adc_handle.Instance = DMA1_Channel1; //初始化DMA1的通道1g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; //数据传输方向设置为外设到内存g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; //外设地址不递增g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE; //内存地址递增g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //数据对齐使用半字g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; //数据对齐使用半字g_dma_adc_handle.Init.Mode = DMA_NORMAL; //传输模式配置为正常模式g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; //优先级设置为中等HAL_DMA_Init(&g_dma_adc_handle);//2.将DMA和ADC句柄连接起来__HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);g_adc_dma_handle.Instance = ADC1; //选择ADC1g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; //数据对齐方式为右对齐g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; //非扫描模式,仅使用一个通道g_adc_dma_handle.Init.ContinuousConvMode = ENABLE; //打开连续转换模式g_adc_dma_handle.Init.NbrOfConversion = 1; //通道数选择1个g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE; //禁止规则通道组间断模式 g_adc_dma_handle.Init.NbrOfDiscConversion = 0; //配置间断模式的规则通道个数g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //软件触发方式HAL_ADC_Init(&g_adc_dma_handle); //进行初始化HAL_ADCEx_Calibration_Start(&g_adc_dma_handle); //校准ADCadc_ch_conf.Channel = ADC_CHANNEL_1; //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_1; //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf); //通道配置HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar, 0); }与前面的实验相比,此函数只需要修改采样时间即可
adc_ch_conf.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; -
主函数循环代码
while (1){if (g_adc_dma_sta == 1){/* 计算DMA 采集到的ADC数据的平均值 */sum = 0;for (i = 0; i < ADC_DMA_BUF_SIZE; i++) /* 累加 */{sum += g_adc_dma_buf[i];}adcx = sum / (ADC_DMA_BUF_SIZE / ADC_OVERSAMPLE_TIMES); /* 取平均值 */adcx >>= 4; /* 除以2^4倍, 得到12+4位 ADC精度值, 注意: 提高 N bit精度, 需要 >> N *//* 显示结果 */lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE); /* 显示ADC采样后的原始值 */temp = (float)adcx * (3.3 / 65536); /* 获取计算后的带小数的实际电压值,比如3.1111 */adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE); /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */}LED0_TOGGLE();delay_ms(100);}在主函数的循环代码中, 通过增加采样次数和进行移位操作,提高了ADC采样精度。
提高精度的原理
-
增加采样次数:
通过增加采样次数来进行过采样。过采样可以降低噪声,提高信噪比,从而提高分辨率。将多个采样值相加并取平均值,可以增加有效位数。
-
移位操作:
通过移位操作,将结果右移,以提高分辨率。增加N位精度,需要对累加后的结果右移N位。
-
7. 内部温度传感器实验
-
功能: 通过ADC的通道16读取STM32F103内部温度传感器的电压值,并将其转换为温度值,显示在TFTLCD屏上。LED0闪烁用于提示程序正在运行。
-
温度计算方法:

-
ADC内部温度传感器初始化函数
void adc_temperature_init(void) {ADC_ChannelConfTypeDef adc_ch_conf;g_adc_handle.Instance = ADC1; //选择ADC1g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; //数据对齐方式为右对齐g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; //非扫描模式,仅使用一个通道g_adc_handle.Init.ContinuousConvMode = DISABLE; //关闭连续转换模式g_adc_handle.Init.NbrOfConversion = 1; //通道数选择1个g_adc_handle.Init.DiscontinuousConvMode = DISABLE; //禁止规则通道组间断模式 g_adc_handle.Init.NbrOfDiscConversion = 0; //配置间断模式的规则通道个数g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //软件触发方式HAL_ADC_Init(&g_adc_handle); //进行初始化HAL_ADCEx_Calibration_Start(&g_adc_handle); //校准ADCadc_ch_conf.Channel = ADC_CHANNEL_16; //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_1; //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_handle, &adc_ch_conf); //通道配置 } -
ADC MSP初始化函数
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc) {if(hadc->Instance == ADC1){RCC_PeriphCLKInitTypeDef adc_clk_init = {0};//使能时钟__HAL_RCC_ADC1_CLK_ENABLE();adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC; //ADC外设时钟adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6; //分频因子6,时钟为72M/6=12MHz HAL_RCCEx_PeriphCLKConfig(&adc_clk_init); //设置ADC时钟} } -
使能一次ADC DMA传输函数
uint32_t adc_get_result() {//4.启动A/D转换HAL_ADC_Start(&g_adc_handle); //开启ADC//5.等待规则通道转换完成HAL_ADC_PollForConversion(&g_adc_handle, 10); //轮询转换//6.获取规则通道A/D转换结果return (uint16_t)HAL_ADC_GetValue(&g_adc_handle); //返回最近一次规则组的转换结果 } -
获取内部温度传感器的结果函数
short adc_get_temperature(void) {uint32_t adcx;adcx = adc_get_result();short result;double temperature;temperature = adcx * (3.3 / 4096);temperature = (1.43 - temperature) / 0.0043 + 25;result = temperature * 100;return result; } -
主函数
int main(void) {short temp;HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */delay_init(72); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */lcd_init(); /* 初始化LCD */adc_temperature_init(); /* 初始化ADC内部温度传感器采集 */lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);lcd_show_string(30, 70, 200, 16, 16, "Temperature TEST", RED);lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);lcd_show_string(30, 120, 200, 16, 16, "TEMPERATE: 00.00C", BLUE);while (1){temp = adc_get_temperature(); /* 得到温度值 */if (temp < 0){temp = -temp;lcd_show_string(30 + 10 * 8, 120, 16, 16, 16, "-", BLUE); /* 显示负号 */}else{lcd_show_string(30 + 10 * 8, 120, 16, 16, 16, " ", BLUE); /* 无符号 */}lcd_show_xnum(30 + 11 * 8, 120, temp / 100, 2, 16, 0, BLUE); /* 显示整数部分 */lcd_show_xnum(30 + 14 * 8, 120, temp % 100, 2, 16, 0X80, BLUE); /* 显示小数部分 */LED0_TOGGLE(); /* LED0闪烁,提示程序运行 */delay_ms(250);} }
8. 光敏传感器实验
声明:资料来源(战舰STM32F103ZET6开发板资源包)
- Cortex-M3权威指南(中文).pdf
- STM32F10xxx参考手册_V10(中文版).pdf
- STM32F103 战舰开发指南V1.3.pdf
- STM32F103ZET6(中文版).pdf
- 战舰V4 硬件参考手册_V1.0.pdf
相关文章:
STM32-16-ADC
STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 STM32-12-OLED模块 STM32-13-MPU STM32-14-FSMC_LCD STM32-15-DMA…...
单例模式(C语言)
C语言的设计模式(单例模式) 单例模式(Singleton Pattern)是一种设计模式,目的是确保一个类只有一个实例,并提供一个全局访问点。 #include "stdio.h" #include "stdlib.h"// 定义一个…...
js前端格式化日期函数
开发需求 在前端中我们通常使用new Date()函数获取到的日期时间是下面这种样子:Thu Jun 06 2024 17:29:11 GMT0800 (中国标准时间),我们想要把它转换成常见的指定格式,比如 年-月-日 时:分:秒年/月/日 时:分:秒年-月-日年/月/日 所以就封装…...
五个超实用的 ChatGPT-4o 提示词
GPT-4o 是 OpenAI 最近推出的最新人工智能模型,不仅具备大语言模型的能力,而且拥有多模态模型的看、读、说等能力,而且速度比 GPT-4 更快。下面我们就来介绍几个超实用的 GPT-4o 提示词,帮助大家更好地了解 GPT-4o 的功能和应用场…...
基于51单片机多功能防盗报警proteus仿真( proteus仿真+程序+设计报告+原理图+讲解视频)
基于51单片机多功能防盗报警系统 1. 主要功能:2. 讲解视频:3. 仿真4. 程序代码5. 设计报告6. 原理图7. 设计资料内容清单&&下载链接 基于51单片机多功能防盗报警系统( proteus仿真程序设计报告原理图讲解视频) 仿真图proteus8.9及以上…...
gitee和github的协同
假设gitee上zhaodezan有一个开发库,但是从andeyeluguo上拉取最新的(从github上同步过来最新的) git remote add dbgpt_in_gitee https://gitee.com/andeyeluguo/DB-GPT.git remote -v git pull --rebase dbgpt_in_gitee main 有冲突可能需要…...
压力测试-性能指标-Jmeter使用-压力测试报告
文章目录 1.压测目的2.性能指标3.Jmeter3.1Jmeter使用3.1.1 运行Jmeter3.1.2 添加线程组3.1.3设置HTTP请求3.1.4 设置监视器 3.2 查看Jmeter压测结果3.2.1 查看结果树3.2.2 查看汇总报告3.2.3 查看聚合报告3.2.4 查看汇总图 1.压测目的 内存泄漏:OOM,重…...
通过Slf4j中的MDC实现在日志中添加用户IP功能
一、slf4j中MDC是什么 slf4j除了trace、debug、info、warn、error这几个日志接口外,还可以配合MDC将数据写入日志。换句话说MDC也是用来记录日志的,但它的使用方式与使用日志接口不同。 在使用日志接口时我们一般这么做 log.debug("log debug"…...
代码随想录算法训练营第四十九天| 139.单词拆分、背包问题总结
139.单词拆分 题目链接:139.单词拆分 文档讲解:代码随想录/单词拆分 视频讲解:视频讲解-单词拆分 状态:已完成(0遍) 解题过程 这几天博主忙着面试和入职,一晃已经周四了,这个礼拜…...
STM32F103VE和STM32F407VE的引脚布局
STM32F103VE vs STM32F407VE 引脚对比表 引脚 STM32F103VE STM32F407VE 备注 1 VSS VSS 地 2 VDD VDD 电源 3 VSSA VSSA 模拟地 4 VDDA VDDA 模拟电源 5 OSC_IN OSC_IN 外部时钟输入 6 OSC_OUT OSC_OUT 外部时钟输出 7 NRST NRST 复位 8 PC13 (GPIO) PC13 (GPIO) GPIO 9 PC14 (…...
搜维尔科技:使用 Xsens 动作捕捉技术创建栩栩如生的动画
使用Xsens 动作捕捉技术创建栩栩如生的动画 搜维尔科技:使用 Xsens 动作捕捉技术创建栩栩如生的动画...
鸿蒙开发 一 (三)、ArkTS开发实战上
ArkTS 从 TypeScript 优化而来, 但有些用法又不太一样, 在开发中, 经常会出现一些报错提示,下面我们也汇总一些常见错误,捡一些常见的整理一下 Promise 的用法: //TypeScript 写法:private load…...
TensorRT教程(1)初探TensorRT
1. TensorRT简要介绍 TensorRT(NVIDIA TensorRT)是 NVIDIA 开发的一个用于深度学习推理的高性能推理引擎。它可以针对 NVIDIA GPU 进行高效的深度学习推理加速,提供了许多优化技术,使得推理速度更快,并且可以在生产环境…...
多表连接查询和子查询
一、连接查询 连接查询是SQL语言最强大的功能之一,它可以执行查询时动态的将表连接起来,然后从中查询数据。 1.1、连接两表的方法 在SQL中连接两表可以有两种方法,一种是无连接规则连接,另一种是有连接规则连接。 无连接规则连…...
数据挖掘与机器学习——聚类算法
目录 无监督学习 聚类算法 概念: 功能: 应用场景: 评判标准: 划分聚类: K-means聚类 逻辑实现: 聚类方式 问题: 解决: 可能存在的问题: 1.初始值对K-means聚…...
QT快速下载
去QT官网之后,如下图所示 比如要下载qt-opensource-windows-x86-5.14.2.exe,进入5.14对应的文件夹,找到对应的版本 点击Details, 下载对应的种子,然后通过迅雷下载 个人实测,家庭网络平均18M的速率...
最短路问题
最短路问题是图论里非常经典的一个考点 接下来着重讲述五种求最短路的算法:朴素版dijkstra算法、堆优化版的dijkstra算法、bellman-ford算法、spfa算法、floyd算法 总体思维导图: 总体思路: 最短路分为两大类 { 在以下给出的时间复杂度中n…...
spark MLlib 中的分类模型
理解这些机器学习模型的数学原理需要一定的数学基础,下面我将简要介绍每个模型的数学原理,并附上相关的数学公式。 1. LinearSVC(线性支持向量机) 数学原理: 线性支持向量机的目标是找到一个超平面,最大化…...
24上半年报考人数“不增反降”?备考下半年软考的难了......
近日,工信教考发布了一篇《2024年上半年计算机软件资格考试顺利举行》的文章,公布了2024年上半年软考报考人数共计52.77万人,其中,初级资格5.12万人、中级资格24.37万人、高级资格23.28万人。 软考高级占总报名人数的44%…...
初出茅庐的小李博客之使用立创开发板(ESP32)连接到EMQX Platform【MQTT TLS/SSL 端口连接】
介绍 手上有一块立创开发板,本着不吃灰的原则把它用起来,今天就来用它来连接上自己部署的MQTT服务器进行数据通信。 硬件:立创开发板 开发环境:Arduino IDE Win11 MQTT 平台:EMQX Platform 立创开发板介绍࿱…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
