其实是在整理手上的元器件的时候,想用一个示波器,但是我没有,因此就想到自己简单做一个,能满足自己的简单需求即可。
经过测试 10Khz 及以下的波形都可以正常显示,可以先去文章末尾看效果。更高的频率还没试过。缺点就是:只能测量正电压,0-3.3。一般测量开发板调试电机时输出的PWM波是够用了。
====>>> 文章汇总(有代码汇总) <<<====
其他开发板也行,只要能驱动这个屏幕就行。
普中的开发板的话,我主页也有同样 LCD屏的驱动教程,完全可以直接替代移植。
示波器监测部分
显示部分:
选择 STM32F103RCT6主控;
懒的话,可以直接把 软件模拟8080接口驱动LCD的工程拿过来(主页置顶文章有)。跳过前面和本节的步骤。
因为LCD占用的引脚比较多,而且在开发板上都是定死的,而ADC之类的,都有的选则,所以先配置这个。
这里使用的是 正点原子STM32F103 Mini开发板,采用软件模拟8080通信协议进行驱动。
LCD的配置已经说过了,这里就不再重复了。看往期文章的 软件模拟8080并口驱动LCD屏部分。
====>>> LCD 配置在这里 <<<====
配置完成之后的脚印分布如图。
然后添加 LCD 驱动文件。先测试LCD是否可以正常驱动。同理,看上面说的那篇文章。
懒的话,可以直接把 软件模拟8080接口驱动LCD的工程拿过来(主页置顶文章有)。跳过前面和本节的步骤。
在确保 LCD 可以运行的情况下,然后开始配置剩下的部分。
因为需要保证 ADC每次采用的时间间隔相同,不能再while(1)中进行循环采样。
在《STM32中文参考手册》中可以看到如下内容。
也就是说,可以利用一个定时器触发ADC采样。
定时器都是挂在APB1和APB2上,时钟频率都一样,都是72MHZ。
这里 分频系数为 36-1
,分频完之后的频率是72Mhz / 36 = 2Mhz
,计数值为100
,也就是每秒产生20000
次中断。
也就是每秒触发ADC采用20000次。换句话说,ADC的采样频率是 20000HZ。
任选一个ADC通道即可。
DMA是需要循环转移数据的。
配置完ADC之后,ADC的时钟也需要再改一下,配置ADC之前,这个地方是灰色的。现在就可以改了,配置完不超过14Mhz就可以了。
调整示波器的显示,正经的示波器上是个旋钮,这里用按键代替,一个开发板齐活。
开发板上有三个按钮。WK_UP、KEY0、KEY1. 原理图如下, 把这三个按钮都配置为中断。
需要注意的就是 按键中断 和 DMA中断的优先级(跟DMA相关的中断优先级总是要调试很久。。。不明白,心累,改天补补课研究研究)。
最后再配置个指示灯,用于给个提示,正在获取数据 刷新波形之前让 LED 亮,波形刷新之后让他灭,等待下次获取的时候再亮。。。
设置为推挽输出,其他的不重要。
到这里所有的配置就没了,重新生成工程即可。
要注意,因为重新生成了代码,前面LCD的源文件和头文件如果没了,需要重新添加源文件和头文件路径。
先规划一下 LCD 显示界面,把LCD的驱动代码包装一下,方便绘制。
先看一下最终效果。网格效果应该不会再改了,还有一些静态的不需要刷新的文字,放在了函数void setBackGroundText(void);
里面,到最后根据布局再回来更改。
在icode
文件夹中再创建一个GUI
文件夹,在GUI
文件夹中创建gui.c
和gui.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(); // 设置背景静态文字
这节是重点
这里设计ADC的采集方法并显示在上面的网格中。
先说思路,方便理解代码:
可以看到,按照这个思路,其实ADC并不是在一直连续的显示电压的变化,而是每隔一段时间截取一段,显示之后,再次截取,避免LCD的刷新速度影响。
在icode
文件夹中再创建一个OSC
文件夹,在OSC
文件夹中创建osc.c
和osc.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);
}
中间四个函数的逻辑,就是上面流程图的逻辑,这里就不放了,可以直接去下载工程。全部代码主页置顶文章有
在主函数中其实非常简单,初始化即可,剩下的都在 回调函数和中断中完成。
#include "lcd,h"
#include "osc,h"
// 1. 初始化LCD
LCD_Init();
// 2. 初始化示波器(核心代码)
OSC_Init();
// 3. 设置运行标志位
oscState = 1;
另找一个开发板,用定时器生成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);
把两个开发板的 GND 连起来。
把输出信号的开发板引脚,连到示波器开发板的PA1引脚。
按键功能:
下面信息含义:
上面信息的含义:
LED 闪烁:亮表示正在测量及显示;灭表示已经在LCD上刷新了。
视频效果:https://www.bilibili.com/video/BV1AP411M75M/
1000HZ测试效果
10000HZ测试效果
正弦波测试效果
三角波测试效果
代码在主页置顶文章,或者点个关注点个赞,私聊我直接给你发也行。
2023年2月22日记。
刚刚调试电机PWM波,手贱想看看量12V电压会有什么情况,就碰了一下,只一瞬间。。。芯片烧了。。。
-100。
请勿尝试高电压!!!