STM32F103+OLED自制迷你示波器:采样、触发与波形显示全解析
发布时间:2026/9/15 18:18:05 锦皓数字建站

简介这是一份基于STM32F103的简易OLED示波器完整工程源码适合嵌入式初学者、电子DIY爱好者以及需要快速实现信号波形显示的学习者。项目通过将二维采样数据一维化配合OLED逐行滚动刷新在有限内存下实现动态波形展示可学习ADC采集、OLED驱动、中断实时更新与嵌入式内存优化等关键技能。资源包共55个文件约746KB核心为C源码与H头文件包含adc.c、oled.c、usart.c等驱动模块同时提供Keil工程配置uvprojx/uvoptx、编译产物axf/hex、调试映射map/lst及说明文档readme/txt结构清晰便于直接打开工程运行。目前已有1546人学习下载对于想要从零理解OLED示波器原理并动手实践的读者是一份兼具参考价值和可操作性的入门资料。1. 为什么用 STM32F103 和 OLED 做一台迷你示波器把示波器塞进一块 0.96 寸的 OLED 屏幕里听起来像玩具但实际上它能解决一个很具体的问题当手上没有台式示波器又想看单片机引脚上的 PWM 占空比、串口波特率对不对、或者某个传感器输出波形是否正常的时候一块 128×64 像素的 OLED 加一颗 STM32F103 就能完成最核心的工作。这个方案的成本不到二十块钱却能实现最高几十 kHz 的等效采样率足以覆盖大部分低速数字信号和音频范围信号的观测需求。这类项目在嵌入式社区里一直有人做原因很简单STM32F103 的 ADC 能做到 12 位分辨率定时器可以产生精确的触发信号再加上 DMA 搬运数据CPU 几乎不需要参与采样过程。而 OLED 屏的响应速度虽然比不上 TFT但作为波形显示介质已经足够更重要的是 IIC 接口只占用两根 IO 线对 PCB 布局极其友好。适合的人群是正在学单片机的学生、需要临时调试硬件又没有示波器的工程师以及想深入理解采样和显示原理的爱好者。这篇文章会从 OLED 的显示原理讲起逐步搭建一套能真正捕获和显示波形的完整方案。2. OLED 屏幕显示波形的基本原理与 IIC 驱动链路2.1 SSD1306 显存结构与页寻址机制常见的 0.96 寸 OLED 屏幕分辨率为 128×64控制芯片是 SSD1306内部有一块 1024 字节的 GDDRAM对应每个像素 1 bit。这块显存被划分成 8 页每页纵向代表 8 个像素横向正好 128 列。也就是说第 0 页包含第 0 到 7 行的像素数据第 1 页包含第 8 到 15 行依此类推。写入数据时一个字节的每一位对应同一列上的 8 个像素bit0 对应当前页的第一行bit7 对应第八行。这意味着画一个波形点时不能像操作 TFT 那样直接写一个坐标的像素值而是要先计算出这个坐标落在哪一页、列的哪个 bit 位然后读改写显存中对应位置的数据。这个机制决定了 OLED 示波器的刷新策略和普通 LCD 完全不同。参数典型值说明分辨率128×64横向 128 列纵向 64 行页数8每页 8 行像素显存大小1024 字节128 列 × 8 页接口IIC / SPIIIC 最大速率约 400 kHz刷新率上限约 30 fps受 IIC 带宽限制用 IIC 驱动 OLED 时一次完整刷新需要发送 1024 字节数据加上控制字节和地址位每条 IIC 消息大约传输 1100 字节。IIC 速率如果是 400 kHz理论最大带宽是 50 KB/s传输一帧显存大约需要 22 ms实际加上协议开销后大约 30 ms 一帧。这个数字决定了示波器波形更新的上限。2.2 用 GPIO 模拟 IIC 驱动 OLED 的最小代码虽然 STM32F103 有硬件 IIC 外设但它的硬件 IIC 在中断处理上比较麻烦社区里大部分 OLED 驱动代码都选择用 GPIO 模拟 IIC。这个做法在示波器场景里还有一个额外好处你可以随时调整时序不需要重新配置外设。2.2.1 IIC 起始信号与字节发送IIC 通信的要点是SCL 高电平时 SDA 不能变化只有 SCL 低电平时 SDA 才能改变。起始信号是 SCL 高电平期间 SDA 从高到低跳变停止信号则相反。发送一个字节时从最高位开始依次拉低或拉高 SDA然后翻转 SCL 产生时钟。#define OLED_IIC_SCL_PIN GPIO_PIN_6 #define OLED_IIC_SDA_PIN GPIO_PIN_7 #define OLED_IIC_PORT GPIOB void OLED_IIC_Delay(void) { // 约 1.25us对应 400kHz 时序 for (volatile uint8_t i 0; i 4; i) { __NOP(); } } void OLED_IIC_Start(void) { OLED_IIC_SDA_HIGH(); OLED_IIC_SCL_HIGH(); OLED_IIC_Delay(); OLED_IIC_SDA_LOW(); OLED_IIC_Delay(); OLED_IIC_SCL_LOW(); // 拉低 SCL为发送数据做准备 } void OLED_IIC_WriteByte(uint8_t dat) { for (uint8_t i 0; i 8; i) { if (dat 0x80) { OLED_IIC_SDA_HIGH(); } else { OLED_IIC_SDA_LOW(); } dat 1; OLED_IIC_Delay(); OLED_IIC_SCL_HIGH(); OLED_IIC_Delay(); OLED_IIC_SCL_LOW(); } // 释放 SDA准备接收 ACK OLED_IIC_SDA_HIGH(); OLED_IIC_SCL_HIGH(); OLED_IIC_Delay(); OLED_IIC_SCL_LOW(); }起始信号之后先发送设备地址。SSD1306 的 IIC 地址是 7 位地址 0x3C加上写位后是 0x78。OLED 屏的地址由模块上的电阻决定少数模块是 0x3D如果屏幕无响应先把地址改成 0x7A 试试。之后发送控制字节0x00 表示后面跟的是命令0x40 表示后面跟的是显存数据。2.2.2 页地址模式下的坐标映射SSD1306 支持页地址模式和水平地址模式。示波器项目里推荐用页地址模式因为波形显示往往只需要更新部分列逐页写入可以跳过不需要刷新的区域。void OLED_SetPos(uint8_t x, uint8_t page) { OLED_IIC_WriteCmd(0xB0 page); // 设置页地址 0~7 OLED_IIC_WriteCmd(0x00 (x 0x0F)); // 列地址低 4 位 OLED_IIC_WriteCmd(0x10 (x 4)); // 列地址高 4 位 } void OLED_WriteData(uint8_t dat) { OLED_IIC_Start(); OLED_IIC_WriteByte(0x78); // 设备地址 写标志 OLED_IIC_WriteByte(0x40); // 数据模式 OLED_IIC_WriteByte(dat); OLED_IIC_Stop(); }设置坐标时x 是列地址 0 到 127page 是页地址 0 到 7。写入数据后列地址会自动加一当列地址到达 127 后会自动回绕到 0但页地址不会变。这里有个容易出错的地方如果一次性写入超过 128 字节超出的数据会写到当前页的第 0 列开始覆盖数据但页地址不会自动加一。所以在写入完整帧时需要按照「设置页地址 → 连续写 128 字节 → 切下一页」的顺序来操作而不能一口气写 1024 字节。2.3 画点和画线的实现策略2.3.1 画点函数的显存操作在 OLED 上画一个点需要计算该坐标对应的页和位。y 坐标 0 到 7 属于第 0 页8 到 15 属于第 1 页。页号等于 y 除以 8位号等于 y 对 8 取余。uint8_t OLED_GRAM[128][8]; // 显存缓冲 [列][页] void OLED_DrawPoint(uint8_t x, uint8_t y, uint8_t dot) { if (x 127 || y 63) return; uint8_t page y / 8; uint8_t bit y % 8; if (dot) { OLED_GRAM[x][page] | (1 bit); } else { OLED_GRAM[x][page] ~(1 bit); } }这个函数只操作内存不直接写屏幕。所有绘图操作都先改显存缓冲最后统一刷新。使用缓冲的好处在于示波器波形更新时不需要整屏重绘只需要把变化区域对应的页数据发给屏幕。显示层内容更新拆成两步先跑数据采集再刷屏显示。波形触发后采样数据放入缓冲区更新正弦波或 PWM 波形显示。每次显示前还需要把之前的波形擦掉把旧数据点清空或者用背景色重画一次否则会留下拖影。擦除导致闪烁是非常常见的问题解决办法是只在波形变化区域做局部擦除不重绘整个屏幕。2.3.2 波形连线用的 Bresenham 直线算法离散采样点之间跨度可能超过一个像素如果只画点波形会断断续续。用直线把相邻采样点连起来是最直观的方案这里需要 Bresenham 直线算法它只做整数加减法在单片机上非常高效。void OLED_DrawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t dot) { int16_t dx (x1 x0) ? (x1 - x0) : (x0 - x1); int16_t dy (y1 y0) ? (y1 - y0) : (y0 - y1); int16_t sx (x0 x1) ? 1 : -1; int16_t sy (y0 y1) ? 1 : -1; int16_t err dx - dy; while (1) { OLED_DrawPoint(x0, y0, dot); if (x0 x1 y0 y1) break; int16_t e2 2 * err; if (e2 -dy) { err - dy; x0 sx; } if (e2 dx) { err dx; y0 sy; } } }Bresenham 算法的本质是维护一个误差项 err通过比较 e2 与 dx、dy 的关系来决定 x 方向还是 y 方向步进。在 OLED 示波器里x 方向总是递增的波形的时基从左往右展开所以 sx 恒为 1算法会退化为对每个 x 计算唯一对应的 y 值但保留通用写法有利于后续扩展。3. STM32F103 的 ADC 采样、定时器触发与 DMA 搬运3.1 采样方式的选择为什么不用 while 循环轮询最简单的采样方式是在 while 循环里调用 HAL_ADC_GetValue()读取 ADC 转换结果然后画点。但这种方式存在两个问题一是采样间隔不稳定循环里画点和刷新显示会占用时间导致采样点之间的时间间隔抖动二是 CPU 在采样期间被完全占用无法同时处理按键、触发逻辑和显示刷新。正确的做法是用定时器触发 ADC 转换再用 DMA 把结果搬进内存。定时器的更新事件作为 ADC 的触发源ADC 转换完成后由 DMA 把数据寄存器里的值搬到数组里。整个过程 CPU 只负责启动和事后处理数据。3.2 定时器配置设置采样率采样率直接决定了示波器能看到的频率范围。STM32F103 的定时器时钟源自 APB1当 APB1 预分频为 2 时定时器时钟是 72 MHz。要产生 f_sample 的触发频率定时器的预分频和自动重载值这样计算PSC (t_clk / (f_sample × (ARR 1))) - 1这里 t_clk 是定时器时钟 72 MHzARR 是自动重载值。以采样率 100 kHz 为例选择 PSC 71ARR 9则分频后频率为 72 MHz / 72 / 10 100 kHz。3.2.1 CubeMX 中定时器触发 ADC 的参数设置使用 STM32CubeMX 配置时按以下方式设置参数参数设置值说明TIM2 Clock SourceInternal Clock使用内部时钟 72 MHzPrescaler7172 MHz 分频为 1 MHzCounter Period91 MHz / 10 100 kHzTrigger Output (TRGO)Update Event更新事件作为 ADC 触发源ADC Continuous ConversionDisabled使用外部触发不允许连续转换ADC External TriggerTimer 2 Capture Compare选择 TIM2 作为触发源ADC DMA ContinuousEnabledDMA 循环模式持续搬运注意 ADC 的触发源选择在 CubeMX 中 ADC1 的 External Trigger Conversion Source 需要选择 Timer 2 Capture Compare而不是 Timer 2 Update。这里容易混淆是因为 TIM2 确实配置了 Update Event 作为 TRGO但 ADC 外设的触发输入取自定时器的内部触发连接具体选项在 ADC 配置界面的 External Trigger 下拉框里。3.3 DMA 配置与环形缓冲区3.3.1 DMA 循环模式实现不间断采样采样数据需要持续写入内存DMA 必须配置为循环模式。这样 DMA 传输完设定的数据个数后会自动把传输计数器重新加载继续从 ADC 数据寄存器读取数据写回缓冲区头部。这是一种无 CPU 干预的连续采样方式也是能保证采样率稳定的前提。// 在 CubeMX 生成的 main.c 基础上增补 #define ADC_BUF_SIZE 1024 uint16_t adc_buf[ADC_BUF_SIZE]; volatile uint8_t dma_transfer_done 0; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { dma_transfer_done 1; } } // 启动采样 HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, ADC_BUF_SIZE);HAL_ADC_Start_DMA 函数的第三个参数是传输次数不是字节数。因为 ADC 数据寄存器是 16 位的所以 DMA 外设会按半字16 bit传输单元读取 ADC1-DR 寄存器。这点如果写错比如误传了 1024 字节DMA 依然能跑但缓冲区后一半数据会是随机值。在循环模式下DMA 传输完 1024 个点后不会停止而是从头开始覆盖写入。dma_transfer_done 标志位在每次循环结束时被置 1主循环检测到这个标志后把 adc_buf 里的数据复制一份到显示缓冲区然后清标志。这个复制动作不能省因为 DMA 还在继续写 adc_buf如果不拷贝就显示屏幕上会出现波形撕裂。3.3.2 采样率的实际验证验证采样率是否准确不需要示波器只需要把采样结果通过串口发出来或者直接在 OLED 上显示一个已知频率的方波。用 TIM2 输出一个 1 kHz 的 PWM 信号接到 PA0配置 ADC 采样率 100 kHz那么 100 kHz / 1 kHz 100 个采样点对应一个完整方波周期。如果显示波形一个周期占 100 个像素说明采样率正确。这个验证方法非常关键因为它能同时检查三件事定时器配置是否正确、ADC 触发是否生效、DMA 搬运是否有丢数。如果周期点数偏多说明采样率比预想的高ADC 可能没被定时器触发而是自由运行了如果点数偏少说明采样率低于配置值可能误用了系统主频做定时器时钟。3.4 触发逻辑让波形稳定显示没有触发逻辑的示波器波形会在屏幕上左右乱跑因为每次采样起始点不固定。最简单的触发方式是软件触发也叫电平触发加边沿检测。uint16_t trigger_level 2048; // 3.3V 电压的中间值 uint16_t find_trigger_index(uint16_t *buf, uint32_t len, uint16_t level) { for (uint32_t i 1; i len; i) { if (buf[i-1] level buf[i] level) { return i; // 上升沿触发前一个点低于触发电平当前点高于触发电平 } } return 0; // 没找到触发点返回缓冲区起始位置 }触发点的查找在 DMA 采集完成后进行。找到触发点后从该位置开始取出 N 个点显示。如果触发点在缓冲区中部后半段数据是本次采样值前半段数据要取上一次保留的缓冲区数据拼接起来。一种更省事的方法是DMA 缓冲区设为显示宽度加一个触发窗口比如显示 128 个点缓冲区采 256 个点找到触发点后截取后续 128 个点。这种方法每次丢弃前一半数据但能保证触发稳定。4. ADC 数据映射到 OLED 像素坐标的数值处理4.1 垂直刻度12 位 ADC 数据到 64 行像素的映射STM32F103 的 ADC 是 12 位分辨率数值范围 0 到 4095。OLED 显示区域高度 64 个像素直接把 ADC 值除以 64 映射到 y 坐标会丢失大量波形细节。正确的做法是引入垂直偏移和增益两个参数。int16_t adc_to_y(uint16_t adc_val, uint16_t v_offset, uint16_t v_gain) { // 先减去垂直偏移再缩放 int32_t tmp (int32_t)adc_val - v_offset; tmp (tmp * v_gain) / 64; // 动态调整波形高度 tmp 56 - tmp; // y 轴翻转底部是 0V顶部是 3.3V留出 8 像素边距 if (tmp 0) return 0; if (tmp 63) return 63; return (uint8_t)tmp; }v_offset 的默认值是 2048对应 1.65V 电平线在屏幕中央。v_gain 是增益系数默认 64 表示 ADC 满量程映射到 64 像素此时信号幅度和屏幕高度刚好匹配。如果把 v_gain 改为 32同一信号的显示幅度会减半相当于示波器上把垂直档位调大一倍。4.1.1 触发电平和波形参考线的绘制显示界面上画一条触发电平线可以帮助确认触发点位置。触发线画在 x 方向全屏、y 方向为触发值对应的像素位置用虚线区分于波形实线。OLED 上画虚线位图的土办法是每隔 1 个像素画一个点即 x 为偶数时置位奇数时跳过。void OLED_DrawDashLine(uint8_t y) { for (uint8_t x 0; x 128; x 2) { OLED_DrawPoint(x, y, 1); } }触发线本身的含义是触发阈值在屏幕上的位置调整触发阈值时能看到虚线上下移动。当波形穿过虚线且有上升跳变时示波器即触发采样。4.2 水平时基每格对应多少采样点示波器面板上时基的单位是「每格多少毫秒」。OLED 示波器的整个屏幕宽度是 128 像素如果把屏幕纵向平均分成 4 个水平网格每格 32 像素。假设采样率固定为 100 kHz每像素对应的采样点数是 100 kHz / 1000 100 个采样点。时基参数实际上是通过「抽取间隔」来实现的。显示缓冲区有 128 列但 ADC 一次采集可能有 256 个点。采集点数和显示列数不一致时需要每 N 个采样点取一个显示N 就是抽取间隔。时基档位抽取间隔每格时间跨度可观测信号频率上限0.1 ms/格10.32 ms约 3 kHz1 ms/格103.2 ms约 300 Hz10 ms/格10032 ms约 30 Hz这种做法和真实示波器的时基调节本质相同改变采样数据到屏幕像素的时间映射关系。不同之处在于真实示波器会同步调整采样率而 OLED 示波器可以保持采样率不变仅用软件抽取。缺点是高频信号在慢时基下会出现混叠这是后期要处理的问题。4.3 波形绘制与屏幕刷新策略波形绘制使用「先擦旧线、再画新线」的方法。示波器的显示区域是固定的如果每次都整屏清空再重画OLED 的 IIC 总线会成为瓶颈帧率大幅下降。更好的方式是维护一个上一帧波形坐标缓冲区只擦除那些上一帧有波形、但当前帧没有波形的点。uint8_t last_wave_y[128]; // 上一帧每个 x 对应的 y 坐标 uint8_t wave_y[128]; // 当前帧每个 x 对应的 y 坐标 void OLED_RefreshWaveform(void) { // 擦除上一帧的波形 for (uint8_t x 0; x 128; x) { OLED_DrawPoint(x, last_wave_y[x], 0); } // 画当前帧的波形 for (uint8_t x 1; x 128; x) { OLED_DrawLine(x - 1, last_wave_y[x-1], x, wave_y[x], 1); } // 全屏刷新显存 OLED_RefreshGRAM(); // 保存当前帧坐标 memcpy(last_wave_y, wave_y, sizeof(wave_y)); }逐点擦除和逐点画线的开销差异并不大因为 OLED_DrawPoint 只是改内存数组真正耗时的操作是 OLED_RefreshGRAM 的 IIC 传输。这里有另一种优化思路只把波形所在页发给屏幕也就是比较上一帧和当前帧的显存差异把有变化的页重发。void OLED_RefreshGRAM_Partial(void) { for (uint8_t page 0; page 8; page) { // 如果该页所有点都没变化跳过 if (memcmp(OLED_GRAM[0] page, OLED_GRAM_last[0] page, 128) 0) { continue; } OLED_SetPos(0, page); for (uint8_t x 0; x 128; x) { OLED_WriteData(OLED_GRAM[x][page]); } } memcpy(OLED_GRAM_last, OLED_GRAM, sizeof(OLED_GRAM)); }局部刷新策略把无变化页面的 IIC 传输省掉了。对于波形比较平静的场景比如直流信号或者低频方波大部分页面内容不变每次刷新的数据量从 1024 字节降到几百字节帧率能提高一倍以上。这种优化在 IIC 接口的 OLED 上效果明显如果是 SPI 接口的 OLED收益就没那么大了因为 SPI 的带宽瓶颈不在数据量而在屏幕自身的刷写时间。4.4 波形缓冲区满时的处理ADC 采集的速度远快于 OLED 的显示速度。100 kHz 采样率下1 ms 就能采 100 个点而 OLED 刷一帧可能需要 30 ms。如果不做缓冲波形数据会不断覆盖用户看到的永远是最后一段数据无法回看前面的波形。解决方法是把 DMA 缓冲区扩展到多个显示帧的大小。比如显示一帧需要 128 个点DMA 缓冲区设为 1024 个点相当于存储了 8 帧数据。主循环检测到 DMA 传输完成时只提取最新的一帧数据去显示其他数据丢弃。这样做的目的是保持实时性让显示尽量跟得上采集的速度。但这里有个权衡缓冲区太大显示延迟增加缓冲区太小显示可能跟不上采样速度出现丢帧。对 OLED 示波器来说2 到 4 帧的缓冲是比较合理的折中。5. 做一个能测直流电压和波形参数的小工具5.1 电压档位校准与显示这个项目的核心价值在于是一个能实际测量的工具。校准的思路是找一个已知电压比如 STM32F103 的 3.3V 供电把 ADC 采集到的原始值记为 full_scale。然后计算每 LSB 对应的电压值3.3V / 4095 0.0008059V。把这个值存为常量后续显示电压时用 adc_val 乘以这个系数。#define V_REF 3.3f #define ADC_FULLSCALE 4095.0f #define V_PER_LSB (V_REF / ADC_FULLSCALE) float adc_to_voltage(uint16_t adc_val) { return (float)adc_val * V_PER_LSB; } // 显示电压值到指定的字符串缓冲区 void FormatVoltage(uint16_t adc_val, char *out) { float volt adc_to_voltage(adc_val); sprintf(out, %.2fV, volt); }如果对精度有更高要求可以在 PA0 引脚接一个精密电压源比如用 TL431 做 2.5V 基准然后反推 V_REF 的值。这种单点校准对 12 位 ADC 来说已经足够因为它能消除参考电压不准带来的误差。5.2 频率测量的实现技巧OLED 示波器有一个隐蔽的高频优势ADC 采样率做不到测量频率的需求时可以用定时器输入捕获来测频。STM32F103 的定时器有正交编码器和输入捕获功能直接测量引脚信号的频率比用 ADC 采样再算频率要准确得多。把频率测量和波形显示结合起来可以让这个简易示波器同时显示频率和电压值。频率测量用 TIM2 的输入捕获通道在捕获中断里记录两次上升沿之间的定时器计数值差换算成时间后求倒数就是频率。volatile uint32_t ic_value 0; volatile uint32_t ic_diff 0; volatile uint32_t freq_hz 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { uint32_t val __HAL_TIM_GET_COUNTER(htim2); ic_diff (val ic_value) ? (val - ic_value) : (val 0xFFFF - ic_value); ic_value val; if (ic_diff 0) { freq_hz 72000000UL / (htim-Init.Prescaler 1) / ic_diff; } } }捕获中断里的 js 计算用了条件判断处理计数器溢出回绕。72 MHz 的定时器时钟下如果预分频为 016 位计数器溢出需要 65535 / 72MHz ≈ 0.91ms对应最低可测频率约 1.1 kHz。如果需要测更低的频率把预分频调大即可。5.3 用 OLED 剩余空间显示参数信息128×64 的屏幕空间有限波形显示占满了大部分像素。如果从顶部留出 8 个像素高的区域作为参数栏波形区域就只剩下 56 行。但换来的是屏幕上有频率和电压的实时数值实际使用体验比满屏波形好很多。SSD1306 不带字库显示数字和字母需要自己做字模。推荐用 5×7 像素的 ASCII 字模每个字符 5 字节存储刚好一个字符 5 列 7 行。绘制时把字符的字节数据按位展开到显存中。const uint8_t ascii_5x7[][5] { // 字符 0 的字模 {0x7E, 0x81, 0x81, 0x81, 0x7E}, // 字符 1 的字模 {0x00, 0x00, 0x42, 0xFE, 0x02}, // 依次到 9 }; void OLED_ShowChar(uint8_t x, uint8_t y, char ch) { uint8_t idx ch - 0; for (uint8_t i 0; i 5; i) { uint8_t data ascii_5x7[idx][i]; for (uint8_t j 0; j 7; j) { if (data (1 (6 - j))) { OLED_DrawPoint(x i, y j, 1); } } } }字模数据里位 6 对应字符第一行位 0 对应第七行逐位移位判断后置位画点。这样在 OLED 示波器的主界面上左上角显示当前测量值右上角显示时基参数底部保留一条触发电平标记线美中不足的是——这套系统用的是 IIC 接口OLED 刷新率只能做到 30 帧上下想要动起来不拖影头等大事是先把刷新率提上去这就是为什么有人宁愿换成 4 线 SPI 的 OLED牺牲两个 GPIO 换来 10 倍刷新带宽。如果你在调试时发现波形刷新慢得像放幻灯片优先检查 IIC 总线速率、DMA 缓冲区大小和 ADC 触发源配置是否在拉低整体效率。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。