ESP32学习笔记:I2C

今天我们来玩儿i2c。
i2c概述i2c全称是inter-integrated circuit,是飞利浦半导体公司(06年迁移到nxp了)在1982年发明的,是使用非常广泛的一种通信协议,很多传感器、存储芯片、oled等,都是在使用i2c。标准输出模式下能达到100kbps的传输速率,快速模式下能达到400kbps的传输速率,高速模式下能达到3.4mbps,超高速下最快能达到5mbps。
与uart一样,iic仅用两条线在设备间通信:
scl -- 时钟信号
sda-- 数据信号
i2c主机与从机之间共享时钟信号,时钟始终由主机控制,总线下面可以挂多个设备,是一种同步,多主,多从,半双工的通信协议,下面我们简单介绍一下通信原理:
默认情况下,两条线都被上拉,scl=1,sda=1。
启动与停止信号:
通信开始,要先发开启动信号,结束的时候,要发送结束信号。
开始信号由主设备发出启动,具体为在scl高电平期间,sda从高电平切换到低电平;
停止信号由主设备发出结束,具体为在scl高电平期间,sda从低电平切换到高电平;
当然,在传输过程中,有时候需要更改数据方向,重新传输等,我们没必要发停止信号,直接重新发启动信号启动即可。
地址字节
我们的总线上可能挂很多从设备,在我们主设备发送了启动信号之后,总线上的从设备就都被“唤醒”了,等着主设备发送地址宠幸。所以这里有一个从机地址的概念,从机地址以8位字节发送的,msb在前,最后一位表示接下来读或写,所以高7位构成了从机地址,也可以看出,同一个总线上,可以寻址128个从设备。
一旦从设备的地址匹配,就继续读取最后一位,低电平代表写入,高电平代表读取。其它从设备就忽略后面的数据。
ack与nack
在每个字节传输之后,接收设备发送一个应答信号,确认或者不确认,接收设备通过在scl高电平期间,将sda拉低生成一个确认信号ack,拉高生成一个不确认信号nack,这里ack主要用于表示字节正确传输了,nack表示数据传输有错误,需要从新发送。应答信号主设备,从设备都可以产生,比如,主设备从从设备读取最后一个字节的数据后,就要发送nack结束传输。
数据信号
数据以8位字节格式传输,高字节在前,传输的字节数量没有限制,但是每个字节后面必须要有一个数据接收方产生的应答信号。传输过程中,scl为低的时候,sda数据可以改变,scl为高的时候,sda的数据必须稳定。
命令字节
当写入或读取从设备中特定寄存器时,主机首先要向已寻址的从机写入寄存器地址,其实也是一个数据字节,我们这里称之为命令字节。
写入设备
主设备在发出启动信号之后,紧着着发送要操作从设备的地址,最后一位为低电平表示接下来写入数据,然后在时钟信号下一位一位的写入数据,在从设备发出ack应答之后,发送结束信号结束通信。
读取数据
主设备在发出启动信号之后,紧着着发送要操作从设备的地址,最后一位为高电平表示接下来读取数据,然后接管sda数据线并在时钟的控制下向主设备发送数据,主设备同样要在每个字节接收完毕的时候发送ack响应,当主设备不想接收的时候,就在最后一个字节接收后发送nack响应,然后恢复对总线的控制并发送结束信号。
scl的控制权始终在主机这里。
当然,实际还要很多组合传输协议,这里由于篇幅问题就不展开说了,基本上大同小异,我们根据不同设备的数据手册来传输就可以啦。i2c还有很多特性,快速命令,仲裁,多主控等等,普通的应用接触不到,感兴趣的小伙伴自行研究下。
硬件esp32有2个硬件i2c总线接口,接口可以配置为主机或从机模式,支持如下特性:
标准模式 (100 kbit/s)快速模式 (400 kbit/s)高达 5 mhz,但受 sda 上拉强度的限制7位/10位寻址模式双寻址模式,用户可以通过编程命令寄存器来控制 i²c 接口,让他们有更大的灵活性sda与scl是低电平有效的,所以我们应该在两根数据线上用电阻上拉,io内部也是开漏输出的,一般5v系统接4.7k上拉,3.3v系统接2.4k上拉即可。esp32上,sda默认连接gpio21,scl默认连接gpio22,当然,我们可以在代码中配置到任何引脚。
软件启动i2c
启动wire库并作为主机或者从机加入总线,这个函数调用一次即可,参数为7位从机地址,不带参数就以主机的形式加入总线。
wire.begin();wire.begin(address)主设备从从设备请求字节
由主设备向从设备请求字节,之后用available()和read()函数读取字节,第三个参数位为stop,在请求后会发送停止消息,释放i2c总线,否则总线就不会被释放。
wire.requestfrom(address, quantity);wire.requestfrom(address, quantity, stop);给指定地址的从设备传输数据
给指定地址的从设备传输数据,之后调用write()函数排队传输字节,要通过endtransmission()结束传输。
wire.begintransmission(address)endtransmission()有以下几个返回结果:
0:成功1:数据太长,无法放入发送缓冲区2:在发送地址时收到 nack3:在发送数据时收到 nack4:其他错误写数据
向从设备写入数据,在调用 begintransmission() 和 endtransmission() 之间。
wire.write(value)wire.write(string)wire.write(data, length)举个例子
#include byte val = 0;void setup(){ wire.begin(); // join i2c bus}void loop(){ wire.begintransmission(44); // transmit to device #44 (0x2c) // device address is specified in datasheet wire.write(val); // sends value byte wire.endtransmission(); // stop transmitting val++; // increment value if(val == 64) // if reached 64th position (max) { val = 0; // start over from lowest value } delay(500);}读数据
调用requestfrom()后从从设备读取数据。
wire.read()举个例子
#include void setup(){ wire.begin(); // join i2c bus (address optional for master) serial.begin(9600); // start serial for output}void loop(){ wire.requestfrom(2, 6); // request 6 bytes from slave device #2 while(wire.available()) // slave may send less than requested { char c = wire.read(); // receive a byte as character serial.print(c); // print the character } delay(500);}还有其它一些函数,例如修改时钟频率等等,大家用到的时候自行了解一下。
完整程序
这里我们用一个例子来演示一下,i2c启动之后,我们开始扫描总线上存在的设备,并通过串口打印结果出来,我在i2c下面接了一个oled的设备。
#include wire.hvoid setup(){ serial.begin(115200); serial.println(); serial.println(scanning for i2c devices ...); serial.print(rn); int i2cdevices = 0; byte address; wire.begin(); for (address = 1; address < 127; address++) { wire.begintransmission(address); if (wire.endtransmission() == 0) { serial.print(found i2c device: ); serial.print( (0x); if (address < 16) { serial.print(0); } serial.print(address, hex); serial.println()); i2cdevices++; } } if (i2cdevices == 0) { serial.println(没有发现i2c设备!n); } else { serial.print(发现了); serial.print(i2cdevices); serial.println(个i2c设备!n); } }void loop(){}wire.endtransmission()返回0,代表这个地址通信成功,我们就认为总线上存在这个地址的设备。
i2c oled
i2c只是个通信协议,具体的还是要结合实物来演示,比如一些传感器或者屏幕,这里我们用i2c协议的0.96寸oled屏幕来演示下:
oled使用ssd1306控制芯片,所以我们需要下载一个库ssd1306,另外还需要配合图形库gfx操作,代码中,我们先包含对应头文件,然后创建一个adafruit_ssd1306对象,第三个参数是用的i2c对象。
adafruit_ssd1306 display(screen_width, screen_height, &wire, -1);初始化时候用display.begin(ssd1306_switchcapvcc, 0x3c)初始化显示对象,传入地址,然后就可以自由简单的显示我们想要显示的数据了。
关于adafruit_gfx库,非常强大的一个图形库,我们后面单独讲解具体的原理,这里先了解一下即可。
完整程序
#include #include #include #define screen_width 128 // oled display width, in pixels#define screen_height 64 // oled display height, in pixelsadafruit_ssd1306 display(screen_width, screen_height, &wire, -1);void setup() { serial.begin(115200); if(!display.begin(ssd1306_switchcapvcc, 0x3c)) { serial.println(f(ssd1306 allocation failed)); for(;;); } delay(1000); display.display(); display.cleardisplay(); display.settextcolor(white); display.settextsize(1); display.setcursor(0,0); display.print(chiphome); display.display(); display.setcursor(0,8); display.print(12345678); display.display(); delay(1000);}void loop() {}ssd1306示例代码演示:
感谢大家,关于esp32的学习,希望大家enjoy!

接收机链路IIP3的计算方法
WVBP-833-WR12+81000-86000 MHz矩形波导带通滤波器
信道编码有哪几种 信道编码的作用及种类 信道编码的主要特点
5G工业网关赋能救护车远程监控,助力高效救援
天壤加入元脑生态,携手浪潮信息让企业大模型开发普适化
ESP32学习笔记:I2C
衢州季丰与TUV南德签约 帮助光伏产品制造商节约测试时间和成本
人工智能技术怎样实现智能写作
iPhone照片恢复软件-开心手机恢复大师来教你
360的扫地机器人卖这么便宜是不是在搅局?
超级计算机世界500强最新消息:再次世界领先!中国超级计算机赶超美国,神威·太湖之光勇夺第一
空气产品公司在中国财经峰会上连续第三年荣膺“杰出品牌形象奖”
Wi-Fi和家庭基站面面观
TD-LTE芯片多模多频 商用将从数据终端转向手机
中国电信将助力上海打造成为全球领先的5G+光网双千兆示范城市
物理隔离的切换方式
LED功率下降和ALS衰减怎么办_世强帮助突破设计障碍
骁龙865为什么外挂5G基带?可节约成本
高通推出全球业内最先进的Wi-Fi和蓝牙连接系统
基于Silicon Laboratories创建的太阳能无线传感器套件