【瑞萨RA-eco-RA6M4评测】不用Zephyr了吗,我用他做了一个环境监测站
本帖最后由 eefocus_3914144 于 2026-7-30 23:09 编辑> 本文档记录将 Zephyr `blinky` 示例从官方 EK-RA6M4 (144-pin) 平台移植到 RA-Eco-RA6M4V2.0 (100-pin) 开发板的完整过程,并在此基础上扩展了 OLED 显示、SHT30 温湿度传感器和"丝滑滚动时钟"演示功能。
> [千万别入坑Zephyr 我用Zephyr快速搭建RA6M4环境监测系统 | 丝滑时钟\_哔哩哔哩\_bilibili](https://www.bilibili.com/video/BV1tf3t6dEdt/?spm_id_from=333.1387.homepage.video_card.click&vd_source=e1bd226340c8b87027d5dcfc6b0c3344)]
---
## 目录
1. [项目概述](#1-项目概述)
2. [硬件差异对比](#2-硬件差异对比)
3. [构建与烧录](#3-构建与烧录)
4. [设备树 (Overlay) 移植](#4-设备树-overlay-移植)
5. (#5-kconfig-prjconf-配置)
6. (#6-ssd1306-oled-驱动移植)
7. (#7-sht30-温湿度传感器移植)
8. (#8-i2c-worker-线程移植)
9. [丝滑滚动时钟 (rolling_clock) 移植](#9-丝滑滚动时钟-rolling_clock-移植)
10. [主程序 (main.c) 集成](#10-主程序-mainc-集成)
11. [常见问题与注意事项](#11-常见问题与注意事项)
12. [工程源码](#12-工程源码)
---
## 1. 项目概述
项目效果图:
!(https://www.eefocus.com/forum/data/attachment/forum/202607/30/095515x25lscs4n4tzllnl.jpg)
### 1.1 移植起点
- **基线示例**:Zephyr 官方 `samples/basic/blinky`
- **源平台**:Renesas EK-RA6M4 (144-pin LGA)
- **目标平台**:RA-Eco-RA6M4V2.0 (100-pin LGA,12MHz 晶振)
### 1.2 功能扩展
在基础 LED 闪烁功能之上,叠加以下功能:
| 功能 | 器件 | 接口 | 库/驱动 |
|------|------|------|---------|
| LED 闪烁 (×3) | 板载 LED | GPIO | Zephyr GPIO API |
| 串口 Shell | USB-UART | UART (SCI9) | Zephyr Shell |
| OLED 显示 | SSD1306 (128×64) | I2C (IIC0, 400kHz) | 自研 ssd1306.c |
| 温湿度采集 | SHT30 | I2C (IIC0) | 自研 sht30.c |
| 滚动时钟动画 | SSD1306 | I2C (IIC0) | 自研 rolling_clock.c |
### 1.3 源码结构
blinky/
├── CMakeLists.txt
├── prj.conf
├── ek_ra6m4.overlay ← 设备树 overlay(关键移植点)
├── boards/
│ └── nrf54h20dk_nrf54h20_cpuppr.overlay ← 备用平台
├── src/
│ ├── main.c ← 主程序
│ └── drivers/
│ ├── ssd1306.h / .c ← OLED 驱动
│ ├── sht30.h / .c ← 温湿度驱动
│ ├── i2c_worker.h / .c← I2C 防卡死线程
│ └── rolling_clock.h / .c← 丝滑滚动时钟动画
---
## 2. 硬件差异对比
| 项目 | EK-RA6M4 (源) | RA-Eco-RA6M4V2.0 (目标) | 影响 |
|------|---------------|----------------------|------|
| MCU | R7FA6M4AF3CFC | R7FA6M4AF3CFP | 相同 IP,仅封装差异 |
| 封装 | LGA 144 | LGA 100 | 减少 44 个引脚 |
| 主晶振 | 24 MHz | **12 MHz** | **PLL 配置必须修改** |
| LED1 | P404 | **P002** | 需改 DTS |
| LED2 | P404 | P404 | 无变化 |
| LED3 | P400 | **P405** | 需改 DTS |
| Shell UART | SCI9 (P411/P410) | SCI9 (P110/P109) | pinmux 调整 |
| I2C0 | 已使能 | 需在 overlay 显式打开并配置 pinmux | 见下文 |
| WDT | 默认 | 需 overlay 启用 | 用于防卡死 |
---
## 3. 构建与烧录
### 3.1 构建
```bash
# 假设已设置 ZEPHYR_BASE 与工具链
west build -b ek_ra6m4 .
# 清理重建
west build -b ek_ra6m4 . -p always
```
> 由于 RA-Eco-RA6M4V2.0 与 EK-RA6M4 同属 R7FA6M4 家族,板上差异由 overlay 吸收,无需新增 board 定义。
### 3.2 烧录
```bash
west flash
```
默认使用 J-Link 协议将固件写入 RA-Eco-RA6M4V2.0。
### 3.3 预期运行
- 三个 LED 同时以 50ms 周期翻转
- Shell 在 USB-UART 上以 115200-8N1 输出提示符 `blinky> `
- OLED 显示 "RA6M4 Zephyr" 标题、温湿度行、滚动时钟(HH:MM:SS)
- 串口打印启动 banner、SHT30 初始化日志、滚动时钟取自系统时间
---
## 4. 设备树 (Overlay) 移植
移植的核心文件是 `ek_ra6m4.overlay`,完整内容如下(与项目源码一致),并附逐段说明。
### 4.1 LED 与控制台别名
```dts
/ {
aliases {
led0 = &led1; /* P002 */
led1 = &led2; /* P404 */
led2 = &led3; /* P405 */
};
leds {
led1 { gpios = <&ioport0 2 GPIO_ACTIVE_HIGH>; };
led2 { gpios = <&ioport4 4 GPIO_ACTIVE_HIGH>; };
led3 { gpios = <&ioport4 5 GPIO_ACTIVE_HIGH>; };
};
chosen {
zephyr,console = &uart9;
zephyr,shell-uart = &uart9;
};
};
```
- 注意目标板的 P002 属于 Port 0 (`ioport0`),P404/P405 属于 Port 4 (`ioport4`)。
- 控制台强制绑定 `uart9`,避免使用默认 SCI0(目标板该引脚未引出)。
### 4.2 时钟与 PLL
```dts
&xtal { clock-frequency = <DT_FREQ_M(12)>; };/* 12 MHz 晶振 */
&pll{ div = <1>; mul = <15 0>; }; /* 12*15 = 180 MHz */
```
时钟链路:
```
XTAL(12MHz) ──┐
├── PLL → ICLK = 180 MHz
XTAL/1=12MHz ─┘
```
`prj.conf` 中同步设置 `CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=180000000`,否则 SysTick 计算会偏慢。
### 4.3 WDT 启用
```dts
&wdt { status = "okay"; };
```
主程序用 5 秒窗口看门狗防止 I2C 卡死导致整机冻结。
### 4.4 Pinmux(SCI9 / IIC0)
```dts
&pinctrl {
sci9_default: sci9_default {
group1 {
psels = <RA_PSEL(RA_PSEL_SCI_9, 1, 10)>, /* TX = P110 */
<RA_PSEL(RA_PSEL_SCI_9, 1,9)>; /* RX = P109 */
drive-strength = "medium";
};
};
iic0_default: iic0_default {
group1 {
psels = <RA_PSEL(RA_PSEL_I2C, 4, 0)>, /* SDA = P400 */
<RA_PSEL(RA_PSEL_I2C, 4, 1)>; /* SCL = P401 */
drive-strength = "medium";
drive-open-drain; /* I2C 必须开漏 */
};
};
};
```
> **重点**:`drive-open-drain;` 不可省略,否则 SDA/SCL 推挽输出会损坏总线。
### 4.5 外设节点
```dts
&sci9 {
pinctrl-0 = <&sci9_default>;
pinctrl-names = "default";
status = "okay";
uart9: uart {
current-speed = <115200>;
status = "okay";
};
};
&iic1 { status = "disabled"; }; /* 关闭未用 I2C 总线,省电 */
&iic0 {
pinctrl-0 = <&iic0_default>;
pinctrl-names = "default";
status = "okay";
clock-frequency = <400000>; /* 400 kHz Fast-Mode */
interrupts = <91 1>, <92 1>, <93 1>, <94 1>;
interrupt-names = "rxi", "txi", "tei", "eri";
};
```
### 4.6 可选 RTC
```dts
&{/soc} {
rtc0: rtc@40083000 {
compatible = "renesas,ra-rtc";
reg = <0x40083000 0x80>;
clocks = <&subclk>;
alarms-count = <1>;
interrupts = <40 5>, <41 5>, <42 5>;
interrupt-names = "cup", "prd", "alm";
status = "okay";
};
};
```
> 本项目时钟改由系统时间 (`date set`) 提供,RTC 节点虽已声明但默认未启用。如需使用硬件 RTC,可在 `prj.conf` 中打开 `CONFIG_RTC=y` 并让 `main.c` 走 RTC API。
---
## 5. Kconfig (prj.conf) 配置
```ini
# === 基础 ===
CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_CONSOLE=y
CONFIG_CONSOLE=y
CONFIG_LOG=y
CONFIG_STDOUT_CONSOLE=y
CONFIG_SHELL=y
CONFIG_SHELL_BACKEND_SERIAL=y
CONFIG_SHELL_LOG_LEVEL_INF=y
CONFIG_SHELL_PROMPT_UART="blinky> "
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=180000000
# === I2C 与 WDT ===
CONFIG_I2C=y
CONFIG_WATCHDOG=y
# === 可选:硬件 RTC ===
# CONFIG_RTC=y
# CONFIG_RTC_SHELL=y
# CONFIG_SHELL_DATE=y
```
要点:
- `CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=180000000` 必须与 overlay 中的 PLL 倍频一致,否则 `k_msleep()` 实际节拍与标称值不符。
- `CONFIG_SHELL_PROMPT_UART` 自定义为 `blinky> `,便于识别当前应用。
---
## 6. SSD1306 OLED 驱动移植
`sdk/src/drivers/ssd1306.c` 是直接基于 I2C API 的轻量驱动,没有走 Zephyr 的 `display` 框架,便于理解和裁剪。
### 6.1 移植要点
| 原版特征(STM32 HAL 等) | Zephyr 移植后 |
|-----------------------|--------------|
| `HAL_I2C_Master_Transmit(...)` | `i2c_write(i2c_dev, buf, len, SSD1306_I2C_ADDR)` |
| 固定 `hi2c1` | 通过 `ssd1306_init(dev)` 注入 `device *` |
| `HAL_Delay(100)` | `k_msleep(100)` |
| 无 framebuffer | 维护 128×64/8 = 1024 字节 FB,提供 `ssd1306_fb_flush()` 一次性刷屏 |
| 仅页/位图写入 | 新增 `fb_clear / fb_pixel / fb_hline / fb_puts / fb_clear_region / fb_draw_strip_window` 等图元 |
| 同步阻塞刷新 | 同步阻塞(25ms / 400kHz),后续可改为异步 |
### 6.2 关键接口
- `int ssd1306_init(const struct device *i2c_dev);` — 注入 I2C 设备并发出初始化命令序列。
- `void ssd1306_fb_clear(void);` — 清空 1024 字节 FB。
- `void ssd1306_fb_flush(void);` — 全屏刷新。
- `void ssd1306_fb_draw_strip_window(int x, int y, int w, int h, const uint8_t *strip, int strip_rows, int offset_y, int wrap_line);` — **滚动时钟专用**:从纵向 strip 位图中按 offset_y 取一段绘制到 (x,y) 处。
### 6.3 注意事项
- 初始化命令序列(含 `SSD1306_DISPLAYON`、`SETCONTRAST=0xCF`、`CHARGEPUMP=0xCF` 等)严格按 SSD1306 datasheet。
- `ssd1306_fb_clear_region()` 用位掩码精确擦除行内区域,可避免覆盖其他像素,是滚动时钟擦除动作的核心。
---
## 7. SHT30 温湿度传感器移植
### 7.1 数据格式
`struct sht30_data`:
```c
struct sht30_data {
int16_ttemperature;/* ×100 ℃,例如 2315 表示 23.15 ℃ */
uint16_t humidity; /* ×100 %RH,例如 5500 表示 55.00 % */
};
```
### 7.2 CRC 校验
SHT3x 系列采用 8-bit CRC(多项式 0x31,初始值 0xFF)。`sht3x_crc()` 移植自 Sensirion 参考实现,对 2 字节数据计算 1 字节 CRC。
### 7.3 上电防卡死序列
SHT30 在上电后或总线异常时可能丢失 ACK,原始驱动直接发送软复位命令容易假死。本移植在 `sht30_init()` 中加入 5 步恢复流程:
1. `i2c_recover_bus(i2c_dev)` — 手动时钟恢复(部分平台无实现,忽略 ENOSYS)。
2. `k_msleep(500)` — 模块冷启动延时。
3. **General-Call Reset**(地址 0x00,命令 0x06)— 强制所有支持通用呼叫的 I2C 设备复位。
4. 软复位循环重试地址 0x44(最多 5 次)。
5. 软复位循环重试地址 0x45(最多 3 次)。
主程序调用入口使用 `i2c_worker_call_init(i2c_dev, 5000)`,5 秒超时足以覆盖以上全部流程。
### 7.4 周期模式
- `SHT30_CMD_PERIODIC_4PS` (0x2737):4 m/s 周期测量。
- 每次读取用 `SHT30_CMD_FETCH` (0xE000) 抓取 6 字节(温度 2B+CRC、湿度 2B+CRC)。
- 温度换算:`(raw * 17500 / 65535) - 4500`(单位 0.01 ℃)。
- 湿度换算:`raw * 10000 / 65535`(单位 0.01 %RH)。
---
## 8. I2C Worker 线程移植
### 8.1 动机
主循环既要刷 OLED(阻塞 25ms)又要读 SHT30,还要响应 Shell 输入。**I2C 总线一旦卡死,主循环会被永久阻塞,Shell 与看门狗都失效。**
### 8.2 实现要点
- 后台线程 `i2c_worker`(优先级 5,栈 1024 B)只跑 SHT30 初始化与读取。
- 主循环通过两个信号量 `i2c_worker_req` / `i2c_worker_done` 与之同步。
- 主线程 `k_sem_take(&i2c_worker_done, K_MSEC(timeout_ms))` —— 等待期间仍可被调度,让 Shell 继续工作。
- 超时返回 `-ETIMEDOUT`,主循环不会卡死;看门狗检测到主循环长时间未喂狗 → 5 秒后硬件复位。
### 8.3 调用约定
```c
int i2c_worker_call_init (const struct device *dev, int timeout_ms);
int i2c_worker_call_read (const struct device *dev, struct sht30_data *data,
int timeout_ms);
```
返回值:
| 值 | 含义 |
|----|------|
| `0` | 成功 |
| `< 0` | SHT30 驱动层错误码 |
| `-ETIMEDOUT` | worker 未在 timeout_ms 内完成(总线卡死) |
---
## 9. 丝滑滚动时钟 (rolling_clock) 移植
> 这是本项目最具移植技巧性的部分。源码中明确注释: *"ported from ZeroCHN stm32L073_OLED demo"*。
### 9.1 原理
将 0–9 共 10 个数字纵向拼成一个"长条"位图,每次秒进位时并不直接切换数字,而是**逐帧上移整张长条**,当前数字"卷"走、下一个数字"滚"入,形成丝滑过渡效果。
### 9.2 位图布局
| 条带 | 尺寸 | 内容 |
|------|------|------|
| `big_strip` | 24 px 高 × 20 px 宽 × 10 数字 = 240×20 | 用于 HH:MM 大字 |
| `small_strip` | 16 px 高 × 14 px 宽 × 10 数字 = 160×14 | 用于 SS 小字 |
| `colon_bmp` | 24 px 高 × 4 px 宽 | 冒号(顶部点 + 散点装饰 + 底部点) |
存储格式:``,每字节代表一列 8 个垂直像素。
### 9.3 屏幕布局(行 40–63)
```
H1 H2: M1 M2 S1 S2
20 204 20 20 14 14 (宽 px)
|2px gap|
0 22 4450 72 94 110 (x 坐标)
```
- 大字(HH:MM)置于 `Y_BIG=40`,高 24 px。
- 小字(SS)置于 `Y_SMALL=48`,高 16 px,与大字底对齐。
- 整宽 124 px,居中后左侧留 2 px 空白。
### 9.4 滚动状态机
每个数字维护一个 `Y_*` 偏移(`uint16_t`),等于 `digit × strip_h`。
```c
static void animate_Y(uint16_t *Y, uint8_t d, uint8_t strip_h, uint16_t total_rows) {
uint16_t target = d * strip_h;
if (*Y < target) (*Y)++; /* 向前滚 */
else if (*Y > target) { (*Y)++; if (*Y >= total_rows) *Y = 0; } /* 9→0 时穿过并回绕 */
}
```
调用频率越高,过渡越丝滑;本项目用 `ANIM_FRAME_MS=50`(≈20 fps),完整 24 帧过渡耗时约 1.2 秒。
### 9.5 绘制流程
```
1. ssd1306_fb_clear_region(0, 40, 128, 24) ← 擦除时钟区
2. animate_Y(...) × 6 ← 推进各数字偏移
3. ssd1306_fb_draw_strip_window(...) × 6 ← 从长条切窗绘制
4. blink_colon ? draw_colon() : skip ← 冒号闪烁
```
### 9.6 移植踩坑
| 原版(STM32) | 移植到 Zephyr |
|--------------|---------------|
| `HAL_SPI_Transmit(...)` 一次刷整屏 | 改为分段刷屏(页地址+列地址+128 B 数据),参见 `ssd1306_fb_flush()` |
| 字模数据放在 `const` 数组,`flash` 直接寻址 | 同样 `static const uint8_t big_strip[...]`,但需注意 RA6M4 内部 Flash 读延迟;放在 CCM/AXI 区域提升速度 |
| 位图列内 bit 排布:LSB=top | **保持 LSB=top**,否则翻转动画方向反了 |
| 0–9 拼成长条后整体向上滚 | 同样逻辑,但用 `fb_draw_strip_window` 做"切片 + 位掩码"绘制 |
| 阻塞 `HAL_Delay(33)` 控制 30 fps | 改为 `k_msleep(50)`,让出 CPU 给 Shell |
### 9.7 API
```c
void rolling_clock_init(void);
void rolling_clock_draw(uint8_t h1, uint8_t h2,
uint8_t m1, uint8_t m2,
uint8_t s1, uint8_t s2,
uint8_t blink_colon);
```
`h1..s2` 是十进制的 6 个数字(每个 0–9),`blink_colon` 控制冒号闪烁。
---
## 10. 主程序 (main.c) 集成
### 10.1 启动序列
```c
1. printf("RA-Eco-RA6M4V2.0 Rolling Clock Demo (SHT30=...)\n");
2. i2c_dev = DEVICE_DT_GET(I2C0_NODE); /* 获取 I2C0 设备 */
3. ssd1306_init(i2c_dev); /* OLED 初始化 */
4. ssd1306_fb_clear(); ssd1306_fb_flush(); /* 清屏 */
5. 配置 3 颗 LED 为 GPIO_OUTPUT_ACTIVE
6. wdt_setup_and_start(); /* 5 s 看门狗 */
7. i2c_worker_init(); /* 启动 I2C worker */
8. i2c_worker_call_init(i2c_dev, 5000); /* worker 中初始化 SHT30 */
9. rolling_clock_init(); /* 滚动时钟状态清零 */
10. time_t now = time(NULL); /* 取系统时间 */
```
### 10.2 主循环
```c
while (1) {
wdt_kick(); /* 喂狗 */
toggle 3 LEDs; /* 视觉心跳 */
if (tick_ms % 333 == 0) i2c_worker_call_read();/* ≈3 Hz 读 SHT30 */
tick_ms += 50; if (tick_ms >= 1000) { tick_ms-=1000; 秒进位; 翻转冒号闪烁位; 重新读 time(NULL); }
rolling_clock_draw(h1,h2,m1,m2,s1,s2,colon_blink);
draw_divider(23); draw_divider(31); /* 上下分割线 */
ssd1306_fb_puts_15x(4, PAGE_TITLE1, "RA6M4 Zephyr");
ssd1306_fb_puts(0, PAGE_DATA, data_line); /* T/H 行 */
ssd1306_fb_flush(); /* 25 ms 阻塞刷屏 */
k_msleep(50); /* 让出 CPU */
}
```
### 10.3 时钟源选择
`main.c` 当前使用 **系统时间 (`time(NULL)`)** 而非硬件 RTC:
- 默认 RTC 未启用(`CONFIG_RTC=y` 在 prj.conf 中注释)。
- 上电时通过 `date set` shell 命令设置时间。
- 也可直接换成 RTC API:
```c
struct rtc_time rtc;
rtc_get_time(rtc_dev, &rtc);
hour = rtc.tm_hour; minute = rtc.tm_min; second = rtc.tm_sec;
```
### 10.4 帧率与资源占用
| 项目 | 数值 |
|------|------|
| 滚动时钟帧率 | 20 fps(ANIM_FRAME_MS=50) |
| SHT30 采样率 | 3 Hz(每 333 ms 一次) |
| OLED 全屏刷新耗时 | ≈25 ms @ 400 kHz |
| LED 翻转频率 | 10 Hz |
| 主循环周期 | ≈50 ms |
---
## 11. 常见问题与注意事项
### 11.1 时钟不匹配 → `k_msleep` 偏慢
**症状**:Shell 心跳明显慢于 1 Hz,或秒进位偏慢。
**原因**:`prj.conf` 的 `CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC` 未与 PLL 结果同步。
**修复**:保证 overlay 中 `xtal * pll.mul / pll.div == 180 MHz`,并把 `CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC` 也设为 `180000000`。
### 11.2 OLED 全白/全黑
- **检查项 1**:`ssd1306_init()` 中 `SSD1306_CHARGEPUMP` 命令与子命令是否成对发出。
- **检查项 2**:`drive-open-drain;` 是否在 I2C pinmux 中。
- **检查项 3**:I2C 速率是否 ≤ 400 kHz(SHT30/SSD1306 普遍不支持 1 MHz)。
### 11.3 SHT30 永远初始化失败
- 接线检查 SDA/SCL 是否与 overlay 中 pin 编号一致。
- 用逻辑分析仪确认 ACK;按 §7.3 顺序执行 General-Call Reset + 软复位重试。
- 关闭外部上拉电阻测量:很多开发板已经板上上拉。
### 11.4 I2C 总线卡死 → Shell 无响应
- **根因**:`i2c_worker_call_*` 的 timeout 太小。
- **缓解**:看门狗 5 秒未喂狗会自动复位 MCU;调大 timeout 让 worker 跑完恢复流程。
- **进阶**:在 `i2c_worker_dispatch` 中检测到超时后调用 `i2c_recover_bus()`,再恢复 worker。
### 11.5 滚动时钟"跳变"而非丝滑
- **帧率过低**:`ANIM_FRAME_MS` 应 ≤ 50(≥ 20 fps)。
- **位图 bit 排布错**:MSB/LSB top 顺序颠倒会让动画看起来是"瞬移"。
- **忘记 9→0 回绕**:`animate_Y` 中 `if (*Y >= total_rows) *Y = 0;` 缺一不可。
### 11.6 烧录失败
- 检查 `pyocd` 或 J-Link 驱动是否安装(J-Link 协议默认)。
- RA-Eco-RA6M4V2.0 需要在烧录前按住 BOOT/Reset 或短接特定引脚(参见板卡手册)。
---
## 附录 A:完整 overlay 文件(参考)
参见项目根目录 [`ek_ra6m4.overlay`](./ek_ra6m4.overlay)。
## 附录 B:相关源码位置
| 内容 | 文件 |
|------|------|
| 滚动时钟实现 | (./src/drivers/rolling_clock.c) |
| 滚动时钟 API | (./src/drivers/rolling_clock.h) |
| SSD1306 OLED | (./src/drivers/ssd1306.c) |
| SHT30 传感器 | (./src/drivers/sht30.c) |
| I2C 防卡死线程 | (./src/drivers/i2c_worker.c) |
| 主程序 | (./src/main.c) |
| 设备树 overlay | (./ek_ra6m4.overlay) |
| 项目 Kconfig | (./prj.conf) |
## 附录 C:参考链接
- Zephyr 官方文档:(https://docs.zephyrproject.org)
- SSD1306 数据手册:(https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf)
- Sensirion SHT3x CRC 与协议:(https://developer.sensirion.com)
- RA6M4 硬件手册:(https://www.renesas.com/products/microcontrollers-microprocessors/ra-cortex-m-mcus)
---
## 工程源码
页:
[1]