自制简易示波器

其实是在整理手上的元器件的时候,想用一个示波器,但是我没有,因此就想到自己简单做一个,能满足自己的简单需求即可。

经过测试 10Khz 及以下的波形都可以正常显示,可以先去文章末尾看效果。更高的频率还没试过。缺点就是:只能测量正电压,0-3.3。一般测量开发板调试电机时输出的PWM波是够用了。

====>>> 文章汇总(有代码汇总) <<<====

1. 硬件准备

  1. 正点原子STM32F103 Mini开发板,主控 STM32F103RCT6.
  2. 配套的 2.8英寸 LCD屏幕。

其他开发板也行,只要能驱动这个屏幕就行。
普中的开发板的话,我主页也有同样 LCD屏的驱动教程,完全可以直接替代移植。

2. 软件规划

示波器监测部分

  1. 定时器定时触发ADC;
  2. ADC监测电压变化;
  3. DMA传输数据ADC数据。

显示部分:

  1. LCD屏进行显示;

3. 硬件配置

3.1. 创建工程

选择 STM32F103RCT6主控;

配置调试接口
在这里插入图片描述
配置外部时钟
在这里插入图片描述
配置时钟树
在这里插入图片描述
工程设置
在这里插入图片描述

3.2. LCD配置

懒的话,可以直接把 软件模拟8080接口驱动LCD的工程拿过来(主页置顶文章有)。跳过前面和本节的步骤。

因为LCD占用的引脚比较多,而且在开发板上都是定死的,而ADC之类的,都有的选则,所以先配置这个。

这里使用的是 正点原子STM32F103 Mini开发板,采用软件模拟8080通信协议进行驱动。

LCD的配置已经说过了,这里就不再重复了。看往期文章的 软件模拟8080并口驱动LCD屏部分。

====>>> LCD 配置在这里 <<<====

配置完成之后的脚印分布如图。

在这里插入图片描述
然后添加 LCD 驱动文件。先测试LCD是否可以正常驱动。同理,看上面说的那篇文章。

在这里插入图片描述

懒的话,可以直接把 软件模拟8080接口驱动LCD的工程拿过来(主页置顶文章有)。跳过前面和本节的步骤。

在确保 LCD 可以运行的情况下,然后开始配置剩下的部分。

3.3. 定时器配置

因为需要保证 ADC每次采用的时间间隔相同,不能再while(1)中进行循环采样。

在《STM32中文参考手册》中可以看到如下内容。
在这里插入图片描述
也就是说,可以利用一个定时器触发ADC采样。

因此,进行如下配置。
在这里插入图片描述

定时器都是挂在APB1和APB2上,时钟频率都一样,都是72MHZ。
这里 分频系数为 36-1,分频完之后的频率是72Mhz / 36 = 2Mhz,计数值为100,也就是每秒产生20000次中断。

也就是每秒触发ADC采用20000次。换句话说,ADC的采样频率是 20000HZ。

3.4. ADC配置

任选一个ADC通道即可。

  1. 设置数据对齐方式,一般都直接选右对齐即可。
  2. 扫描、循环都不用设置。
  3. 设置为定时器3外部事件触发。
  4. 设置采用周期。
  5. 配置DMA,从外设到内存。
    在这里插入图片描述

DMA是需要循环转移数据的。

在这里插入图片描述
配置完ADC之后,ADC的时钟也需要再改一下,配置ADC之前,这个地方是灰色的。现在就可以改了,配置完不超过14Mhz就可以了。
在这里插入图片描述

3.5. 按键配置

调整示波器的显示,正经的示波器上是个旋钮,这里用按键代替,一个开发板齐活。

开发板上有三个按钮。WK_UP、KEY0、KEY1. 原理图如下, 把这三个按钮都配置为中断。

  • WK_UP:上升沿中断、下拉;
  • KEY0:下降沿中断、上拉;
  • KEY1:下降沿中断、上拉;

在这里插入图片描述

需要注意的就是 按键中断 和 DMA中断的优先级(跟DMA相关的中断优先级总是要调试很久。。。不明白,心累,改天补补课研究研究)。

在这里插入图片描述

3.6. 指示灯配置

最后再配置个指示灯,用于给个提示,正在获取数据 刷新波形之前让 LED 亮,波形刷新之后让他灭,等待下次获取的时候再亮。。。

开发板上原理图有两个,这里随便用一个,选 LED0 吧。
在这里插入图片描述

设置为推挽输出,其他的不重要。

到这里所有的配置就没了,重新生成工程即可。

要注意,因为重新生成了代码,前面LCD的源文件和头文件如果没了,需要重新添加源文件和头文件路径。

4. 软件代码

4.1. GUI 代码

先规划一下 LCD 显示界面,把LCD的驱动代码包装一下,方便绘制。

先看一下最终效果。网格效果应该不会再改了,还有一些静态的不需要刷新的文字,放在了函数void setBackGroundText(void);里面,到最后根据布局再回来更改。

在这里插入图片描述

icode文件夹中再创建一个GUI文件夹,在GUI文件夹中创建gui.cgui.h文件。并将源文件和头文件在keil中添加到工程。

gui.h

#ifndef __GUI_H
#define __GUI_H

/*
	author:Haozi
	
	Author URI:https://blog.csdn.net/weixin_46253745
	
	Describe:对 LCD 的驱动文件进行了包装,方便绘制GUI
*/

#include "stdint.h"		// uint16_t 定义

void drawLineWithColor(uint16_t startX, uint16_t startY, uint16_t endX, uint16_t endY, uint16_t color);
void drawStringWithColor(uint16_t startX, uint16_t startY, uint16_t width, uint8_t *p, uint16_t color);
void setBackGroundColor(void);
void drawNetwork(void);
void setBackGroundText(void);

#endif

gui.c

#include "gui.h"
#include "stdint.h"		// uint16_t 定义
#include "lcd.h"

/*
	author:Haozi
	
	Author URI:https://blog.csdn.net/weixin_46253745
	
	Describe:对 LCD 的驱动文件进行了包装,方便绘制GUI
*/


/* ===================================================== */
// 描述:画线函数
// 参数:
//		起始和结束的x y坐标。
//		color:线的颜色
// 返回值:
/* ===================================================== */
void drawLineWithColor(uint16_t startX, uint16_t startY, uint16_t endX, uint16_t endY, uint16_t color)
{
    
    
	POINT_COLOR = color;
	LCD_DrawLine(startX, startY, endX, endY);
}


/* ===================================================== */
// 描述:显示字符串函数
// 参数:
//		startX、startY:起始的x y坐标。
//		width:			区域宽度。
//		p:				字符串地址。
//		color:			线的颜色
// 返回值:
/* ===================================================== */
void drawStringWithColor(uint16_t startX, uint16_t startY, uint16_t width, uint8_t *p, uint16_t color)
{
    
    
    POINT_COLOR = color;
	// 字符区域大小 和 字体大小 直接定死了
    LCD_ShowString(startX, startY, width, 16, 16, p);
}

/* ===================================================== */
// 描述:设置LCD背景。设置显示方向为横向;背景颜色为黑色。
// 参数:
// 返回值:
/* ===================================================== */
void setBackGroundColor(void)
{
    
    
	LCD_Display_Dir(1);		// 设置LCD显示方向为横向
	
	LCD_Clear(BLACK);		// 清空LCD,用黑色覆盖
	BACK_COLOR = BLACK;		// 背景颜色
	POINT_COLOR = YELLOW;	// 线的颜色
}

/* ===================================================== */
// 描述:显示字符串函数
// 参数:
//		startX、startY:起始的x y坐标。
//		width:			区域宽度。
//		p:				字符串地址。
//		color:			线的颜色
// 返回值:
/* ===================================================== */
void drawNetwork(void)
{
    
    
    uint16_t y = 0;
    uint16_t x = 0;
	
    for(x = 20; x < lcddev.width; x += 20)
    {
    
    
        for(y = 20; y < (lcddev.height - 20); y += 5)
        {
    
    
            LCD_Fast_DrawPoint(x, y, 0XAAAA);
        }
    }

    for(y = 20; y < (lcddev.height - 20); y += 20)
    {
    
    
        for(x = 0 ; x < lcddev.width ; x += 5)
        {
    
    
            LCD_Fast_DrawPoint(x, y, 0xAAAA);
        }
    }

	POINT_COLOR = 0X534c;
    drawLineWithColor(0, lcddev.height / 2, lcddev.width, lcddev.height / 2, POINT_COLOR);
    drawLineWithColor(lcddev.width / 2, 20, lcddev.width / 2, (lcddev.height - 20), POINT_COLOR);
    LCD_DrawRectangle(0, 20, lcddev.width, (lcddev.height - 20)); // 矩形
}

/* ===================================================== */
// 描述:设置背景上静态的文字
// 参数:
// 返回值:
/* ===================================================== */
void setBackGroundText(void)
{
    
    
	// 待定,最后根据布局再看吧。
}

然后在主函数中调用即可看到前面的效果。

// main函数上面
#include "lcd.h"
#include "gui.h"

	// main函数中
	
	// 1. 初始化LCD
	LCD_Init();

	// 2. 初始化 LCD 背景设置
	setBackGroundColor();	// 设置背景颜色
	drawNetwork();			// 绘制背景网络
	setBackGroundText();	// 设置背景静态文字

4.2. 电压采集及绘制

这节是重点

这里设计ADC的采集方法并显示在上面的网格中。

先说思路,方便理解代码:
在这里插入图片描述
可以看到,按照这个思路,其实ADC并不是在一直连续的显示电压的变化,而是每隔一段时间截取一段,显示之后,再次截取,避免LCD的刷新速度影响。

icode文件夹中再创建一个OSC文件夹,在OSC文件夹中创建osc.cosc.h文件。并将源文件和头文件在keil中添加到工程。

这里的代码有点长,放几个关键的说一下,全部代码主页置顶文章有

初始化代码相对简单。

/* ===================================================== */
// 描述:示波器初始化
// 参数:
// 返回值:
/* ===================================================== */
void OSC_Init(void)
{
    
    
	// 初始化 示波器 背景设置
	setBackGroundColor();	// 设置背景颜色
	setBackGroundText();	// 设置背景静态文字
	
	// 1. adc校准
    HAL_ADCEx_Calibration_Start(&hadc1);
	// 2. 开启DMA传输
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&originalAdc, numOfCollect);
	// 3. 关掉传输一半中断
    __HAL_DMA_DISABLE_IT((&hadc1)->DMA_Handle, DMA_IT_HT);
	// 4. 设置ADC采样频率。并开始计时采样
    setAdcFrequency(numF);
    HAL_TIM_Base_Start(&htim3);
}

DMA中断代码,这段其实时关键,中间的代码有点多,又分成了几个函数。

/* ===================================================== */
// 描述:DMA传输回调函数,用于绘制获取的ADC波形并输出提示信息。
// 参数:
// 返回值:
// 注意:在绘制期间,会关闭定时器,停止触发ADC。
// 		可以看出,这里的逻辑是,
//		1. 打开定时器计时,定时器开始计数,可以触发中断;
//		2. 定时器触发中断,ADC获取连续的一段电压值;(在这里频率是可以设置的)
//		3. 关闭定时器计数,ADC停止转换;
//		4. 绘制刚刚获取的ADC波形,绘制完成之后,再次打开定时器。
// 		
//		简单来说,其实电压值并不是在连续转换。而是转换一段 停一会 再次转换一段。
/* ===================================================== */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    
    
	// 失能定时器3
    HAL_TIM_Base_MspDeInit(&htim3);

    if(oscState == 1)
    {
    
    
        // HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
        drawStringWithColor(240, 1, 120, "Running ", YELLOW);
		/*
			显示所有需要的内容
			1. 绘制背景网格
			2. 计算被测波形的频率;
			3. 将波形显示在屏幕上;
			4. 更新波形信息
		*/
		
		// 执行过程中 让LED亮
		HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
		
		drawNetwork();
		updateWaveFrequency();
		OSC_ShowWave();
		OSC_ShowInfo();
		
		// 刷新完成之后 灭掉LED
		HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
    }
    else
    {
    
    
        drawStringWithColor(240, 1, 120, "Stopping", RED);
    }
	
	// 使能定时器3
    HAL_TIM_Base_MspInit(&htim3);
}

中间四个函数的逻辑,就是上面流程图的逻辑,这里就不放了,可以直接去下载工程。全部代码主页置顶文章有

4.3. 主函数

在主函数中其实非常简单,初始化即可,剩下的都在 回调函数和中断中完成。

#include "lcd,h"
#include "osc,h"

// 1. 初始化LCD
LCD_Init();
// 2. 初始化示波器(核心代码)
OSC_Init();
// 3. 设置运行标志位
oscState = 1;

4.4. 测试工程(可有可无)

另找一个开发板,用定时器生成PWM波,进行测试。

我这里用另一个开发板的三个定时器,分别生成 100hz、1Khz、10Khz的PWM波。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在主函数中,打开定时器,随便设置个占空比,然后监测一下试试。

	HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
	HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_2);
	
	__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 4000);
	__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 8000);
	
	__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 300);
	__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 500);

	__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, 20);
	__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_2, 60);

5. 使用说明及测试

把两个开发板的 GND 连起来。
把输出信号的开发板引脚,连到示波器开发板的PA1引脚。

按键功能:

  1. 长按 WK_UP 键,暂停刷新 / 重新启动;
  2. 短按 WK_UP 键,切换模式,被选中的模式会有一个方格,选中中间的偏移的话,中间的两根线会变成白色;
  3. KEY0、KEY1,在不同模式下,增加或减少。

下面信息含义:

  1. max:这个屏幕中最大的电压值;
  2. min:这个屏幕中最大的电压值;
  3. dif:这个屏幕中最大与最小电压的差;
  4. Hz:被测信号的频率。

上面信息的含义:

  1. 1V / 2V / 4V 显示的电压范围;
  2. 5ms:表示一个小方格(红色点组成的方格)对应的实际时常;
  3. [—|—]符号:波形左右偏移量。
  4. Running / Stoping:示波器状态。

LED 闪烁:亮表示正在测量及显示;灭表示已经在LCD上刷新了。

视频效果:https://www.bilibili.com/video/BV1AP411M75M/

1000HZ测试效果
在这里插入图片描述
10000HZ测试效果
在这里插入图片描述
正弦波测试效果
在这里插入图片描述
三角波测试效果
在这里插入图片描述

代码在主页置顶文章,或者点个关注点个赞,私聊我直接给你发也行。


2023年2月22日记。
刚刚调试电机PWM波,手贱想看看量12V电压会有什么情况,就碰了一下,只一瞬间。。。芯片烧了。。。
-100。
请勿尝试高电压!!!

猜你喜欢

转载自blog.csdn.net/weixin_46253745/article/details/128065037
今日推荐