1. 综述

  I2C(IIC,Inter-Integrated Circuit),两线式串行总线,由PHILIPS公司开发用于连接微控制器及其外围设备。

  它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU和被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps以上。但在STM8中,400kHZ已经是最快速度了。

2.关于STM8S103手册的I2C简介

芯片手册中只对I2C的特点进行了简单的讲解,但并未深入解析其中的过程。

3. I2C详细解析

  I2C总共由五个核心函数,分别为:①起始信号②停止信号③应答信号④发送数据⑤接收数据,通过这五个核心基本函数就能于大多数的传感进行通信了。

3.1 起始信号

  当SCL为高电平期间,SDA由高电平到低电平的跳变过程;起始信号是一种电平跳变时序信号,而不是一个电平信号,如图虚线框所示。

3.2 停止信号

  当SCL为高电平期间,SDA由低电平到高电平的跳变过程;停止信号也是一种电平跳变时序信号,而不是一个电平信号,如图虚线框所示。

 3.3 应答信号

  I2C的数据字节定义为8位长,对于发送端每发送1个字节后,需要将数据线(SDA)释放,由接收端反馈一个应答信号(ACK)。应答信号为低电平时,则将其规定为有效信号(ACK简称应答位),表示接收端已经成功接收了该字节;应答位为高电平时,规定为非应答位(NACK),一般表示接收端没有成功接收该字节。

  对于反馈有效应答位ACK的要求是,接收端在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收端是主机,则在它接收到最后一个字节后,发送一个NACK信号,以通知发送端结束数据发送,并释放SDA线,以便主机接收端发送一个停止信号。

3.4 发送数据

  在发送起始信号后开始通信,主机发送一个8位数据。然后,主机释放SDA线并等待从从机发出得确认信号(ACK)。详细过程请看4.3.7代码示例。

3.5 接收数据

  在发送起始信号后开始通信,主机发送一个8位数据。然后,从机收到数据返回一个确认信号(ACK)给主机,这时候主机才开始接收数据,待主机接收数据完成后,发送一个NACK信号给从机,以通知接收端结束数据接收。详细过程请看4.3.8代码示例。

3.6 数据有效性

  I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

3.7 I2C通信总过程

4. 例程

4.1 编译环境:

  我的编译环境是IAR,这款软件是现在STM8的主流平台,比较推荐。不过我打算等到STCubeMX更新出比较方便的版本后再去使用Keil5,因为我在用STM32的时候就是利用Keil5,的确很方便,你们也可以学着用一下。

4.2 主芯片:

  我的主芯片是STM8S系列中的103,其中STM8S的003、005、和103、105,配置一样(外设和CPU频率,FLASH),在代码相同的情况下均可进行烧写。

4.3 代码&解析

  I2C的基本函数代码我已经和传感的代码区隔开来,可以移植,几乎适用于市面上使用I2C驱动的传感器。

4.3.1 SDA、SCL引角初始化

   //IIC引脚
GPIO_Init(IIC_SCL_GPIO_Port, IIC_SCL_Pin, GPIO_MODE_OUT_PP_HIGH_FAST);
GPIO_Init(IIC_SDA_GPIO_Port, IIC_SDA_Pin, GPIO_MODE_OUT_PP_HIGH_FAST);

  在引角的控制上面,我选择了直接操作GPIO的寄存器,这样操作比较快,虽然我们感觉不出来,但是省出来的时间越来越多了,也就能够体现出这样写的好处了,不过不理解怎么用的话,也可以使用库函数进行写高、低电平。

  我在SDA引角初始化的时候,选择了推挽输出_高电平_高速,这里就有人会有疑问了,SDA是会进行接收ACK信号的,需要接收即为输入模式,怎么这里改成输出模式,看过我STM8_GPIO介绍的博客的小伙伴应该会想到,怎么不使用开漏输出,这个模式既能接收也能发送。没错,开漏输出模式的确可以,但我在那篇博客中也有说到,开漏输出模式不稳定,通过示波器观察到是斜三角的,而推挽输出是完整的矩形。图我就懒得去弄了 -。-  ,而如何解决推挽输出能够接收ACK的操作看我下一小节。

4.3.1 I2C结构体和引角配置

  这里的结构体是方便I2C多线程,以后需要用到多个I2C接口时候,只需要再定义多一个该结构变量,赋予其他引角便可,省去了再次编写代码的时间和空间。

  我在26和26行编写了两行代码,分别是将SDA模式改成输出和输入模式,直接更改寄存器里的值就能完成实现模式的更换,想知道为什么这样写可以改变模式的话,可以自行百度,也可以察看相对应芯片的寄存器手册。STM8S103中的则在6.2小节中就有介绍。因为讲解起来比较麻烦,这里就不进行更深入的说明了。

 /* Struct --------------------------------------------------------------------*/

 typedef struct iic
{
//具体信息:引脚 读写判定
GPIO_TypeDef * pSCL_Port; //SCL Gpio
uint8_t uSCL_Pin; //SCL Pin
GPIO_TypeDef * pSDA_Port; //SDA Gpio
uint8_t uSDA_Pin; //SDA Pin uint8_t uSDA_Mode_Pin_Position;//SDA Mode }IIC_HandleTypedef; /* Define --------------------------------------------------------------------*/ #define IIC_SCL_1(_HANDLE_) ( (_HANDLE_)->pSCL_Port->ODR |= ( (uint8_t)(_HANDLE_)->uSCL_Pin))
#define IIC_SCL_0(_HANDLE_) ( (_HANDLE_)->pSCL_Port->ODR &= (~(uint8_t)(_HANDLE_)->uSCL_Pin)) #define IIC_SDA_1(_HANDLE_) ( (_HANDLE_)->pSDA_Port->ODR |= ( (uint8_t)(_HANDLE_)->uSDA_Pin))
#define IIC_SDA_0(_HANDLE_) ( (_HANDLE_)->pSDA_Port->ODR &= (~(uint8_t)(_HANDLE_)->uSDA_Pin))
#define IIC_SDA_R(_HANDLE_) ( (BitStatus)(_HANDLE_)->pSDA_Port->IDR & (_HANDLE_)->uSDA_Pin) #define IIC_GPIO_SDA_MODE_Opt(_HANDLE_) (_HANDLE_)->pSDA_Port->ODR |= (uint8_t)1<<(_HANDLE_)->uSDA_Mode_Pin_Position
#define IIC_GPIO_SDA_MODE_Ipt(_HANDLE_) (_HANDLE_)->pSDA_Port->ODR &= ~((uint8_t)1<<(_HANDLE_)->uSDA_Mode_Pin_Position)

 4.3.2 延时函数

  延时函数顾名思义,就单纯的延时,延时时间可以根据芯片的速率调整,具体时间通过示波器或者可以观察到脉冲的仪器进行测量即可。

  这里定义了两个延时函数目的是在SCL低电平期间先提前改变SDA的电平,待到SDA电平稳定时,再将SCL电平改变进行读取。

 void vIIC_Delay_4us(void)
{
uint8_t i=;
while(i--)
{
asm(" NOP");asm(" NOP");asm(" NOP");asm(" NOP");
} } void vIIC_Delay_2us(void)
{
asm(" NOP");asm(" NOP");asm(" NOP");
}

4.3.3 IIC引角赋值&结构体参数初始化

  每次调用I2C接口时都需要对IIC的句柄进行初始化。

 void vIIC_Handle_Init(IIC_HandleTypedef * hIICx, GPIO_TypeDef * pSCL_Port, uint8_t uSCL_Pin, GPIO_TypeDef * pSDA_Port, uint8_t uSDA_Pin)
{
//GPIO
hIICx->pSCL_Port = pSCL_Port;
hIICx->uSCL_Pin = uSCL_Pin ;
hIICx->pSDA_Port = pSDA_Port;
hIICx->uSDA_Pin = uSDA_Pin ; switch(uSDA_Pin)
{
case GPIO_PIN_0 : hIICx->uSDA_Mode_Pin_Position = ;break;
case GPIO_PIN_1 : hIICx->uSDA_Mode_Pin_Position = ;break;
case GPIO_PIN_2 : hIICx->uSDA_Mode_Pin_Position = ;break;
case GPIO_PIN_3 : hIICx->uSDA_Mode_Pin_Position = ;break;
case GPIO_PIN_4 : hIICx->uSDA_Mode_Pin_Position = ;break;
case GPIO_PIN_5 : hIICx->uSDA_Mode_Pin_Position = ;break;
case GPIO_PIN_6 : hIICx->uSDA_Mode_Pin_Position = ;break;
case GPIO_PIN_7 : hIICx->uSDA_Mode_Pin_Position = ;break; }
}

4.3.4 起始信号

  这里与3.1讲解的操作有点不同,就是3.1中最后没有将SCL拉低包括在内,而为了发送数据的方便,我也将SCL在此函数中拉低了。

 void vIIC_Start_Signal(IIC_HandleTypedef * hIICx)
{ IIC_SDA_1 (hIICx); //拉高数据线
IIC_SCL_1 (hIICx); //拉高时钟线
vIIC_Delay_4us ( ); //延时
IIC_SDA_0 (hIICx); //拉低数据线
vIIC_Delay_4us ( ); //延时
IIC_SCL_0 (hIICx); //拉低时钟线
vIIC_Delay_4us ( ); //延时 }

4.3.5 结束信号

  这里与3.2讲解的操作也有所不同,因为在数据接收完或者是发送完成后,SDA的电平不能确定,有可能是高也有可能是低电平,但在结束信号的时候,SDA需要是低电平时候拉低SCL才能作为结束信号的开始。

 void vIIC_Stop_Signal(IIC_HandleTypedef * hIICx)
{ IIC_SDA_0 (hIICx); //拉低数据线
vIIC_Delay_4us ( ); //延时
IIC_SCL_1 (hIICx); //拉高时钟线
vIIC_Delay_4us ( ); //延时
IIC_SDA_1 (hIICx); //拉高数据线
vIIC_Delay_4us ( ); //延时 }

4.3.6 应答信号(ACK)

  由于因为发送端和操作的不同,这里需要将ACK分成三种,①Ack(主动拉低SDA形成应答信号)  ②NAck(主动不拉低SDA不形成应答信号)  ③ReadAck(等待应答信号)。

  ①Ack(主动拉低SDA形成应答信号)

  该信号在你没有读取到最后一个数据时由主机发送,使从机继续发送数据。

 void vIIC_Ack(IIC_HandleTypedef * hIICx)
{ IIC_SDA_0 (hIICx); //拉低数据位
vIIC_Delay_2us ( ); //延时
IIC_SCL_1 (hIICx); //拉高时钟位
vIIC_Delay_4us ( ); //延时
IIC_SCL_0 (hIICx); //拉低时钟位
vIIC_Delay_2us ( ); //延时 }

②NAck(主动不拉低SDA不形成应答信号)

  该信号在你读取完最后一个数据时由主机发送,使从机停止发送数据。

 void vIIC_NAck(IIC_HandleTypedef * hIICx)
{ IIC_SDA_1 (hIICx); //SDA拉高 不应答对方
vIIC_Delay_2us ( );
IIC_SCL_1 (hIICx);
vIIC_Delay_4us ( );
IIC_SCL_0 (hIICx);
vIIC_Delay_2us ( ); }

③ReadAck(等待应答信号)

  该信号在主机发送完数据后等待从机应答时候使用。

 bool bIIC_ReadACK(IIC_HandleTypedef * hIICx) //返回为:=1有ACK,=0无ACK
{
IIC_GPIO_SDA_MODE_Ipt (hIICx); //将SDA的模式改成输入模式
IIC_SDA_1 (hIICx);                  //拉高数据线
vIIC_Delay_2us ( );                  //延时
IIC_SCL_1 (hIICx);                  //拉高时钟线
vIIC_Delay_2us ( );              //延时 if(IIC_SDA_R(hIICx)) //判断是否成功接收应答,如‘有’返回0,‘没有’则返回1
{
IIC_SCL_0 (hIICx); //拉低时钟线
vIIC_Delay_2us ( ); //延时
IIC_GPIO_SDA_MODE_Opt(hIICx); //接收完应答后,将SDA的模式改回输出模式
return FALSE; //没有应答
}
else
{
IIC_SCL_0 (hIICx); //拉低时钟线
vIIC_Delay_2us ( ); //延时
IIC_GPIO_SDA_MODE_Opt(hIICx); //接收完应答后,将SDA的模式改回输出模式
return TRUE; //产生应答
} }

4.3.7 发送数据

  所要发送的数据为8位,学过串口协议的应该知道按位发送,我们这里将要发送的数据进行由高到低位的一个顺序发送,具体操作如下,不懂的朋友可以将以下代码通过画图画出来,以方便理解。

 void vIIC_SendByte(IIC_HandleTypedef * hIICx, uint8_t uSendByte)
{ uint8_t i; for (i=; i<; i++) //循环8次
{
if(uSendByte & 0X80) //将发送的数据最高位与1相与,若发送的数据最高位为1,则将SDA拉高,否则拉低
IIC_SDA_1 (hIICx);
else
IIC_SDA_0 (hIICx);
uSendByte <<= ; //数据左移1位
vIIC_Delay_2us ( );          //延时
IIC_SCL_1 (hIICx);    //时钟线拉高
vIIC_Delay_4us ( ); //延时
IIC_SCL_0 (hIICx); //时钟线拉低
vIIC_Delay_2us ( ); //延时 } }

4.3.8 数据接收

  具体操作都写在注释部分,在SCL高电平时候去读取SDA的电平。

 uint8_t uIIC_RecvByte(IIC_HandleTypedef * hIICx)
{
uint8_t i,uReceiveByte = ; IIC_GPIO_SDA_MODE_Ipt(hIICx); //将SDA的模式设置为输入模式
IIC_SDA_1 (hIICx);    //拉高数据线
for(i=;i<;i++) //进行8次的循环
{
uReceiveByte <<= ; //将接收到的数据左移 vIIC_Delay_2us ( );           //延时
IIC_SCL_1 (hIICx);      //拉高时钟线
vIIC_Delay_2us ( );      //延时 if(IIC_SDA_R (hIICx))   //读取SDA电平
{
uReceiveByte |=0x01; //若SDA电平为高则将数据的最低位或上1,即为加1;若SDA电平为低,不进行该操作,则数据最低位为0
} vIIC_Delay_2us ( );    //延时
IIC_SCL_0 (hIICx); //拉低时钟线
vIIC_Delay_2us ( );      //延时
}
IIC_GPIO_SDA_MODE_Opt(hIICx); //将SDA的模式设置为输出模式 return uReceiveByte; }

5.结尾 

  I2C协议核心基本函数为以上,将所有的核心函数结合起来便可与传感器设备进行通信了,但本博客只是单纯讲解了I2C协议,并未与传感器进行通信,若理解完I2C协议后可前往下一章博客进行与传感器通信的实践。    

  对STM8的I2C协议讲解到这里结束,感谢各位看官的点击。

  如果觉得有所收获请点下推荐,若认为该博客中存在错误的说明或者对博客中某方面有疑问请留言。

作 者:浩宇99✌
出 处:https://www.cnblogs.com/zhenghaoyu/p/10719233.html
版权声明:本文原创发表于 博客园,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。

基于STM8的IIC协议---STM8-第五章的更多相关文章

  1. 基于STM8的IIC协议--实例篇--时钟模块(DS3231)读取

    1. 综述 由上篇博客可知道IIC协议如何用代码实现,本篇博客就不涉及协议内容,只讲解如何使用. 本次的实验传感为:DS3231(时钟模块),对于时钟模块的具体信息我也就不多介绍,大家可以自行度娘,具 ...

  2. 《机器学习实战(基于scikit-learn和TensorFlow)》第五章内容学习心得

    本章在讲支持向量机(Support Vector Machine). 支持向量机,一个功能强大的机器学习模型,能够执行线性或非线性数据的分类.回归甚至异常值检测的任务.它适用于中小型数据集的分类. 线 ...

  3. stm8的IIC库的使用

    一.前言 stm8是一款低功耗的MCU芯片,它具备stm32库函数和资源丰富的优势.也同时具有价格便宜,低功耗的特点.在一些项目中,能起到很好的作用.下面我介绍一下stm8的IIC硬件库函数驱动代码及 ...

  4. FPGA基础设计(四):IIC协议

    很多数字传感器.数字控制的芯片(DDS.串行ADC.串行DAC)都是通过IIC总线来和控制器通信的.不过IIC协议仍然是一种慢速的通信方式,标准IIC速率为100kbit/s,快速模式速率为400kb ...

  5. (原创)巩固理解基于DS18B20的1-wire协议(MCU,经验)

    1.Abstract     如前篇随笔所写,将以前遇到最难懂的两个部分重拾一下.前一篇写的是I2C协议(http://www.cnblogs.com/hechengfei/p/4117840.htm ...

  6. IIC协议建模——读写EEPROM

    案例采用明德扬设计思想完成.IIC协议是非常常用的接口协议,在电子类岗位招聘要求中经常出现它的身影.关于IIC协议这里只做简要介绍,详细信息请自行百度或查阅相关Datasheet,网上资料非常多.该篇 ...

  7. 复习IIC协议---以AT24C02为例

    1.总纲--复习IIC(inter integrated circuit)协议以及自己顺便读一下数据手册. /********************************************* ...

  8. Python网络编程02 /基于TCP、UDP协议的socket简单的通信、字符串转bytes类型

    Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes类型 目录 Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes ...

  9. 基于GBT28181:SIP协议组件开发-----------第四篇SIP注册流程eXosip2实现(一)

    原创文章,引用请保证原文完整性,尊重作者劳动,原文地址http://www.cnblogs.com/qq1269122125/p/3945294.html. 上章节讲解了利用自主开发的组件SIP组件l ...

随机推荐

  1. 【Python】关于decode和encode

    #-*-coding:utf-8 import sys ''' *首先要搞清楚,字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码, 即先将 ...

  2. iOS原生自动布局NSLayoutConstraint

    AutoLayout概念是苹果自iOS6开始引入的概念. 目前为止,实现自动布局技术选型方面也可以使用xib和storyboard.在开发过程中通常登录.注册等变动可能性较小的视图,我会采用xib开发 ...

  3. Python 列表(List)

    Python 列表(List) 序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. Python有6个序列的内置类型 ...

  4. CentOS 7 / RHEL 7:修改OpenSSH 默认端口

    1.备份sshd_config cp -p /etc/ssh/sshd_config /etc/ssh/sshd_config.orig.$(date +%F) 2.vi /etc/ssh/sshd_ ...

  5. vue.js 精学记录

    v-bind:class使用的方式: 1.对象与法::class="{'active':isActive}" 此时当isActive为true时,会拥有active 2.数组语法: ...

  6. 小程序cover-view踩过的坑

    1.前段时间开发一个小程序,里边用到cover-view,发现cover-view其实很多都是不支持的,比如动画,如果你想要在cover-view上边加动画你会发现没有效果,虽然在模拟器里边可以显示动 ...

  7. parrotsec 和 kali安装系统的时候出现“executing grub-install dummy”的解决方案

    在物理机的环境下安装系统出现点问题,弄了好一会才弄出解决方法 1.parrot和kali安装的时候出现了无efi分区不能继续的问题,要知道我之前安装的时候一直都是\ ; 内存; \home三个分区搞定 ...

  8. 12. Application-specific scanners (特定应用程序扫描器)

    ike-scan是使用IKE协议发现.指纹和测试IPsec VPN服务器的命令行工具. 它通过向网络中的每个主机发送特制的IKE数据包来扫描VPN服务器的IP地址. 运行IKE的大多数主机都会响应,识 ...

  9. 服务程序在c#中的写法

    1.在VS.NET2003中新建一个WINDOWS服务程序的项目WinSrv_A. 2.更改SERVICE1.CS属性SERVICENAME为你所要建立的服务名称,在服务管理器->名称中你可以看 ...

  10. 深入理解JavaScript事件循环机制

    前言 众所周知,JavaScript 是一门单线程语言,虽然在 html5 中提出了 Web-Worker ,但这并未改变 JavaScript 是单线程这一核心.可看HTML规范中的这段话: To ...