发布时间:2023-05-23 10:30
在学习STM32的时候,难免会使用IIC通信,在STM32中有硬件IIC和软件模拟IIC,ST公司为了防止专利,就直自己设计一套硬件IIC,但是个人感觉不太好用哎,还是喜欢软件模拟IIC,有两个原因:
①:硬件IIC通信接口只有两个:I2C1和I2C2。接口太少了。
②:得是固定的那几个接口,不太灵活。
下面我给大家完整介绍一下IIC通信过程:
IIC软件模拟通信一共需要两根线,SDA和SCL,SDA是数据线,专门传输数据;SCL是时钟线,专门是控制作用。
IIC通信是半双工通信(就一根数据线),通信过程是双向的,MCU既可以发送给外设控制信号,外设也可以发送数据给MCU。所以就得控制SDA是输出模式还是输入模式。
IIC通信的大致流程:
1、发送开始信号;
2、发送控制指令;(控制指令可以是多个)
3、等待应答;(MCU向外设发送控制指令,发送完成后,外设给一个应答信号)
4、停止信号;
下面是控制信号时序:
(1) 起始信号和停止信号:
开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。
结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
(2) 数据的有效性:
IIC 总线进行数据传送时,SDA 数据线上的数据必须在时钟线 SCL 的高电平期间保持稳定,数据线的电平状态只有在 SCL 线的时钟信号为低电平时才能改变。
(3) 数据传输:
发送到 SDA 线上的每个字节必须为 8 位,每次传输可以发送的字节数量可以不受限
制,但每个字节传输结束后必须跟一个应答信号,首先传送的是数据的最高位。
(4) 应答信号:
发送器每发送一个字节,就在时钟脉冲 9 期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK 简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 对于反馈有效应答位 ACK 的要求是,接收器在第 9 个时钟脉冲之前的低电平期间将 SDA 线拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个 NACK 信号,以通知被控发送器结束数据发送,并释放 SDA 线,以便主控接收器发送一个停止信号。
IIC.H文件:
//先把IIC.H文件粘出来,多理解一下这个宏定义
#ifndef __OLED_H
#define __OLED_H
#include "stm32f10x.h"
#define IIC_GPIO GPIOB
#define IIC_SCL GPIO_Pin_6
#define IIC_SDA GPIO_Pin_7
#define IIC_GPIO_RCC RCC_APB2Periph_GPIOB
/*设置输入输出状态*/ /* 8:表示输入;3:表示输出;28:每位GPIO由CRL4位数据控制 */
#define IIC_SDA_IN() {IIC_GPIO->CRL&=0x0FFFFFFF;IIC_GPIO->CRL|=8<<28;}//CRL是GPIO低八位控制寄存器,每4位控制因为GPIO模式以及速度
#define IIC_SDA_OUT() {IIC_GPIO->CRL&=0x0FFFFFFF;IIC_GPIO->CRL|=3<<28;}//CRL是GPIO低八位控制寄存器,每4位控制因为GPIO模式以及速度
//控制SDA,SCL的输出电平
#define IIC_SCL_State(n) GPIO_WriteBit(IIC_GPIO,IIC_SCL,(BitAction)n)
#define IIC_SDA_State(n) GPIO_WriteBit(IIC_GPIO,IIC_SDA,(BitAction)n)
#define IIC_SDA_Read GPIO_ReadInputDataBit(IIC_GPIO,IIC_SDA)//读取SDA的值
void IIC_Start(void);
void IIC_Stop(void);
void IIC_Send_Byte(u8 dat);
void IIC_NAck(void);//不产生ACK应答
void IIC_Ack(void);//产生ACK应答
u8 IIC_WaitAck(void);
u8 CT_IIC_Read_Byte(unsigned char ack);
#endif
IIC.C文件
#include "iic.h"
#include "delay.h" // Device header
void IIC_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(IIC_GPIO_RCC,ENABLE);
GPIO_InitStructure.GPIO_Pin = IIC_SCL|IIC_SDA; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(IIC_GPIO, &GPIO_InitStructure); //根据设定参数初始化GPIO
GPIO_SetBits(IIC_GPIO,GPIO_Pin_7|GPIO_Pin_6); //拉高IIC数据线和时钟线
}
//起始信号
void IIC_Start(void)
{
IIC_SDA_OUT();
IIC_SDA_State(1);
IIC_SCL_State(1);
IIC_SDA_State(0);
IIC_SCL_State(0);
}
//结束信号
void IIC_Stop(void)
{
IIC_SDA_OUT();
IIC_SCL_State(1);
IIC_SDA_State(0);
IIC_SDA_State(1);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_WaitAck(void)
{
u8 ucErrTime=0;
IIC_SDA_IN(); //SDA设置为输入
IIC_SDA_State(1);
IIC_SCL_State(1);
while(IIC_SDA_Read) //等待SDA=0,产生应答信号
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL_State(0);
return 0;
}
//IIC写入一个字节
void IIC_Send_Byte(u8 dat)
{
u8 i;
IIC_SDA_OUT();
for(i=0;i<8;i++)
{
IIC_SCL_State(0);//将时钟信号设置为低电平
if(dat&0x80)//将dat的8位从最高位依次写入
{
IIC_SDA_State(1);
}
else
{
IIC_SDA_State(0);
}
IIC_SCL_State(1);//将时钟信号设置为高电平
IIC_SCL_State(0);//将时钟信号设置为低电平
dat<<=1;
}
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL_State(0);//SCL低
IIC_SDA_OUT();
IIC_SDA_State(0);//SDA低
IIC_SCL_State(1);//SCL高
IIC_SCL_State(0);//SCL低
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL_State(0);//SCL低
IIC_SDA_OUT();
IIC_SDA_State(1);//SDA高
IIC_SCL_State(1);//SCL高
IIC_SCL_State(0);//SCL低
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
//MCU读取数据完成后,可以发送应答,可以不发送应答
u8 CT_IIC_Read_Byte(unsigned char ack)
{
u8 i,receive=0;
IIC_SDA_IN();//SDA设置为输入
delay_us(30);
for(i=0;i<8;i++ )
{
IIC_SCL_State(0) ;
IIC_SCL_State(1);//SCL高
receive<<=1;
if( IIC_SDA_Read )receive++;
}
if (!ack)IIC_NAck();//发送nACK
else IIC_Ack(); //发送ACK
return receive;
}