STM32-ADC过采样实验

news/2024/12/15 22:51:12/

我们之前已经有过一些关于STM32-ADC的笔记和实验代码了,链接如下:

关于ADC的笔记1_Mr_rustylake的博客-CSDN博客

STM32-ADC单通道采集实验_Mr_rustylake的博客-CSDN博客

STM32-单通道ADC采集(DMA读取)实验_Mr_rustylake的博客-CSDN博客

STM32-ADC多通道输入实验_Mr_rustylake的博客-CSDN博客

首先简单介绍一下过采样。对于12位的STM32,其所能分辨的最小电压(即最小刻度)为3.3V / 2^12 = 0.0008V。在不改进硬件的情况下,可以通过过采样和求均值的方式提供ADC分辨率。

根据增加的分辨率位数计算过采样分辨率频率的方程:

fos = 4^w * fs,fos是过采样频率,w是希望增加的分辨率位数,fs是初始采样频率要求。

比如从12位提升ADC分辨率到16位分辨率,采样频率就要提高256倍。将采样结果求和,再将求和结果右移N位(N为用户想提升的位数,本例中为4),就能得到提高分辨率的结果了,这个过程称为抽取。

本次ADC过采样实验的实验要求是:通过ADC1通道1(PA1)过采样实现16位分辨率采集电压,并显示ADC转换的数字量和转换后的电压值。

首先确定我们的最小刻度,Vref = 3.3V,所以0V <= Vin <= 3.3V,所以最小刻度是3.3V / 65536(2^16)。

接下来确定转换时间。采样时间1.5个ADC时钟周期为例,可以得到转换时间为1.17 * 256us。

时间转换公式参考如下公式:Tcvtmin=(12.5+X)周期=(12.5 + X)/(12MHz)=1.17us。

接下来编写实验代码:

先编写函数文件adc.c:

#include "./BSP/ADC/adc.h"ADC_HandleTypeDef g_adc_handle;
DMA_HandleTypeDef g_dma_handle;
uint8_t g_adc_dma_sta; //标志DMA的传输是否完成void adc_dam_init(uint32_t mar){ADC_ChannelConfTypeDef adc_ch_conf;__HAL_RCC_DMA1_CLK_ENABLE();g_dma_handle.Instance = DMA1_Channel1;g_dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;  //外设到内存g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;  //因为选取的是DMA1的数据寄存器,选择不增量g_dma_handle.Init.MemInc = DMA_MINC_ENABLE;  //对于存储器需要存储多个数据,所以选择增量模式g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //外设数据位宽,我们选择16位半字(全字可以理解为全角中文字符)g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;  //存储器数据位宽,我们也选择16位半字g_dma_handle.Init.Mode = DMA_NORMAL;   //选择普通模式,因为在传输完成之后我们需要进行进一步操作现实我们获取到的值,所以选择normalg_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;   //只有1个DMA随便选HAL_DMA_Init(&g_dma_handle);//联系DMA和ADC的句柄__HAL_LINKDMA(&g_adc_handle, DMA_Handle, &g_dma_handle);  //第二个参数为第一个ADC句柄的第三个成员,指向对应的DMA句柄g_adc_handle.Instance = ADC1;g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; //右对齐g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; //不扫描g_adc_handle.Init.ContinuousConvMode = ENABLE; //连续模式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);adc_ch_conf.Channel = ADC_CHANNEL_1;adc_ch_conf.Rank = ADC_REGULAR_RANK_1; //转换顺序adc_ch_conf.SamplingTime = ADC_SMAPLINGTIME_1CYCLES_5; //设置为最大值HAL_ADC_ConfigChannel(&g_adc_handle, &adc_ch_conf);HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 2, 3);HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);HAL_ADCEx_Calibration_Start(&g_adc_handle);HAL_DMA_Start_IT(&g_dma_nch_handle, (uint32_t)&ADC1->DR, mar, 0);HAL_ADC_Start_IT(&g_adc_nch_handle, &mar, 0);
}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();  //使能ADC时钟__HAL_RCC_ADC1_CLK_ENABLE();   //使能GPIO时钟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分频,72/6=12MHzHAL_RCCEx_PeriphCLKConfig(&adc_clk_init, &g_adc_handle);}
}uint32_t adc_get_result(void){HAL_ADC_Start(&g_adc_handle);HAL_ADC_PollForConversion(&g_adc_handle, 10); //第二个参数比1大就行return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);
}uint32_t adc_get_result_average(uint32_t ch, uint8_t times){uint32_t temp_val = 0;uint8_t t;for(t = 0; t < times; t++){temp_val += adc_get_result();delay_ms(5);}return temp_val / times;
}void adc_dma_enable(uint16_t cndtr){/*ADC1->CR2 &= ~(1 << 0); //关闭ADCDMA1_Channel1->CCR &= ~(1 << 0);//关闭DMAwhile(DMA1_Channel1->CCR & (1 << 0));DMA1_Channel1->CNDTR = cndtr;DMA1_Channel1->CCR |= (1 << 0); //开启DMAADC1->CR2 |= (1 << 0);  //开启ADCADC1->CR2 |= (1 << 22);  //触发规则组转换*///hal库法__HAL_ADC_DISABLE(&g_adc_nch_handle);__HAL_DNA_DISABLE(&g_dma_nch_handle);while(__HAL_DMA_GET_FLAG(&g_dma_nch_handle, __HAL_DMA_GET_FLAG_INDEX(&g_dma_nch_handle)));DMA1_Channel1->CNDTR = cndtr;__HAL_DMA_ENABEL(&g_dma_nch_handle);__HAL_ADC_ENABLE(&g_adc_nch_handle);HAL_ADC_Start(&g_adc_nch_handle);
}void DMA1_Channel1_IRQHandle(void){if(DMA1->ISR & (1 << 1)){g_adc_dma_sta = 1;DMA1->IECR |= 1 << 1;}
}

接下来是函数头文件adc.h:

#ifndef __ADC_H
#define __ADC_H#include "SYSTEM/sys/sys.h"
#include "BSP/DMA/dma.h"extern ADC_HandleTypeDef g_adc_handle;void adc_dam_init(uint32_t mar);
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc);
uint32_t adc_get_result(void);
uint32_t adc_get_result_average(uint32_t ch, uint8_t times);
void adc_dma_enable(uint16_t cndtr);
void DMA1_Channel1_IRQHandle(void);#endif

接下来是主函数代码main.c:

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc.h"/* ADC过采样技术, 是利用ADC多次采集的方式, 来提高ADC精度, 采样速度每提高4倍* 采样精度提高 1bit, 同时, ADC采样速度降低4倍, 如提高4bit精度, 需要256次采集* 才能得出1次数据, 相当于ADC速度慢了256倍. 理论上只要ADC足够快, 我们可以无限* 提高ADC精度, 但实际上ADC并不是无限快的, 而且由于ADC性能限制, 并不是位数无限* 提高结果就越好, 需要根据自己的实际需求和ADC的实际性能来权衡.*/
#define ADC_OVERSAMPLE_TIMES    256                         /* ADC过采样次数, 这里提高4bit分辨率, 需要256倍采样 */
#define ADC_DMA_BUF_SIZE        ADC_OVERSAMPLE_TIMES * 10   /* ADC DMA采集 BUF大小, 应等于过采样次数的整数倍 */uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];                   /* ADC DMA BUF */extern uint8_t g_adc_dma_sta;                               /* DMA传输状态标志, 0,未完成; 1, 已完成 */
extern ADC_HandleTypeDef g_adc_dma_handle;                  /* ADC(DMA读取)句柄 */int main(void)
{uint16_t i;uint32_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_dma_init((uint32_t)&g_adc_dma_buf);     /* 初始化ADC DMA采集 */adc_channel_set(&g_adc_dma_handle, ADC_ADCX_CHY, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_1CYCLE_5); /* 设置ADCX对应通道采样时间为1.5个时钟周期, 已达到最高的采集速度 */lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);lcd_show_string(30,  70, 200, 16, 16, "ADC OverSample TEST", RED);lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH1_VAL:", BLUE);lcd_show_string(30, 130, 200, 16, 16, "ADC1_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];}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);      /* 显示ADCC采样后的原始值 */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);}
}

到这里我们的实验代码就编写完了。


http://www.ppmy.cn/news/71476.html

相关文章

RLC串联电路的一些形象化的结算

考虑普通的串联电路 考虑仅包含电阻和电感的一段恒流源驱动的电路&#xff0c;电阻和电感串联。 假定此时感抗值 电阻值。此时最终的阻抗值与电阻值和电抗值构成等腰直角三角型。最终的阻抗值 1.414*R. 电路两端的端电压会相对电流超前45度角。 OK&#xff0c;这很容易…

drawio@绘制带有latex公式的图表@示意图@流程图@白板模式whiteboard

文章目录 drawio绘制带有latex公式的图表示意图流程图白板模式whiteboard使用drawio小结 公式编辑Use mathematical typesetting in diagramsUse mathematical typesetting in diagramsTroubleshooting关于文本框元素公式渲染问题&#x1f388;Maths is not rendered 模式切换d…

IIC通信理解

前言 就个人对IIC通信的理解&#xff0c;通过用图文的方式&#xff0c;尽量简洁的记录下此文。希望能对大家理解IIC通信协议有所帮助。 理解IIC 对于IIC协议的理解&#xff0c;我个人是将完整的IIC时序协议&#xff0c;分成六大块理解。分别是开始条件,结束条件,发送字节,发送字…

关于modbus通讯协议

什么是modbus通讯协议&#xff1f; Modbus是一种通用的串行通信协议&#xff0c;最初由Modicon公司开发&#xff0c;用于PLC&#xff08;可编程逻辑控制器&#xff09;和其他工业设备之间的通信。现在已成为工业通信领域的标准&#xff0c;广泛应用于可编程控制器、传感器、仪…

打印菱形(两种思路)

一、输入的行数等于上半部分的金字塔行数 思路&#xff1a; 仔细观察图形&#xff0c;可以发现&#xff0c;此图形中是由空格和*按照不同个数的输出组成的。 上三角&#xff1a;先输出空格&#xff0c;后输出*&#xff0c;每行中空格&#xff1a;从上往下&#xff0c;一行减少一…

掌握ZBrush的19个建模技巧,让你的雕刻作品更逼真

ZBrush 是一个数字雕刻和绘画软件&#xff0c;它以强大的功能和直观的工作流程彻底改变了整个三维行业&#xff0c;按照世界领先的特效工作室和全世界范围内的游戏设计者的需要&#xff0c;以一种精密的结合方式开发成功的&#xff0c;它提供了极其优秀的功能和特色&#xff0c…

git提交代码到GitLab步骤及拉取远程分支内容

一、本地建立一个空文件夹 点击鼠标右键点击红色箭头方向 Git Hash Here 二、git init 进行初始化 这个时候文件夹中会出现 .git 文件夹 三、添加远程仓库地址 git remote add origin (address) # 添加远程仓库地址 address是远程仓库代码链接 四、如果有分支把远程分支拉到…

Linux的tail,grep,sed命令总结,以使用上述三种命令获取日志信息为例

目录 tail命令语法说明基本参数命令举例 grep命令语法说明匹配模式选择杂项输入控制文件控制 sed命令语法格式举例 使用命令组合查询日志信息 业务需求需要对软件日志进行查询和呈现&#xff0c;查询的条件是时间区间和关键词&#xff0c;系统运行在linux环境下&#xff0c;为此…