C语言软件iic虚拟总线中间层设计详解

目录
  • 简介
  • IIC-协议
    • 接线方式
    • 总线
    • 工作本质
  • 虚拟总线(中间层)设计
  • 使用示例

简介

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/s400kbit/s,由于SDA设置为开漏模式,因此双向通信时需外置上拉电阻。

工作本质

我们可以看到CLKSDA都有一个上拉电阻,而CLKSDA都为开漏(OUT-OD)下,所以只有一个下拉的MOS可以控制,因此只有下拉高阻状态。当高阻状态时线上电平因为上拉电阻的存在所以为VCC,而下拉状态时,因为MOS对地,所以线上电平为GND。当主机主动控制时,就可以完成写入的操作,当需要读取时,只需置高阻,等待从机主动拉低,即可完成双向通信。

虚拟总线(中间层)设计

首先 IIC 总线的CLKSDA 这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虚拟总线中间层的资料请关注我们其它相关文章!

(0)

相关推荐

  • C语言中的字符型数据与ASCII码表

    目录 1.字符型常量 2.字符型变量 3.字符型数据的输入输出 (1)scanf()和printf()函数输入输出字符 (2)字符输入函数getchar() 总结 1.字符型常量 字符型常量指单个字符,是用一对单引号及其所括起来的字符表示. 例如:‘A’.‘a’.‘0’.’$‘等都是字符型常量. C语言的字符使用的就是ASCII字符集,总共有128个,每个相应的ASCII码都表示一个字符: (1)每一个字符都有唯一的次序值,即ASCII码. (2)数字’0’,‘1’,‘2’,…,‘9’.大写字母

  • C语言利用goto语句设计实现一个关机程序

    目录 前言 一.什么是goto语句 二.goto语句的作用是什么 三.goto语句的缺点 四.goto语句的结构与用法 五.goto语句的巧用实例——关机小程序 总结撒花 前言 goto语句其实在平常中我们 除了学习分支语句和循环语句时,介绍循环语句时,才会知道有goto语句这个用法,那读者可能会问:我们还有学习的必要吗? 答案是显而易见的,正如黑格尔所说的:存在即合理!既然存在,就会有存在的必要!虽然我们现在不会遇到且用到 ,当在搞Linux硬件驱动等的时候,其内核含有较多的goto语句,如果

  • 通俗易懂的C语言快速排序和归并排序的时间复杂度分析

    目录 快速排序和归并排序的时间复杂度分析——通俗易懂 归并排序的时间复杂度分析 快速排序的时间复杂度 快速排序的最坏情况O(n^2) 总结 快速排序和归并排序的时间复杂度分析——通俗易懂 今天面试的时候,被问到归并排序的时间复杂度,这个大家都知道是O(nlogn),但是面试官又继续问,怎么推导出来的.这我就有点懵了,因为之前确实没有去真正理解这个时间复杂度是如何得出的,于是就随便答了一波(理解了之后,发现面试的时候答错了......). 归并排序和快速排序,是算法中,非常重要的两个知识点,同时也

  • C语言嵌入式实现支持浮点输出的printf示例详解

    目录 简介 背景 C语言可变参数函数 踩坑 功能实现 简介 mr-printf 模块为 mr-library 项目下的可裁剪模块,以C语言编写,可快速移植到各种平台(主要以嵌入式mcu为主). mr-printf 模块用以替代 libc 中 printf, 可在较小资源占用的同时支持绝大部分 printf 功能,于此同时还支持对单独功能模块的裁剪以减少用户不需要功能的资源占用. 背景 printf 大家应该使用的比较多,但是在嵌入式平台中,尤其是单片机中,libc中的printf对内存的占用较高

  • C语言实现求解素数的N种方法总结

    目录 前言 必备小知识 C语言详解<试除法>求解素数 试除法境界1 试除法境界2 试除法境界3 试除法境界4 C语言详解<筛选法>求解素数 筛选法境界5 前言 哈喽各位友友们,我今天又学到了很多有趣的知识,现在迫不及待的想和大家分享一下!我仅已此文,手把手带领大家探讨利用试除法.筛选法求解素数的n层境界!都是精华内容,可不要错过哟!!! 必备小知识 质数又称素数.一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数:否则称为合数(规定1既不是质数也不是合数).这里

  • C语言实现三子棋小游戏全程详解

    目录 前言 思想提升及重要代码片段 三子棋代码 前言 授人以鱼不如授人以渔,这篇文章重点从思想上的角度来写,助你举一反三,在写代码时不再害怕,真的很想对你有帮助呀,代码放在文章最后啦 思想提升及重要代码片段 为什么要拆成三个文件写:1.便于复用代码.重复的功能只要写一遍就可以了,下次要用在其它程序上时只要更改很小的部分或者可以不用更改.2.便于多人协作.在设计软件之初就可以很清楚地分配各个开发部门的任务.模块的编写者本身只要关注他所写的东西,清楚这一部分的功能,留出接口就可以了.另外,对于整个工

  • C语言实现的一个三子棋游戏详解流程

    目录 前言 一.三子棋完成程序运行结果 二.三子棋代码实现 1.创建源文件与头文件 2.整体页面的制作 3.制作并打印棋盘 1.在test.c文件中,定义函数game(); 2.在game.h 头文件中 3.在game.c源文件中 4.人机互动下棋 1.在test.c源文件中 2.在game.h头文件中 3.在game.c源文件中 4.此时打印效果 5.判断输赢 1.在test.c源文件中 2.在game.h头文件中 3.在game.c源文件中 4.最终实现结果 最后 前言 三子棋是我们先前所学

  • R语言学习ggplot2绘制统计图形包全面详解

    目录 一.序 二.ggplot2是什么? 三.ggplot2能画出什么样的图? 四.组装机器 五.设计图纸 六.机器的零件 1. 零件--散点图 1) 变换颜色 2) 拟合曲线 3) 变换大小 4) 修改透明度 5) 分层 6) 改中文 2. 零件--直方图与条形图 1) 直方图 2) 润色 3) 条形图 3. 零件--饼图 4. 零件--箱线图 5. 零件--小提琴图 6. 零件打磨 7. 超级变变变 8. 其他常用零件 七.实践出真知 八.学习资源 九.参考资料 一.序 作为一枚统计专业的学

  • Go语言基础go fmt命令使用示例详解

    go fmt 命令主要是用来帮你格式化所写好的代码文件[很多第三方集成软件都是使用了go fmt命令] 一.使用: go fmt <文件名>.go 使用go fmt命令,更多时候是用gofmt,而且需要参数 -w,否则格式化结果不会写入文件.gofmt -w src,可以格式化整个项目. 二.参数介绍 -l 显示那些需要格式化的文件 -w 把改写后的内容直接写入到文件中,而不是作为结果打印到标准输出. -r 添加形如"a[b:len(a)] -> a[b:]"的重写规

  • C语言递归思想实现汉诺塔详解

    目录 1.递归思想简介 2.汉诺塔问题 3.汉诺塔递归的c语言实现 总结 1.递归思想简介 在c语言中,程序调用自身的编程技巧称为递归( recursion). 递归的定义看上去似乎很抽象,使用代码描述能够让人容易理解,下面是一个函数递归的例子. /* 递归求n的阶乘 */ int factorial(int n) //定义一个求阶乘的函数叫做factorial(),需要一个整形参数,返回一个整形值 { if (n <= 1) //递归结束的条件 { return 1; } else { ret

  • go语言context包功能及操作使用详解

    目录 Context包到底是干嘛用的? context原理 什么时候应该使用 Context? 如何创建 Context? 主协程通知有子协程,子协程又有多个子协程 context核心接口 emptyCtx结构体 Backgroud TODO valueCtx结构体 WithValue向context添加值 Value向context取值 示例 WithCancel可取消的context cancelCtx结构体 WithDeadline-超时取消context WithTimeout-超时取消

  • Go语言中循环语句使用的示例详解

    目录 一.概述 1. 循环控制语句 2. 无限循环 二.Go 语言 for 循环 1. 语法 2. for语句执行过程 3. 示例 4. For-each range 循环 三.循环嵌套 1. 语法 2. 示例 四.break 语句 1. 语法 2. 示例 五. continue 语句 1. 语法 2. 示例 六.goto 语句 1. 语法 2. 示例 一.概述 在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句. 循环程序的流程图: Go 语言提供了以下几种类型循环

  • C语言中进程间通讯的方式详解

    目录 一.无名管道 1.1无名管道的原理 1.2功能 1.3无名管道通信特点 1.4无名管道的实例 二.有名管道 2.1有名管道的原理 2.2有名管道的特点 2.3有名管道实例 三.信号 3.1信号的概念 3.2发送信号的函数 3.3常用的信号 3.4实例 四.IPC进程间通信 4.1IPC进程间通信的种类 4.2查看IPC进程间通信的命令 4.3消息队列 4.4共享内存 4.5信号灯集合 一.无名管道 1.1无名管道的原理 无名管道只能用于亲缘间进程的通信,无名管道的大小是64K.无名管道是内

  • Vue3 如何通过虚拟DOM更新页面详解

    目录 引言 Vue 虚拟 DOM 执行流程 DOM 的创建 patch 函数 patchElement 函数 节点自身属性的更新 子元素的更新 patchChildren 位运算 为什么位运算性能更好 如何运用位运算 最长递增子系列 贪心 + 二分 引言 上一讲我们主要介绍了 Vue 项目的首次渲染流程,在 mountComponent 中注册了effect 函数,这样,在组件数据有更新的时候,就会通知到组件的 update 方法进行更新 Vue 中组件更新的方式也是使用了响应式 + 虚拟 DO

  • Go语言defer的一些神奇规则示例详解

    目录 测试题 分析 规则一当defer被声明时,其参数就会被实时解析 规则二 defer可能操作主函数的具名返回值 规则三 延迟函数执行按后进先出顺序执行 坑实例 测试题 defer有一些规则,如果不了解,代码实现的最终结果会与预期不一致.对于这些规则,你了解吗? 这是关于defer使用的代码,可以先考虑一下返回值. package main import ( "fmt" ) /** * @Author: Jason Pang * @Description: 快照 */ func de

随机推荐