Zephyr 4.3 开发笔记:Renesas RA 驱动 AHT20 (SCI I2C)
日期 : 2025年
平台 : Renesas RA 系列 (使用 SCI 模拟 I2C)
硬件环境:
- 传感器: AHT20 (I2C 地址 0x38) 连接于 P410/P411
- 显示屏: SSD1306 (SPI)
- 关键硬件特性 : I2C 总线外部上拉电阻为 10kΩ
1. 核心问题分析
1.1 资源冲突:SCI0 (UART vs I2C)
现象 :
默认情况下,Zephyr 会将系统的控制台 (Console/Shell) 映射到 UART0 。在瑞萨 RA 芯片的硬件层面上,UART0 和我们使用的 I2C 接口通常都指向同一个物理模块------SCI0 (Serial Communications Interface 0)。
冲突原因 :
SCI 模块是多功能的,但在同一时刻只能工作在一种模式下(UART、I2C 或 SPI)。
- 默认配置:Zephyr 尝试初始化 SCI0 为 UART 模式用于打印日志。
- 应用需求:传感器 AHT20 接在 P410/P411 引脚,这两个引脚对应 SCI0 的 I2C 功能。
- 结果:如果不同时禁用 UART 控制台,I2C 驱动将无法获取 SCI0 硬件锁,导致初始化失败或引脚配置冲突。
解决方案 :
必须在设备树中删除控制台属性 并禁用 UART0 节点,完全释放 SCI0 给 I2C 驱动使用。
1.2 信号质量:10kΩ 大电阻导致的波形畸变
现象 :
通常 I2C 推荐 4.7kΩ 或 2.2kΩ 上拉电阻。当前硬件使用了 10kΩ ,导致在 100kHz 频率下,信号(尤其是时钟信号)上升沿过缓(呈鲨鱼鳍状)。SCI 硬件对边沿检测严格,从而引发 I2C_MASTER_EVENT_ABORTED 错误。
解决方案 (软件配置) :
利用设备树 pinctrl 对 SDA 和 SCL 采用差异化配置,无需修改硬件即可解决:
- SDA (数据) : Open-Drain + 内部上拉 (内部上拉与外部 10k 并联,降低阻值,改善波形)。
- SCL (时钟) : Push-Pull (推挽) (利用 MCU 主动驱动高电平,彻底消除大电阻带来的上升沿延迟)。
2. 最终代码实现
2.1 设备树配置 (app.overlay)
请注意 chosen 节点中的删除操作,以及 pinctrl 中的差异化配置。
dts
/*
* 硬件连接配置:
* SSD1306 (SPI): SCK=P111, MOSI=P109, D/C=P110, CS=P301, RES=P208
* AHT20 (I2C): SDA=P411, SCL=P410 (通过 SCI0)
*/
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/pinctrl/renesas/pinctrl-ra.h>
/ {
chosen {
zephyr,display = &ssd1306_spi;
/*
* [冲突解决] 禁用控制台
* 因为 UART0 和 I2C 共享 SCI0 硬件资源,必须禁用控制台以释放 SCI0。
*/
/delete-property/ zephyr,console;
/delete-property/ zephyr,shell-uart;
};
};
/* 强制开启 IO Port 控制器 */
&ioport1 { status = "okay"; };
&ioport2 { status = "okay"; };
&ioport3 { status = "okay"; };
&ioport4 { status = "okay"; };
/*
* [冲突解决] 禁用 UART0 节点
* 确保 UART 驱动不会尝试初始化 SCI0
*/
&uart0 {
status = "disabled";
};
&pinctrl {
/* SPI0 屏幕引脚 */
spi0_new_custom: spi0_new_custom {
group1 {
psels = <RA_PSEL(RA_PSEL_SPI, 1, 9)>,
<RA_PSEL(RA_PSEL_SPI, 1, 11)>;
drive-strength = "high";
};
};
/*
* [信号优化] SCI0 I2C 引脚配置
* 针对 10kΩ 外部上拉电阻的特殊优化
*/
sci0_i2c_custom: sci0_i2c_custom {
/*
* SDA (P411): 配置为开漏 + 内部上拉
* 必须是开漏 (I2C标准)。开启内部上拉以辅助外部的 10k 电阻。
*/
group_sda {
psels = <RA_PSEL(RA_PSEL_SCI_0, 4, 11)>;
drive-strength = "medium";
bias-pull-up; /* 关键:内部电阻并联外部电阻 */
drive-open-drain; /* 关键:双向通信必须开漏 */
};
/*
* SCL (P410): 配置为推挽输出
* 这里的技巧是不设置 drive-open-drain。
* 推挽模式下 MCU 主动拉高 SCL,无视 10k 电阻的影响,保证时钟边缘陡峭。
*/
group_scl {
psels = <RA_PSEL(RA_PSEL_SCI_0, 4, 10)>;
drive-strength = "medium";
};
};
};
/* SCI0 模块配置为 I2C 模式 */
&sci0 {
status = "okay";
pinctrl-0 = <&sci0_i2c_custom>;
pinctrl-names = "default";
sci0_i2c: i2c {
compatible = "renesas,ra-i2c-sci";
status = "okay";
channel = <0>;
clock-frequency = <100000>; /* 100kHz 标准速率 */
#address-cells = <1>;
#size-cells = <0>;
/* AHT20 传感器配置 (复用 DHT20 驱动) */
aht20: dht20@38 {
compatible = "aosong,dht20";
reg = <0x38>;
};
};
};
/* SPI0 屏幕配置 */
&spi0 {
status = "okay";
pinctrl-0 = <&spi0_new_custom>;
pinctrl-names = "default";
cs-gpios = <&ioport3 1 GPIO_ACTIVE_LOW>;
ssd1306_spi: ssd1306@0 {
compatible = "solomon,ssd1306fb";
reg = <0>;
spi-max-frequency = <4000000>;
width = <128>;
height = <64>;
segment-offset = <0>;
page-offset = <0>;
display-offset = <0>;
multiplex-ratio = <63>;
segment-remap;
com-invdir;
prechargep = <0x22>;
data-cmd-gpios = <&ioport1 10 GPIO_ACTIVE_HIGH>;
reset-gpios = <&ioport2 8 GPIO_ACTIVE_LOW>;
};
};
2.2 项目配置 (prj.conf)
必须在 Kconfig 层面彻底关闭串口功能,防止内核尝试调用串口驱动。
properties
CONFIG_HEAP_MEM_POOL_SIZE=4096
CONFIG_MAIN_STACK_SIZE=2048
# [冲突解决] 禁用串口控制台
# 这一步至关重要,否则 UART 驱动会抢占 SCI0 寄存器
CONFIG_SERIAL=n
CONFIG_CONSOLE=n
CONFIG_UART_CONSOLE=n
# 硬件驱动支持
CONFIG_GPIO=y
CONFIG_I2C=y
CONFIG_SPI=y
# 传感器驱动: AHT20 (使用 DHT20 兼容驱动)
CONFIG_SENSOR=y
CONFIG_DHT20=y
# 显示驱动
CONFIG_DISPLAY=y
CONFIG_SSD1306=y
CONFIG_CHARACTER_FRAMEBUFFER=y
# 允许 printf 处理浮点数
CONFIG_CBPRINTF_FP_SUPPORT=y
2.3 主程序 (main.c)
c
/*
* Zephyr AHT20 + SSD1306 演示程序
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/display/cfb.h>
#include <stdio.h>
int main(void)
{
const struct device *dev_disp;
const struct device *dev_sensor;
uint8_t font_height;
uint8_t font_width;
char buf[32];
/* 1. 初始化屏幕 */
dev_disp = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
if (!device_is_ready(dev_disp)) {
return 0;
}
display_blanking_off(dev_disp);
if (cfb_framebuffer_init(dev_disp)) {
return 0;
}
cfb_framebuffer_set_font(dev_disp, 0);
cfb_get_font_size(dev_disp, 0, &font_width, &font_height);
/* 2. 获取传感器 (AHT20) */
dev_sensor = DEVICE_DT_GET(DT_NODELABEL(aht20));
while (1) {
struct sensor_value temp, humidity;
bool sensor_ok = false;
/* 读取传感器 */
if (device_is_ready(dev_sensor)) {
/* AHT20 必须先 fetch 更新数据,再 get 获取数值 */
if (sensor_sample_fetch(dev_sensor) == 0) {
sensor_channel_get(dev_sensor, SENSOR_CHAN_AMBIENT_TEMP, &temp);
sensor_channel_get(dev_sensor, SENSOR_CHAN_HUMIDITY, &humidity);
sensor_ok = true;
}
}
/* --- 屏幕显示 --- */
cfb_framebuffer_clear(dev_disp, false);
cfb_print(dev_disp, "AHT20 Monitor", 0, 0);
if (sensor_ok) {
double t_val = sensor_value_to_double(&temp);
double h_val = sensor_value_to_double(&humidity);
snprintf(buf, sizeof(buf), "Temp: %.1f C", t_val);
cfb_print(dev_disp, buf, 0, font_height + 5);
snprintf(buf, sizeof(buf), "Humi: %.1f %%", h_val);
cfb_print(dev_disp, buf, 0, (font_height * 2) + 5);
} else {
cfb_print(dev_disp, "Sensor Error!", 0, font_height + 5);
if (!device_is_ready(dev_sensor)) {
cfb_print(dev_disp, "Init Fail (SCI?)", 0, (font_height * 2) + 5);
} else {
cfb_print(dev_disp, "Read Fail", 0, (font_height * 2) + 5);
}
}
/* 运行状态指示条 */
static int count = 0;
struct cfb_position start = {0, 62};
struct cfb_position end = {(count % 128), 63};
cfb_draw_rect(dev_disp, &start, &end);
count += 5;
cfb_framebuffer_finalize(dev_disp);
k_sleep(K_MSEC(1000));
}
return 0;
}