STM32F1系列学习总结:对I2C通信学习(标准库方式)

*学习路线指引*:

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压力,在传输大量数据时。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值