I2C

1. I2C基本规则

① start:scl = high,sda ↑

② stop:scl = high,sda↓

③ bit 1:scl = high,sda = 1

④ bit 0:scl = high,sda = 0

总之:scl为高,sda才有效,可以保持电平或者上升下降;scl为低,sda可以进行变化;

 

2. ACK

收到8bit数据之后,从机会立刻把SDA置位0;

主机写完8bit之后,先把scl调低,然后才可以把sda拉高 —— 由于此时从机早已把sda拉低,所以波形图上看不到主机把sda拉高,如下图0x78的ACK:

之后,主机会在下一个scl为high的时候读到低电平,表示收到ACK。

下图中从机没有对0x7A发ACK,可以看到0x7A的sda拉高:

                                                                  i2c.scan()图示

再附一个容易理解的i2c图示:

 

 

3. I2C读写规则

① i2c写一个字节:start + { 8 bit + wait_ack } + stop

② i2c写多个字节:start + { 8 bit + wait_ack } + { 8 bit + wait_ack } + stop

一般情况下写的时候需要指明slv_add和reg_addr,所以多采用第二种写两个字节的方式:

start + { slv_addr + wait_ack } + { reg_addr + wait_ack } + stop

③ i2c读多个字节:start + { slv_addr + wait_ack } + { reg_addr + wait_ack } + { (slv_addr+1) + wait_ack } + read_byte + ... + stop

这里的(slv_addr+1)指的是地址最后一位置1表示读。

注意SCCB与I2C的规则几乎完全一样,除了读字节,SCCB是在写完从机地址和寄存器地址之后,要stop并restart的:

start + { slv_addr + wait_ack } + { reg_addr + wait_ack } + stop + start + { (slv_addr+1) + wait_ack } + read_byte + ... + stop

 

 

4. PICO的I2C与SCCB

PICO上支持的I2C的操作:

 1 from machine import I2C
 2 
 3 i2c = I2C(freq=400000)          # create I2C peripheral at frequency of 400kHz
 4                                 # depending on the port, extra parameters may be required
 5                                 # to select the peripheral and/or pins to use
 6 
 7 i2c.scan()                      # scan for peripherals, returning a list of 7-bit addresses
 8 
 9 i2c.writeto(42, b'123')         # write 3 bytes to peripheral with 7-bit address 42
10 i2c.readfrom(42, 4)             # read 4 bytes from peripheral with 7-bit address 42
11 
12 i2c.readfrom_mem(42, 8, 3)      # read 3 bytes from memory of peripheral 42,
13                                 #   starting at memory-address 8 in the peripheral
14 i2c.writeto_mem(42, 2, b'\x10') # write 1 byte to memory of peripheral 42
15                                 #   starting at address 2 in the peripheral

① i2c.scan()表示依次遍历地址,收到ACK表示扫描到地址,得到的是I2C规定的7bit地址!(可以参考4. I2C扫描出的地址“错误”);

② i2c.writeto(slv_addr, bytearray([data0, data1, ...])) 直接写;这里没有指定寄存器,假如你把data0设定为寄存器地址,也可以用来往寄存器里面写内容

③ i2c.writeto_mem(slv_addr, reg_addr, bytearray(data0, data1, ...))

④ i2c.readfrom(slv_addr, read_byte_num) 直接读,没有办法指定寄存器

⑤ i2c.readfrom_mem(slv_addr, reg_addr, num) 指定寄存器的读,这个会经常用到

 

关于SCCB读写OV7670的最后一个坑,在这里提前说下:

使用readfrom_mem()无法读取正确的寄存器内容,找了很久才发现SCCB和I2C的读法不一样!

正准备手动实现SCCB的读,重新审视了PICO I2C的函数,有了灵感:

1 def sccb.readfrom_mem(slv_addr, reg_addr):
2     i2c.writeto(slv_addr, reg_addr)  # start + slv_addr + reg_addr +stop
3     return i2c.readfrom(slv_addr, 1)        # start + (slv_addr+1) + read_one_byte

不就是中间多一个stop + start吗?!

 

 

5. I2C扫描出的地址“错误”

看代码中I2C地址是0x78H,结果扫描出来是0x3c —— 没有注意到它们是两倍关系。

I2C.scan()扫描出来的确实是地址,只有7bit,比如0x3c:0111100XB;

写入数据的时候最后一位填0或1,分别表示读写,所以代码上面才会有0x78H。

 

 

6. 摄像头的I2C设备总是扫描不到?

这是我遇到的第一个问题,我以为I2C不通,所以买了OLED096用来测试I2C通路。

 

使用SoftI2C调通OLED096之后,同样的代码再来调试OV7670还是不通;

经过查询得知OV7670需要MCLK提供clock才可以正常I2C通信,于是PWM安排上,不通。

 

买了一个便宜的逻辑分析仪,看到现象:

OLED096在哪个PIN都可以被I2C识别,同样的代码换到OV7670却不可以,SDA和SCL巍然不动。

 

想要使用手动实现以下I2C,实现之前脑子一热:再试一下PICOW的硬件I2C吧,结果竟然读出来OV7670的slave addr?!

焕然大悟:这应该是硬件差别,也就是上拉电阻的区别!

 

 

7. I2C与上拉电阻

I2C通信的时候,master和slave的SDA与SCL连接后,至少有一个设备要通过上拉电阻连接至高电平

PICOW的硬件I2C:把SCL和SDA都上拉至高电平 —— 可以接任何设备;

软件I2C:把SCL上拉高电平,SDA则只是设置为开漏 —— 只能接上拉至高电平的I2C设备;

 

 

OLED的I2C带有上拉电阻,所以使用任意PIN的软件I2C均可以;

OV7670的I2C没有通过上拉电阻接到高电平,与软件I2C连接的时候,高电平其实就是两个开路,当然无法通信了。

 关于开漏输出和推挽输出,这个文档写的很清晰易懂:https://zhuanlan.zhihu.com/p/41942876

 

 

8. 之前为什么要使用软件I2C?

硬件I2C无法提供I2C.start()和I2C.stop()等操作!软件I2C可调用的接口更多。

这其实也是一个坑:PICOW自带的I2C直接调用writeto()和readfrom()就是完整的读和写,不需要自己多余start和stop!

 自己写I2C的时候才需要实现start()和stop() —— 假如我一开始就有逻辑分析仪,就不会走这个弯路了。

posted @ 2022-11-19 12:04  moonのsun  阅读(500)  评论(0)    收藏  举报