Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >MCU常见通信总线串讲(三)—— I2C总线协议

MCU常见通信总线串讲(三)—— I2C总线协议

作者头像
秋名山码神
发布于 2023-11-09 01:16:51
发布于 2023-11-09 01:16:51
1K00
代码可运行
举报
文章被收录于专栏:码神随笔码神随笔
运行总次数:0
代码可运行

🙌秋名山码民的主页 😂oi退役选手,Java、大数据、单片机、IoT均有所涉猎,热爱技术,技术无罪 🎉欢迎关注🔎点赞👍收藏⭐️留言📝

前言

首先明确一个概念,关于MCU中通信总线和通信协议,通信总线是一种用于连接各种外设和模块的物理接口,它可以传输数据和控制信息。通信协议则是指在通信总线上传输数据时所遵循的规则和约定,以确保不同设备之间能够正确地交换信息,我们也可以把他叫做通信总线协议。

系列文章,主要讲解以下几个总线协议,读者可以按需选择:

  1. UART和USART
  2. RS232、RS485总线
  3. IIC总线
  4. SPI总线
  5. CAN总线
  6. USB总线

一、I2C总线的概念

其实站在我个人开发的角度来说,I2C使用的比SPI多,他的主要概念如下:

I2C(Inter-Integrated Circuit)是一种串行通信协议,用于连接微控制器和外部设备,例如传感器、存储器芯片、显示屏等。

特点:

  1. I2C是一个支持设备的总线,多个设备共用的信号线,他支持多个主机或者多个从机
  2. 一个I2C总线只使用两条总线线路,一条双向串行数据线(SDA),一条串行时钟线(SCL)。数据线用来表示数据,时钟线用于同步数据的收发。
  3. I2C总线上的从机设备,都有一个单独的地址,主机通过这个地址来实现对不同设备的访问
  4. 总线通过上拉电阻接到电源。当I2C设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
  5. I2C还有一个仲裁模式,即当多个从机都传输数据的时候,为了防止数据冲突,会产生仲裁,决定哪个设备来使用总线
  6. 具有3种传输模式:标准模式传输速率为100kbps,快速模式为400kbps,高速模式可达3.4Mbps,但目前大多I2C设备尚不支持高速模式。
  7. 连接到相同总线的IC数量受到总线的最大电容400p F限制。

二、 I2C的协议层

I2C协议层规定了在I2C通信中需要遵循的一些基本规则和标准,其中包括以下内容:

  1. 物理层规定
    • 定义了I2C总线的物理结构、传输介质(通常是双绞线)、电气特性(如电压电平、上拉电阻)等。
    • 规定了起始条件(Start condition)和停止条件(Stop condition)的时序和电平要求。

通信的起始和停止:

起始(S)和停止(P)信号是两种特殊的状态,SCL线是高电平时SDA线从高电平向低电平切换,表示通信的起始。当SCL是高电平时SDA线由低电平向高电平切换,表示通信的停止

  1. 数据链路层规定
    • 定义了数据帧的格式,包括地址帧和数据帧的组成结构。
    • 规定了主设备和从设备之间的地址识别机制,包括7位或10位地址模式的规定。

I2C总线上的每个设备都有自己的独立地址,主机发起通信时,通过SDA信号线发送设备地址(SLAVE_ADDRESS)来查找从机。I2C协议规定设备地址可以是7位或10位,实际中7位的地址应用比较广泛

  1. 时序规定
    • 规定了时钟脉冲的频率、数据传输的时序要求,以确保通信的稳定性和可靠性。
    • 定义了如何进行数据的读取和写入,包括数据的传输顺序和速率的限制。

主机写数据到从机

主机读数据在从机

复和形式 读和写数据除了基本的读写,I2C通信更常用的是复合格式,该传输过程有两次起始信号(S)。一般在第1次传输中,主机通过SLAVE_ADDRESS寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与SLAVE_ADDRESS的区别);在第2次的传输中,对该地址的内容进行读或写。也就是说,第1次通信是告诉从机读写地址,第2次则是读写的实际内容。

  1. 错误处理规则
    • 规定了在通信过程中出现错误时的处理方式,例如发生冲突、丢失数据等情况应当如何处理。

SDA数据线在SCL的每个时钟周期传输一位数据。传输时,SCL为高电平的时候SDA表示的数据有效,即此时的SDA为高电平时表示数据“1”,为低电平时表示数据“0”。当SCL为低电平时,SDA的数据无效,一般在这个时候SDA进行电平切换,为下一次表示数据做好准备。

  1. 多点连接规定
    • 规定了多个从设备共享同一条总线时的地址分配和冲突解决机制。
  2. 高级功能规定
    • 例如10位地址模式、扩展寄存器等高级功能的使用规范。

有了这些规定,每个厂家生产出来支持I2C的东西也可实现互通

三、STM32 I2C实战

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "I2C.h"


#define SCL_H         GPIOB->BSRR = GPIO_Pin_8
#define SCL_L         GPIOB->BRR  = GPIO_Pin_8 

#define SDA_H         GPIOB->BSRR = GPIO_Pin_9
#define SDA_L         GPIOB->BRR  = GPIO_Pin_9 

#define SCL_read      GPIOB->IDR  & GPIO_Pin_8
#define SDA_read      GPIOB->IDR  & GPIO_Pin_9 

static void I2C_delay(void)
{
    volatile int i = 7;
    while (i)
        i--;
}

static bool I2C_Start(void)
{
    SDA_H;
    SCL_H;
    I2C_delay();
    if (!SDA_read)
        return false;
    SDA_L;
    I2C_delay();
    if (SDA_read)
        return false;
    SDA_L;
    I2C_delay();
    return true;
}

static void I2C_Stop(void)
{
    SCL_L;
    I2C_delay();
    SDA_L;
    I2C_delay();
    SCL_H;
    I2C_delay();
    SDA_H;
    I2C_delay();
}

static void I2C_Ack(void)
{
    SCL_L;
    I2C_delay();
    SDA_L;
    I2C_delay();
    SCL_H;
    I2C_delay();
    SCL_L;
    I2C_delay();
}

static void I2C_NoAck(void)
{
    SCL_L;
    I2C_delay();
    SDA_H;
    I2C_delay();
    SCL_H;
    I2C_delay();
    SCL_L;
    I2C_delay();
}

static bool I2C_WaitAck(void)
{
    SCL_L;
    I2C_delay();
    SDA_H;
    I2C_delay();
    SCL_H;
    I2C_delay();
    if (SDA_read) {
        SCL_L;
        return false;
    }
    SCL_L;
    return true;
}

static void I2C_SendByte(uint8_t byte)
{
    uint8_t i = 8;
    while (i--) {
        SCL_L;
        I2C_delay();
        if (byte & 0x80)
            SDA_H;
        else
            SDA_L;
        byte <<= 1;
        I2C_delay();
        SCL_H;
        I2C_delay();
    }
    SCL_L;
}

static uint8_t I2C_ReceiveByte(void)
{
    uint8_t i = 8;
    uint8_t byte = 0;

    SDA_H;
    while (i--) {
        byte <<= 1;
        SCL_L;
        I2C_delay();
        SCL_H;
        I2C_delay();
        if (SDA_read) {
            byte |= 0x01;
        }
    }
    SCL_L;
    return byte;
}

void i2cInit(void)
{
    GPIO_InitTypeDef gpio;
	//已更改
	  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);    
    gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
    gpio.GPIO_Speed = GPIO_Speed_2MHz;
    gpio.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_Init(GPIOB, &gpio);

}

bool i2cWriteBuffer(uint8_t addr, uint8_t reg, uint8_t len, uint8_t * data)
{
    int i;
    if (!I2C_Start())
        return false;
    I2C_SendByte(addr << 1 | I2C_Direction_Transmitter);
    if (!I2C_WaitAck()) {
        I2C_Stop();
        return false;
    }
    I2C_SendByte(reg);
    I2C_WaitAck();
    for (i = 0; i < len; i++) {
        I2C_SendByte(data[i]);
        if (!I2C_WaitAck()) {
            I2C_Stop();
            return false;
        }
    }
    I2C_Stop();
    return true;
}
/
int8_t i2cwrite(uint8_t addr, uint8_t reg, uint8_t len, uint8_t * data)
{
	if(i2cWriteBuffer(addr,reg,len,data))
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
	//return FALSE;
}
int8_t i2cread(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf)
{
	if(i2cRead(addr,reg,len,buf))
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
	//return FALSE;
}
//
bool i2cWrite(uint8_t addr, uint8_t reg, uint8_t data)
{
    if (!I2C_Start())
        return false;
    I2C_SendByte(addr << 1 | I2C_Direction_Transmitter);
    if (!I2C_WaitAck()) {
        I2C_Stop();
        return false;
    }
    I2C_SendByte(reg);
    I2C_WaitAck();
    I2C_SendByte(data);
    I2C_WaitAck();
    I2C_Stop();
    return true;
}

bool i2cRead(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf)
{
    if (!I2C_Start())
        return false;
    I2C_SendByte(addr << 1 | I2C_Direction_Transmitter);
    if (!I2C_WaitAck()) {
        I2C_Stop();
        return false;
    }
    I2C_SendByte(reg);
    I2C_WaitAck();
    I2C_Start();
    I2C_SendByte(addr << 1 | I2C_Direction_Receiver);
    I2C_WaitAck();
    while (len) {
        *buf = I2C_ReceiveByte();
        if (len == 1)
            I2C_NoAck();
        else
            I2C_Ack();
        buf++;
        len--;
    }
    I2C_Stop();
    return true;
}

uint16_t i2cGetErrorCounter(void)
{
    // TODO maybe fix this, but since this is test code, doesn't matter.
    return 0;
}

最后

如果本文对你有所帮助,还请三连支持一下博主!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-11-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【K8s】kubeadm 安装 k8s 集群
本篇文章主要是通过 VMware 来创建虚拟机,在虚拟机上通过 kubeadm 安装 k8s 集群;
Librant
2025/01/13
3250
containerd快速安装指南🚀
本指南旨在提供一个简洁有效的方法来安装containerd。我们将通过一份易于理解的脚本步骤,指导您完成安装🔧。请根据您的实际需求,适当调整containerd版本及其相关依赖。
GousterCloud
2024/03/29
3110
containerd快速安装指南🚀
谁再说不会 K8S 高可用部署,就把这个给他甩过去!
文档:https://kubernetes.io/zh-cn/docs/home/
民工哥
2022/10/27
1.5K0
谁再说不会 K8S 高可用部署,就把这个给他甩过去!
【玩转腾讯云】腾讯云部署K8s集群
如果想要了解 K8s 的一些特性,并且将其应运的很好,那就需要动手部署一个 K8s 集群。下面讲解下在腾讯云上 K8s 集群部署流程。
程序猿Damon
2021/05/06
9.7K1
【玩转腾讯云】腾讯云部署K8s集群
containerd配置HTTP私仓
**🔍 浏览器中输入:http://192.168.11.20,即可访问刚才部署的harbor**
GousterCloud
2024/04/01
8210
containerd配置HTTP私仓
Ubuntu 23.10.1 上安装 Kubernetes 1.28.2
要ping一下自己的机器名,如果返回127.0.0.1,请到 /etc/hosts修改成实际的IP地址
NeXT80
2022/07/26
6K3
Ubuntu 23.10.1 上安装 Kubernetes 1.28.2
基于Containerd部署Kubernetes
当Kubernetes 1.20开始准备弃用Docker,相信很多人在k8s 1.20版本出现的时候,都听说了即将弃用docker,不过还没有完全弃用,但这也是未来的趋势了。k8s的底层还是容器。
互联网-小阿宇
2022/11/21
8460
kubeadm 安装kubernetes
-–image-repository:由于默认拉取镜像地址k8s.gcr.io国内无法访问,这里指定阿里云镜像仓库地址
以谁为师
2023/09/12
3560
kubeadm 安装kubernetes
使用kubeadm部署高可用IPV4/IPV6集群-V1.32
https://github.com/cby-chen/Kubernetes 开源不易,帮忙点个star,谢谢了
小陈运维
2025/03/24
1480
使用kubeadm部署高可用IPV4/IPV6集群-V1.32
使用kubeadm部署高可用IPV4/IPV6集群
https://github.com/cby-chen/Kubernetes 开源不易,帮忙点个star,谢谢了
小陈运维
2024/05/05
3650
使用kubeadm初始化IPV4/IPV6集群
使用kubeadm初始化IPV4/IPV6集群 图片 CentOS 配置YUM源 cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=kubernetes baseurl=https://mirrors.ustc.edu.cn/kubernetes/yum/repos/kubernetes-el7-$basearch enabled=1 EOF setenforce 0 yum install -y kubelet kubeadm
小陈运维
2022/08/19
2540
使用kubeadm初始化IPV4/IPV6集群
4.最新实践基于Containerd安装部署高可用Kubernetes集群
[TOC] 0x00 前言简述 描述: 在我博客以及前面的文章之中讲解Kubernetes相关集群环境的搭建, 随着K8S及其相关组件的迭代, 与读者当前接触的版本有所不同,所以在当前【2022年4月26日 10:08:29】时间节点,博主使用ubuntu 20.04 、haproxy、keepalive、containerd、etcd、kubeadm、kubectl 等相关工具插件【最新或者稳定的版本】进行实践高可用的kubernetes集群的搭建,这里不再对k8s等相关基础知识做介绍,如有新入门的童鞋
全栈工程师修炼指南
2022/09/29
4K0
4.最新实践基于Containerd安装部署高可用Kubernetes集群
使用kubeadm部署高可用IPV4/IPV6集群-v1.32
https://github.com/cby-chen/Kubernetes 开源不易,帮忙点个star,谢谢了
小陈运维
2025/03/23
1310
部署kubernetes-v1.25.3(k8s)- 基于containerd容器运行时
<p align="center" ><font color="EA5607" ><b>大家好,我是秋意临。<b></font></p>
秋意零
2022/11/15
2.5K0
2.基于Containerd运行时搭建Kubernetes集群实践
本章主要讲述,使用kubeadm进行安装配置K8S集群,并指定使用containerd作为容器运行时的具体安装步骤,以及尽可能在案例中加入k8s集群常用组件及其操作配置。
全栈工程师修炼指南
2021/07/25
4.4K0
2.基于Containerd运行时搭建Kubernetes集群实践
kubeadm部署kubernetes集群
(3) 生成Kube Config文件,kubelet需要用这个文件与Master通信。
星哥玩云
2022/07/28
3470
搭建Kubernetes集群基于calico网络插件
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/101822.html原文链接:
全栈程序员站长
2021/06/10
1.1K0
搭建Kubernetes集群基于calico网络插件
Install kubernetes v1.24.4 on centos 7.9
确保 br_netfilter 模块被加载。这一操作可以通过运行 lsmod | grep br_netfilter 来完成。若要显式加载该模块,可执行 sudo modprobe br_netfilter。
不会Coding的一休哥
2022/09/06
6260
使用 Kubespray 部署 Kubernetes 集群
本文介绍了如何使用 Kubespray 在本地开发测试部署 Kubernetes 集群及其注意事项。
CNCF
2021/05/27
2.3K0
搭建Kubernetes集群基于calico网络插件
实验环境 IP 备注 系统 192.168.1.10 master centos7 192.168.1.20 node1 centos7 192.168.1.30 node2 centos7 版本信息 docker 18.09.0 k8s v1.20.2 更改主机名 master [root@localhost ~]# hostname master [root@localhost ~]# bash [root@master ~]# node1 [root@localhost ~]# hostname n
互联网-小阿宇
2022/11/21
4120
搭建Kubernetes集群基于calico网络插件
推荐阅读
相关推荐
【K8s】kubeadm 安装 k8s 集群
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验