*学习路线指引*:
1.I2C介绍--时序
2.软件模拟I2C
3.硬件模拟I2C
4.硬件I2C配合DMA实现数据搬运,减轻CPU负担
一、我的学习认知程度--I2C介绍--时序、数据格式
在I2C通信时,一个时间内只能有一个设备发数据,或者一个设备接收收据,并且有应答机制,即双方非常关注对方是否已经接收到数据,防止数据覆盖。
时序:
这是一主多从的模式,主机产生其实波形信号代表I2C通信开始。
SCL低电平时机写数据,高电平时机读数据。
收完或者读完一个字节要发送应答。
软件模拟I2C的数据格式:
写操作的格式:先产生起始条件,然后开始发送字节,第一个字节一般为目标地址7位加写位1位组成8位数据,应答,第二个字节则是写目标寄存器的地址,应答,第三位开始就是数据位,直到写完最后一个数据就产生结束条件。(开始,写从机地址加写位,写寄存器地址,往这里写数据,写数据,写完结束)
读操作的格式:先产生起始条件,然后开始发送字节,第一个字节也是为目标地址7位加写位1位组成8位数据,应答,第二个字节则是读目标寄存器的地址,应答,产生重复起始条件,然后第三个字节是目标地址7位加读位1位组成8位数据,应答,第四字节开始就是数据位,直到读完最后一个数据就产生一个结束条件。(开始,写从机地址加写位,因为还要写读寄存器地址,写读寄存器地址,重新开始,写从机地址加读位,读数据,读完结束)
硬件模拟I2C的数据格式:
写操作和读操作的格式:流程和软件模拟I2C的一样,只不过是实现方式不一样,硬件实现是库函数帮我们完成,我们不用手动翻转电平实现波形发送,最主要区别是软件需要手动发送应答,而硬件不需要手动发送应答,硬件帮我们发送,硬件需等待事件来确定对方是否接收完成,我要发下一个数据,等待事件实质就是看看标志位TXE和RXNE,知道时机,正确读取数据。
二、我的学习认知程度--软件模拟I2C
结合代码理解:
void MyI2C_Start(void) //用软件手动翻转电平实现时序
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i)); //把数据逐位在SCL低电平期间放上
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1);
for (i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1); //高电平期间读取
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
MyI2C_W_SCL(0);
}
return Byte;
}
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
这里提一下地址位7位加1位读写位组成八位数据的两种表示方式:
比如有一个从机地址位0x50,那么下图的MPU6050_ADDRESS赋值什么呢。
1. 地址位7位加1位读写位组成八位数据直接当成字节数据发送。101 0000 和 0 =》 1010 0001
2.地址左移1位 | 1或0 。 0x50 <<1 | 1 = 0101 0000 << 1 = 1010 0000 | 1 = 1010 0001
故两种方式表达意思一样,拥用哪一种看个人习惯。
故MPU6050_ADDRESS = 0xa1 即 1010 0001
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)//写步骤
{
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
MyI2C_SendAck(1);
MyI2C_Stop();
return Data;
}
三、我的学习认知程度--硬件模拟I2C
结合代码理解:
#define JY901S_ADDRESS 0xa0 //为什么是0xa0,因为I2C_Send7bitAddress()函数会给我们补应该读写位,补哪个看第三个参数,由于是或与读写位,所以我们要把从机地址0x50先左移一位,即0xa1
也可以这样 #define JY901S_ADDRESS 0x50<<1 或者 I2C_Send7bitAddress(I2C2, 0x50<<1, I2C_Direction_Transmitter);
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)
{
/* Check the parameters */
assert_param(IS_I2C_ALL_PERIPH(I2Cx));
assert_param(IS_I2C_DIRECTION(I2C_Direction));
/* Test on the direction to set/reset the read/write bit */
if (I2C_Direction != I2C_Direction_Transmitter)
{
/* Set the address bit0 for read */
Address |= OAR1_ADD0_Set;
}
else
{
/* Reset the address bit0 for write */
Address &= OAR1_ADD0_Reset;
}
/* Send the address */
I2Cx->DR = Address;
}
void JY901S_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT) //等待事件,实质就是看那些标志位,结合下图理解
{
uint32_t Timeout;
Timeout = 10000;
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
{
Timeout --;
if (Timeout == 0)
{
break;
}
}
}
void JY901S_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_GenerateSTART(I2C2, ENABLE);
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//EV5
I2C_Send7bitAddress(I2C2, JY901S_ADDRESS, I2C_Direction_Transmitter);
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6
I2C_SendData(I2C2, RegAddress);
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8
I2C_SendData(I2C2, Data);
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2
I2C_GenerateSTOP(I2C2, ENABLE);
}
int8_t JY901S_ReadReg(uint8_t RegAddress ,int8_t length, u8 *Z_Angle)
{
int8_t count = 0;
I2C_GenerateSTART(I2C2, ENABLE);
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//EV5
I2C_Send7bitAddress(I2C2, JY901S_ADDRESS, I2C_Direction_Transmitter);
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6
I2C_SendData(I2C2, RegAddress);
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2
I2C_GenerateSTART(I2C2, ENABLE);
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//EV5
I2C_Send7bitAddress(I2C2, JY901S_ADDRESS, I2C_Direction_Receiver);
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//EV6
for(count=0;count<length;count++){
if(count!=length-1)
{
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);//EV_7
Z_Angle[count]=I2C_ReceiveData(I2C2); //带ACK的读数据
}
else{
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);
JY901S_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);//EV_7
Z_Angle[count]=I2C_ReceiveData(I2C2); //最后一个字节NACK
}
}
I2C_AcknowledgeConfig(I2C2, ENABLE);
return count;
}
void JY901S_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_Init(I2C2, &I2C_InitStructure);
I2C_Cmd(I2C2, ENABLE);
}
void JY901S_GetData(u16 *Z_Angle)
{
unsigned char Temp[2];
JY901S_ReadReg(Yaw, 2, &Temp[0]);
*Z_Angle = (Temp[1] << 8) | Temp[0];
}
结合两图,简要点说就是:
检测寄存器的那几个标志位的电平情况,再和事件的进制比较,如果相等,说明标志位成功置位。
对照这两图,便可写出时序,在某个地方等待某个事件收再进行下一个操作。再去看看代码吧,是不是清楚很多。
四、我的学习认知程度--硬件I2C配合DMA实现数据搬运,减轻CPU负担
实例分析:
循环模式DMA,将陀螺仪jy901s模块数据从数据寄存器搬运到目标数组。(待更)
五.总结
最后,总结一下:
1.可以软件模拟,也可以使用硬件资源。
2.理解时序最重要。
3.应答和等待事件应用好。
4.DMA功能可以减轻CPU压力,在传输大量数据时。