最近在学习stm32开发板,最近学习到了需要IIC通信的MPU6050六轴传感器,看了正点原子的例程,发现其中有很多位操作不是很理解。经过补习了一番C语言,总结了一些位操作的知识。
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);
}
}
首先让我们看一下这个函数,里面多次用到了IIC_SDA=(txd&0x80)>>7; 这个方法,0x80实际上就是二进制的10000000,txd&0x80就是取txd低八位的最高位(如果不能理解就先向下看), 此时的“>>7”是将结果右移七位,若txd = 10010110, 那么结果就是 00000001;这里的操作是取变量txd的最高位,然后通过循环将txd的值赋给IIC_SDA。因为IIC通信是从最高位开始读取,所以要进行这样一部操作。
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;
}
这个函数应该比较好理解
receive<<=1;
if(READ_SDA)receive++;
READ_SDA是在IO上直接读取到的数据0/1 ;
如果SDA = 1 的话,receive则会+1,反之则不操作,相当于对最低位进行赋值。
之后再将receive左移一位,达成逐位赋值的效果。
u8 MPU_Read_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{
IIC_Start();
IIC_Send_Byte((addr<<1)|0);//发送器件地址+写命令
if(IIC_Wait_Ack()) //等待应答
{
IIC_Stop();
return 1;
}
IIC_Send_Byte(reg); //写寄存器地址
IIC_Wait_Ack(); //等待应答
IIC_Start();
IIC_Send_Byte((addr<<1)|1);//发送器件地址+读命令
IIC_Wait_Ack(); //等待应答
while(len)
{
if(len==1)*buf=IIC_Read_Byte(0);//读数据,发送nACK
else *buf=IIC_Read_Byte(1); //读数据,发送ACK
len--;
buf++;
}
IIC_Stop(); //产生一个停止条件
return 0;
}
首先看IIC_Send_Byte((addr<<1)|0); 这句,因为寄存器内最低位为设置读/写,所以要将addr左移一位并对最低位赋值 x|0 或x|1。
在这里我要说一下这个指针 *buf
u8 MPU_Get_Gyroscope(short *gx,short *gy,short *gz)
{
u8 buf[6],res;
res=MPU_Read_Len(MPU_ADDR,MPU_GYRO_XOUTH_REG,6,buf);
if(res==0)
{
*gx=((u16)buf[0]<<8)|buf[1];
*gy=((u16)buf[2]<<8)|buf[3];
*gz=((u16)buf[4]<<8)|buf[5];
}
return res;;
}
例程中调用的时候是这么写的
res=MPU_Read_Len(MPU_ADDR,MPU_GYRO_XOUTH_REG,6,buf);
首先为什么len的入口参数为6,因为我们读取了三个数据,gx,gy,gy,
由于每一个都是16位,所以要分高低位分别读取,所以len为6。
其次
*gx=((u16)buf[0]<<8)|buf[1];
*gy=((u16)buf[2]<<8)|buf[3];
*gz=((u16)buf[4]<<8)|buf[5];
为什么这么写
在read_len函数中我们将所有的值都赋给了 *buf这个指针,正确来讲是赋给了 *buf指针所指的内存区域,
*buf每+1就会指向距离现在位置8byte距离的下一个内存区域,(u8 *buf)
所以进行上述操作,就可以将read到的每一个8byte大小的数据,通过指针 *buf 从内存空间依次获取,重新组装,从而得到我们需要的u16大小的gx,gy,gz的值了。
void usart1_report_imu(short aacx,short aacy,short aacz,short gyrox,short gyroy,short gyroz,short roll,short pitch,short yaw)
{
u8 tbuf[28];
u8 i;
for(i=0;i<28;i++)tbuf[i]=0;//清0
tbuf[0]=(aacx>>8)&0XFF;
tbuf[1]=aacx&0XFF;
tbuf[2]=(aacy>>8)&0XFF;
tbuf[3]=aacy&0XFF;
tbuf[4]=(aacz>>8)&0XFF;
tbuf[5]=aacz&0XFF;
tbuf[6]=(gyrox>>8)&0XFF;
tbuf[7]=gyrox&0XFF;
tbuf[8]=(gyroy>>8)&0XFF;
tbuf[9]=gyroy&0XFF;
tbuf[10]=(gyroz>>8)&0XFF;
tbuf[11]=gyroz&0XFF;
tbuf[18]=(roll>>8)&0XFF;
tbuf[19]=roll&0XFF;
tbuf[20]=(pitch>>8)&0XFF;
tbuf[21]=pitch&0XFF;
tbuf[22]=(yaw>>8)&0XFF;
tbuf[23]=yaw&0XFF;
usart1_niming_report(0XAF,tbuf,28);//飞控显示帧,0XAF
}
最后是这个函数,里面有这样一个操作:
tbuf[0]=(aacx>>8)&0XFF;
tbuf[1]=aacx&0XFF;
这里tbuf[0]为高八位,tbuf[1]为第八位, 以此类推。
这里我要说一下为什么要 &0XFF,(&11111111);
accy等变量都是16位数据,而对一个变量进行&0xFF操作就是取它的低八位。
让一个16位的变量最终返回的值为一个八位数据。
之前的IIC_SDA=(txd&0x80)>>7; 也有着异曲同工之处,
至此就是我对例程中的一些奇葩操作的理解了。