I2C读写
I2C从机的地址最低为是读写标志位,1表示读,0表示写,剩下七位表示从机地址
mpu6050
mpu在A0未接,或者接地的时候,地址是0x68,但这是前七位,左移一位变成0xD0
STM32内存读写函数
/*
* @param DevAddress Target device address: The device 7 bits address value
* in datasheet must be shifted to the left before calling the interface
*/
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
1、I2C指针,即用I2C1 还是 I2C2…
2、器件地址uint16_t DevAddress
3、要写入的内存地址 uint16_t MemAddress
4、内存地址类型,是一个地址存8bit ,还是16bit数据 , uint16_t MemAddSize
5、要写入的数组指针uint8_t *pData
6、数据 大小
7、多少时间没完成定义为超时
这个里面只要传入移位后的地址,不用关系最低一位,函数会自动更具是写还是读,去改变最后一位地址
但这个好像是专门给memoery设备用的
Master发送接收函数
-
写寄存器内容
-
addr = 0x68; addr = (addr << 1) | 0; u8 data[10] = {0}; data[0] = reg; for(int i = 0; i < len; i++) { data[1 + i] = buf[i]; } HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)addr, (uint8_t*)data, len + 1, 10000);
-
读的话,先将地址左移一位后,将最低位置为1,将要写的寄存器地址放在第一个字节上,后面跟上要写入的数据,然后用Transmit函数发送
-
-
这个图是写单个字节的,上面的函数是写多个字节的
-
-
这是多个字节的,我猜想,调用一次Transmit函数,就会有一个起始和结束信号
-
-
读寄存器内容
-
u8 tmp = (addr << 1) | 0; HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)tmp, (uint8_t*)®, 1, 10000); tmp = (addr << 1) | 1; HAL_I2C_Master_Receive(&hi2c1, (uint16_t)tmp, (uint8_t *)buf, len, 10000);
-
先发送写,和要读取的寄存器地址,然后再发送读,然后从机就会发送对应的寄存器数据读取到buf
-
总结:用HAL_I2C_Mem_Write和Read更方便(I2C_MEMADD_SIZE_8BIT)
Transmit和Receive从机读写位
I2C_MasterRequestWrite(hi2c, DevAddress, Timeout, tickstart)
hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress);
((uint8_t)((__ADDRESS__) & (uint8_t)(~I2C_OAR1_ADD0)))
其实并不需要手动给T和R的地址最低为赋值,因为这两个函数会自动更具是T和R对最低为赋值
Mpu6050采样分频
采样频率 = 陀螺仪输出频率 /(1+SMPLRT_DIV)
采样频率也就是最终寄存器里数据的更新频率,就是给单片机的频率
陀螺仪输出频率是mpu内部读取AD的速率,和低通滤波器有关
加速度计的读取频率一直是1khz
当设置位0或者7(禁用DLPF)的时候,Gyro的频率位8khz
// Set DATA RATE of 1KHz by writing SMPLRT_DIV register
Data = 0x07;
HAL_I2C_Mem_Write(I2Cx, MPU6050_ADDR, SMPLRT_DIV_REG, 1, &Data, 1, i2c_timeout);
//这个没有设置低通滤波器,应该是默认的,猜测陀螺仪输出的是8khz,采样频率=8k/1+7=1khz
//lpf:数字低通滤波频率(Hz)
//返回值:0,设置成功
// 其他,设置失败
u8 MPU_Set_LPF(u16 lpf)
{
u8 data=0;
if(lpf>=188)data=1;
else if(lpf>=98)data=2;
else if(lpf>=42)data=3;
else if(lpf>=20)data=4;
else if(lpf>=10)data=5;
else data=6;
return MPU6050_WriteByte(MPU_CFG_REG,data);//设置数字低通滤波器
}
//rate:4~1000(Hz)
//返回值:0,设置成功
// 其他,设置失败
u8 MPU_Set_Rate(u16 rate)
{
u8 data;
if(rate>1000)rate=1000;
if(rate<4)rate=4;
data=1000/rate-1;
data=MPU6050_WriteByte(MPU_SAMPLE_RATE_REG,data); //设置数字低通滤波器
return MPU_Set_LPF(rate/2); //自动设置LPF为采样率的一半
}
//采样频率和低通滤波器应该同时设置,并且低通滤波器设置为采样频率的一半
数字低通滤波器
用于滤除高频干扰,高于这个频率的的干扰会被消除掉
带宽越小,延迟越高,所以到采样频率高的时候,带宽也要选的大,就会引入噪声
https://www.docin.com/p-2195041935.html
这个文档描述了不在标准数据手册中的寄存器配置,有的是用于DMP的
所以,最简单的方法就是定时去读对应的寄存器就可以,中断可能是用于DMP的
当DLPF设置为6的时候,也就是截止频率为5hz,那么高于5z的震荡频率就会被截止,mpu6050在静止的情况下,输出的漂移误差就会很小,但是低的截止频率,意味着mpu6050需要缓存更多的采样做计算,所以延时也会比较高
这个的设置应该要考虑被测物体的振动频率,也就是来回往复的频率,如果高于这个DLPF,那么就会被过滤
中断寄存器的配置
https://blog.csdn.net/sjf8888/article/details/97912391
#define MPU_INT_EN_REG 0X38 //中断使能寄存器
#define MPU_INTBP_CFG_REG 0X37 //中断/旁路设置寄存器
MPU_Write_Byte(MPU_INT_EN_REG, 0X01); //数据就绪中断使能
MPU_Write_Byte(MPU_INTBP_CFG_REG, 0X80); //INT引脚0X80低电平触发
//可以开启很大中断,有自由落体中断等
//0x80表示低电平触发
中断使能寄存器:INT_ENABLE
寄存器名 助记符 寄存器地址
中断使能寄存器 INT_ENABLE 0x38
寄存器位 功能
bit7 自由落体中断使能
bit6 加速度中断使能
bit5 静止中断使能
bit4 运动检测中断使能
bit3 FIFO溢出中断使能
bit2 无定义
bit1 无定义
bit0 数据就绪中断使能
中断引脚配置寄存器 INT_PIN_CFG 0x37
寄存器位 功能
bit7 配置INT引脚逻辑电平,当设置为0时,INT引脚静息为低电平,触发中断时产生高电平信号
bit6 配置引脚驱动,0:推挽模式;1:开漏模式
bit5 配置中断输出方式,0:50us电平脉冲;1:持续信号直到中断结束
bit4 配置中断锁存清除方式,0:只读(不允许手动清零);1:可读写
bit3 配置FSYNC中断逻辑电平,0:高电平;1:低电平
bit2 FSYNC端口中断使能,0:禁用;1:启用
bit1 I2C辅助总线状态标志位,当此位为1时,主机可以直接访问I2C支路从机
bit0 CLKOUT端口输出使能,0:禁用;1:输出参考时钟
去除直流分量
https://learn.openenergymonitor.org/electricity-monitoring/ctac/digital-filters-for-offset-removal
- 用高通滤波器,只保留高频分量
- 用低通滤波器找到低频分量,然后减去这个低频分量
out = (1-a)in + aout
//the closer 'a' is to 1 the lower the cutoff frequency of your filter.
//数字低通滤波器的最简单公式
// You may well have a problem with the 1MHz sample rate and 5Hz cutoff because: a = exp(-2*pi*f/fs) where f is the cutoff frequency and fs is the sample frequency. So for your example:
a= exp(-2*pi*5/1E6) = 0.99997
arduino上的库移植到STM32
将驱动的函数替换为32的函数就可以
STM32 I2C从机
不需要关注读写位,因为要Receive的时候,肯定是写,然后可以判断第一个字节是什么寄存器来将后面的数据进行对应的赋值,然后用Transmit的时候,会等待主机发的读指令,然后将数据发送出去
如果从机主机没有开启Receive,那么从机就发送不出去
hi2c1.Init.OwnAddress1 = 64;
//OwnAddress1的范围是0~128,因为只有七位
//64也就是0x20,stm32会自动将这个值左移1位
F103C6的HAL库对I2C有bug,需要手动添加引脚的速度
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
MPU6050的每个寄存器地址对应一个8位的数据,也就说,每次写,都是只要发两个字节,第一个是寄存器地址,第二个是数据
在32的slave上,为了可以更方便的对32位的数据进行读写,每个寄存器地址对应4个字节的数据,也就是说,master每次写,都要发5个字节
还有发送中断,应该是在master调用读的时候,然后并不会,slave收读到一个字节.
解决方法:master在读的时候也发送五个字节,将要读的地址放在最低为,原来的地址位变为一个特定的功能码
https://community.st.com/s/question/0D50X0000BD4pQd/stm32f072-i2c-slave-receive-callback
解决方法是用AddrCallback每个字节读
DATA[0] | DATA[1]~DATA[4] |
---|---|
0x00 | 要读取地址的变量地址 |
0x10 | DLPF |
0x20 | accX |
0x21 | accY |
0x22 | accZ |
地址0x00~0x0F,规定为功能码
0x10~0x1F,规定为能读能写的寄存器地址(配置参数)
0x20~0xFF,规定为只能读的寄存器地址(传感器输出)
读取接收都是小端模式,就是低内存存放数据的低位
-
读取accX的值
-
Master->Slave
-
data[0] data[1~4] 0x00 0x20 0x00 0x00 0x00 -
Slave->Master
-
data[0] data[1] data[2] data[3] dataL_L dataL_H dataH_L dataL_H
-
-
写入DLPF
-
Master->Slave
-
data[0] data[1~4] 0x10 0x01 0x00 0x00 0x00 -
设置从机的DLPF寄存器为0x01,从机不会返回数据
-