10:STM32------I2C通信
目录
一:I2C通信协议
1:I2C简历
2:硬件电路
3:I2C时序基本单元
A : 开/ 终条件
2:发送一个字节
3:接收一个字节
4:应答机制
4:I2C时序
1:指定地址写
2:当前地址读
3: 指定地址读
二:MPU6050
1:简历
2:参数
3:硬件电路
4:框图
5:寄存器地址
三:案例
A:软件I2C读写 MPU6050
1:连接图
2:代码
B:硬件I2C读写 MPU6050
1:简介
2:l2C框图
3:l2C基本结构
4:主机发送
5:主机接收
6:连接图
7:函数介绍
8:代码
一:I2C通信协议
软件l2C的读写
1:I2C简历
I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步,半双工
带数据应答
支持总线挂载多设备(一主多从、多主多从)
许多外设都遵守I2C的通信协议,eg: 上面图片中的: MPU6050,OLED, AT24C02, DS3231模块
SCL: 时钟 SDA:数据 主机对CS具有完全的控制权
半双工 : 一根数据线负责发送和接收数据, eg:I2C通信的SDA线
同步: 接收方可以在时钟信号的指引下进行采样
我们一般使用的为一主多从,下面讲的也为一主多从
2:硬件电路
所有I2C设备的SCL连在一起,SDA连在一起
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
下面为一主多从模式下的I2C通信
主机:
A : 对SCL线的完全控制--------任何时候,都是主机完全掌控SCL线
B: 在空闲状态下,主机可以主动发起对SDA的控制, 只有在从机发送数据和从机应答的时候, 主机才会转交SDA的控制权给从机
从机:
A : 从机不允许控制SCL线------对于SCL时钟线,在任何时刻都只能被动的读取
B : 对于SDA数据线,从机不允许主动发起对SDA的控制, -----只有在主机发送读取从机的命令后,或者从机应答的时候, 从机才能短暂地取得SDA的控制权
弱上拉---弹簧杆子模型
只允许向下拉,不允许向上拉.
向下拉为--低电频; 不拉----高电频
线与现象:
A : 只要有任意一个或多个从机设备输出了低电平, 总线就处手低电平
B : 只有所有设备都输出高电平, 总线才处手高电平
优点:
辟免了引脚模式的频繁切换
完全杜绝了电源短路现象,保证电路的安全
3:I2C时序基本单元
A : 开/ 终条件
起始条件:SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平
起始和终止,都是由主机产生的 , 从机不允许产生起始和终止. 在总线空闲状态时,从机必须始终双手放开, 不允许主动跳出来,去碰总线
2:发送一个字节
主机发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后主机释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
注意: 这里是高位先行, 所以第一位是一个字节的最高位B7, 然后依次是次高B6 B5..........B0
和串口是不一样的, 串口时序是低位先行,这里I2C是高位先行
由于这整个时序是主机发送一个字节---------所以在这个单元里,SCL和SDA全程都由主机掌控------只有在从机发送数据和从机应答的时候, 主机才会转交SDA的控制权给从机----- 从机不允许控制SCL线------对于SCL时钟线,在任何时刻都只能被动的读取
SCL全程由主机控制
3:接收一个字节
主机接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后Z主机释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
主机在接收之前,需要释放SDA------这时候是从机发送数据需要在SCL低电频期间把数据放在SDA上面, 把SDA的控制器交给从机
SCL全程由主机控制
4:应答机制
谁发送数据,谁接收应答
主机发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据(应答数据),数据0表示应答,数据1表示非应答
从机给主机发送数据, 主机需要应答从机, 看还需不需要从机继续给主机发送数据
数据0表示应答,数据1表示非应答------0把主机SCL时钟线拉低,,使得从机可以继续把数据放在SDA数据上; 1:把主机SCL时钟线拉高, 从机不能在SDA数据线上放数据. 主机可以读取数据, 主机也可以在CSL高电频期间给SDA一个上升沿结束
主机接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据(应答数据),判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
主机给从机发送数据, 从机给主机应答, 看还需不需要主机继续给从机发送数据
数据0表示应答,数据1表示非应答------0把主机SCL时钟线拉低,,使得主机可以继续把数据放在SDA数据上; 1:把SCL时钟线拉高, 从机不能在SDA数据线上读取数据. 从机读取数据, 主机也可以在CSL高电频期间给SDA一个上升沿结束
4:I2C时序
1:指定地址写
指定地址写----主机在指定的地址对从机写入数据
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
首先把每个从设备都确定一个唯一的设备地址, 从机设备地址就相当于每个设备的名字, 主机在起始条件之后,要先发送一个字节叫一下从机名字, 所有从机都会收到第一个字节,和自己的名字进行比较
从机设备地址,在12C协议标准里分为7位地址和10位地址--说下7位地址的模式
起始条件:SCL高电平期间,SDA从高电平切换到低电平
第一个字节: 前七位写入要写从机的地址, 确定给谁写入数据. 第8位: 读写位---0表示,之后的时序主机要进行写入操作, 1表示,之后的时序主机要进行读出操作.
应答位 : 在每一个字节完成后都需要一个应答位, 这里是主机给从机发送数据, 从机给主机应答, 看还需不需要主机继续给从机发送数据-----------在这个时刻,主机要释放SDA,释放SDA之后,引脚电平回弹到高电平,上升沿; 从机要在这个位拉低SDA,下降沿; 根据线与的特点为低电频. 这个过程,就代表从机产生了应答-------最终高电平期间,主机读取SDA,发现是0,就说明,我进行寻址,有人给我应答了,传输没有问题; 如果主机读取SDA,发现是1,说明进行寻址,应答位期间,我松手了,没人拽住它,没人给我应答,直接产生停止条件吧,并提示一些信息
第二个字节 : 就可以送到指定设备的内部了, 从机设备可以自己定义第二个字节和后续字节的用途. 一般第二个字节可以是寄存器地址或者是指令控制字等 eg:主机向从机发送了0x19这个数据, 在MPU6050里,就表示要操作你0x19地址下的寄存器了
第三个字节 :这个字节就是主机想要写入到0x19地址下寄存器的内容了
这个数据帧的目的就是,对于指定从机地址为1101000的设备, 在其内部0x19地址的寄存器中,写入0xAA这个数据
2:当前地址读
当前地址读-----------主机在当前地址读取从机的数据
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
由手当前地址读并不能指定读的地址, 所以这个时序用的不是很多
3: 指定地址读
指定地址读----主机在指定地址读取从机的数据
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
又叫做复合模式 : 也就是把指定地址写的前2个字节Copy过来, 不用结束, 但是需要重新开始. 在重新写第一个字节, 因为第一个字节的最后一位决定了我们的操作为读取数据还是写入数据; 然后就正常的读取从机的数据
二:MPU6050
1:简历
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
2:参数
16位ADC采集传感器的模拟信号,量化范围:-32768~32767
加速度计满量程选择:±2、±4、±8、±16(g)----------ACCEL_CONFIG
陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)------GYRO_CONFIG
可配置的数字低通滤波器
可配置的时钟源
可配置的采样分频---SMPLRT_DIV
I2C从机地址:1101000(AD0=0) 1101001(AD0=1)
3:硬件电路
AD0: 通过改变7位从机地址的最后一位, l来改变从机的地址
AD0=0 7位从机地址=1101000
AD0=1 7位从机地址=1101001
4:框图
5:寄存器地址
16进制表示寄存器的地址 | 10进制表示寄存器的地址 | 寄存器名称 | 读写权限 | 第7位 | 第6位 | ... | ... | .... | .... | ... | ... |
SMPLRT_DIV----采样分频器
CONFIG-------配置寄存器
GYRO_CONFIG--------陀螺仪配置寄存器
ACCEL_CONFIG----加速度配置寄存器
ACCEL---加速度; _H:高8位, _L:低8位
TEMP----温度传感器
GYRO------陀螺仪传感器
PWR_MGMT_1----电源管理寄存器1; PWR_MGMT_2:电源管理寄存器2
WHO_AM_I----器件的ID号码
三:案例
A:软件I2C读写 MPU6050
1:连接图
我们这个代码使用的是软件I2C, 就是使用普通的GPIO口实现反转电频的操作, 它不需要32内部的外设资源支持,所以这里的端口可以任意指定
2:代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MYI2C.h"
#include "MPU6050.h"/**
* @brief 每个函数都是一SCL低电频结束的,除了MYI2C_Stop函数使用在除了MYI2C_Stop函数以外的函数,在函数开始的时候SCL都为低电频*/void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);Delay_us(10);
}void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);Delay_us(10);
} uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);Delay_us(10);return BitValue;
}void MyI2C_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;//开漏输出模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);//高电平
}
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(0);MyI2C_W_SCL(1);MyI2C_W_SDA(1);
}/**
* @brief 主机发送一个字节给从机---主机发送字节在SCL低电频的时候,主机把数据放在SDA上面;然后拉高SCL从机读取数据;然后再拉低SCL主机继续放数据,循环8次* @param Byte 要发送的字节* @retval 无*/
void MyI2C_SendByte(uint8_t Byte)
{ MyI2C_W_SCL(0);for (uint8_t i=0;i<8;i++){ //0x80 1000 0000 &依次取出发送字节的每一位(从高到低)I2C是高位先行MyI2C_W_SDA(Byte&(0x80>>i));MyI2C_W_SCL(1);MyI2C_W_SCL(0);}
}/**
* @brief 从机发送一个字节给主机----主机接收字节从机发送数据,使用主机需要把SDA的控制权交从机(SDA置1),
循环8次:从机把数据放在SDA上,主机可以读取数据;拉低SCL从机放数据* @retval 无*/
uint8_t MyI2C_ReceiveByte(void)
{ uint8_t Byte=0x00; //0000 0000MyI2C_W_SCL(0);MyI2C_W_SDA(1); //从机发送数据,主机需要把SDA的控制权给从机for (uint8_t i=0;i<8;i++){ // |---置1MyI2C_W_SCL(1);if(MyI2C_R_SDA()==1){Byte |=(0x80>>i);} //0x80 1000 0000MyI2C_W_SCL(0);}return Byte;
}/**
* @brief 主机在接收完一个字节之后,在下一个时钟发送一位数据* @param AckBit 要发送的应答* @retval 无*/
void MyI2C_SendAck(uint8_t AckBit)
{ MyI2C_W_SCL(0);MyI2C_W_SDA(AckBit);MyI2C_W_SCL(1);MyI2C_W_SCL(0);
}
/**
* @brief 主机在发送完一个字节之后,在下一个时钟接收一位数据,* @retval 无*/
uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit;MyI2C_W_SDA(1);//主机接收应答,把SDA的控制权给从机MyI2C_W_SCL(1);AckBit = MyI2C_R_SDA();MyI2C_W_SCL(0);return AckBit;}#include "stm32f10x.h" // Device header
#include "MYI2C.h"
#include "MUP6050_Rge.h"
#define MPU6050_addrees 0xD0/**
* @brief 指定地址写* @param RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器* @param Data 第三个字节,实际在RegAddress地址的寄存器下写入的数据* @retval 无*/void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{MYI2C_Start();MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址MyI2C_ReceiveAck();MyI2C_SendByte(RegAddress);MyI2C_ReceiveAck();MyI2C_SendByte(Data);MyI2C_ReceiveAck();MYI2C_Stop();}/**
* @brief 指定地址读* @param RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器* @retval 无*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{ uint8_t data;MYI2C_Start();MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址MyI2C_ReceiveAck();MyI2C_SendByte(RegAddress);MyI2C_ReceiveAck();MYI2C_Start();MyI2C_SendByte(MPU6050_addrees|0x01);//0000 0001 因为要进行写入的操作,所以最后一位要置1MyI2C_ReceiveAck();data=MyI2C_ReceiveByte();MyI2C_SendAck(1);MYI2C_Stop();return data;
}/**
* @brief 读取MPU6050的id号码
MPU6050_WHO_AM_I 0x75 MPU6050的id号码在0x75这个寄存器里面* @retval 无*/
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}void MPU6050_init(void)
{MyI2C_Init();//写寄存器--应该先解除芯片的睡眠模式MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//电源管理寄存器1配置MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//电源管理寄存器2配置MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//采样分频器寄存器的配置MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//配置寄存器的配置MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//陀螺仪传感器寄存器的配置MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//加速度配置寄存器的配置
}void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{uint8_t DataH, DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);*AccX = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);*AccY = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);*AccZ = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);*GyroX = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);*GyroY = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);*GyroZ = (DataH << 8) | DataL;
}#ifndef __MPU6050_RGE_H
#define __MPU6050_RGE_H#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75#endifint main(void)
{ //I2C测试
// uint8_t ACK;
// OLED_Init();
// MyI2C_Init();
//
// MYI2C_Start();
// MyI2C_SendByte(0XD0);
// ACK = MyI2C_ReceiveAck();
// MYI2C_Stop();
// OLED_ShowNum(1,1,ACK,3);//MPU6050的MPU6050_ReadReg测试// uint8_t ID;
// OLED_Init();
// MPU6050_init();
//
// OLED_ShowString(1, 1, "ID:");
// ID = MPU6050_ReadReg(0x75);
// OLED_ShowHexNum(1, 4, ID, 2);
// OLED_ShowNum(2, 4, ID, 2);
//
// MPU6050的MPU6050_WriteReg写寄存器--应该先解除芯片的睡眠模式
// MPU6050_WriteReg(0x6B,0x00);//解除芯片的睡眠模式
// MPU6050_WriteReg(0x19,0x66);
// uint8_t num=MPU6050_ReadReg(0x19);
// OLED_ShowHexNum(3, 4, num, 2);//------------------------------------------------------------------------------------uint8_t ID;int16_t AX, AY, AZ, GX, GY, GZ;OLED_Init();MPU6050_init();OLED_ShowString(1, 1, "ID:");ID = MPU6050_GetID();OLED_ShowHexNum(1, 4, ID, 2);while (1){MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);OLED_ShowSignedNum(2, 1, AX, 5);OLED_ShowSignedNum(3, 1, AY, 5);OLED_ShowSignedNum(4, 1, AZ, 5);OLED_ShowSignedNum(2, 8, GX, 5);OLED_ShowSignedNum(3, 8, GY, 5);OLED_ShowSignedNum(4, 8, GZ, 5);}
}
主机先把SDA置1, 在读取SDA有意义?
uint8_t MyI2C_ReceiveAck(void) {uint8_t AckBit;MyI2C_W_SDA(1);//主机接收应答,把SDA的控制权给从机MyI2C_W_SCL(1);AckBit = MyI2C_R_SDA();MyI2C_W_SCL(0);return AckBit;}
第一 : I2C的引脚都是开漏输出+弱上拉的配置, 主机输出SDA为1, 并不是强制SDA为高电频, 而是释放SDA.
第二 : I2C是在在进行通信, 主机释放SDA, 从机如果在的话,会把SDA拉低, 所以即使主机之前把SDA置1了, 之会在读取SDA的值,也可能为0. 如果读取的结果为0的话代表了从机给了应答.
不断读取SDA,没有写入,读取的结果始终相同?
uint8_t MyI2C_ReceiveByte(void) { uint8_t Byte=0x00; //0000 0000MyI2C_W_SCL(0);MyI2C_W_SDA(1); //从机发送数据,主机需要把SDA的控制权给从机for (uint8_t i=0;i<8;i++){ // |---置1MyI2C_W_SCL(1);if(MyI2C_R_SDA()==1){Byte |=(0x80>>i);} //0x80 1000 0000MyI2C_W_SCL(0);}return Byte; }
I2C正在进行通信, 它是有从机的存在的, 当主机不断驱动SDA时钟时, 从机有义务改变SDA的电频; 所以主机在每次循环读取SDA的时候, 读取的数据是受从机控制的, 这个数据也是从机想要给主机发送的数据
B:硬件I2C读写 MPU6050
1:简介
STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
支持多主机模型
支持7位/10位地址模式
支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
支持DMA
兼容SMBus协议
STM32F103C8T6 硬件I2C资源:I2C1、I2C2
支持多主机模型-----STM32采用可以变多主机的模式, 谁想做主机谁跳出
兼容SMBus协议------系统管理总线, 主要用于电源管理系统中 ,是由l2C改进而来的
2:l2C框图
发送数据 : 是这里的数据寄存器(DATA_REGISTER)和数据移位寄存器; 需要发送数据时可以把一个字节数据写到数据寄存器DR中, 当移位寄存器没有数据移位时, 数据寄存器的值就会转到移位寄存器里面去,; 在移位的过程中就可以把新的数据放在数据寄存器里面; 一旦前一个数据移位完成, 数据就可以无锋衔接,继续发送, 当光据由数据寄存器转到移位寄存器时--------就会置状态奇存器的TXE位为1(发送寄存器位空)
接收数据 :
![]()
自身地址寄存器和双地址寄存器 : STM32采用的是可以变多主机的模式, 这个是32作为从机的时候使用的, 32在不进行通信的时候为从机
3:l2C基本结构
使用硬件l2C------GPIO复用开漏输出; 复用,就是GPIO的状态是交由片上外设来控制的, 开漏输出,这是12C协议要求的端口配置
4:主机发送
操作流程
5:主机接收
6:连接图
硬件中的通信引脚不能随便连接需要查看引脚定义表
7:函数介绍
在stm32f10x i2c.h文件中-----初始化I2C
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
在stm32f10x i2c.h文件中-----生成起始条件
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState)
在stm32f10x i2c.h文件中-----生成终止条件
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState)
在stm32f10x i2c.h文件中-----32作为主机时,是否给从机应答
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState)
作用: 把ACK置1; STM作为主机时: ACK=1 给从机应答; ACK=0 给从机非应答
在stm32f10x i2c.h文件中-----发送数据
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data)
实际就是把data数据直接写到DR寄存器里面去
在stm32f10x i2c.h文件中------发送7位地址的专用函数, 第一个字节
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)
在stm32f10x i2c.h文件中-----读取数据
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
在stm32f10x i2c.h文件中-----状态监控函数
ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
这种方式就是同时判断一个或多个标志位 , 来确定EV几EV几这个状态是否发生
8:代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MYI2C.h"
#include "MPU6050.h"#define MPU6050_addrees 0xD0void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{uint32_t Timeout;Timeout = 10000;while (I2C_CheckEvent(I2Cx, I2C_EVENT) ==ERROR){Timeout --;if (Timeout == 0){break;}}
}/**
* @brief 指定地址写* @param RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器* @param Data 第三个字节,实际在RegAddress地址的寄存器下写入的数据* @retval 无*/void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
// MYI2C_Start();
// MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址
// MyI2C_ReceiveAck();
//
// MyI2C_SendByte(RegAddress);
// MyI2C_ReceiveAck();
//
// MyI2C_SendByte(Data);
// MyI2C_ReceiveAck();
// MYI2C_Stop();
//-----------------------------------------------------------------------------I2C_GenerateSTART(I2C2,ENABLE);MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5I2C_Send7bitAddress(I2C2,MPU6050_addrees,I2C_Direction_Transmitter);//用于写从机的地址MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//发送时的EV6I2C_SendData(I2C2,RegAddress);MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8I2C_SendData(I2C2,Data);MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2 结束时产生EV8_2的标志位I2C_GenerateSTOP(I2C2,ENABLE);}/**
* @brief 指定地址读* @param RegAddress 第二个字节,在要操作MPU6050下RegAddress地址的寄存器* @retval 无*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
// uint8_t data;
// MYI2C_Start();
// MyI2C_SendByte(MPU6050_addrees);//叫MPU的名字--写入要操作的外设地址
// MyI2C_ReceiveAck();
// MyI2C_SendByte(RegAddress);
// MyI2C_ReceiveAck();
//
//
// MYI2C_Start();
// MyI2C_SendByte(MPU6050_addrees|0x01);//0000 0001 因为要进行写入的操作,所以最后一位要置1
// MyI2C_ReceiveAck();
// data=MyI2C_ReceiveByte();
// MyI2C_SendAck(1);
// MYI2C_Stop();
// return data;
//----------------------------------------------------------------------------- uint8_t Data;I2C_GenerateSTART(I2C2,ENABLE);MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5I2C_Send7bitAddress(I2C2,MPU6050_addrees,I2C_Direction_Transmitter);//用于写从机的地址MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//发送时的EV6I2C_SendData(I2C2,RegAddress);MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2I2C_GenerateSTART(I2C2,ENABLE);MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5I2C_Send7bitAddress(I2C2,MPU6050_addrees,I2C_Direction_Receiver);//用于写从机的地址MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//接收时的EV6//进入接收模式I2C_AcknowledgeConfig(I2C2, DISABLE);//这个终止条件,也不会截断当前字节, 当前字节接收完成后,再产生终止条件的波形I2C_GenerateSTOP(I2C2, ENABLE);MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED); //EV7Data = I2C_ReceiveData(I2C2);I2C_AcknowledgeConfig(I2C2, ENABLE);//默认状态下ACK就是1,给从机应答return Data;}/**
* @brief 读取MPU6050的id号码
MPU6050_WHO_AM_I 0x75 MPU6050的id号码在0x75这个寄存器里面* @retval 无*/uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}void MPU6050_init(void)
{ //MyI2C_Init();RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//复用开漏输出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_structinit;I2C_structinit.I2C_Ack=I2C_Ack_Enable; //应答位---操作ACK用于确定在接收一个字节后是否给从机应答I2C_structinit.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;//32做为从机: 响应机为的地址I2C_structinit.I2C_ClockSpeed=50000;I2C_structinit.I2C_DutyCycle=I2C_DutyCycle_2;//时钟占空比参数----高电频:低电频=2:1 ; I2C_DutyCycle_16_9---高电频:低电频=16:9I2C_structinit.I2C_Mode=I2C_Mode_I2C;I2C_structinit.I2C_OwnAddress1=0x00;//自身地址1 当32为从机是, 主机呼唤它的地址;于I2C_AcknowledgedAddress关联;/*I2C_AcknowledgedAddress 给7位时; 自身地址1 I2C_OwnAddress1---写7位I2C_AcknowledgedAddress 给10位时; 自身地址1 I2C_OwnAddress1---写10位*/I2C_Init(I2C2,&I2C_structinit);I2C_Cmd(I2C2,ENABLE);//写寄存器--应该先解除芯片的睡眠模式MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//电源管理寄存器1配置MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//电源管理寄存器2配置MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//采样分频器寄存器的配置MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//配置寄存器的配置MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//陀螺仪传感器寄存器的配置MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//加速度配置寄存器的配置
}void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{uint8_t DataH, DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);*AccX = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);*AccY = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);*AccZ = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);*GyroX = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);*GyroY = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);*GyroZ = (DataH << 8) | DataL;
}int main(void)
{ //I2C测试
// uint8_t ACK;
// OLED_Init();
// MyI2C_Init();
//
// MYI2C_Start();
// MyI2C_SendByte(0XD0);
// ACK = MyI2C_ReceiveAck();
// MYI2C_Stop();
// OLED_ShowNum(1,1,ACK,3);//MPU6050的MPU6050_ReadReg测试// uint8_t ID;
// OLED_Init();
// MPU6050_init();
//
// OLED_ShowString(1, 1, "ID:");
// ID = MPU6050_ReadReg(0x75);
// OLED_ShowHexNum(1, 4, ID, 2);
// OLED_ShowNum(2, 4, ID, 2);
//
// MPU6050的MPU6050_WriteReg写寄存器--应该先解除芯片的睡眠模式
// MPU6050_WriteReg(0x6B,0x00);//解除芯片的睡眠模式
// MPU6050_WriteReg(0x19,0x66);
// uint8_t num=MPU6050_ReadReg(0x19);
// OLED_ShowHexNum(3, 4, num, 2);//------------------------------------------------------------------------------------uint8_t ID;int16_t AX, AY, AZ, GX, GY, GZ;OLED_Init();MPU6050_init();OLED_ShowString(1, 1, "ID:");ID = MPU6050_GetID();OLED_ShowHexNum(1, 4, ID, 2);while (1){MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);OLED_ShowSignedNum(2, 1, AX, 5);OLED_ShowSignedNum(3, 1, AY, 5);OLED_ShowSignedNum(4, 1, AZ, 5);OLED_ShowSignedNum(2, 8, GX, 5);OLED_ShowSignedNum(3, 8, GY, 5);OLED_ShowSignedNum(4, 8, GZ, 5);}
}
注意部分
//进入接收模式I2C_AcknowledgeConfig(I2C2, DISABLE);//这个终止条件,也不会截断当前字节, 当前字节接收完成后,再产生终止条件的波形I2C_GenerateSTOP(I2C2, ENABLE);MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED); //EV7Data = I2C_ReceiveData(I2C2);I2C_AcknowledgeConfig(I2C2, ENABLE);//默认状态下ACK就是1,给从机应答return Data;;
进入到主机接收的模式, 就开始接收从机发的数据波形了, 在接收一个字节时,有个EV6 1事件, 这个事件没有标志位,也不需要我们等待适合接收1个字节的情况
读取1个字节: 也就是上面的代码 , 恰好在EV6之后,要清除响应和停止条件的产生, 我们要把应答位ACK置0,同时把停止条件生成位STOP置1; 规定:在接收最后一个字节之前, 就要提前把ACK置0,同时设置停止位STOP
读取多个字节: 那直接等待EV7事件,读取DR,就能收到数据了, 在接收最后一个字节之前,也就是这里的EV7-1事件, 需要提前把ACK置0,STOP置1
硬件的接收应答 : 并不需要一个函数来操作, 发送数据都自带了接收应答的过程, 同样,接收数据也自带了发送应答的过程, 所以发送和接受数据不需要我们处理应答位
相关文章:

10:STM32------I2C通信
目录 一:I2C通信协议 1:I2C简历 2:硬件电路 3:I2C时序基本单元 A : 开/ 终条件 2:发送一个字节 3:接收一个字节 4:应答机制 4:I2C时序 1:指定地址写 2:当前地址读 3: 指定地址读 二:MPU6050 1:简历 2:参数 3:硬件电路 4:框图 5:寄存器地址 …...

Git多人开发解决冲突案例
准备工作: 1.创建一个gitee远程仓库https://gitee.com/xxxxxxx.git 2.初始化两个本地git仓库用户,目的是模拟多人协作开发时提交代码发生冲突的场景 3.解决冲突并提交。 进入正题: lisi 通过vim指令修改readme.md文件内容,推送到…...

医疗机构如何维护电力系统?来看看这个小技巧
在现代医疗领域,电力是不可或缺的。从手术室里的手术灯到病房中的呼吸机,医院的各种医疗设备和系统都依赖于稳定的电源供应。 然而,电力中断和紧急情况不可避免,而这些情况下的电力可靠性可能会直接影响病人的生命和健康。为了确保…...

时序预测 | MATLAB实现ELM极限学习机时间序列预测未来
时序预测 | MATLAB实现ELM极限学习机时间序列预测未来 目录 时序预测 | MATLAB实现ELM极限学习机时间序列预测未来预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.MATLAB实现ELM极限学习机时间序列预测未来; 2.运行环境Matlab2018及以上,data为数…...

【数据分享】1901-2022年我国省市县镇四级的逐年平均气温数据(免费获取/Shp/Excel格式)
气象数据在日常研究中非常常用,之前我们分享过来自国家青藏高原科学数据中心提供的1901-2022年1km分辨率逐月平均气温栅格数据,2001-2022年我国省市县镇四级的逐月平均气温数据,以及基于该栅格数据处理得到的1901-2022年1km分辨率的逐年平均气…...

【Axure高保真原型】日历日期原型模板
今天和大家分享日历日期的原型模板,包括月计划、周计划、日计划的原型案例,以及日期、时间、月份、区间选择器……具体效果可以点击下方视频观看 【原型预览及下载地址】 Axure 原型 备用地址:Untitled Document 【原型效果】 【原型效果…...

深入了解接口测试:Postman 接口测试指南
在现代软件开发生命周期中,接口测试是一个至关重要的部分。使用 Postman 这一工具,可以轻松地进行 接口测试。以下是一份简单的使用教程,帮助你快速上手。 安装 Postman 首先,你需要在电脑上安装 Postman。你可以从官网上下载并…...

【ROS】Ubuntu20.04+ROS Noetic 配置PX4-v1.12.2和Gazebo11联合仿真环境【教程】
【ROS】Ubuntu20.04ROS Noetic 配置PX4-v-v1.12.2和Gazebo11联合仿真环境【教程】 文章目录 【ROS】Ubuntu20.04ROS Noetic 配置PX4-v-v1.12.2和Gazebo11联合仿真环境【教程】0. 安装UbuntuROS1. 安装依赖2. 安装QGC地面站3. 配置PX4-v1.12.23.1 安装PX43.2 测试PX4是否成功安装…...

Java 代理模式之静态代理与动态代理
1,代理模式 代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。 代理模式的目的: (1)通过引入代理对象的方式来间接访问目标对象,防…...

打造基于终端命令行的IDE,Termux配置Vim C++开发环境
Termux配置Vim C开发环境,打造基于终端命令行的IDE 主要利用VimCoc插件,配置C的代码提示等功能。 Termux换源 打开termux,输入termux-change-repo 找到mirrors.tuna.tsinghua.edu.cn,清华源,空格选中,回…...

【初阶C语言】操作符2---表达式求值
前言:本节重点介绍操作符的使用,如,优先级高低、类型转换等 一、逻辑操作符 前言:逻辑操作符包括逻辑与(&&)和逻辑或(||),操作对象:两个 1.逻辑与&…...
代码随想录day50|123. 买卖股票的最佳时机 III188. 买卖股票的最佳时机 IV
123. 买卖股票的最佳时机 III class Solution:def maxProfit(self, prices: List[int]) -> int:dp[[0]*5 for _ in range(len(prices))]dp[0][0]0dp[0][1]-prices[0]dp[0][2]0dp[0][3]-prices[0]dp[0][4]0for i in range(1,len(prices)):dp[i][0] dp[i-1][0]dp[i][1] max…...

Word 表格单元格无法垂直居中
Word使用 由于平时也需要用到word编写一些文档,但是咱们就是用的少,很多操作或者技巧不太清楚,很多小问题处理起来反而需要消耗很多时间,所以在这里记录平时遇到的一些问题。 表格无法垂直居中 类似于上图的情况,总之…...
python实现Flask POST Demo
数据处理逻辑 from flask import Flask, requestapp Flask(__name__)app.route(/, methods[POST]) def index():username request.form[username]password request.form[password]if username "Jhon" and password "1":return f"<html>&l…...
3-Pytorch张量的运算、形状改变、自动微分
3-Pytorch张量的运算、形状改变、自动微分 1 导入必备库2 张量的运算3 张量的算数运算4 一个元素的张量可以使用tensor.item()方法转成标量5 torch.from_numpy()和tensor.numpy()6 张量的变形7 张量的自动微分8 使用with torch.no_grad():包含上下文中使其不再跟踪计算9 使用te…...

用户权限数据转换为用户组列表(3/3) - Excel PY公式
最近Excel圈里的大事情就是微软把PY塞进了Excel单元格,可以作为公式使用,轻松用PY做数据分析。系好安全带,老司机带你玩一把。 实例需求:如下是AD用户的列表,每个用户拥有该应用程序的只读或读写权限,现在需要创建新的…...

VS2022+CMAKE+OPENCV+QT+PCL安装及环境搭建
VS2022安装: Visual Studio 2022安装教程(千字图文详解),手把手带你安装运行VS2022以及背景图设置_vs安装教程_我不是大叔丶的博客-CSDN博客 CMAKE配置: win11下配置vscodecmake_心儿痒痒的博客-CSDN博客 OPENCV配…...

JavaScript的内置类
一、认识包装类型 1.原始类型的包装类 JavaScript的原始类型并非对象类型,所以从理论上来说,它们是没有办法获取属性或者调用方法的。 但是,在开发中会看到,我们会经常这样操作: var message "hello world&q…...
6.英语的十六种时态(三面旗):主动、被动、肯定、否定、一般疑问句、特殊疑问句。
目录 一、do句型(以动词allow举例)。 (1)主动语态表格。 (2)被动语态表格。 (3)否定。 二、be句型(表格里的时态可以参考,查不到对应的资料)…...
SpringBoot连接Redis与Redisson【代码】
系列文章目录 一、SpringBoot连接MySQL数据库实例【tk.mybatis连接mysql数据库】 二、SpringBoot连接Redis与Redisson【代码】 三、SpringBoot整合WebSocket【代码】 四、SpringBoot整合ElasticEearch【代码示例】 文章目录 系列文章目录代码下载地地址一、引入依赖二、修改配…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...