STM32示波器设计:实现更加精准的测量

目录

前言

1、硬件模块

2、示波器基础知识

2.1 当头一棒就是,波形的概念

2 .2 第二就是需要观察的波形参数

2.3 第三就是示波器参数

2.3.1 采样率

2.3.2 带宽

2.3.4 刷新率

3、ADC采集和DAC输出

3.1 ADC 采集实现

3.1.1 配置ADC采集为定时触发DMA采集模式

3.1.2 配置ADC关联的定时器

3.1.3 转换成有效值

3.2 DAC波形输出

3.2.1 选择定时器触发

3.2.2 配置定时器

3.2.3 生成波形数据

4、波形刷新方案

4.1 初始化流程

4.2 生成波形数据

4.3 波形刷新

方案1:消隐一条线,画当前线,而不是消隐完再画线

方案2:特殊处理网格点

方案3:计算像素点,整屏刷新

5、触发模式

6、存储深度


前言

出于产品需求,需要在产品中集成示波器功能,满足显示实时电压的需求。

这篇文章就总结和讨论一下示波器的方案设计(低成本)。

其中,实现方案主要是在3、4章节,1、2是概念部分和环境介绍,不需要的可以跳过。

如下是我设计时的主要参考资料。

参考资料1:二代示波器教程 – 随笔分类 – 硬汉eric2013 – 博客园 (cnblogs.com)

参考资料2:STM32袖珍示波器DS201设计 – 电路城 (cirmall.com)

1、硬件模块

总体而言,能够提供的硬件模块2个:MCU的ADC接口,LCD屏幕,别的就没了;

放大电路、滤波功能统统没有,demo板子直接用MCU的ADC接口采集外部电压,将处理后的数据送到屏幕去刷新。

同时为了模拟测量数据,还使用DAC+定时器来输出正弦波、方波、三角波等波形。

2、示波器基础知识

2.1 当头一棒就是,波形的概念

概念1:波形是指相应物理量在时间和空间上的分布情况的图形抽象。

概念2:正弦波是在时域中定义的,其它任何非正弦波形都可以看作是正弦波的叠加。

2 .2 第二就是需要观察的波形参数

如下图所示。

基本观察参数:最大值、最小值和峰峰值(不是错别字,就是峰峰值,最差);

其它参数还有:顶端值、底端值和幅值,这三者都是在稳定值中计算出来的;

时域参数:上升时间,下降时间,正脉宽,负脉宽,这四者都是测量值,还有频率、占空比,是计算出来的值。

一般我们需要显示的,就是①最大值、最小值和峰峰值,②频率、占空比,③X、Y轴单位大小

2.3 第三就是示波器参数

2.3.1 采样率

采样率是指一秒内能采集到多少个样本值。

理论上说,采样率最大要超过被测信号最大频率的5倍以上,才能较为完整的采集到数据规律。(为什么?被测信号频率除以采样频率,其实就是在被测信号一个周期内能采集到的点数,若小于5个点,则很难具体描绘出数据规律)

STM32单片机的采样率,等于采样时钟除以采样周期,以STM32F4系列芯片为例,ADC最大时钟为36MHz,ADC最小采样周期为3+12个周期,故采样率最大为36/15=2.4MHz。

实际应用中:

1、根据主频时钟,ADC实际分频得到的时钟计算,比如同时使用USB接口时,主频最大168MHz,ADC最大时钟21MHz;

2、可以通过双路或三路ADC同时使用,提高ADC采样率

2.3.2 带宽

带宽的定义:在频幅特性曲线中,当信号衰减至-3db(70.7%)时,此时的频点定义为示波器的带宽。

如何计算?我还没闹明白,留到后面仔细研究。

但是可以明白的是,带宽要大于被测信号的5倍以上(普遍认为的)才能不失真。

采样率低和带宽小对波形带来的的几种影响

 

2.3.4 刷新率

刷新率,即刷新一帧波形图像的速率。

假设刷新一张波形(大约2万个像素点)需要15ms,则刷新率就等于66帧每秒。

刷新率主要受限于MCU主频,以及数据处理效率,和屏幕刷新效率。

数据处理和屏幕刷新的提高有这么几种思路:

1、裸机运行

减少任务切换带来的时间消耗(微秒级)

2、利用缓存

以空间换时间:一种是将函数封装写入存储设备,提高调用效率;一种是开辟缓存(全局变量)存储数据,便于处理波形数据和LCD像素点数据。

3、优化算法

一是优化代码,二是提高编译器优化等级。


对于前期的开发工作,都可以先抛开,先上RTOS,再实现基本的波形实时输出功能,并先在功能上进行优化。优化完毕之后,再在刷新时间上进行尝试优化(可以逐步降级,但不能逐步升级)。

3、ADC采集和DAC输出

前提        {

        芯片平台:STM32F429ZGT6

        ADC:PA3

        DAC:PA4

}

3.1 ADC 采集实现

3.1.1 配置ADC采集为定时触发DMA采集模式

如下以PA3为例,①选择定时器3触发,②选择最小采样周期三,③选择DMA循环模式或Normal模式(半字大小)

3.1.2 配置ADC关联的定时器

如下,选择定时器触发事件为更新事件。

由此我们就用两种手段判断ADC数据转换是否结束:

1、读取ADC的DMAbuf的count值

注意ADC的DMA Count读取函数,读取到的数目,而不是实际长度;比如BUF长度为300,每个单元2byte大小,当DMA满时读取到的数值时300(而不是像串口中断那样,读到的是0)。

2、在DMA完成中断的回调函数中判断

触发完成中断时,肯定意味着1次转换结束,可以直接处理,也可以只关闭不处理(等待线程去处理)。

3.1.3 转换成有效值

ADC数据的第一层处理,就是要根据自己的硬件电路设计,将ADC数值转换成真实电压值。

我这里是MCU直连,所以转换关系就是 vol = adc_value * 3300 / 4096 .

参考资料中,DS201中硬件电路使用了输入放大器和挡位切换电路,需要由硬件工程师计算电阻分压和放大关系,最终得到计算公式。

有了这一层数据,才方便后续进行算法消抖、归一化处理等等。

3.2 DAC波形输出

由于测量内部电压的波形基本是条直线,外部环境输入电压又容易超过3.3V导致MCU烧坏。

所以利用内部DAC输出,用ADC测量DAC,还少了跟地线。

或者更巧妙一点,就只用一个IO,DAC输出的同时用ADC进行测量。

血的教训!

单片机ADC采集的基准电压与芯片供电电压都是3.3V,又没有分压电阻,

所以最大只能测量3.3V。

但是,我接入外部输入时,没控制好,怼了一个12V过去,正点原子380块的STM32H743 MCU直接烧了。


这里DAC的配置,采用的是DMA+DMA+定时器。

3.2.1 选择定时器触发

记得配置为DMA循环模式

3.2.2 配置定时器

还是定时器“更新事件”

3.2.3 生成波形数据

根据自己的测试需求,生成正弦波和方波数据。其本质就是一个uint16_t类型的数组,其中的数据按照一定的周期进行演进。

配置好之后,启动定时器并配置DMAbuf进行输出。

HAL_TIM_Base_Start(&htim6);
HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, pData, Length, DAC_ALIGN_12B_R);

4、波形刷新方案

前提        {

        屏幕尺寸:320*240

        波形窗口:300*200

        LCD API函数(初始化、清屏、画点、画线、显示字符串)

}

4.1 初始化流程

1)初始化DAC模块,启动DAC模块数据传输

2)初始化ADC模块,开始采集ADC数据

3)while(1)循环进行如下判断:死等ADC采集完成,完成之后先处理数据,处理之后,先启动下一次ADC传输,再在ADC传输没有结束之前刷新、刷完波形。

4.2 生成波形数据

在”ADC+DMA中断接收“,我们已经收到了真实电压数据集。

假如此时垂直扫描电压为5V(即Y轴最大电压为5V,最小电压为0V),那么y轴又只有200个像素点,每一个点的数值单元为5000/200=25,所以还要对数据集除以25得到对应的Y轴坐标点。

这里有几个问题:

1、能否直接除以25?

假如数据除以25之后,出现与真实值+1、-1的锯齿特性时,会导致波形有毛刺。

这时就需要设计优化算法,比如最简单的四舍五入。

2、X轴坐标点如何确定?

我目前的处理方式时,按照当前水平扫描时钟(时基挡位)扫描1次,比如当前时基为200us,那就200us扫描一次,扫描够300个坐标点结束,开始处理-刷新。

这里也有很多优化方式:比如扫描次数设置为300的倍数,时基缩小对应的倍数或不变,从中筛选若干个范围并取平均得到300个点。


不论如何,最终送给屏幕刷新的就是一个长度为300的数组,对应300个坐标值。这300个坐标值,就叫做波形数据。

4.3 波形刷新

比较简单的方法是,画线+缓存上一次波形数据并消隐;

画线:顾名思义,就是对300个坐标点,每两个点之间画线;

1、先对上一次的波形数据消隐,消隐即将线的颜色化成背景色(擦除上一帧波形);

2、再绘制当前波形数据的波形;

3、缓存当前波形数据。

//绘制波形
//1\擦除原有波形再绘制新波形
//2\输入数组长度为OSC_WIDTH:300
//X0\X1\Y0\Y1分别波形窗口的角落坐标值
void OscDrawWave(uint8_t WaveBuf[])
{
	static uint16_t X = {0};
	static uint8_t WaveBufPre[OSC_WIDTH] = {0};
	
	if(WaveBufPre[0])
	{
		LCD_SetColor(List_c[OSC_M_WAVE].Back, List_c[OSC_M_WAVE].Back);
		for(X = 0; X < X1 - X0 - 1; X++)
		{
			LCD_DrawLine(
				X0 + X, Y1 - WaveBufPre[X], 
				X0 + X + 1, Y1 - WaveBufPre[X+1]
			);
		}
	}
	
	LCD_SetColor(List_c[OSC_M_WAVE].Point, List_c[OSC_M_WAVE].Back);
	for(X = 0; X < X1 - X0 - 1; X++)
	{
		LCD_DrawLine(
			X0 + X, Y1 - WaveBuf[X], 
			X0 + X+ 1, Y1 - WaveBuf[X+1]
		);
	}
	
	for(X = 0; X < X1 - X0; X++){
		WaveBufPre[X] = WaveBuf[X];
	}
}

 这样做的好处就是能减少闪烁频度。

进一步的,从显示原理上,还可以通过以下几种方法提高刷新速度:

方案1:消隐一条线,画当前线,而不是消隐完再画线

如上代码中,即使是做了消隐也会出现闪烁,改成擦除一条线再画一条线,感官上就能好很多。

void OscDrawWave(uint8_t WaveBuf[])
{
	static uint16_t X = {0};
	static uint8_t WaveBufPre[OSC_WIDTH] = {0};
	
	for(X = 0; X < X1 - X0 - 1; X++)
	{
		LCD_SetColor(List_c[OSC_M_WAVE].Back, List_c[OSC_M_WAVE].Back);
		LCD_DrawLine(
			X0 + X, Y1 - WaveBufPre[X], 
			X0 + X + 1, Y1 - WaveBufPre[X+1]
		);
		LCD_SetColor(List_c[OSC_M_WAVE].Point, List_c[OSC_M_WAVE].Back);
		LCD_DrawLine(
		X0 + X, Y1 - WaveBuf[X], 
		X0 + X+ 1, Y1 - WaveBuf[X+1]
	);

	for(X = 0; X < X1 - X0; X++){
		WaveBufPre[X] = WaveBuf[X];
	}
}

方案2:特殊处理网格点

对于示波器要刷新的不知有波形,还有网格线,与波形参数。

波形参数一般要变动的是字符串类型的数值,优化空间不大(像素点大小固定)。

而且波形中,往往有大部分像素点是与网格点重合的。

所以对于网格点,就有三种状态,两种判断:

1、消隐时,网格点要绘画成网格颜色,不能是背景色;

2、绘制时,网格点要绘画成波形颜色,不能是网格颜色;

如果不小心将网格点搞成背景色,就会导致波形刷着刷着,网格没了……

方案3:计算像素点,整屏刷新

这种方案是想,计算所有需要变更状态的像素点,送到屏幕进行一次性刷新,或者退后一步逐个刷新像素点。前者“一次性刷新”可以想办法利用类似DMA2D的方式提高刷新速度,后者则可以减少像素点重复刷新次数,进而提高速度。

目前还没有做出来很好的实现方法,后面再研究并分享一下。

5、触发模式

以上讨论的都是,无触发模式的波形处理。

我们当然知道,示波器还有触发功能:自动触发、单次触发,其中触发方式最常用的就是边沿触发,我们假定为垂直扫描电压值的一半+上升沿作为触发条件。

这种条件的实现方式,想到的有一下几种:

1、硬件触发

由硬件决定将数据送进来,第一个信号就是满足条件的起始信号。(我们不用)

2、软件触发

扩大数据采样的样本数量

2.1、软件判断数据的第一个上升沿位置;

2.2、利用外部中断EXTI,选择上升沿触发,触发时记录样本位置;

找到触发位置时,将其后的300个样本值作为坐标点数据

如果没有找到触发位置,则取最后面的300个坐标点强制显示。

6、存储深度

以波形窗口宽度300为例,假设时基最小单位是200us,最大为200ms,相差1000倍,如果想在暂停时极限放大信号状态,就需要3000个坐标点,若还需要能够前后移动一般大小,则总共需要6000个样本点,即6K大小。

存储深度如何实现,略……

物联沃分享整理
物联沃-IOTWORD物联网 » STM32示波器设计:实现更加精准的测量

发表评论