地奇星RA6E2开发板I2C协议详解与BH1750光照传感器驱动实战
本文详细介绍了如何在地奇星RA6E2开发板上通过I2C协议驱动BH1750光照传感器。内容涵盖I2C协议的核心原理、硬件关键点(如开漏输出与上拉电阻),并提供了基于瑞萨FSP框架的图形化配置步骤与完整的驱动代码实战,帮助开发者快速掌握嵌入式I2C通信与传感器应用。
地奇星RA6E2开发板I2C协议详解与BH1750光照传感器驱动实战
最近在做一个智能家居的小项目,需要用到光照传感器,正好手头有地奇星的RA6E2开发板和BH1750传感器模块。很多刚开始接触嵌入式开发的朋友,一看到I2C协议就觉得头大,什么开漏输出、上拉电阻、起始信号,听起来很复杂。其实只要跟着我一步步来,你会发现用I2C驱动一个传感器并没有那么难。今天我就以RA6E2驱动BH1750为例,手把手带你从原理到代码,完整走一遍I2C的实战流程。
1. I2C协议:两线制通信的“交通规则”
1.1 I2C是什么?为什么选它?
I2C,全称Inter-Integrated Circuit,你可以把它理解成芯片之间“聊天”的一种约定好的方式。它最大的优点就是简单,整个“聊天”过程只需要两根线:
- SDA(串行数据线):专门用来传输数据,相当于“说话的内容”。
- SCL(串行时钟线):由主设备产生,用来同步数据节奏,相当于“说话的节拍器”。
为什么很多传感器(比如咱们今天用的BH1750)、EEPROM存储器都喜欢用I2C?因为它省引脚啊!一个MCU要是想接好几个传感器,用I2C总线,不管接多少个,都只需要这两根线,大大简化了硬件布线。
在I2C的“聊天室”里,有明确的角色分工:
- 主设备(Master):通常是咱们的MCU(比如RA6E2)。它负责发起聊天(发送起始信号)、指定跟谁聊(发送从设备地址)、控制聊天节奏(产生时钟),以及结束聊天(发送停止信号)。
- 从设备(Slave):比如BH1750传感器。它很“被动”,只能等待主设备呼叫自己的“名字”(地址),然后根据主设备的指令回复数据或接收命令。
1.2 I2C的硬件关键:为什么必须是“开漏输出”?
这是理解I2C硬件连接最重要的一点。在配置MCU的I2C引脚时,必须设置为开漏输出模式,而不是常见的推挽输出。为什么呢?我打个比方:
想象一下,I2C总线就像一条大家共用的电话线(SDA)和一条统一的计时器线(SCL)。如果大家都用推挽模式(可以主动输出高或低电平),当主设备想输出高电平(比如3.3V),而某个从设备想输出低电平(0V)时,这两股力量就直接“打架”了,相当于电源正负极短路,很可能烧坏芯片。
为了避免这种“打架”,I2C规定大家都用“开漏输出”。这个模式的特点是:芯片只能把线“拉低”(拉到0V),而不能主动“推高”。当它不想拉低时,就断开内部连接,相当于“放手”。
那么,总线上的高电平从哪里来呢?这就需要我们在SDA和SCL这两条线上,各自接一个上拉电阻到电源(比如3.3V)。当总线上所有设备都“放手”时,上拉电阻就把电压拉到了高电平。当任何一个设备需要发送低电平时,它就去“拉低”这根线。这样,大家就不会“打架”了。
注意:上拉电阻的阻值很关键,一般在2.2kΩ到10kΩ之间。阻值太小,电流大,功耗高;阻值太大,上升沿变慢,影响通信速度。对于咱们开发板这种短距离通信,用4.7kΩ或10kΩ都很常见。
总结一下硬件要点:
- MCU的I2C引脚(SDA, SCL)配置为开漏输出。
- SDA和SCL线上必须连接上拉电阻到电源。
- 地奇星RA6E2开发板通常已经把这些上拉电阻集成在板子上了,我们直接用就行。
1.3 I2C的“聊天”流程与信号
I2C通信就像一场有严格礼仪的对话,全靠几个关键信号来组织:
- 起始信号(Start):主设备想说“喂,注意了,我要开始说话了!”。它的动作是:在SCL为高电平期间,让SDA线产生一个从高到低的下降沿。
- 地址帧(Address + R/W):主设备接着说“我要跟地址是0x23的那位说话,并且我要读它的数据”。这里发送的是一个7位(或10位)的从设备地址,加上1位读写控制位(0表示写,1表示读)。BH1750的地址就是0x23。
- 应答信号(ACK):从设备(BH1750)听到自己的地址后,会回应一句“嗯,我在听”。它的动作是在第9个时钟周期,把SDA线拉低。如果地址不对或设备不存在,SDA会保持高,这就是非应答(NACK),告诉主设备“你找错人了”。
- 数据帧(Data):接下来就是传输真正的数据内容了,每8位数据后,都会跟一个应答位。
- 停止信号(Stop):通信结束,主设备说“好了,我说完了”。它的动作是:在SCL为高电平期间,让SDA线产生一个从低到高的上升沿。
整个流程可以概括为:起始信号 -> 发送从机地址和读写位 -> 等待应答 -> 发送/接收数据(每字节后等待应答) -> 停止信号。
地奇星RA6E2的硬件I2C模块支持多种速度模式,我们根据传感器的手册来选择。BH1750支持标准模式(100kbps)和快速模式(400kbps),为了稳定,我们一般先用标准模式。
| 模式 | 速率 | 说明 |
|---|---|---|
| 标准模式 | 100 kbps | 最常用,兼容性好 |
| 快速模式 | 400 kbps | 速度较快,需从设备支持 |
| 快速模式 Plus | 1 Mbps | 更高速率 |
| 高速模式 | 3.4 Mbps | 最高速率 |
2. 实战准备:认识BH1750光照传感器
BH1750是一款数字式环境光强度传感器,它直接通过I2C接口输出数字量,省去了我们做模拟量采集和AD转换的麻烦,非常方便。
它的核心特点:
- 接口:标准的I2C接口,地址为0x23(当ADDR引脚接低电平时)或0x5C(当ADDR引脚接高电平时)。我们的模块通常默认是0x23。
- 测量范围:1 - 65535 lux(勒克斯,光照度单位)。
- 工作模式:支持多种分辨率模式,我们常用的是“连续高分辨率模式”(命令字0x10),精度高,测量一次后会自动进行下一次测量。
接线非常简单,只需要四根线:
- VCC -> 开发板3.3V
- GND -> 开发板GND
- SCL -> 开发板I2C时钟引脚(例如P109)
- SDA -> 开发板I2C数据引脚(例如P110)
3. 地奇星RA6E2的I2C配置(FSP图形化配置)
地奇星RA6E2使用瑞萨的FSP(Flexible Software Package)开发框架,我们可以用图形化工具来配置外设,非常直观。这里假设你已经有一个基础的UART工程(用于打印数据),我们在其基础上添加I2C配置。
3.1 打开I2C时钟与配置引脚
首先,我们需要确保I2C模块的时钟是打开的。在FSP配置界面,找到“Pins”标签页,然后按以下步骤操作:
- 在
Peripherals->Connectivity: I3C/IIC->I3C/IIC下,找到你计划使用的I2C通道(例如IIC0)。 - 配置对应的引脚。比如,我们选择
IIC0,将SDA分配给P110,将SCL分配给P109。 - 关键一步:将
Operation Mode选择为Custom,并将Pin Group Select设置为Mode B only。这个模式通常就对应着标准的I2C功能。
3.2 添加并配置I2C Master堆栈
引脚配置好后,我们需要在“Stacks”标签页添加I2C的驱动模块。
- 点击“New Stack” ->
Connectivity->I2C Master (r_iic_b_master)。这会创建一个I2C主设备驱动栈。 - 点击新生成的
g_i2c_master0模块,在右侧的属性(Properties)窗口中,我们需要修改几个关键参数:Parameter Checking:可以选择BSP或Enabled,用于调试时检查参数错误。Rate:设置通信速率。我们先设为Standard(100kbps)。Slave:这里填从设备的7位地址。对于BH1750,地址是0x23,所以这里填0x23。注意FSP这里通常要求填7位地址,有的驱动也可能要求左移一位后的地址,具体看说明,我们这里先按0x23填。Callback:填写一个回调函数名,比如BH1750_callback。这个函数用于处理I2C中断事件(如发送完成、接收完成、出错等)。对于简单的读写,我们可以先写个空函数。
配置完成后,记得按下 Ctrl+S 保存,然后点击 Generate Project Content 按钮生成代码。FSP会自动把我们的图形化配置转换成底层初始化代码。
4. 手把手编写BH1750驱动代码
配置完成后,我们开始写代码。为了让工程结构清晰,我们新建两个文件夹来管理代码。
4.1 创建文件与工程结构
在 src 目录下:
- 创建一个
i2c文件夹,里面放传感器驱动文件:bsp_bh1750.c/bsp_bh1750.h(BH1750的驱动)
- 创建一个
Apply文件夹,里面放应用层文件:app.c/app.h(主应用程序)
最终的工程文件结构看起来像这样:
src/
├── hal_entry.c (主入口,FSP生成)
├── i2c/
│ ├── bsp_bh1750.c
│ └── bsp_bh1750.h
├── Apply/
│ ├── app.c
│ └── app.h
└── uart/ (假设已存在UART驱动)
├── bsp_uart.c
└── bsp_uart.h
4.2 编写驱动层代码 (bsp_bh1750)
首先来看头文件 bsp_bh1750.h,它很简单,就是声明初始化函数和读取函数。
#ifndef __BSP_BH1750_H
#define __BSP_BH1750_H
#include "hal_data.h" // 包含FSP生成的所有硬件抽象层定义
#include "uart/bsp_uart.h" // 为了使用printf调试
#include "stdio.h"
// 函数声明
void BH1750_Init(void);
uint32_t BH1750_read(void);
#endif
接下来是核心的 bsp_bh1750.c 文件。我会逐段解释。
#include "bsp_bh1750.h"
// I2C回调函数。在简单阻塞式读写中,这个函数可能用不到,但必须定义以防止链接错误。
void BH1750_callback(i2c_master_callback_args_t *p_args)
{
// 防止编译器报“未使用参数”的警告
(void)p_args;
}
// BH1750初始化函数
void BH1750_Init(void)
{
uint8_t cmd = 0x10; // BH1750的初始化命令:连续高分辨率模式
fsp_err_t err = FSP_SUCCESS; // FSP操作的错误码
// 1. 打开I2C主机控制器,使用FSP自动生成的配置结构体
err = R_IIC_B_MASTER_Open(&g_i2c_master0_ctrl, &g_i2c_master0_cfg);
if (err != FSP_SUCCESS) {
printf("错误:I2C初始化失败,错误码: %d\n", err);
return; // 初始化失败,直接返回
}
// 2. (可选)打印一下配置的从机地址,确认是否正确
printf("I2C已初始化,从机地址: 0x%02lX\n", g_i2c_master0_cfg.slave);
// 3. 向BH1750发送模式设置命令
err = R_IIC_B_MASTER_Write(&g_i2c_master0_ctrl, &cmd, 1, false);
if (err != FSP_SUCCESS) {
printf("错误:BH1750模式设置失败,错误码: %d\n", err);
return;
}
// 4. 等待传感器完成初始化。BH1750手册要求上电后至少等待180ms才能读取数据。
R_BSP_SoftwareDelay(180, BSP_DELAY_UNITS_MILLISECONDS);
printf("BH1750初始化完成,进入连续测量模式。\n");
}
初始化函数做了四件事:打开I2C外设、打印地址(调试用)、发送模式命令、等待传感器稳定。这里 R_IIC_B_MASTER_Write 函数的最后一个参数是 false,表示发送完这1字节数据后,不产生停止信号。为什么?因为对于某些传感器,连续的写操作可能不需要立刻停止。但BH1750单次写命令后可以停止,这里用 false 或 true 通常都可以。
接下来是读取光照值的函数:
uint32_t BH1750_read(void)
{
uint8_t data[2] = {0}; // 用于存放读回来的两个字节数据
uint16_t raw_lux = 0; // 合并后的16位原始数据
uint32_t lux = 0; // 计算后的实际光照值
fsp_err_t err = FSP_SUCCESS;
// 1. 从BH1750读取2字节光照数据
// 注意:最后一个参数为false,表示读操作完成后,主设备会发送停止信号。
err = R_IIC_B_MASTER_Read(&g_i2c_master0_ctrl, data, 2, false);
if (err != FSP_SUCCESS) {
printf("错误:BH1750读取失败,错误码: %d\n", err);
return 0; // 读取失败,返回0
}
// 2. 等待一小段时间,确保数据稳定(非必须,但建议保留)
R_BSP_SoftwareDelay(180, BSP_DELAY_UNITS_MILLISECONDS);
// 3. 将两个字节数据合并为一个16位整数
// BH1750的数据格式是高字节在前(MSB),低字节在后(LSB)
raw_lux = (data[0] << 8) | data[1];
// 4. 根据BH1750数据手册的公式,将原始数据转换为光照值(单位:lux)
// 公式:光照值 = 原始数据 / 1.2
lux = (uint32_t)(raw_lux / 1.2f);
return lux;
}
这里最关键的是 R_IIC_B_MASTER_Read 函数,它封装了完整的I2C读时序:起始信号 -> 发送设备地址(读)-> 接收数据 -> 停止信号。我们只需要提供存放数据的数组和长度即可。
4.3 编写应用层代码 (app)
应用层代码就非常简洁了,主要就是初始化和循环读取。
app.h 文件:
#ifndef __APP_H
#define __APP_H
#include "hal_data.h"
#include "stdio.h"
void Run(void); // 主运行函数
#endif
app.c 文件:
#include "Apply/app.h"
#include "i2c/bsp_bh1750.h"
#include "uart/bsp_uart.h" // 假设你的UART初始化在这里
void Run(void)
{
// 1. 初始化串口,用于打印结果
UART0_Init();
// 2. 初始化BH1750传感器
BH1750_Init();
printf("欢迎使用地奇星RA6E2开发板\r\n");
printf("BH1750光照传感器实验开始...\r\n\r\n");
while(1)
{
// 3. 循环读取光照强度
uint32_t light_value = BH1750_read();
printf("当前光照强度 = %ld lux\r\n", light_value);
// 4. 延时1秒再读
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
}
}
4.4 修改主入口 (hal_entry.c)
最后,别忘了在FSP生成的 hal_entry.c 文件中调用我们的 Run() 函数。
#include "hal_data.h"
#include "Apply/app.h" // 包含我们的应用头文件
FSP_CPP_HEADER
void R_BSP_WarmStart(bsp_warm_start_event_t event);
FSP_CPP_FOOTER
void hal_entry(void)
{
/* TODO: add your own code here */
Run(); // 调用我们的主程序
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
5. 实验现象与调试心得
将代码编译下载到地奇星RA6E2开发板,连接好BH1750模块(VCC, GND, SCL, SDA)和串口模块(用于打印数据)。给开发板上电,打开串口助手(如Putty、SecureCRT等),设置好波特率(与你UART初始化的一致,比如115200)。
你应该能看到串口不断打印出光照强度值。用手遮住传感器,数值会变小;用手机手电筒照射传感器,数值会急剧变大。
几个常见的坑点和调试建议:
- I2C地址不对:这是最常见的问题。BH1750的地址引脚(ADDR)电平决定了地址是0x23还是0x5C。一定要确认你的模块状态。如果读不到数据,可以尝试用逻辑分析仪或示波器抓一下I2C波形,看看起始信号后发出的地址对不对。
- 没有上拉电阻:虽然开发板可能集成了,但如果你是自己搭的电路,务必在SDA和SCL线上接上4.7kΩ的上拉电阻到3.3V。
- 时序问题:BH1750发送模式命令后,需要等待足够的时间(>180ms)才能读取有效数据。延时不够会导致读到0或错误值。
- FSP配置错误:仔细检查引脚分配是否正确,I2C堆栈是否成功添加,速率和地址配置是否无误。生成代码后,可以检查一下
hal_data.c文件,看看g_i2c_master0_cfg这个结构体里的参数是不是你设置的值。 - 接线错误:再三检查VCC、GND、SDA、SCL这四根线有没有接错或接触不良。
好了,到这里,你已经成功在地奇星RA6E2上通过I2C驱动了BH1750传感器。整个过程其实就分三步:理解I2C协议(知道怎么“聊天”)、用FSP配置硬件(准备好“聊天工具”)、编写驱动代码(发出“聊天”指令)。希望这个详细的实战教程能帮你打通I2C学习的任督二脉,以后遇到其他I2C设备,比如温湿度传感器、加速度计等,都可以举一反三,快速上手。
更多推荐
所有评论(0)