1. 项目概述
本项目旨在使用STM32微控制器和OLED显示屏实现一个简易的示波器。该示波器能够实时采集模拟信号,并通过OLED显示屏显示波形。项目的主要功能包括:
-
模拟信号的采集与处理
-
波形的实时显示
-
简单的触发功能
2. 硬件设计
2.1 主要硬件组件
-
STM32微控制器:负责信号采集、处理和显示控制。
-
OLED显示屏:用于显示波形,通常使用I2C或SPI接口。
-
模拟信号输入:通过STM32的ADC(模数转换器)采集模拟信号。
-
按键:用于控制触发、时间基准等参数。
2.2 硬件连接
-
OLED显示屏:
-
SCL -> STM32的SCL引脚(I2C时钟)
-
SDA -> STM32的SDA引脚(I2C数据)
-
VCC -> 3.3V
-
GND -> GND
-
-
模拟信号输入:
-
信号源 -> STM32的ADC输入引脚(如PA0)
-
-
按键:
-
按键1 -> STM32的GPIO引脚(用于触发控制)
-
按键2 -> STM32的GPIO引脚(用于时间基准调整)
-
3. 软件设计
3.1 开发环境
-
IDE:STM32CubeIDE
-
库:HAL库
3.2 程序结构
-
main.c:主程序,包含初始化、主循环和中断处理。
-
adc.c:ADC配置和信号采集。
-
oled.c:OLED显示屏的驱动和波形显示。
-
key.c:按键检测和处理。
3.3 程序讲解
3.3.1 主程序(main.c)
#include "stm32f1xx_hal.h"
#include "adc.h"
#include "oled.h"
#include "key.h"
int main(void) {
HAL_Init();
SystemClock_Config();
MX_ADC1_Init();
OLED_Init();
KEY_Init();
uint16_t adc_value;
uint8_t trigger = 0;
while (1) {
adc_value = ADC_Read();
if (KEY_Read() == 1) {
trigger = 1;
}
if (trigger) {
OLED_DrawWaveform(adc_value);
}
}
}
3.3.2 ADC配置和信号采集(adc.c)
#include "adc.h"
ADC_HandleTypeDef hadc1;
void MX_ADC1_Init(void) {
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
HAL_ADC_Init(&hadc1);
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_13CYCLES_5;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}
uint16_t ADC_Read(void) {
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
return HAL_ADC_GetValue(&hadc1);
}
3.3.3 OLED显示屏驱动和波形显示(oled.c)
#include "oled.h"
#include "stm32f1xx_hal.h"
void OLED_Init(void) {
OLED_GPIO_Init(); //先调用底层的端口初始化
/*写入一系列的命令,对OLED进行初始化配置*/
OLED_WriteCommand(0xAE); //设置显示开启/关闭,0xAE关闭,0xAF开启
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80); //0x00~0xFF
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F); //0x0E~0x3F
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00); //0x00~0x7F
OLED_WriteCommand(0x40); //设置显示开始行,0x40~0x7F
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常,0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常,0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度
OLED_WriteCommand(0xCF); //0x00~0xFF
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/反色显示,0xA6正常,0xA7反色
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
OLED_Clear(); //清空显存数组
OLED_Update(); //更新显示,清屏,防止初始化后未显示内容时花屏
}
void OLED_DrawWaveform(uint16_t value) {
int16_t x, y, dx, dy, d, incrE, incrNE, temp;
int16_t x0 = X0, y0 = Y0, x1 = X1, y1 = Y1;
uint8_t yflag = 0, xyflag = 0;
if (y0 == y1) //横线单独处理
{
/*0号点X坐标大于1号点X坐标,则交换两点X坐标*/
if (x0 > x1) {temp = x0; x0 = x1; x1 = temp;}
/*遍历X坐标*/
for (x = x0; x <= x1; x ++)
{
OLED_DrawPoint(x, y0); //依次画点
}
}
else if (x0 == x1) //竖线单独处理
{
/*0号点Y坐标大于1号点Y坐标,则交换两点Y坐标*/
if (y0 > y1) {temp = y0; y0 = y1; y1 = temp;}
/*遍历Y坐标*/
for (y = y0; y <= y1; y ++)
{
OLED_DrawPoint(x0, y); //依次画点
}
}
else //斜线
{
/*使用Bresenham算法画直线,可以避免耗时的浮点运算,效率更高*/
/*参考文档:https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf*/
/*参考教程:https://www.bilibili.com/video/BV1364y1d7Lo*/
if (x0 > x1) //0号点X坐标大于1号点X坐标
{
/*交换两点坐标*/
/*交换后不影响画线,但是画线方向由第一、二、三、四象限变为第一、四象限*/
temp = x0; x0 = x1; x1 = temp;
temp = y0; y0 = y1; y1 = temp;
}
if (y0 > y1) //0号点Y坐标大于1号点Y坐标
{
/*将Y坐标取负*/
/*取负后影响画线,但是画线方向由第一、四象限变为第一象限*/
y0 = -y0;
y1 = -y1;
/*置标志位yflag,记住当前变换,在后续实际画线时,再将坐标换回来*/
yflag = 1;
}
if (y1 - y0 > x1 - x0) //画线斜率大于1
{
/*将X坐标与Y坐标互换*/
/*互换后影响画线,但是画线方向由第一象限0~90度范围变为第一象限0~45度范围*/
temp = x0; x0 = y0; y0 = temp;
temp = x1; x1 = y1; y1 = temp;
/*置标志位xyflag,记住当前变换,在后续实际画线时,再将坐标换回来*/
xyflag = 1;
}
/*以下为Bresenham算法画直线*/
/*算法要求,画线方向必须为第一象限0~45度范围*/
dx = x1 - x0;
dy = y1 - y0;
incrE = 2 * dy;
incrNE = 2 * (dy - dx);
d = 2 * dy - dx;
x = x0;
y = y0;
/*画起始点,同时判断标志位,将坐标换回来*/
if (yflag && xyflag){OLED_DrawPoint(y, -x);}
else if (yflag) {OLED_DrawPoint(x, -y);}
else if (xyflag) {OLED_DrawPoint(y, x);}
else {OLED_DrawPoint(x, y);}
while (x < x1) //遍历X轴的每个点
{
x ++;
if (d < 0) //下一个点在当前点东方
{
d += incrE;
}
else //下一个点在当前点东北方
{
y ++;
d += incrNE;
}
/*画每一个点,同时判断标志位,将坐标换回来*/
if (yflag && xyflag){OLED_DrawPoint(y, -x);}
else if (yflag) {OLED_DrawPoint(x, -y);}
else if (xyflag) {OLED_DrawPoint(y, x);}
else {OLED_DrawPoint(x, y);}
}
}
}
3.3.4 按键检测和处理(key.c)
#include "key.h"
#include "stm32f1xx_hal.h"
void KEY_Init(void) {
// 按键GPIO初始化
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2; // 假设按键连接到PA0
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入模式
GPIO_Init(GPIOA, &GPIO_InitStruct);
}
uint8_t KEY_Read(void) {
// 读取按键状态
uint8_t current_key_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2); // 读取当前按键状态
// 检测按键状态变化
if (current_key_state != last_key_state) {
Delay_ms(DEBOUNCE_TIME); // 延时去抖
current_key_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2); // 再次读取按键状态
if (current_key_state == Bit_RESET) { // 按键按下
key_state = 1;
} else {
key_state = 0;
}
}
last_key_state = current_key_state; // 更新上一次按键状态
}
4. 功能实现
4.1 信号采集
通过STM32的ADC模块实时采集模拟信号,并将其转换为数字信号。
4.2 波形显示
将采集到的数字信号映射到OLED显示屏的Y坐标,并在屏幕上绘制波形。
4.3 触发功能
通过按键控制波形的触发,确保波形在屏幕上稳定显示。
5. 总结
本项目实现了一个基于STM32的简易示波器,能够实时采集和显示模拟信号。通过OLED显示屏,用户可以直观地观察波形。项目代码结构清晰,易于扩展和修改,适合初学者学习和实践。