一、IIC通信
I2C,两线式串行总线,由数据线SDA和时钟SCL构成的穿行总线,可发送和接受数据。在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps以上。半双工通信方式
二、IIC协议
1. 空闲状态:I2C总线,总线的SDA和SCL同时处于高电平时,规定为总线的空闲状态。
2. 起始信号和停止信号:
-
起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号。
-
停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。
-
代码解析
void IIC_Start(void)
{
SDA_H;
SCL_H;
DelayUs(iic_info.speed); //延时,速度控制
SDA_L; //当SCL线为高时,SDA线一个下降沿代表开始信号
DelayUs(iic_info.speed);
SCL_L; //钳住SCL线,以便发送数据
}
void IIC_Stop(void)
{
SDA_L;
SCL_L;
DelayUs(iic_info.speed);
SCL_H;
SDA_H; //拉高SDA线,当SCL为高时,SDA线一个上升沿代表停止
DelayUs(iic_info.speed);
}
3. 应答信号ACK
- 发送器每发送一个字节(8位),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答为)表示接收器成功接收该字节;应答信号为高电平,规定为费应答位(NACK),一般表示接收器接收该字节没有成功。 ***对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。***如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P
- 代码解析
void IIC_Ack(void)
{
SCL_L;
SDA_L;
DelayUs(iic_info.speed);
SCL_H;
DelayUs(iic_info.speed);
SCL_L;
}
void IIC_NAck(void)
{
SCL_L;
SDA_H;
DelayUs(iic_info.speed);
SCL_H;
DelayUs(iic_info.speed);
SCL_L;
}
4.数据有效性
- I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据上的高电平或低电平状态才允许变化。
- 即数据在SCL的上升沿到来之前就需要准备好。并在下降沿来到之前必须稳定
5.数据的传送
- 在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。具体的数据传输方式可以看https://www.cnblogs.com/aaronLinux/p/6218660.html
- 代码解析结合上图
void IIC_SendByte(unsigned char byte)
{
unsigned char count = 0;
SCL_L; //拉低时钟开始数据传输
for(; count < 8; count++) //循环8次,每次发送一个bit
{
if(byte & 0x80) //发送最高位
SDA_H;
else
SDA_L;
byte <<= 1; //byte左移一位
DelayUs(iic_info.speed);
SCL_H;
DelayUs(iic_info.speed);
SCL_L;
}
}
三、代码
1.i2c.c
#include "stm32f10x.h"
#include "i2c.h"
#include "delay.h"
#include "usart.h"
IIC_INFO iic_info;
/*
************************************************************
* 函数名: IIC_SpeedCT1
*
* 函数功能: 软件IIC速度控制
*
* 入口参数: speed:延时参数
*
* 返回参数: 无
*
* 说明: 单位:微妙
************************************************************
*/
void IIC_SpeedCtl(unsigned short speed)
{
iic_info.speed = speed;
}
/*
************************************************************
* 函数名: IIC_Init
*
* 函数功能: 软件IIC总线IO初始化
*
* 入口参数: 无
*
* 返回参数: 无
*
* 说明:使用开漏方式,不用切换IO口输入输出方向
************************************************************
*/
void IIC_Init(void)
{
GPIO_InitTypeDef gpio_initstruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
gpio_initstruct.GPIO_Mode = GPIO_Mode_Out_OD; //开漏,不用切换出入输出方向
gpio_initstruct.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_initstruct);
IIC_SpeedCtl(5);
SDA_H; //拉高SDA线,处于空闲状态
SCL_H; //拉高SCL,处于空闲状态
}
/*
************************************************************
* 函数名: IIC_Start
*
* 函数功能: 软件IIC开始信号
*
* 入口参数: 无
*
* 返回参数: 无
*
* 说明:
************************************************************
*/
void IIC_Start(void)
{
SDA_H;
SCL_H;
DelayUs(iic_info.speed); //延时,速度控制
SDA_L; //当SCL线为高时,SDA线一个下降沿代表开始信号
DelayUs(iic_info.speed);
SCL_L; //钳住SCL线,以便发送数据
}
/*
************************************************************
* 函数名: IIC_Stop
*
* 函数功能: 软件IIC停止信号
*
* 入口参数: 无
*
* 返回参数: 无
*
* 说明:
************************************************************
*/
void IIC_Stop(void)
{
SDA_L;
SCL_L;
DelayUs(iic_info.speed);
SCL_H;
SDA_H; //拉高SDA线,当SCL为高时,SDA线一个上升沿代表停止
DelayUs(iic_info.speed);
}
/*
************************************************************
* 函数名: IIC_SpeedCT1
*
* 函数功能: 软件IIC等待应答
*
* 入口参数: timeOut:超时时间
*
* 返回参数: 无
*
* 说明: 单位:微妙
************************************************************
*/
_Bool IIC_WaitAck(unsigned int timeOut)
{
SDA_H;DelayUs(iic_info.speed);
SCL_H;DelayUs(iic_info.speed);
while(SDA_R) //如果读到SDA线为1,等待,应答信号应是0
{
if(--timeOut == 0)
{
UsartPrintf(USART1, "WaitAck TimeOut\r\n");
IIC_Stop(); //超时未收到应答,则停止总线
return IIC_Err; //返回失败
}
DelayUs(iic_info.speed);
}
SCL_L;
return IIC_OK; //返回成功
}
/*
************************************************************
* 函数名: IIC_ACK
*
* 函数功能: 软件IIC产生一个应答
*
* 入口参数: 无
*
* 返回参数: 无
*
* 说明: 当SDA线为低时,SCL线一个正脉冲代表发送一个应答信号
************************************************************
*/
void IIC_Ack(void)
{
SCL_L;
SDA_L;
DelayUs(iic_info.speed);
SCL_H;
DelayUs(iic_info.speed);
SCL_L;
}
/*
************************************************************
* 函数名: IIC_ACK
*
* 函数功能: 软件IIC产生一个非应答
*
* 入口参数: 无
*
* 返回参数: 无
*
* 说明: 当SDA线为高时,SCL线一个正脉冲代表发送一个非应答信号
************************************************************
*/
void IIC_NAck(void)
{
SCL_L;
SDA_H;
DelayUs(iic_info.speed);
SCL_H;
DelayUs(iic_info.speed);
SCL_L;
}
/*
************************************************************
* 函数名: IIC_SendByte
*
* 函数功能: 软件IIC发送一个字节
*
* 入口参数: byte:需要发送的字节
*
* 返回参数: 无
*
* 说明:
************************************************************
*/
void IIC_SendByte(unsigned char byte)
{
unsigned char count = 0;
SCL_L; //拉低时钟开始数据传输
for(; count < 8; count++) //循环8次,每次发送一个bit
{
if(byte & 0x80) //发送最高位
SDA_H;
else
SDA_L;
byte <<= 1; //byte左移一位
DelayUs(iic_info.speed);
SCL_H;
DelayUs(iic_info.speed);
SCL_L;
}
}
/*
************************************************************
* 函数名: IIC_RecvByte
*
* 函数功能: 软件IIC接受一个字节
*
* 入口参数: 无
*
* 返回参数: 无
*
* 说明:
************************************************************
*/
unsigned char IIC_RecvByte(void)
{
unsigned char count = 0, receive = 0;
SDA_H; //拉高SDA线,开漏状态下,需线拉高以便读取数据
for(; count < 8; count++ ) //循环8次,每次发送一个bit
{
SCL_L;
DelayUs(iic_info.speed);
SCL_H;
receive <<= 1;
if(SDA_R) //如果SDA线为1,则receive变量自增,每次自增都是对bit0的+1,然后下一次循环会先左移一次
receive++;
DelayUs(iic_info.speed);
}
return receive;
}
/*
************************************************************
* 函数名: IIC_WriteByte
*
* 函数功能: 软件IIC写一个数据
*
* 入口参数: slaveAddr:从机地址
* regAddr:寄存器地址
* byte:需要写入的数据
*
* 返回参数: 0-写入成功 1-写入失败
*
* 说明: *byte是缓存写入数据的变量的地址,因为有些寄存器只需要控制下寄存器,并不需要写入值
************************************************************
*/
_Bool I2C_WriteByte(unsigned char slaveAddr, unsigned char regAddr, unsigned char *byte)
{
unsigned char addr = 0;
addr = slaveAddr << 1; //IIC地址是7bit,这里需要左移1位,bit0:1-读 0-写
IIC_Start(); //起始信号
IIC_SendByte(addr); //发送设备地址(写)
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
IIC_SendByte(regAddr); //发送寄存器地址
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
if(byte)
{
IIC_SendByte(*byte); //发送数据
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
}
IIC_Stop(); //停止信号
return IIC_OK;
}
/*
************************************************************
* 函数名: IIC_ReadByte
*
* 函数功能: 软件IIC写一个数据
*
* 入口参数: slaveAddr:从机地址
* regAddr:寄存器地址
* byte:需要写入的数据
*
* 返回参数: 0-写入成功 1-写入失败
*
* 说明: *byte是缓存写入数据的变量的地址,因为有些寄存器只需要控制下寄存器,并不需要写入值
************************************************************
*/
_Bool I2C_ReadByte(unsigned char slaveAddr, unsigned char regAddr, unsigned char *val)
{
unsigned char addr = 0;
addr = slaveAddr << 1; //IIC地址是7bit,这里需要左移1位,bit0:1-读 0-写
IIC_Start(); //起始信号
IIC_SendByte(addr); //发送设备地址(写)
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
IIC_SendByte(regAddr); //发送寄存器地址
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
IIC_Start(); //重启信号
IIC_SendByte(addr + 1); //发送设备地址(读)
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
*val = IIC_RecvByte(); //接收
IIC_NAck(); //产生一个非应答信号,代表读取接收
IIC_Stop(); //停止信号
return IIC_OK;
}
/*
************************************************************
* 函数名: IIC_WriteBytes
*
* 函数功能: 软件IIC写多个数据
*
* 入口参数: slaveAddr:从机地址
* regAddr:寄存器地址
* byte:需要写入的数据
*
* 返回参数: 0-写入成功 1-写入失败
*
* 说明: *buf是一个数组或指向一个缓存区的指针
************************************************************
*/
_Bool I2C_WriteBytes(unsigned char slaveAddr, unsigned char regAddr, unsigned char *buf, unsigned char num)
{
unsigned char addr = 0;
addr = slaveAddr << 1; //IIC地址是7bit,这里需要左移1位,bit0:1-读 0-写
IIC_Start(); //起始信号
IIC_SendByte(addr); //发送设备地址(写)
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
IIC_SendByte(regAddr); //发送寄存器地址
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
while(num--) //循环写入数据
{
IIC_SendByte(*buf); //发送数据
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
buf++; //数据指针偏移到下一个
DelayUs(10);
}
IIC_Stop(); //停止信号
return IIC_OK;
}
/*
************************************************************
* 函数名: IIC_ReadBytes
*
* 函数功能: 软件IIC读多个数据
*
* 入口参数: slaveAddr:从机地址
* regAddr:寄存器地址
* byte:需要写入的数据
*
* 返回参数: 0-写入成功 1-写入失败
*
* 说明: *buf是一个数组或指向一个缓存区的指针
************************************************************
*/
_Bool I2C_ReadBytes(unsigned char slaveAddr, unsigned char regAddr, unsigned char *buf, unsigned char num)
{
unsigned short addr = 0;
addr = slaveAddr << 1; //IIC地址是7bit,这里需要左移1位,bit0:1-读 0-写
IIC_Start(); //起始信号
IIC_SendByte(addr); //发送设备地址(写)
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
IIC_SendByte(regAddr); //发送寄存器地址
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
IIC_Start(); //循环写入数据
IIC_SendByte(addr + 1); //发送设备地址(读)
if(IIC_WaitAck(5000)) //等待应答
return IIC_Err;
while(num--)
{
*buf = IIC_RecvByte();
buf++; //偏移下一个数据存储地址
if(num == 0)
{
IIC_NAck(); //最后一个数据需要回NOACK
}
else
{
IIC_Ack(); //回应ACK
}
}
IIC_Stop();
return IIC_OK;
}
2.i2c.h
#ifndef _I2C_H_
#define _I2C_H_
#define IIC_OK 0
#define IIC_Err 1
//SDA PB11
//SCL PB10
#define SDA_H GPIO_SetBits(GPIOB, GPIO_Pin_11)
#define SDA_L GPIO_ResetBits(GPIOB, GPIO_Pin_11)
#define SDA_R GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)
#define SCL_H GPIO_SetBits(GPIOB, GPIO_Pin_10)
#define SCL_L GPIO_ResetBits(GPIOB, GPIO_Pin_10)
typedef struct
{
unsigned short speed;
} IIC_INFO;
extern IIC_INFO iic_info;
void IIC_Init(void);
void IIC_SpeedCtl(unsigned short speed);
_Bool I2C_WriteByte(unsigned char slaveAddr, unsigned char regAddr, unsigned char *byte);
_Bool I2C_ReadByte(unsigned char slaveAddr, unsigned char regAddr, unsigned char *val);
_Bool I2C_WriteBytes(unsigned char slaveAddr, unsigned char regAddr, unsigned char *buf, unsigned char num);
_Bool I2C_ReadBytes(unsigned char slaveAddr, unsigned char regAddr, unsigned char *buf, unsigned char num);
void IIC_Start(void);
void IIC_Stop(void);
_Bool IIC_WaitAck(unsigned int timeOut);
void IIC_Ack(void);
void IIC_NAck(void);
void IIC_SendByte(unsigned char byte);
unsigned char IIC_RecvByte(void);
#endif