其实在上学期就用了IIC通信写OLED屏幕,一直没有写,今天有时间写出来进行记录。 (PS:图片内容来自于野火教程)
一.物理层
它的物理层有如下特点: (1) 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。 (2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线 (SDA) ,一条串行时钟线 (SCL)。数据线即用来表示数据,时钟线用于数据收发同步。 (3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。 (4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。 (5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。 (6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。 (7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。
二.数据层
IIC的数据层分读数据和写数据,在读写之前,也要了解它的起始信号以及如何保证数据的有效性。 1.起始和结束 起始信号:时钟线高电平的时候,数据线处于下降沿 结束信号:时钟线高电平的时候,数据线处于上升沿 2.数据有效性 I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。SDA 数据线在SCL 的每个时钟周期传输一位数据。传输时,SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL 为低电平时,SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。
三.响应
I2C 的数据和地址传输都带响应。响应包括“应答 (ACK)”和“非应答 (NACK)”两种信号。作为数据接收端时,当设备 (无论主从机) 接收到 I2C 传输的一字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答 (ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答 (NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制SDA,若 SDA 为高电平,表示非应答信号 (NACK),低电平表示应答信号 (ACK)。
四.代码
#include "IIC.h"
void delay_us(uint32_t time)
{
uint8_t i=0;
while(time--)
{
i=10; //自己定义
while(i--) ;
}
}
void IIC_Start(void) //起始信号
{
SDA_SET;
SCL_SET;
delay_us(2);
SDA_RESET;
delay_us(2);
SCL_RESET;
delay_us(2);
}
void IIC_Stop(void) //终止信号
{
SDA_RESET;
delay_us(2);//eroor
SCL_SET;
delay_us(2);
SDA_SET;
delay_us(2);
}
void IIC_Ack() //应答信号
{
IIC1_SDA(GPIO_PIN_RESET);
IIC1_SCL(GPIO_PIN_SET);
IIC1_SCL (GPIO_PIN_RESET);
IIC1_SCL(GPIO_PIN_SET);
}
void IIC1_NAck() //菲应答信号
{
SCL_RESET;
delay_us(10);
SDA_SET;
delay_us(10);
SCL_SET;
delay_us(10);
SCL_RESET;
delay_us(10);
}
u8 IIC_Wait_Ack()
{
u8 count=0;
IIC1_SDA(GPIO_PIN_SET);
IIC1_SCL(GPIO_PIN_SET);
while(IIC1_SDA_IS_HIGH())
{
count++;
if(count==250)
{
IIC_Stop();
return 1;
}
}
IIC1_SCL(GPIO_PIN_RESET);
return 0;
}
//SCL高时传输数据会稳定,
void Write_IIC_Byte(u8 TX)
{
u8 i=0;
IIC1_SCL(GPIO_PIN_RESET);
for(i=0;i<8;i++)
{
if((TX&0x80)>0)
{
IIC1_SDA(GPIO_PIN_SET); //SDA负责数据传输,
}
else
{
IIC1_SDA(GPIO_PIN_RESET);
}
TX<<=1;
IIC1_SCL(GPIO_PIN_SET);//SCL高时传输数据会稳定,
delay_us(10); //此处延时为了让数据做停留
IIC1_SCL(GPIO_PIN_RESET);
}
}
u8 IIC_Read_Byte(u8 ack)
{
u8 i=0,res=0;
for(i=0;i<8;i++)
{
IIC1_SCL(GPIO_PIN_SET);
res<<=1;
if(IIC1_SDA_IS_HIGH ())
{
res++;
}
IIC1_SCL(GPIO_PIN_RESET);
}
if(ack==0)
{
IIC1_NAck();
}
else
{
IIC_Ack();
}
return res;
}
在写IIC通信时序的时候,如果出现无法通信的情况,可以用逻辑分析仪对波进行分析,然后修改时序。
在用cubemx进行配置时,可以选择硬件IIC和软件IIC,这里我比较推荐软件IIC,比较灵活,CUBEMX配置的时候,将IO口配置为输出模式并且上拉,源文件中进行适当修改,我把代码贴在下面
#ifndef __IIC_H
#define __IIC_H
#include "stm32f1xx.h"
//#include "stdint.h"
typedef unsigned char u8;
#define IIC1_SCL(pin_status) HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, pin_status);
#define IIC1_SDA(pin_status) HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, pin_status);
#define IIC1_SCL_IS_HIGH() (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) != GPIO_PIN_RESET)
#define IIC1_SDA_IS_HIGH() (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_7) != GPIO_PIN_RESET)
#define SCL_SET HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);
#define SCL_RESET HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);
#define SDA_SET HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
#define SDA_RESET HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);
#include "stdio.h"
#include "stdint.h"
void IIC_Start(void);
void IIC_Stop(void);
void IIC_Ack(void);
void IIC_NAck(void);
u8 IIC_Wait_Ack(void);
void Write_IIC_Byte(u8 TX);
u8 IIC_Read_Byte(u8 ack);
void E2PROM_Init(void);
void delay_us(uint32_t time);
#endif
这里我用的是PA6和PA7,大家可以自行修改。 IIC可以用来写OLED,代码太多我就不贴出来了,发个下载链接,大家自取。
|