学习记录 i2c协议

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

前言

学习记录stm32f407和imx6ull两种板子中i2c的实现

一、I2C协议

I2C(Inter_Integraated Circuit)无需 USART、CAN 等协议所需的外部收发设备。多用于多个集成电路(IC之间的通讯。
该总线为半双工协议,相比于spi(spi总线的主机地址固定)i2c是多主机总线,且i2c可在通讯过程中改变主机。

SCL:串行时钟线
SDA:双向串行数据线

每个连接到i2c的设备拥有一个地址,并通过这个地址进行访问。总线通过上拉电阻接到电源,I2C有设备空闲时会输出高阻态。当I2C中所有设备均为空闲时,上拉电阻把总线拉成高电平。

由仲裁决定那个设备优先占有总线
三种传输模式:100Kbit/s 400Kbit/s 3.4Mbit/s
连接到总线的设备数量(IC数量)只受到总线的电容(400pF)的限制

I2C协议定义了起始、停止、数据有效性、响应、仲裁、时钟同步、地址广播等环节

一种SDA总线上的数据包格式:
S | SLAVE_ADDRESS | R/W | A | DATA | A/?A / RS | SLAVE_ADDRESS | R/W | A | DATA | A/?A | P
S:起始信号
R/W:写/读
SLAVE_ADDRESS:从机地址
A/?A:应答(ACK)或非应答(NACK)信号
DATA:数据
RS:重新开始
P:停止信号

SCL线是高电平时若SDA线发生从高向低的转换称为”S”
SCL线是高电平时若SDA线发生从低到高的转换称为“P”

I2C使用SDA信号线传输数据,SLC线进行信号同步;SDA数据线在SCL一个时钟周期内发送一位数据。SCL为高电平时SDA线上的信号有效。SCL为低电平时SDA线上的信号无效

I2C地址 SLAVE_ADDRESS查找(七位或者十位)
七位的使用更加广泛。地址后紧跟一个数据位(R/W),1表示主机从从机读取数据,0表示从机向主机写数据。SDA信号线会根据R/W的选择交替SDA信号线的使用权。

无论主从机,设备收到一个信号后若想下一位接着发送则发送ACK信号,希望对方停止则为NACK信号。发送端释放SDA线的控制权,接收端将SDA线置1表达NACK置0表示ACK。

可以直接控制GPIO模拟SDA线和CLK线产生通讯时序(软件模拟I2C协议),通过硬件实现I2C则仅需检测外设状态和访问寄存器(硬件I2C还支持smbus协议,多一根SMBA线用作警告信号)

STM32F407
引脚 I2C1 I2C2 I2C3
SCL PB6/PB10 PH4/PF1/PB10 PH7/PA8
SDA PB7/PB9 PH5/PF0/PB11 PH8/PC9

使能I2C中断后,所有I2C信号中断进入同一个中断服务函数到I2C中断服务程序,由寄存器判断中断具体由哪个事件产生

二、code

1.32中初始化结构体

stm32f4xx_i2c.h
stm32f4xx_i2c.c

type struct{ 
unit32_t I2C_ClockSpeed() ; //SCL时钟频率,小于400000
unit16_t I2C_Mode() ; // 工作模式,I2C或者SMBUS
unit16_t I2C_DutyCycle() ; //时钟占空比,low/high=2:1/16:9模式
unit16_t I2C_OwnAddress1() ; //自身I2C地址
unit16_t I2C_Ack() ; //使能/关闭响应
unit16_t I2C_AcknowledgedAddress() ; //地址长度
} I2C_InitTypeDef

I2C地址寄存器可通过OwnAddress1修改,可支持同时使用两个I2C设备地址,分别储存在OAR1与OAR2中(OAR2不支持10位地址)

调用I2C_Init完成初始化
7位I2C地址 0x40+A5:A0
0x70地址一直存在,可以同时向所有设备广播信息

初始化I2C

void IIC_Init(void) //初始化I2C
{ 			
  GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
	IIC_SCL=1;
	IIC_SDA=1;
}

I2C起始信号“S”

void IIC_Stop(void)
{ 
	SDA_OUT();//sda线输出
	IIC_SCL=0;
	IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
 	delay_us(4);
	IIC_SCL=1; 
	IIC_SDA=1;//发送I2C总线结束信号
	delay_us(4);							   	
}

等地应答信号“ACK”
返回值:1,接收应答失败
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;//时钟输出0 
	return 0;  
} 

产生”ACK”应答

void IIC_Ack(void)
{ 
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=0;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}

不产生ACK应答

void IIC_NAck(void)
{ 
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=1;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}		

I2C发送一个字节,返回从机有无应答
1,有应答
0,无应答

void IIC_Send_Byte(u8 txd)
{                         
    u8 t;   
	SDA_OUT(); 	    
    IIC_SCL=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {               
        IIC_SDA=(txd&0x80)>>7;
        txd<<=1; 	  
		delay_us(2);   //对TEA5767这三个延时都是必须的
		IIC_SCL=1;
		delay_us(2); 
		IIC_SCL=0;	
		delay_us(2);
    }	 
} 	 

读一个字节,ack=1时,发送ACK。ack=0,发送nACK

u8 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;
}

PCA9685 写(reg,data)

void PCA9685_write(unsigned char reg,unsigned char data)
{ 
    IIC_Start();
    IIC_Send_Byte(PCA9685_adrr);
    IIC_Wait_Ack();
    IIC_Send_Byte(reg);
    IIC_Wait_Ack();
    IIC_Send_Byte(data);
    IIC_Wait_Ack();
    IIC_Stop();
}

PCA9685 读(reg)

u8 PCA9685_read(unsigned char reg)
{ 
    u8 res;
    IIC_Start();
    IIC_Send_Byte(PCA9685_adrr);
    IIC_Wait_Ack();
    IIC_Send_Byte(reg);
    IIC_Wait_Ack();    
		IIC_Start();                
    IIC_Send_Byte(PCA9685_adrr|0X01);
    IIC_Wait_Ack();
    res=IIC_Read_Byte(0);		
    IIC_Stop();             
    return res;  
}

设置PWM频率

void setPWMFreq(u8 freq) //
{ 
   u8 prescale,oldmode,newmode;
	 double prescaleval;
	 prescaleval = 25000000.0/(4096*freq*0.915);
   prescale = (u8)floor(prescaleval+0.5)-1;

   oldmode = PCA9685_read(PCA9685_MODE1);
   newmode = (oldmode&0x7F) | 0x10; // sleep
   PCA9685_write(PCA9685_MODE1, newmode); // go to sleep
   PCA9685_write(PCA9685_PRESCALE, prescale); // set the prescaler
   PCA9685_write(PCA9685_MODE1, oldmode);
   delay_ms(5);
   PCA9685_write(PCA9685_MODE1, oldmode | 0xa1); 
}

设置PWM(舵机4096为一个pwm波周期)

void setPWM(u8 num, u16 on, u16 off) 
{ 
	PCA9685_write(LED0_ON_L+4*num,on);
	PCA9685_write(LED0_ON_H+4*num,on>>8);
	PCA9685_write(LED0_OFF_L+4*num,off);
	PCA9685_write(LED0_OFF_H+4*num,off>>8);
}
hex name function
00 mode1 设置寄存器1
01 mode2 设置寄存器2
02 subadr1 i2c_bus_subaddress1
03 subadr2 i2c_bus_subaddress2
04 aubadr3 i2c_bus_subaddress3
05 allcalladr
06 LED0_ON_L 每个输出管脚配有以下四个寄存器
07 LED0_ON_H
08 LED0_OFF_L
09 LED0_OFF_H
共十六路
0x06+4x LEDx_ON_L
0x06+4x+1 LEDx_ON_H
0x06+4x+2 LEDx_OFF_L
0x06+4x+3 LEDx_OFF_H
FA ALL_LED_ON_L
FB ALL_LED_ON_H
FC ALL_LED_OFF_L
FD ALL_LED_OFF_H
FE PRE_SCALE 控制周期寄存器
FF TestMode
model1 描述
D7 restart 写1复位,写完自动清除。SLEEP位写0后至少500毫秒微秒
D6 EXTCLOCK 0,使用内部时钟(25mhz)1,使用外部时钟。修改前先SLEEP
D5 AI 0,内部地址读写后不增加 ,1 ,内部地址读写后增加
D4 SLEEP 0,退出SLEEP模式 1,进入SLEEP模式 500微秒后产生稳定时钟
D3 SUB1
D2 SUB2
D1 SUB3
D0 ALLCALL 不影响0x70通用i2c地址,通过A5:A0自定义i2c地址。通用I2C地址

计算角度对应的PWM

u16 calculate_PWM(u8 angle){ 
	return (int)(204.8*(0.5+angle*1.0/90));
}

总结

本文地址:https://blog.csdn.net/Artzeey/article/details/109637987

(0)
上一篇 2022年3月21日
下一篇 2022年3月21日

相关推荐