这段时间在做公司的一个项目,由于需要用到比较多的IO口,硬件方面便采用PCA6416进行扩展,采样IIC通信。现将自己的学习过程分享,希望与志同道合之人共同进步。本次IIC通信,主机为STM32 105, 从机为PCA6416
个人觉得IIC通信时序其实并不难,难点在于应答信号的处理。现在首先了解一下IIC通信时序和IIC总线的几个状态(这方面内容可百度):
1.空闲状态:IIC总线SDA和SCL均处于高电平;
2. 起始信号:在SCL高电平期间,SDA电平由高变低,开始通信之前必须由主机发送起始信号;
3.停止信号:在SCL高电平期间,SDA电平由低变高,结束通信时由主机发送停止信号;
4.应答信号:每传送完一个字节数据后,都必须有应答信号,应答信号为低电平;应答信号分两种:
4.1.主机发送数据时,从机应答信号给主机,主机接收应答信号;
4.2.主机读取数据时,主机应答信号给从机,从机接收应答信号;
5.1从PCA6416的数据手册中,可以直到pca6416地址定义如下:
高6位为固定地址,ADDR位根据实际电路设定(高电平为1,低电平为0),读/写位(读--1,写--0);主机每发送完一个字节后都必须接收从机的应答信号;主机每接收完一个字节数据后,都必须发送一个应答信号(非应答信号)
5.2接下来了解下PCA6416的命令字节,方便后续代码的阅读。从数据手册可以查得6416部分命令字节定义如下:
5.3 pca6416写命令的主要步骤:(1)启动条件;(2)发送从机地址0x40(写);(3)发送命令字节(确定PCA6416哪个寄存器接收后面的数据);(4)发送有效数据;(5)停止条件;从手册上可查阅时序图:
5.4 pca6416读命令的主要步骤:(1)启动条件;(2)发送从机地址0x40(写);(3)发送命令字节(确定哪些寄存器即将被访问);(4)重新启动;(5)发送从机地址0x41(读);(6)接收完数据应答;(7)停止条件
6.接下来便是完整的程序代码:
/************PCA6416 I2C接口设置(端口根据实际电路原理设置)*************************/
#define PCA6416_PORT GPIOB
#define PCA6416_RST GPIO_Pin_12
#define PCA6416_SCL GPIO_Pin_10
#define PCA6416_SDA GPIO_Pin_11
#define PCA6416_DELAY I2C_Delay(3000) //延时
//以下PCA6416的地址从数据手册查询
#define PCA6416_DEVICE_ADDR 0x40 //6416地址(写操作) 0x41(读操作)
#define PCA6416_REG_IN0 0x00 //输入寄存器0
#define PCA6416_REG_IN1 0x01 //输入寄存器1
#define PCA6416_REG_OUT0 0x02 //输出寄存器0
#define PCA6416_REG_OUT1 0x03 //输出寄存器1
#define PCA6416_REG_POL0 0x04 //极性反转寄存器0
#define PCA6416_REG_POL1 0x05 //极性反转寄存器1
#define PCA6416_REG_CFG0 0x06 //方向配置寄存器0
#define PCA6416_REG_CFG1 0x07 //方向配置寄存器1
void I2C_PORT_Init(void);
void I2C_SDA_DIR_SET(u8 io_dir);
void I2C_Ready(void);
void I2C_Start(void);
void I2C_Stop(void);
void I2C_Ack(void);
void I2C_Send_Ack(void);
void I2C_Send_NoAck(void);
void I2C_Write_Byte(u8 a);
u8 I2C_Read_Byte(void);
void I2C_Write_PCA6416(u8 addr, u8 reg_addr, u8 low_byte);
void I2C_Read_PCA6416(u8 addr, u8 reg_addr, u8 *pBuffer, u16 num);
void I2C_PORT_Init(void) //IIC端口初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStructure.GPIO_Pin = PCA6416_SCL;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(PCA6416_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = PCA6416_SDA;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(PCA6416_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = PCA6416_RST;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(PCA6416_PORT, &GPIO_InitStructure);
GPIO_SetBits(PCA6416_PORT,PCA6416_RST);
I2C_Ready();
}
void I2C_Delay(unsigned int count)
{
while (count)
count--;
}
void I2C_SDA_DIR_SET(u8 io_dir) //sda引脚输入输出设置
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
if(io_dir==0)
{
GPIO_InitStructure.GPIO_Pin = PCA6416_SDA;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //output
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(PCA6416_PORT, &GPIO_InitStructure);
}
else if(io_dir==1)
{
GPIO_InitStructure.GPIO_Pin = PCA6416_SDA;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //(I2C经常使用开漏模式,但开漏需要外部上拉,而实际电路没有上拉,所以采用上拉输入)
GPIO_Init(PCA6416_PORT, &GPIO_InitStructure);
}
}
void I2C_Ready(void)
{
//GPIO_SetBits(PCA6416_PORT,PCA6416_RST);
//PCA6416_DELAY;
GPIO_SetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
GPIO_SetBits(PCA6416_PORT,PCA6416_SDA);
PCA6416_DELAY;
}
void I2C_Start(void) //起始条件
{
GPIO_SetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
GPIO_SetBits(PCA6416_PORT,PCA6416_SDA);
PCA6416_DELAY;
GPIO_ResetBits(PCA6416_PORT,PCA6416_SDA);
PCA6416_DELAY;
GPIO_ResetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
}
void I2C_Stop(void) //停止条件
{
GPIO_ResetBits(PCA6416_PORT, PCA6416_SDA);
PCA6416_DELAY;
GPIO_SetBits(PCA6416_PORT, PCA6416_SCL);
PCA6416_DELAY;
GPIO_SetBits(PCA6416_PORT,PCA6416_SDA);
PCA6416_DELAY;
}
void I2C_Ack(void) //读取从机应答信号
{
//u16 i;
GPIO_ResetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
I2C_SDA_DIR_SET(1);
//while((GPIO_ReadInputDataBit(PCA6416_PORT,PCA6416_SDA))&&(i<0x2b0)) {i++;}
PCA6416_DELAY;
GPIO_SetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
PCA6416_DELAY;
I2C_SDA_DIR_SET(0);
GPIO_ResetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
PCA6416_DELAY;
}
void I2C_Send_Ack(void) //主机发送应答信号
{
GPIO_ResetBits(PCA6416_PORT,PCA6416_SDA);
PCA6416_DELAY;
PCA6416_DELAY;
GPIO_SetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
PCA6416_DELAY;
GPIO_ResetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
PCA6416_DELAY;
GPIO_SetBits(PCA6416_PORT,PCA6416_SDA);
}
void I2C_Send_NoAck(void) //主机发送非应答信号
{
GPIO_SetBits(PCA6416_PORT,PCA6416_SDA);
PCA6416_DELAY;
GPIO_SetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
PCA6416_DELAY;
GPIO_ResetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
}
void I2C_Write_Byte(u8 a) //主机发送一个字节
{
char i;
for(i=0;i<8;i++)
{
GPIO_ResetBits(PCA6416_PORT,PCA6416_SCL); //scl=0;
PCA6416_DELAY;
PCA6416_DELAY;
if(a&0x80)
GPIO_SetBits(PCA6416_PORT,PCA6416_SDA); //sda=1;
else
GPIO_ResetBits(PCA6416_PORT,PCA6416_SDA);
a=a<<1;
PCA6416_DELAY;
GPIO_SetBits(PCA6416_PORT,PCA6416_SCL); //scl=1;
PCA6416_DELAY;
PCA6416_DELAY;
}
GPIO_ResetBits(PCA6416_PORT,PCA6416_SCL);
PCA6416_DELAY;
}
u8 I2C_Read_Byte(void) //主机读取一个字节
{
u8 i,temp;
temp = 0;
GPIO_SetBits(PCA6416_PORT,PCA6416_SDA);
PCA6416_DELAY;
I2C_SDA_DIR_SET(1);
PCA6416_DELAY;
for(i=0;i<8;i++)
{
GPIO_SetBits(PCA6416_PORT, PCA6416_SCL);
PCA6416_DELAY;
PCA6416_DELAY;
temp = ((temp<<1)|(GPIO_ReadInputDataBit(PCA6416_PORT, PCA6416_SDA)));
PCA6416_DELAY;
GPIO_ResetBits(PCA6416_PORT, PCA6416_SCL);
PCA6416_DELAY;
PCA6416_DELAY;
}
I2C_SDA_DIR_SET(0);
PCA6416_DELAY;
GPIO_ResetBits(PCA6416_PORT,PCA6416_SDA);
PCA6416_DELAY;
return temp;
}
void I2C_Write_PCA6416(u8 addr, u8 reg_addr, u8 low_byte) //主机完整发送数据
{
I2C_Start();
I2C_Write_Byte(addr);
I2C_Ack();
I2C_Write_Byte(reg_addr);
I2C_Ack();
I2C_Write_Byte(low_byte);
I2C_Ack();
I2C_Stop();
}
void I2C_Read_PCA6416(u8 addr, u8 reg_addr, u8 *pBuffer, u16 num) //主机完整读取数据
{
I2C_Start();
I2C_Write_Byte(addr);
I2C_Ack();
I2C_Write_Byte(reg_addr);
I2C_Ack();
I2C_Start();
I2C_Write_Byte(addr | 0x01);
I2C_Ack();
while (num)
{
*pBuffer = I2C_Read_Byte();
if (num == 1)
I2C_Send_NoAck();
else
I2C_Send_Ack();
pBuffer++;
num--;
}
I2C_Stop();
}
以上便是此次学习IIC的主要过程,不足之处烦请指出,与君共勉。