写在前面
突然发现本鸽子已经四个月没写什么像样的技术文章了,因为还在上学,这个学期的学习压力实在有点大( 老ddl战神了)。所以只能先把博客先鸽了。╮(╯▽╰)╭
不过这个学期还是学到了很多东西的,现在期末考试完,也稍微有了一点闲暇时间,可以来记录一下这个学期的收获。o( ̄▽ ̄)ブ
这次得的是一等奖第一名,感谢队友的辛勤努力,希望再接再厉。(ง •_•)ง
题目分析
- 由于单片机的ADC只能采集0-3.3V的电压,所以需要经过前级调理电路把电压转换到0-3.3V。
- 根据题目发挥部分的要求,最大电压为2.5+5=7.5V(5V直流偏置加5V峰峰值),最小电压为-2.5V(5V峰峰值)
所以硬件采用的方案是电平搬移+衰减,先将整体电平抬高到0V之上,然后经过衰减得到0-3.3V的电平。
在这个过程中需要保持电平的线性关系。这就要求需要在测量边界处留出一些余量。 - 频率采用单片机的输入捕获,最高可以做到1MHz。
- 峰峰值的测量使用ADC找最值的方法,而不是峰值检波电路。
- 波形的判断采用波形因数法,计算速度快,调参后准确率高。
ADC采样简介
根据ST官方手册,STM32是逐次比较型ADC,其基本原理简单来说是让信号电平给一个电容序列充电,然后把获得的充电电压依次与1/2参考电压,3/4参考电压….比较,最后将结果输出。
下面是基本原理图。
一次采集电压由两个过程组成,采样和转换。
采样时间指的是把电容从零充电到信号电平所需要的时间,转换时间指的是内部的开关序列将参考电压通过二分法逼近到信号电压所需要的时间。
所以ADC采样率由下面这个式子给出
ADC采样率 = ADC主频 / (采样时间 + 转换时间)
ADC主频取决于时钟域的来源与分频,H7系列最高不能超过36MHz,F1系列最高不能超过14Mhz。
采样时间由用户设定,可以有以下几种
转换时间取决于ADC分辨率,可以有以下几种
根据上文,STM32H743在16位下,最高采样速率可以达到3.6M每秒,36/(1.5+8.5)=3.6。
但是需要注意,最大速率下的ADC不一定稳定,最大能稳定的采样速率还跟芯片的封装有关,如下表所示
其中BGA封装在高分辨率的情况下最大速度远远大于LQFP封装。
过采样
过采样是多次采样求平均值。比如过采样设置4,那么将采集4个值进行累加。接着配置右移动2位的话,相当于将刚才的累加值除以4,得到平均值。
这样就不需要在程序里求平均了,非常适合采样稳定度要求高,而速率要求没那么高的场合。
而过采样还有一种用法就是牺牲采样速率提高采样精度,根据过采样技术,每提高1位ADC分辨率,需要增加4倍的采样率。比如G0系列过采样设置256,右移4位,相当于在12位ADC的基础上又获得了4位精度,可以当作16位ADC来使用。
这里我开启的是四倍过采样求平均,效果很好,对于最大值和最小值的捕捉非常准确。
这里我做了一个取舍,使用的是14位分辨率,采样速率能到4M每秒,4倍过采样,相当于1M采样率。
最值计算
将采样数组进行排序,这里使用的是冒泡排序,时间复杂度为O(n^2)。
取最大的1%进行平均,获得最大值。最小值同理。
全部求和并除以长度,获得均值。
1 | /*计算并显示最大最小值峰峰值*/ |
输入捕获
输入捕获的原理是通过检测IO电平的上升沿和下降沿来获取高电平时间和低电平时间,通过时间来计算周期和占空比。
在第一次上升沿处记录时间A,第一次下降沿记录时间B,第二次上升沿记录时间C
那么周期就是C-A,高电平时间为B-A
1 | /*Main函数里的输入捕获处理 计算频率*/ |
波形判断
有很多种判断波形的方法,FFT获得谐波分量,求导判断突变点,我在这里使用的是波形因数法。本质上是对面积进行积分,利用不同形状的波形的占比不同来判断。
波形因数法的原理是不同波形的波形因数不同,即图中颜色区域的面积与矩形的占比。理论值正弦波0.707,方波1,三角波0.5
实际上计算结果不一定接近理论值,但一定为方波最大,正弦波其次,三角波最小。
而且不同采样率也会影响计算出的值,需要通过测试来确定三种波形的阈值。
代码编写的原理是,先找到均值的跳变点,即左端小于均值,右端大于均值,即认为是左点。右点同理,然后再归一化计算矩形系数。
1 | /*波形判断函数 变量为buffer数组,数组个数,数组最大最小值*/ |
波形显示
波形显示就是把ADC采集到的电压数据依次绘制到屏幕上。这样在屏幕上看到的就是信号随时间的变化,也就是波形。
这里波形显示原理与真正的示波器原理类似但不完全相同,真正的示波器有存储显示,抹迹显示还有卷动显示等方式。
在这里只需要采集并显示即可,也就是存储显示。
确定绘制起始点
因为每次采样并不一定能从波形的起始点开始,所以需要对采集得到的波形数组处理进行一定的处理。
比如下图就是将波形从均值处截取。把之前的数据丢弃,将截取后的数组显示在屏幕上。这样每次观察到的波形就是一样的。
这其实就是示波器的触发值的设置,这里采用的是均值上升沿触发。
幅值与坐标对应关系
首先需要在屏幕上划分一块显示波形的区域,如X轴0-800,y轴150-450这一块区域作为显示区域。
对于一个采集到的连续的波形数组来说,数组的下标对应着时间,数组的每一个值都对应着一个幅值。
所以可以获得这样一个关系:
X[i] = 数组下标i / X轴缩放倍数 + X轴偏移
Y[i] = (数组值i-均值)×Y轴缩放倍数 + Y轴平均值(300) + Y轴偏移
在这里,采样数组是一个长度恰好为800的数组,所以不需要经过X轴的缩放,也方便了代码的理解和编写。
1 | /*波形重排显示 变量为存储波形的数组,数组大小,数组最大值,数组最小值*/ |
波形内插
在信号频率很高的情况下,一个周期的采样点数很少,此时若用直线去连接这些点,恢复出来的波形和原始波形的差距就会比较大。
我们可以使用波形内插的方法来缓解这个问题,有多种内插方式,对于信号波形来说,一般使用的是sinc插值。
这里是一个Matlab的测试案例,使用sinc插值之后,可以看到恢复出的信号非常接近原始信号。
1 | %参数设定 |
用C语言重构上述代码,方便移植到嵌入式设备上。
1 |
|
这个C语言测试案例是将一个80个点的离散信号通过内插恢复到800个点,然后将800点输入matlab并绘图,和原图比较,发现非常接近原始波形。
一些照片
结语
这篇文档算是对本次比赛中所用的技术的一个整理,方便自己日后复盘调用,也旨在用通俗易懂的方式给后来人讲解清楚一些技术的原理和应用方法。
希望能有所帮助。
参考链接
STM32ADC https://blog.csdn.net/wallace89/article/details/117048846
Matlab内插 https://blog.csdn.net/Differoucius/article/details/121142456