尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

BH1750调试

BH1750调试 BH1750FVI是一款数字型光强度传感器集成芯片BH1750的内部由光敏二极管、运算放大器、ADC采集、晶振等组成。通过光敏二极管将光信号转变为微弱的模拟信号通过运算放大电路放大后交给adcadc对电流进行采样并量化为一个16位的二进制数字存在寄存器。引脚说明VCC电源正极严格接 3.3V(绝对不可接 5V否则 BH1750 芯片会立刻烧毁)。GND电源地接开发板的GND。SCLI2C 时钟线接开发板 I2C 总线的 SCL例如I2C1_SCL。通常模块已集成上拉电阻可以直接连。SDAI2C 数据线接开发板 I2C 总线的 SDA例如I2C1_SDA。同样可直接连。ADDRI2C 设备地址选择脚通常不需要接。悬空时默认 I2C 地址为0x23。如果接 3.3V地址变为0x5C。BH1750指令通信时序BH1750少了一个发送寄存器地址的步骤因为它只有一个寄存器imx6ull板上核心板设备树配置代码实现#include linux/fs.h #include linux/init.h #include linux/miscdevice.h #include linux/module.h #include linux/platform_device.h #include linux/export.h #include linux/i2c.h #include linux/mod_devicetable.h #includelinux/miscdevice.h #include linux/delay.h #include asm/uaccess.h #define DEV_NAME ptBH1750 #define BH1750_PWR_ON 0x01 #define BH1750_MODE_HIGH_RES 0x10 int myprobe(struct i2c_client *, const struct i2c_device_id *); int myremove(struct i2c_client *); int myopen (struct inode *, struct file *); ssize_t myread (struct file *, char __user *, size_t, loff_t *); int myrelease (struct inode *, struct file *); struct i2c_client *pcli; static struct file_operations fops{ .owner THIS_MODULE, .open myopen, .read myread, .release myrelease, }; struct of_device_id match_table[] { { .compatible pute,puteBH1750, }, { } }; struct i2c_device_id id_table[]{ { .name ptBH1750, }, { } }; static struct miscdevice misc_dev{ .minor MISC_DYNAMIC_MINOR, .name DEV_NAME, .fops fops, }; int myopen (struct inode *inode, struct file *f) { pr_info(open called ..\n); f-private_data pcli; return 0; } ssize_t myread (struct file *f, char __user *userdata, size_t count, loff_t *off) { struct i2c_client *pcli; int ret; u8 cmd; u8 rd_buf[2]; u16 raw_val; int lux; char kbuf[10]; pcli (struct i2c_client *)f-private_data; if (!pcli) { return -EINVAL; } cmd BH1750_PWR_ON; ret i2c_master_send(pcli, cmd, 1); if (ret ! 1) { dev_err(pcli-dev, BH1750 Power On failed (ret%d)\n, ret); return (ret 0) ? ret : -EIO; } cmd BH1750_MODE_HIGH_RES; ret i2c_master_send(pcli, cmd, 1); if (ret ! 1) { dev_err(pcli-dev, BH1750 Set Mode failed (ret%d)\n, ret); return (ret 0) ? ret : -EIO; } msleep(180); ret i2c_master_recv(pcli, rd_buf, 2); if (ret ! 2) { dev_err(pcli-dev, BH1750 Read Data failed (ret%d)\n, ret); return (ret 0) ? ret : -EIO; } raw_val (rd_buf[0] 8) | rd_buf[1]; if (copy_to_user(userdata, raw_val, sizeof(raw_val))) { return -EFAULT; } return sizeof(raw_val); } int myrelease (struct inode *node, struct file *f) { pr_info(release called ..\n); return 0; } int myprobe(struct i2c_client *pclient, const struct i2c_device_id *id) { int ret 0; pcli pclient; misc_register(misc_dev); if(ret0) { pr_err(misc error\n); return ret; } pr_info(BH1750 OK\n); return 0; } int myremove(struct i2c_client *pcli) { misc_deregister(misc_dev); return 0; } static struct i2c_driver drv { .probe myprobe, .remove myremove, .driver { .name ptBH1750, .owner THIS_MODULE, .of_match_table match_table, }, .id_table id_table, }; module_i2c_driver(drv); MODULE_LICENSE(GPL);应用层#include stdio.h #include stdlib.h #include unistd.h #include fcntl.h int main(int argc, char **argv) { int fd open(/dev/ptBH1750, O_RDWR); if (fd 0) { perror(open /dev/ptBH1750 failed); return 1; } unsigned short raw_val; float lux; while (1) { if (read(fd, raw_val, sizeof(raw_val)) sizeof(raw_val)) { lux raw_val / 1.2; printf(原始值: %d, 光照强度: %.1f Lux\n, raw_val, lux); } else { printf(读取失败\n); } sleep(1); } close(fd); return 0; }遇到的错误在内核驱动代码中使用了浮点数运算Linux 内核为了极致追求速度和内存精简通常禁止在内核空间使用浮点数float或double运算。当使用raw_val / 1.2时编译器悄悄把“除法”变成了对__aeabi_ddiv等函数的调用。但是编译出来的这个.ko模块里并没有包含这些计算函数的源代码Linux 内核里面也没有这些函数的定义。所以当insmod试图把.ko加载进内核时内核一查表发现找不到这些函数在哪里于是抛出了Unknown symbol的错误并拒绝加载。解决办法很简单把数据放到应用层处理。结果关于i2c的子系统第一层用户层通过标准调用操作节点第二层I2C设备驱动层它把 用户空间 APP 的读/写指令翻译成针对特定 I2C 设备 (i2c_client) 的i2c_master_send/recv操作并负责把数据安全地递交给 APP。I2C_clientI2C 总线上某一个特定物理设备的数据结构struct i2c_client { unsigned short addr; // 核心 1从机地址 (比如 0x23) char name[I2C_NAME_SIZE]; // 核心 2设备名字 struct i2c_adapter *adapter; // 核心 3指向哪条 I2C 总线 (比如 I2C1) struct device dev; // 标准设备结构体 };i2c_driver如何控制该硬件的代码逻辑struct i2c_driver { int (*probe)(struct i2c_client *, const struct i2c_device_id *); int (*remove)(struct i2c_client *); struct device_driver driver; const struct i2c_device_id *id_table; };第三层I2C核心层:负责打包数据、寻找总线、调度并发定义了i2c_master_send()、i2c_master_recv()、i2c_transfer()等一套标准 API。当驱动层调用i2c_master_send时核心层会立即介入。它根据传入的pcli把数据包装成一个I2C 总线能看懂的标准“数据帧。它帮您加上I2C 的起始位Start。它自动把设备地址 0x23加上去。它准备好在数据发完后自动加上停止位Stopint i2c_master_send(struct i2c_client *client, const char *buf, int count) { struct i2c_adapter *adap client-adapter; // 1. 拿到邮局硬件控制器 struct i2c_msg msg; // 2. 打包信封目标地址是 client-addr写操作内容是 buf msg.addr client-addr; msg.flags 0; // 0 表示写操作 msg.buf (char *)buf; msg.len count; // 3. 交给邮局发送 return i2c_transfer(adap, msg, 1); }pcli结构体里有两个要素设备地址和总线编号I2C1/I2C2。核心层拿到封好的包后会去查找pcli-adapter如果pcli指向的是 I2C1 总线核心层就会把写好的“标准快递单”准确无误地扔给负责 I2C1 总线的适配器驱动层Adapter第四层I2C 适配器驱动层核心层给的数据包直接倒进 i.MX6ULL 芯片的物理寄存器里或者从物理寄存器里读出 BH1750 返回的二进制数字。有一个结构体里面有一个重要函数指针struct i2c_algorithm { /* 关键就是下面这个函数指针 */ int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); /* ... */ };核心层Core把包装好的标准 I2C 数据帧struct i2c_msg传给这一层。这一层拿到数据帧后调用master_xfer这个函数。当master_xfer被调用时这一层会去直接读写 i.MX6ULL 芯片底层的 I2C 硬件寄存器。物理动作打开 I2C 控制器的时钟。配置波特率比如 100kHz。设置 I2C 控制寄存器启用主机模式。往数据寄存器Data Register里写入要发送的字节。等待硬件发送完成忙等待。读取数据寄存器获取 BH1750 发回来的数据。它负责把上层核心层封装好的标准 I2C 数据帧直接翻译成 i.MX6ULL 芯片底层寄存器的读写指令并通过写入寄存器指挥芯片内部的硬件电路自动在 SCL 和 SDA 引脚上产生准确的电压波形包括起始位、地址、数据、ACK和停止位同时实时监控硬件状态寄存器的变化确保数据成功发送或接收第五层物理硬件层
返回列表