本文使用正点原子战舰v4开发板,用软件模拟iic协议而非使用硬件iic。
STEP1: 定义、声明引脚功能。我们用PB6、PB7模拟
IIC
的SCL
、SDA
。另外定义IO方向
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入SDA
//IO方向设置
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
STEP2: 初始化相关GPIO,开启相关时钟。这里初始
GPIOB
、开启GPIOB的时钟。设置B6、B7位推挽输出即可。IIC总线空闲时表现为高电平,所以初始化之后将时钟信号、数据信号拉高。
void iic1_init(void){
/*IIC相关句柄*/
GPIO_InitTypeDef GPIO_InitStruct;
/*开启相关时钟使能*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
/*初始化*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //初始化为高
}
STEP3: 开始信号的时序编写。
SCL
为高电平期间,SDA
由高电平向低电平跳变,开始传送数据。如下图起始信号。
void IIC_Start(void){
SDA_OUT();//输出模式
IIC_SCL = 1;
IIC_SDA = 1;
delay_us(4);
IIC_SDA = 0;
delay_us(4);
IIC_SCL = 0;
}
STEP4: 停止信号。
SCL
高电平期间,SDA
由低到高的跳变。如上图。
void IIC_Stop(void){
SDA_OUT();//sda线输出模式
IIC_SCL = 0;
IIC_SDA = 0;
delay_us(4);
IIC_SCL = 1;
//delay_us(4);
IIC_SDA = 1;
delay_us(4);
}
STEP5: 应答信号时序代码编写。应答信号,发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
- 对于反馈有效应答位
ACK
的要求是:接收器在第9个时钟脉冲之前的低电平期间将数据线SDA
拉低,并且确保在该时钟的高电平期间为稳定的低电平
void IIC_Ack(void){
SDA_OUT();//sda线输出模式
IIC_SCL = 0;
IIC_SDA = 0;
delay_us(2);
IIC_SCL = 1;
delay_ms(2);
IIC_SCL = 0;
}
STEP6: 非应答信号.第九个时钟期间,
SDA
为高电平
void IIC_NAck(void){
SDA_OUT();//sda线输出模式
IIC_SCL = 0;
IIC_SDA = 1;
delay_us(2);
IIC_SCL = 1;
delay_ms(2);
IIC_SCL = 0;
}
STEP7: 等待应答。
/*发送数据后等待应答:都在高电平期间,若从机发低电平产生应答信号,若没发则iic就停止了。
*返回值:1接收应答失败,IIC停止
*返回值:0接收应答成功,什么都不做
*/
u8 IIC_Wait_Ack(void){
u8 ucErrTime = 0;
SDA_IN(); //SDA线输入
IIC_SDA = 1; delay_us(1);
IIC_SCL = 1; delay_us(1);
while(READ_SDA){
ucErrTime++;
if(ucErrTime > 250){
IIC_Stop();
return 1;
}
}
IIC_SCL = 0;
return 0;
}
STEP8: IIC发送一个字节数据
在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL
串行时钟的配合下,在SDA
上逐位地串行传送每一位数据。数据位的传输是边沿触发。数据传输时满足这样的要求
void IIC_Send_Byte(u8 txd){
u8 t;
SDA_OUT();
IIC_SCL = 0; //拉低时钟
for(t = 0; t < 8; t++){
if((txd&0x80)>>7){
IIC_SDA = 1;
}else{
IIC_SDA = 0;
}
txd <<=1; //左移7位后,拿到数据,右移1位,保证高位永远是高位
delay_us(2);
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;//高电平期间数据保持不变
delay_us(2); //数据有效性
}
}
STEP9: 读一个字节数据。写和读的切换无非时
SDA
输入输出模式的切换。
/*读1个字节,ack=1时,发送ACK,ack=0,发送nACK */
void IIC_Read_Byte(unsigned char ack){
unsigned char i,receive=0;
SDA_IN(); //SDA设置为输入
for(i = 0;i < 8; i++)
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck(); //发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}