C语言软件iic虚拟总线中间层设计详解
作者:MacRsh
简介
mr-soft-iic 模块为 mr-library 项目下的可裁剪模块,以C语言编写,可快速移植到各种平台(主要以嵌入式mcu为主)。 mr-soft-iic 模块通过 io 模拟实现 iic 协议。
IIC-协议
SPI 一般为一主多从设计。由2根线组成:CLK
(时钟)、SDA
(数据)。
接线方式
主机 | 从机 |
---|---|
CLK | CLK |
SDA | SDA |
主机从机一 一对应相接。
总线
IIC 通过地址码识别设备,一条 IIC 总线最多支持挂载127
个设备,通常速率为100kbit/s
和400kbit/s
,由于SDA
设置为开漏模式,因此双向通信时需外置上拉电阻。
工作本质
我们可以看到CLK
和SDA
都有一个上拉电阻,而CLK
和SDA
都为开漏(OUT-OD)下,所以只有一个下拉的MOS可以控制,因此只有下拉
和高阻
状态。当高阻
状态时线上电平因为上拉电阻的存在所以为VCC,而下拉
状态时,因为MOS对地,所以线上电平为GND。当主机主动控制时,就可以完成写入的操作,当需要读取时,只需置高阻,等待从机主动拉低,即可完成双向通信。
虚拟总线(中间层)设计
首先 IIC 总线的CLK
、SDA
这2条线是不会变动的,所以我们可以把这部分单独设计为iic-bus
,IIC总线需要知道当前有哪个设备拥有IIC总线的使用权,为了防止出现抢占还需要配置一个互斥锁。
struct mr_soft_iic_bus { void (*set_clk)(mr_uint8_t level); // 操作 CLK 的函数指针 void (*set_sda)(mr_uint8_t level); // 操作 SDA 的函数指针 mr_uint8_t (*get_sda)(void); // 读取 SDA 的函数指针 struct mr_soft_iic *owner; // 当前该总线的所有者 mr_uint8_t lock; // 互斥锁 };
IIC设备唯一独有的只有设备addr,所以我们把这部分定义为iic-device
。IIC设备还需要知道自己归属于哪条IIC总线。
struct mr_soft_iic { mr_uint8_t addr; // 设备地址 struct mr_soft_iic_bus *bus; // 该设备归属的总线 };
当创建了一条iic-bus
,一个iic-device
后我们需要一个挂载函数,即将iic-device
挂载到iic-bus
上
void mr_soft_iic_attach(struct mr_soft_iic *iic, struct mr_soft_iic_bus *iic_bus) { iic->bus = iic_bus; }
那么由于是虚拟总线设计,当我们要开始传输前需要先去获取总线。
mr_err_t mr_soft_iic_bus_take(struct mr_soft_iic *iic) { mr_uint8_t iic_bus_lock; mr_base_t level; /* check iic-bus owner */ if(iic->bus->owner != iic) { /* check mutex lock */ do { iic_bus_lock = iic->bus->lock; } while (iic_bus_lock != MR_UNLOCK); /* lock mutex lock */ iic->bus->lock = MR_LOCK; /* exchange iic-bus owner */ iic->bus->owner = iic; } else { /* lock mutex lock */ iic->bus->lock = MR_LOCK; } return MR_EOK; }
当我们使用完毕后需要释放总线
mr_err_t mr_soft_iic_bus_release(struct mr_soft_iic *iic) { /* check spi-bus owner */ if(iic->bus->owner == iic) { iic->bus->lock = MR_UNLOCK; return MR_EOK; } return -MR_ERROR; }
到此其实虚拟总线已经设计完毕,设备需要使用仅需通过挂载 、获取、释放 三步操作即可,其余操作交由中间层处理。 为调用接口的统一,设计iic-msg
struct mr_soft_iic_msg { mr_uint8_t read_write; // 读写模式:IIC_WR/ IIC_RD mr_uint8_t addr; // 写入/读取 地址 mr_uint8_t *buffer; // 数据地址 mr_size_t size; // 数据数量 };
然后通过transfer
函数统一调用接口。
mr_err_t mr_soft_iic_transfer(struct mr_soft_iic *iic, struct mr_soft_iic_msg msg) { mr_err_t ret; /* check msg */ if(msg.read_write > IIC_RD) return -MR_ERROR; if(msg.addr == MR_NULL) return -MR_EINVAL; if(msg.buffer == MR_NULL || msg.size == MR_NULL) return MR_EOK; /* take iic-bus */ ret = mr_soft_iic_bus_take(iic); if(ret != MR_EOK) return ret; /* send iic device and register address */ mr_soft_iic_bus_start(iic->bus); mr_soft_iic_bus_send(iic->bus, iic->addr << 1); mr_soft_iic_bus_send(iic->bus, msg.addr); if(msg.read_write == IIC_WR) { /* send iic start and device write cmd */ mr_soft_iic_bus_start(iic->bus); mr_soft_iic_bus_send(iic->bus, iic->addr << 1); /* send */ while(msg.size) { mr_soft_iic_bus_send(iic->bus,*msg.buffer); ++ msg.buffer; -- msg.size; } /* send iic stop */ mr_soft_iic_bus_stop(iic->bus); } else { /* send iic start and device write cmd */ mr_soft_iic_bus_start(iic->bus); mr_soft_iic_bus_send(iic->bus, iic->addr << 1 | 0x01); /* receive */ while(msg.size) { *msg.buffer = mr_soft_iic_bus_receive(iic->bus, (msg.size == 0)); ++ msg.buffer; -- msg.size; } /* send iic stop */ mr_soft_iic_bus_stop(iic->bus); } /* release iic-bus */ mr_soft_iic_bus_release(iic); return MR_EOK; }
使用示例
/* -------------------- 配置 -------------------- */ /* 创建一条 iic 总线 */ struct mr_soft_iic_bus iic_bus; /* 适配 iic 总线接口 */ void set_clk(mr_uint8_t level) { GPIO_WriteBit(GPIOA,GPIO_Pin_0,level); } void set_sda(mr_uint8_t level) { GPIO_WriteBit(GPIOA,GPIO_Pin_1,level); } mr_uint8_t get_sda(void) { return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1); } /* 配置 iic 总线 */ struct mr_soft_iic_bus iic_bus; iic_bus.set_clk = set_clk; iic_bus.set_sda = set_sda; iic_bus.get_sda = get_sda; iic_bus.lock = MR_UNLOCK; iic_bus.owner = MR_NULL; /* 创建一个 iic 设备 */ struct mr_soft_iic iic_device; /* 配置 iic 设备 */ iic_device.addr = 0x31; // iic 设备地址 /* -------------------- 使用 -------------------- */ int main(void) { /* 需要发送的数据 */ mr_uint8_t buffer[10]={0,1,2,3,4,5,6,7,8,9}; /* 初始化 gpio */ GPIO_InitTypeDef GPIO_InitStructure = {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* 挂载 iic 设备到 iic 总线 */ mr_soft_iic_attach(&iic_device,&iic_bus); /* 创建 iic 消息 */ struct mr_soft_iic_msg iic_msg; iic_msg.addr = 0x55; // 写入/读取 地址 iic_msg.buffer = buffer; // 数据地址 iic_msg.size = 10; // 数据数量 iic_msg.read_write = IIC_WR; // 只写模式 /* 发送消息 */ mr_soft_iic_transfer(&iic_device,iic_msg); }
剩余底层代码位于开源代码中,请下载开源代码。
开源代码仓库链接 gitee.com/chen-fanyi/…
路径:master/mr-library/ device / mr_soft_iic
请仔细阅读README.md !!!!!
以上就是C语言软件iic虚拟总线中间层设计详解的详细内容,更多关于C语言软件iic虚拟总线中间层的资料请关注脚本之家其它相关文章!