I2C从机的工作机制
I2C协议中从机设备确实需要等待主机发起通信。主机控制时钟线(SCL)和数据线(SDA),从机只能在主机允许的时序内响应。从机无法主动发起通信,必须由主机发送地址帧并匹配从机地址后,才能进行数据传输。
主机在任何时刻都可以通过发送停止条件(STOP condition)终止当前通信。停止条件由SCL高电平时SDA的上升沿构成,从机必须立即释放总线。
部分代码解释
Wire.onRequest(I2C_write); // 主机请求数据时调用括号内的函数一次
- 在I2C_write要把所有要发送的数据发送出去,但是
Wire
库内部定义默认缓冲区只有32个,如果需要更多缓冲区需要去他的头文件里修改 - 在 I2C 通信中,ESP32 的 Wire 库会在主机发送 STOP 信号后自动处理缓冲区,无需手动清除发送缓冲区。
Wire.onReceive(I2C_read); // 主机发送数据时调用调用括号内的函数一次
- 同理只会在接收后调用一次,所以要把所有数据读完
类似EEPROM的协议
- 该协议采用类似 EEPROM 的通信方式,格式固定:首先接收一个字节作为寄存器地址,然后根据操作类型进行数据读写。
既然I2C_read()只调用一次为什么不能在函数末尾清除地址和接收标志位,要单独开一个空闲函数
- 部分单片机的库是会在发送完成第一个地址后发送结束信号的,目的为了兼容更多单片机
- 发送数据也需要等待接收完成后的停止信号才发送数据
代码
注意事项
- 每次传输后需要等待10ms以上才能在次传输,因为需要空闲去清除地址标志位
- 缓冲区大小默认是32字节,需要自己去库文件修改
- 使用方法只需要在void setup() 里调用I2C_int()一次即可
MY_I2C.cpp文件
#include "MY_I2C.h"
TaskHandle_t I2C;
uint32_t uwTick=0;
uint8_t ram[256];
struct I2C i2c={.offset=0,.first_byte_state=1};
void I2C_int()
{
// 注册 I2C 请求和接收函数
Wire.onRequest(I2C_write); // 主机请求数据时调用
Wire.onReceive(I2C_read); // 主机发送数据时调用
Wire.begin((uint8_t)SLAVE_ADDR,5,6,100000);//地址,sda,scl,频率hz
xTaskCreatePinnedToCore( I2C_task, //任务函数名
"I2C_task", //任务标识
4096, //任务堆栈大小
NULL,
1, //任务优先级
&I2C,
1
);
}
void I2C_read(int bytesReceived)
{
while(Wire.available())//缓冲区里还有多少数据
{
if(i2c.first_byte_state==0)//按地址接收
{
ram[i2c.offset++]=Wire.read();
}
if(i2c.first_byte_state==1)//第一次读地址
{
i2c.offset=Wire.read();
i2c.first_byte_state=0;
}
}
}
void I2C_write()
{
if(i2c.first_byte_state==0)//按地址发送
{
uint8_t i = i2c.offset;
while (i < sizeof(ram) && Wire.availableForWrite() > 0)//发送剩余的数据&&缓冲区还有空间
Wire.write(ram[i++]);
}
}
void I2C_task(void *pvParameters)
{
while(1)
{
if(i2c.first_byte_state==1)
{
i2c.tick=millis();
}
else
{
if(millis()-i2c.tick>10)//空闲10m清空标志位
{
i2c.first_byte_state=1;
i2c.offset=0;
}
}
vTaskDelay( 1);
}
vTaskDelete(NULL);
}
MY-I2C.h文件
#ifndef _MY_I2C_H_
#define _MY_I2C_H_
#include <Wire.h>
#include <Arduino.h>
#define SLAVE_ADDR 0x42 // 从机 I2C 地址
extern uint32_t uwTick;
extern uint8_t ram[256];
struct I2C {
uint8_t offset;//地址
uint8_t first_byte_state;//开始标志位
uint32_t tick;
};
void I2C_int();//初始化
void I2C_read(int bytesReceived);
void I2C_write();
void I2C_task(void *pvParameters);
#endif
博主也是看gpt和互联网的一些资料,如有错误请见谅