苏兮博客-记录学习经验 - 尽意
type: channel
link: https://www.suxii.cn/author/1/
description: 尽意
items: Flutter模板方法模式在多Tab列表中的应用
items: Flutter 实现流式消息接收
items: Flutter LayoutBuilder 原理深度解析
items: ESP32C3使用Slint渲染TFT屏幕
items: ESP32C3驱动TFT屏幕
items: ESP32C3使用Slint渲染OLED屏幕
items: ESP32C3引入Slint: 解决依赖冲突
items: ESP32C3使用BLE
items: rust裸机开发esp32c3
items: rust开发esp32c3驱动oled屏幕

Flutter模板方法模式在多Tab列表中的应用
type: item
date: 2026-01-16T15:18:25+08:00
description: 项目背景与需求在Flutter开发中，我们经常遇到需要实现多Tab栏列表的场景，每个Tab具有相似的结构（下拉刷新、上拉加载、列表展示），但数据类型和UI展示各不相同。本文将介绍如何使用模板方法模式来优雅地解决这个问题。模板方法模式简介模板方法模式是一种行为设计模式，它在超类中定义了一个算法的框架，允许子类在不修改结构的情况下重写特定步骤。在我们的场景中，每个Tab的加载流程（初始化→刷新→加载更多）是相同的，但具体的数据加载和UI展示是不同的。代码实现解析1. 抽象基类定义const int defaultPageSize = 10; abstract class TabData<T> { final String name; // tab名称 int total; // 数据数量 bool isSelected; // 选中状态 bool hasMore; // 是否有更多数据 List<T> items; // 数据列表 bool _isInited = false; // 是否初始化完成 final RefreshController controller; final Widget Function(BuildContext context,T item) itemBuilder; TabData({ required this.name, required this.itemBuilder, this.total = 0, this.isSelected = false, this.hasMore = true, this.items = const [] }) : controller = RefreshController(); int get pageSize => defaultPageSize; Future<List<T>> loadData(int page); void updateItems(List<T> newItems, bool isRefresh); Future<bool> init() async{ if (_isInited) { return true; } _isInited = true; await onRefresh(); return false; } Future<void> onRefresh() async { try { final data = await loadData(1); updateItems(data, true); hasMore = data.length >= pageSize; controller.refreshCompleted(); } catch (e) { controller.refreshFailed(); rethrow; } } Future<void> onLoadMore() async { if (!hasMore) { controller.loadNoData(); return; } try { final currentPage = (items.length / pageSize).ceil() + 1; final data = await loadData(currentPage); updateItems(data, false); hasMore = data.length >= pageSize; controller.loadComplete(); } catch (e) { controller.refreshFailed(); rethrow; } } void dispose() { controller.dispose(); } }设计要点：定义了完整的生命周期方法（init、onRefresh、onLoadMore）使用泛型T支持不同的数据类型封装了刷新控制器和分页逻辑提供itemBuilder回调实现UI定制化2. 具体子类实现点赞Tab实现：class LikeTabData extends TabData<LikeTabDataEntity>{ LikeTabData():super(name: "点赞",itemBuilder: (context,item){ return Text(item.name); }); @override Future<List<LikeTabDataEntity>> loadData(int page) async{ await Future.delayed(const Duration(seconds: 1)); return List.generate(pageSize, (index)=> LikeTabDataEntity(name: …

Flutter 实现流式消息接收
type: item
date: 2026-01-07T14:52:57+08:00
description: 我们通过 Dio 发起一个 ResponseType.stream 请求，消费后端逐字符返回的 HTTP 流。核心代码如下：void sendMessage() => Dio().get( "http://192.168.149.178:8080/stream/covert?url=${state.text}", options: Options(responseType: ResponseType.stream), cancelToken: CancelToken(), ).then((response) { final data = response.data; if (data is! ResponseBody) return; state.data = ""; update(); data.stream.listen((chunk) { final res = utf8.decoder.convert(chunk); state.data += res; update(); }); });UI 部分使用 Expanded + SingleChildScrollView 展示实时追加的文本：Column( children: [ TextField(controller: logic.text), TextButton(onPressed: logic.sendMessage, child: Text("发送消息")), Expanded( child: SelectionArea( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [Text(state.data)], ), ), ), ), ], )后端使用 Spring WebFlux 将目标网页内容逐字符延迟返回：@GetMapping("/covert") public Flux<String> covert(@RequestParam String url) { return Mono.fromCallable(() -> { // 同步抓取网页内容（在非 Netty 线程执行） var request = new Request.Builder().url(url).build(); try (var resp = client.newCall(request).execute()) { return resp.body().string(); } }) .flux() .flatMap(text -> Flux.fromStream(text.chars().mapToObj(c -> String.valueOf((char) c))) .delayElements(Duration.ofMillis(10)) // 每字符间隔 10ms ); }该方案无需 SSE 或 WebSocket，仅依赖标准 HTTP 分块传输，即可实现“打字机式”流式响应，适用于 AI 对话、实时日志等场景。

Flutter LayoutBuilder 原理深度解析
type: item
date: 2025-12-25T16:13:54+08:00
description: 引言在 Flutter 响应式布局体系中，LayoutBuilder 是一个强大而特殊的组件。与大多数在构建阶段（build phase）确定其子树的组件不同，LayoutBuilder 将子组件的构建推迟到布局阶段（layout phase），从而能够根据父级传递的实际布局约束动态构建 UI。本文将深入源码层面，全面剖析 LayoutBuilder 的工作原理。核心架构概览类继承层次Widget ├── RenderObjectWidget │ └── ConstrainedLayoutBuilder<BoxConstraints> │ └── LayoutBuilder (具体实现)主要角色分工ConstrainedLayoutBuilder<T> - 抽象基类，定义约束构建器的通用协议LayoutBuilder - 面向 BoxConstraints 的具体实现_LayoutBuilderElement - 连接 Widget 与 RenderObject 的桥梁_RenderLayoutBuilder - 实际执行布局的 RenderObject源码深度解析1. Element 的初始化与绑定// _LayoutBuilderElement.mount 方法 @override void mount(Element? parent, Object? newSlot) { super.mount(parent, newSlot); // 1. 创建对应的 RenderObject renderObject.updateCallback(_rebuildWithConstraints); // 2. 绑定重建回调 }关键点分析：super.mount() 会创建 _RenderLayoutBuilder 实例updateCallback 将 _rebuildWithConstraints 方法注册到 RenderObject此时回调并未立即执行，只是建立了调用关系2. 布局阶段的核心调用链// _RenderLayoutBuilder.performLayout 方法 @override void performLayout() { final BoxConstraints constraints = this.constraints; // 1. 获取父级约束 rebuildIfNecessary(); // 2. 触发约束重建 if (child != null) { child!.layout(constraints, parentUsesSize: true); // 3. 布局子节点 size = constraints.constrain(child!.size); // 4. 确定自身大小 } else { size = constraints.biggest; } }2.1 约束的来源追踪约束的传递遵循 Flutter 渲染管线的标准流程：// RenderObject 层级传递 void layout(Constraints constraints, { bool parentUsesSize = false }) { this.constraints = constraints; // 父组件传递约束 // ... 后续布局计算 }this.constraints 在 performLayout 中通过 this.constraints 访问，这是 RenderObject 的标准属性。2.2 重建触发机制// RenderConstrainedLayoutBuilder 混入类 void rebuildIfNecessary() { assert(_callback != null); invokeLayoutCallback(_callback!); // 调用注册的回调 } // RenderObject 中的回调执行 void invokeLayoutCallback<T extends Constraints>(LayoutCallback<T> callback) { owner!._enableMutationsToDirtySubtrees(() { callback(constraints as T); // 关键：传递当前约束 }); }3. 安全的回调执行环境_enableMutationsToDirtySubtrees 是保证布局阶段安全修改组件树的关键：void _enableMutationsToDirtySubtrees(VoidCallback callback) { assert(_debugDoingLayout); bool? oldState; assert(()…

ESP32C3使用Slint渲染TFT屏幕
type: item
date: 2025-05-10T20:02:12+08:00
description: 这个教程算是在st7789屏幕没有买之前就已经开始打算了，之前的使用slint驱动oled也算是为这个打下基础吧创建新项目可以参考 ESP32C3使用Slint渲染OLED屏幕 引入slint依赖，创建跟配置slint跟这篇文章一样，引入的依赖基本上大差不差，只不过这个项目还需要引入st7789屏幕的相关依赖。然后在Cargo.toml中添加依赖 [dependencies] # 基础依赖 critical-section = "1.2.0" esp-hal = { version = "1.0.0-beta.0", features = ["esp32c3","unstable"] } esp-alloc = "0.7.0" esp-println = { version = "0.13.1",features = ["esp32c3","log"] } log = "0.4.27" esp-backtrace = { version = "0.15.1" ,features = ["esp32c3","println","panic-handler"]} # 图形相关 embedded-hal = "1.0.0" embedded-graphics-core = "0.4.0" embedded-graphics = "0.8.1" embedded-graphics-framebuf = "0.5.0" display-interface = "0.5.0" display-interface-spi = "0.5.0" mipidsi = "0.9.0" embedded-hal-bus = "0.3.0" # 异步 esp-hal-embassy = { version = "0.7.0", features = ["esp32c3"] } embassy-time = { version = "0.4.0", features = ["generic-queue-8"] } embassy-executor = { version = "0.7.0",features = ["task-arena-size-20480","executor-thread"] } #embassy-futures = "0.1.1" static_cell = { version = "2.1.0", features = ["nightly"] } tinygif = "0.0.4" [dependencies.slint] git = "https://github.com/slint-ui/slint" rev = "29168bc89270798f6075a0a729c14a3f011ceb4f" # 修复提交的哈希 default-features = false features = [ "compat-1-2", "unsafe-single-threaded", # 启用单线程模式 "libm", "renderer-software", ] [build-dependencies] slint-build = {git = "https://github.com/slint-ui/slint",rev = "29168bc89270798f6075a0a729c14a3f011ceb4f"} 创建ui文件创建 ui/main.slint 文件，作为程序的入口文件import { System,LocalTime,Route , Setting} from "./global.slint"; import { HomePage } from "./page/home_page.slint"; import { SettingPage } from "./page/setting_page.slint"; // 导出给rust代码调用 export { System,LocalTime,Setting } export component MainView inherits Window { background: black; width: System.screen_width; height: System.screen_height; default-font-family: "黑体"; // 简单实现路由 if System.route==Route.HomePage: HomePage{} if System.route==Route.SettingPage: SettingPage{} // 显示帧率 if Setting.fps == true:Rectangle { padding: 2px; x:0…

ESP32C3驱动TFT屏幕
type: item
date: 2025-05-09T14:56:00+08:00
description: 因为使用的是新版esp-generate创建的项目，很多api的使用都大改了，网上能搜到的类似的教程也都完全没法用，这个教程是使用最新版esp-hal基于rust开发esp32c3驱动st7789屏幕。看网上有些项目叫MOSI，MISO的，也有叫SDI，SDO，这两个本质就是一个东西，在不同协议叫法不同。SPI 协议中大家都喜欢叫 MOSI / MISO，有些芯片厂商文档上写的是 SDI / SDO；MOSI == SDI（对于从机）：表示“数据输入”MISO == SDO（对于从机）：表示“数据输出”项目创建在 rust裸机开发esp32c3 的基础上，先新建一个最基础的环境引入依赖[dependencies] critical-section = "1.2.0" esp-hal = { version = "1.0.0-beta.0", features = ["esp32c3","unstable"] } esp-println = { version = "0.13.1",features = ["esp32c3","log"] } log = "0.4.27" # 图形相关 embedded-hal = "1.0.0" embedded-graphics-core = "0.4.0" embedded-graphics = "0.8.1" embedded-graphics-framebuf = "0.5.0" display-interface = "0.5.0" display-interface-spi = "0.5.0" mipidsi = "0.9.0" embedded-hal-bus = "0.3.0" 编写main.rs代码#![no_std] #![no_main] use embedded_graphics::Drawable; use embedded_graphics::image::{Image, ImageRawLE}; use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::{DrawTarget, Point, Primitive, RgbColor}; use embedded_graphics::primitives::PrimitiveStyle; use embedded_graphics_core::prelude::Size; use embedded_graphics_core::primitives::Rectangle; use embedded_hal::delay::DelayNs; use embedded_hal::digital::ErrorType; use embedded_hal_bus::spi::ExclusiveDevice; use esp_hal::clock::CpuClock; use esp_hal::delay::Delay; use esp_hal::gpio::{Level, Output, OutputConfig}; use esp_hal::main; use esp_hal::spi::master::{Config, Spi}; use esp_hal::spi::Mode; use esp_hal::time::{Duration, Instant, Rate}; use esp_println::logger::init_logger; use esp_println::println; use log::{error, LevelFilter}; use mipidsi::_mock::MockSpi; use mipidsi::models::ST7789; use mipidsi::Builder; use mipidsi::interface::SpiInterface; use mipidsi::options::Orientation; #[panic_handler] fn panic(e: &core::panic::PanicInfo) -> ! { println!("{}",e); loop { } } #[main] fn main() -> ! { // generator version: 0.3.1 let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); init_logger(LevelFilter::Debug); let peripherals = esp_hal::init(config); let…

ESP32C3使用Slint渲染OLED屏幕
type: item
date: 2025-05-05T18:59:05+08:00
description: 前言：说一下为什么要用slint，既然都选择用rust开发嵌入式了避免不了要折腾，也是为后续驱动st7789做铺垫吧。而其实对应OLED屏幕已经有一个很好用的库了embedded-graphics，几乎能满足OLED屏幕的全部需求了，但是slint有动画，定时器，声明式，这些优势，再嵌入式开发再合适不过了。在写这个的时候也遇到不少坑，所以记录一下吧创建项目引入库编译问题已经在 ESP32C3引入Slint 文章解决了先创建一个最基础的esp32c3的项目然后再Cargo.toml中添加依赖# 基础依赖 critical-section = "1.2.0" esp-hal = { version = "1.0.0-beta.0", features = ["esp32c3","unstable"] } esp-alloc = "0.7.0" esp-println = { version = "0.13.1",features = ["esp32c3","log"] } log = "0.4.27" esp-backtrace = { version = "0.15.1" ,features = ["esp32c3","println","panic-handler"]} # 图形相关 embedded-hal = "0.2.7" embedded-graphics-core = "0.4.0" embedded-graphics = "0.8.1" embedded-graphics-framebuf = "0.5.0" ssd1306 = { version = "0.10.0" ,features = ["graphics"]} # 异步 esp-hal-embassy = { version = "0.7.0", features = ["esp32c3"] } embassy-time = { version = "0.4.0", features = ["generic-queue-8"] } embassy-executor = { version = "0.7.0",features = ["task-arena-size-20480"] } static_cell = { version = "2.1.0", features = ["nightly"] } [dependencies.slint] git = "https://github.com/slint-ui/slint" rev = "29168bc89270798f6075a0a729c14a3f011ceb4f" # 修复提交的哈希 default-features = false features = [ "compat-1-2", "unsafe-single-threaded", # 启用单线程模式 "libm", "renderer-software", ] [build-dependencies] slint-build = {git = "https://github.com/slint-ui/slint",rev = "29168bc89270798f6075a0a729c14a3f011ceb4f"} 配置Slint我们使用slint的ui跟代码分离的模式，修改build.rs新增一个build_slint函数，然后在main函数中调用它 fn main(){ build_slint(); ... } fn build_slint(){ slint_build::compile_with_config( "ui/MainView.slint", slint_build::CompilerConfiguration::new() .embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer) ).unwrap() }创建UI文件在项目根目录下创建 ui/MainView.slint 文件，定义 UI 界面// 由于是在no_std的环境下，没有默认字体，使用需要引入自定义的字体 import "./fonts/simhei.ttf"; // 这里定义了一个全局的状态，用于在rust代码里进行修改ui状态 export global MyState { in-out property <bool> is_show : true; } export component MainView inherits Window { default-font-family: "黑体"; width: 128px; height: 64px; Rectangle …

ESP32C3引入Slint: 解决依赖冲突
type: item
date: 2025-05-03T16:50:07+08:00
description: 为什么要折腾这个？想在 ESP32-C3 开发板上实现一个酷炫的 UI 界面，选了 Rust 生态的 Slint 框架。结果刚加上依赖就报错：error: 不能同时启用 critical-section 和 portable_atomic_unsafe_assume_single_core这两个特性不能同时存在解决问题过程第一步：创建最小测试项目# 生成纯净的 ESP32-C3 项目骨架 esp-generate --chip=esp32c3 slint_demo此时项目仅包含最基础的硬件驱动，编译一次成功：cargo build Compiling slint_demo v0.1.0 Finished dev [optimized] target(s)第二步：引入 Slint 引爆问题按官方文档添加依赖：cargo add slint@1.10.0 --no-default-features --features "compat-1-2 unsafe-single-threaded libm renderer-software"再次编译立刻报错：error: critical-section 与 portable_atomic_unsafe_assume_single_core 冲突第三步：定位问题注释 Slint 保留 ESP-HAL → 编译成功注释 ESP-HAL 保留 Slint → 依然报错Slint 的依赖树里藏了冲突的组件第四步：解决问题搜索 slint critical-section site:github.com锁定关键讨论：Issue #5057发现修复补丁：Commit 29168bc核心修改点：# Slint 内部配置调整 - portable-atomic = { features = ["critical-section"] } + portable-atomic = {} # 解除强制依赖最终配置方案关键依赖声明 (Cargo.toml)[dependencies] # 硬件抽象层 esp-hal = { version = "1.0.0-beta.0", default-features = false, features = ["esp32c3"] # 注意关闭默认特性 } # UI 框架 [dependencies.slint] git = "https://github.com/slint-ui/slint" rev = "29168bc89270798f6075a0a729c14a3f011ceb4f" # 锁定修复版本 default-features = false features = [ "compat-1-2", # 兼容旧版 API "unsafe-single-threaded", # 单核模式 "libm", # 数学库支持 "renderer-software" # 软件渲染 ] # 内存分配器 esp-alloc = "0.7.0"内存管理配置 (main.rs)// 初始化堆内存（72KB 用于 UI） #[main] fn main() -> ! { let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let _peripherals = esp_hal::init(config); esp_alloc::heap_allocator!(size: 72 * 1024); }经验总结依赖管理铁律用 cargo tree -e features 查看依赖关系通过 default-features = false 精准控制特性版本锁定技巧使用 rev = "commit_hash" 锁定特定修复版本定期检查 GitHub Issues 跟踪问题进展

ESP32C3使用BLE
type: item
date: 2025-05-01T16:36:00+08:00
description: 初始化项目参考 项目创建 新建一个基于std的新项目在Cargo.toml添加必要的依赖[package] name = "esp32c3-ble-demo" version = "0.1.0" authors = ["suxii <m@suxii.cn>"] edition = "2021" resolver = "2" rust-version = "1.77" bin name = "esp32c3-ble-demo" harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors [features] default = [] experimental = ["esp-idf-svc/experimental"] [dependencies] log = "0.4" esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync","experimental","std"] } futures = "0.3.31" anyhow = "1.0.98" enumset = "1.1.5" heapless = "0.8.0" esp-idf-sys = "0.36.1" esp-idf-hal = "0.45.2" # 处理序列化，反序列化 serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" [build-dependencies] embuild = "0.33" 开启BLE功能将sdkconfig.defaults 文件替换成# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 CONFIG_BT_BLUEDROID_ENABLED=y CONFIG_BT_ENABLED=y CONFIG_BT_BLE_ENABLED=y CONFIG_BT_GATTS_ENABLE=y CONFIG_BT_BLE_SMP_ENABLE=y CONFIG_BT_GATTS_SEND_SERVICE_CHANGE_MODE=y CONFIG_BT_BTC_TASK_STACK_SIZE=7000 CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y CONFIG_BT_CTRL_BLE_MAX_ACT=10 CONFIG_BT_CTRL_BLE_MAX_ACT_EFF=10 CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=0 CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 CONFIG_BT_CTRL_BLE_SCAN_DUPL=y # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). # This allows to use 1 ms granuality for thread sleeps (10 ms by default). #CONFIG_FREERTOS_HZ=1000 # Workaround for https://github.com/espressif/esp-idf/issues/7631 #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n并且在同级目录新建一个 sdkconfig.esp32c3文件# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 CONFIG_BT_ENABLED=y CONFIG_BT_BLE_ENAB…

rust裸机开发esp32c3
type: item
date: 2025-04-30T01:03:45+08:00
description: 安装esp-generate# 安装新版生成工具 cargo install esp-generate # 安装依赖工具链 cargo install espup espup install激活环境变量source $HOME/export-esp.sh # linuxWindows用户默认在C:\Users\HOME目录下有一个 export-esp.ps1文件，双击执行激活环境变量使用esp-genertate 创建新项目esp-generate --chip=esp32c3 demo这个项目是个hello world项目，不需要额外的配置，接着按s创建项目添加依赖在Cargo.toml的[dependencies]添加println依赖esp-println = { version = "0.13.1",features = ["esp32c3","log"] }点亮LED修改mian.rs点亮一个led#![no_std] #![no_main] use esp_hal::clock::CpuClock; use esp_hal::gpio::{Level, Output, OutputConfig}; use esp_hal::main; use esp_hal::time::{Duration, Instant}; use esp_println::println; #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } #[main] fn main() -> ! { // generator version: 0.3.1 let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); let mut led = Output::new(peripherals.GPIO9, Level::Low, OutputConfig::default()); let mut count = 0; loop { let delay_start = Instant::now(); led.toggle(); println!("第{count}次数改变led状态"); count+=1; while delay_start.elapsed() < Duration::from_millis(1500) {} } // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0-beta.0/examples/src/bin } 成功打印日志

rust开发esp32c3驱动oled屏幕
type: item
date: 2025-04-27T22:49:55+08:00
description: 使用的是 I2C SSD1306 0.96寸的OLED屏幕1.在Cargo.toml中添加图形库依赖# 处理ssd1306屏幕 ssd1306 = {version = "0.10.0",features = ["graphics"]} embedded-graphics = "0.8.1"完整配置 Cargo.toml[package] name = "esp32c3-oled-demo" version = "0.1.0" authors = ["suxii <m@suxii.cn>"] edition = "2021" resolver = "2" rust-version = "1.77" bin name = "esp32c3-oled-demo" harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors [features] default = [] experimental = ["esp-idf-svc/experimental"] [dependencies] log = "0.4" esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync","experimental","std"] } futures = "0.3.31" anyhow = "1.0.98" esp-idf-hal = "0.45.2" # 处理ssd1306屏幕 ssd1306 = {version = "0.10.0",features = ["graphics"]} embedded-graphics = "0.8.1" [build-dependencies] embuild = "0.33" 2.在main.rs中编写rust代码use esp_idf_hal::{ delay::FreeRtos, i2c::*, peripherals::Peripherals, prelude::*, }; use embedded_graphics::{ mono_font::{ascii::FONT_6X10, MonoTextStyle}, pixelcolor::BinaryColor, prelude::*, primitives::{Circle, Line, PrimitiveStyle, Rectangle}, text::Text }; use ssd1306::{I2CDisplayInterface, Ssd1306, prelude::*, size::DisplaySize128x64}; fn main() -> anyhow::Result<()> { esp_idf_hal::sys::link_patches(); // 初始化外设 let peripherals = Peripherals::take()?; // 配置I2C let i2c = peripherals.i2c0; let sda = peripherals.pins.gpio5; let scl = peripherals.pins.gpio6; let config = I2cConfig::new().baudrate(400.kHz().into()); let i2c = I2cDriver::new(i2c, sda, scl, &config)?; // 创建SSD1306接口 let interface = I2CDisplayInterface::new(i2c); let mut display = Ssd1306::new( interface, DisplaySize128x64, DisplayRotation::Rotate0 ).into_buffered_graphics_mode(); // 初始化显示屏 display.init().expect("SSD1306 init failed"); display.flush().expect("Failed to flush display"); // 创建图形样式 let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On); let line_style = PrimitiveStyle::with_stroke(BinaryColor::On, 1); let mut run_x = 64; loop {…