练习项目,使用ESP32S3 -N16R8、4引脚OLED
代码相关的懒得开仓库了,直接在这里放代码得了
使用Arduino开发环境
思路:使用Python生成oled文件,然后用LittleFS将文件上传到ESP32
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import subprocess
import tkinter as tk
from tkinter import filedialog, messagebox
# ============ 画面 / 播放参数 ============
W = 128
H = 64
FPS = 10
INVERT = False # True 时两种模式都做黑白反转
# ============ 输出模式 ============
# 'bw' : 纯黑白二值,与原脚本行为完全一致
# 'gray3' : 3 级灰度 —— 黑 / 灰(50% 网点) / 白
MODE = 'gray3'
# ---- MODE='bw' 参数(原脚本参数,原样保留)----
THRESHOLD = 128 # 二值化阈值 0-255
# ---- MODE='gray3' 参数 ----
# 亮度分区:
# [0, T_BLACK) -> 黑 (实心不点亮)
# [T_BLACK, T_WHITE) -> 灰 (50% 网点)
# [T_WHITE, 255] -> 白 (实心点亮)
#
# 调整建议:
# 想更接近原来的纯黑白观感 → 把 T_BLACK 拉高、T_WHITE 也拉高,
# 让灰带尽量窄(如 140 / 180)
# 想让中灰层次更丰富 → 把两者拉开(如 80 / 200)
# 想让画面整体偏暗 → T_BLACK 降低、T_WHITE 也降低
T_BLACK = 100
T_WHITE = 170
def read_exact(stream, n):
buf = bytearray()
while len(buf) < n:
chunk = stream.read(n - len(buf))
if not chunk:
break
buf.extend(chunk)
return bytes(buf)
def check_ffmpeg():
try:
subprocess.run(
['ffmpeg', '-version'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
)
return True
except (OSError, subprocess.CalledProcessError):
return False
def video_to_oled(input_path, output_path):
# 纯黑白模式沿用原来的点采样;灰度模式换 lanczos,让灰带边界更平滑
scale_flags = 'neighbor' if MODE == 'bw' else 'lanczos'
cmd = [
'ffmpeg',
'-i', input_path,
'-vf', f'fps={FPS},scale={W}:{H}:flags={scale_flags}',
'-f', 'rawvideo',
'-pix_fmt', 'gray',
'-'
]
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
row_bytes = W // 8
frame_pixels = W * H
frame_bytes = W * H // 8
frame_count = 0
with open(output_path, 'wb') as f:
while True:
raw = read_exact(proc.stdout, frame_pixels)
if len(raw) < frame_pixels:
break
xbm = bytearray(frame_bytes)
if MODE == 'bw':
# ===== 原逻辑,未做任何改动 =====
for y in range(H):
in_base = y * W
out_base = y * row_bytes
for x in range(W):
v = raw[in_base + x]
on = (v > THRESHOLD) != INVERT
if on:
xbm[out_base + (x >> 3)] |= 1 << (x & 7)
else:
# ===== 3 级灰度 =====
for y in range(H):
in_base = y * W
out_base = y * row_bytes
for x in range(W):
v = raw[in_base + x]
if v < T_BLACK:
on = False # 黑
elif v >= T_WHITE:
on = True # 白
else:
on = ((x + y) & 1) == 0 # 灰:棋盘 50% 网点
if INVERT:
on = not on
if on:
xbm[out_base + (x >> 3)] |= 1 << (x & 7)
f.write(xbm)
frame_count += 1
proc.wait()
if proc.returncode != 0:
raise RuntimeError('ffmpeg 执行失败,请检查视频文件或 ffmpeg 安装。')
return frame_count
def main():
if not check_ffmpeg():
print('未找到 ffmpeg,请先安装并加入 PATH。')
sys.exit(1)
root = tk.Tk()
root.withdraw()
input_path = filedialog.askopenfilename(
title='选择视频文件',
filetypes=[('视频文件', '*.mp4 *.avi *.mov *.mkv *.webm'), ('所有文件', '*.*')]
)
if not input_path:
return
default_name = os.path.splitext(os.path.basename(input_path))[0] + '.oled'
output_path = filedialog.asksaveasfilename(
title='保存 OLED 帧序列',
defaultextension='.oled',
initialfile=default_name,
filetypes=[('OLED 帧序列', '*.oled'), ('所有文件', '*.*')]
)
if not output_path:
return
try:
n = video_to_oled(input_path, output_path)
size = os.path.getsize(output_path)
if MODE == 'bw':
desc = f'纯黑白(阈值 {THRESHOLD})'
else:
desc = f'3 级灰度(黑<{T_BLACK},灰,白≥{T_WHITE})'
messagebox.showinfo(
'完成',
f'已生成 {n} 帧\n'
f'文件:{output_path}\n'
f'大小:{size} 字节 ({size/1024:.1f} KB)\n'
f'分辨率:{W}x{H},帧率:{FPS} FPS\n'
f'模式:{desc}'
)
except Exception as e:
messagebox.showerror('错误', str(e))
if __name__ == '__main__':
main()
#include
#include
#include
#include
U8G2_SSD1309_128X64_NONAME0_F_HW_I2C oled(
U8G2_R0,
U8X8_PIN_NONE,
9, // SCL
8 // SDA
);
const int W = 128;
const int H = 64;
const int FRAME_SIZE = W * H / 8; // 1024
const int FPS = 10; // 与生成时一致
File videoFile;
uint8_t frame[FRAME_SIZE];
unsigned long lastFrameTime = 0;
void setup() {
Serial.begin(115200);
Wire.setClock(400000); // 提高 I2C 速度,若不稳定改回 100000
oled.begin();
if (!LittleFS.begin(true)) { // true: 挂载失败时格式化
Serial.println("LittleFS 挂载失败");
return;
}
videoFile = LittleFS.open("/danking.oled", "r");
if (!videoFile) {
Serial.println("找不到 /danking.oled");
return;
}
Serial.printf("视频文件大小: %u 字节\n", videoFile.size());
Serial.printf("总帧数: %u\n", videoFile.size() / FRAME_SIZE);
}
void loop() {
if (!videoFile) return;
unsigned long now = millis();
if (now - lastFrameTime < 1000 / FPS) return;
lastFrameTime = now;
size_t n = videoFile.read(frame, FRAME_SIZE);
if (n < FRAME_SIZE) {
// 读到结尾,回到开头循环播放
videoFile.seek(0);
return;
}
oled.clearBuffer();
oled.drawXBM(0, 0, W, H, frame);
oled.sendBuffer();
}

Comments NOTHING