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引角初始化
1 //IIC引脚
2 GPIO_Init(IIC_SCL_GPIO_Port, IIC_SCL_Pin, GPIO_MODE_OUT_PP_HIGH_FAST);
3 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小节中就有介绍。因为讲解起来比较麻烦,这里就不进行更深入的说明了。
1 /* Struct --------------------------------------------------------------------*/
2
3 typedef struct iic
4 {
5 //具体信息:引脚 读写判定
6 GPIO_TypeDef * pSCL_Port; //SCL Gpio
7 uint8_t uSCL_Pin; //SCL Pin
8 GPIO_TypeDef * pSDA_Port; //SDA Gpio
9 uint8_t uSDA_Pin; //SDA Pin
10
11 uint8_t uSDA_Mode_Pin_Position;//SDA Mode
12
13 }IIC_HandleTypedef;
14
15
16
17 /* Define --------------------------------------------------------------------*/
18
19 #define IIC_SCL_1(_HANDLE_) ( (_HANDLE_)->pSCL_Port->ODR |= ( (uint8_t)(_HANDLE_)->uSCL_Pin))
20 #define IIC_SCL_0(_HANDLE_) ( (_HANDLE_)->pSCL_Port->ODR &= (~(uint8_t)(_HANDLE_)->uSCL_Pin))
21
22 #define IIC_SDA_1(_HANDLE_) ( (_HANDLE_)->pSDA_Port->ODR |= ( (uint8_t)(_HANDLE_)->uSDA_Pin))
23 #define IIC_SDA_0(_HANDLE_) ( (_HANDLE_)->pSDA_Port->ODR &= (~(uint8_t)(_HANDLE_)->uSDA_Pin))
24 #define IIC_SDA_R(_HANDLE_) ( (BitStatus)(_HANDLE_)->pSDA_Port->IDR & (_HANDLE_)->uSDA_Pin)
25
26 #define IIC_GPIO_SDA_MODE_Opt(_HANDLE_) (_HANDLE_)->pSDA_Port->ODR |= (uint8_t)1<<(_HANDLE_)->uSDA_Mode_Pin_Position
27 #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电平改变进行读取。
1 void vIIC_Delay_4us(void)
2 {
3 uint8_t i=3;
4 while(i--)
5 {
6 asm(" NOP");asm(" NOP");asm(" NOP");asm(" NOP");
7 }
8
9 }
10
11 void vIIC_Delay_2us(void)
12 {
13 asm(" NOP");asm(" NOP");asm(" NOP");
14 }
4.3.3 IIC引角赋值&结构体参数初始化
每次调用I2C接口时都需要对IIC的句柄进行初始化。
1 void vIIC_Handle_Init(IIC_HandleTypedef * hIICx, GPIO_TypeDef * pSCL_Port, uint8_t uSCL_Pin, GPIO_TypeDef * pSDA_Port, uint8_t uSDA_Pin)
2 {
3 //GPIO
4 hIICx->pSCL_Port = pSCL_Port;
5 hIICx->uSCL_Pin = uSCL_Pin ;
6 hIICx->pSDA_Port = pSDA_Port;
7 hIICx->uSDA_Pin = uSDA_Pin ;
8
9
10 switch(uSDA_Pin)
11 {
12 case GPIO_PIN_0 : hIICx->uSDA_Mode_Pin_Position = 0 ;break;
13 case GPIO_PIN_1 : hIICx->uSDA_Mode_Pin_Position = 2 ;break;
14 case GPIO_PIN_2 : hIICx->uSDA_Mode_Pin_Position = 4 ;break;
15 case GPIO_PIN_3 : hIICx->uSDA_Mode_Pin_Position = 6 ;break;
16 case GPIO_PIN_4 : hIICx->uSDA_Mode_Pin_Position = 8 ;break;
17 case GPIO_PIN_5 : hIICx->uSDA_Mode_Pin_Position = 10;break;
18 case GPIO_PIN_6 : hIICx->uSDA_Mode_Pin_Position = 12;break;
19 case GPIO_PIN_7 : hIICx->uSDA_Mode_Pin_Position = 14;break;
20
21 }
22 }
4.3.4 起始信号
这里与3.1讲解的操作有点不同,就是3.1中最后没有将SCL拉低包括在内,而为了发送数据的方便,我也将SCL在此函数中拉低了。
1 void vIIC_Start_Signal(IIC_HandleTypedef * hIICx)
2 {
3
4 IIC_SDA_1 (hIICx); //拉高数据线
5 IIC_SCL_1 (hIICx); //拉高时钟线
6 vIIC_Delay_4us ( ); //延时
7 IIC_SDA_0 (hIICx); //拉低数据线
8 vIIC_Delay_4us ( ); //延时
9 IIC_SCL_0 (hIICx); //拉低时钟线
10 vIIC_Delay_4us ( ); //延时
11
12 }
4.3.5 结束信号
这里与3.2讲解的操作也有所不同,因为在数据接收完或者是发送完成后,SDA的电平不能确定,有可能是高也有可能是低电平,但在结束信号的时候,SDA需要是低电平时候拉低SCL才能作为结束信号的开始。
1 void vIIC_Stop_Signal(IIC_HandleTypedef * hIICx)
2 {
3
4 IIC_SDA_0 (hIICx); //拉低数据线
5 vIIC_Delay_4us ( ); //延时
6 IIC_SCL_1 (hIICx); //拉高时钟线
7 vIIC_Delay_4us ( ); //延时
8 IIC_SDA_1 (hIICx); //拉高数据线
9 vIIC_Delay_4us ( ); //延时
10
11 }
4.3.6 应答信号(ACK)
由于因为发送端和操作的不同,这里需要将ACK分成三种,①Ack(主动拉低SDA形成应答信号) ②NAck(主动不拉低SDA不形成应答信号) ③ReadAck(等待应答信号)。
①Ack(主动拉低SDA形成应答信号)
该信号在你没有读取到最后一个数据时由主机发送,使从机继续发送数据。
1 void vIIC_Ack(IIC_HandleTypedef * hIICx)
2 {
3
4 IIC_SDA_0 (hIICx); //拉低数据位
5 vIIC_Delay_2us ( ); //延时
6 IIC_SCL_1 (hIICx); //拉高时钟位
7 vIIC_Delay_4us ( ); //延时
8 IIC_SCL_0 (hIICx); //拉低时钟位
9 vIIC_Delay_2us ( ); //延时
10
11 }
②NAck(主动不拉低SDA不形成应答信号)
该信号在你读取完最后一个数据时由主机发送,使从机停止发送数据。
1 void vIIC_NAck(IIC_HandleTypedef * hIICx)
2 {
3
4 IIC_SDA_1 (hIICx); //SDA拉高 不应答对方
5 vIIC_Delay_2us ( );
6 IIC_SCL_1 (hIICx);
7 vIIC_Delay_4us ( );
8 IIC_SCL_0 (hIICx);
9 vIIC_Delay_2us ( );
10
11 }
③ReadAck(等待应答信号)
该信号在主机发送完数据后等待从机应答时候使用。
1 bool bIIC_ReadACK(IIC_HandleTypedef * hIICx) //返回为:=1有ACK,=0无ACK
2 {
上一篇:基于STM8的ADC读取---STM8-第四章
下一篇:解决STM8类型单片机空间太小,使用不了printf串口打印问题
推荐阅读最新更新时间:2024-11-13 11:29
设计资源 培训 开发板 精华推荐
- EVAL-ADUC7060QSPZ,基于 ADuC7060 ARM7TDMI MCU 的开发系统,用于高性能、多通道 24 位 Sigma-Delta ADC
- A21SP16 3W免滤波D类音频功放典型应用
- Discovery board for the ST25TV02KC NFC Forum Type 5 tag IC
- ESP8266系列开发板
- ty_433_wifi_ble
- 使用 ON Semiconductor 的 LM340A 的参考设计
- 简单51单片机心形流水灯
- 培训用15V功放电路
- LTC3607EMSE 低输出电压和主电源降压稳压器的典型应用电路
- ESP32S3扩展内存后的软件开发板-0603封装