10:IIC通信
1:IIC通信
I2C总线(Inter IC BUS) 是由Philips公司开发的一种通用数据总线,应用广泛,下面是一些指标参数:
- 两根通信线:SCL(Serial Clock,串行时钟线)、SDA(Serial Data,串行数据线)。
- 同步,半双工。
- 主从机通信过程中,会带数据应答。
- 支持总线挂载多设备(一主多从、多主多从)。“多主多从”模式更复杂,下面没有特殊说明,默认都是“一主多从”模式。
- 高位先行。
那同步时序和异步时序的优缺点各是什么呢?
- 同步时序:优点是对时间要求不严格,可以软件手动翻转电平实现通信,所以可以大幅降低单片机对外围硬件电路的依赖,在一些低端单片机没有硬件资源的情况下,很容易使用软件来模拟时序;缺点就是多一个时钟线。
- 异步时序:优点是少一根时钟线,节省资源;缺点是对时间要求严格,对硬件电路的依赖严重。
一个通信协议,就必须在硬件和软件上都做出规定
- 硬件上规定电路如何连接、端口的输入输出模式等
- 软件上规定通信时序、字节如何传输、高位/低位先行等
硬件电路规定所有I2C设备的SCL连在一起,SDA连在一起。设备的SCL和SDA均要配置成 开漏输出模式。SCL和SDA各添加一个 上拉电阻,阻值一般为4.7KΩ左右。
总结:为防止总线没协调好,导致电源短路,采用 外置弱上拉电阻+开漏输出 的电路结构。
- CPU:主机,对总线控制权力大。任何时候,都是主机对SCL完全控制。空闲状态下,主机可以主动发起对SDA的控制。
- 被控ICx:从机,对总线控制权力小。任何时刻,从机都只能被动读取SCL线。只有在从机发送数据、或者从机发送应答位时,从机才短暂的具有对SDA的控制权。
- 从机设备地址:为了保证通信正常,每个从机设备都具有一个唯一的地址。在I2C协议标准中,从机设备地址分为7位地址、10位地址,其中7位较为简单且应用更广泛。不同的I2C模块出厂时,厂商都会为其分配唯一的7位地址,具体可以在芯片手册中查询,如MPU6050的地址是110_1000,AT24C02的地址是101_0000等,其中器件地址的最后几位是可以在电路中改变的。总线上都是不同模块一般不会有地址冲突,若总线上有相同的模块就需要外界电路来相应的器件地址。
- 通信引脚结构:输入线都很正常。输出线则采用开漏输出,引脚下拉是“强下拉”,引脚上拉则处于“浮空状态”。于是所有设备都只能输出低电平,为了避免高电平造成的引脚浮空,就需要在总线外面设置上拉电阻(弱上拉)。采用这个模式的好处:
- 完全杜绝了电源短路的情况,保证了电路安全。
2.避免了引脚模式的频繁切换。开漏+弱上拉的模式,同时兼具了输入和输出的功能。要想输出就直接操纵总线;要想输入就直接输出高电平,然后读取总线数据,无需专门将GPIO切换成输入模式。
3.此模式可以实现“线与”功能。只有总线上所有设备都输出高电平,总线才是高电平;否则只要有一个设备输出低电平,总线就是低电平。I2C可以利用这个特征,执行多主机模式下的时钟同步(所以SCL也采用此模式)和总线仲裁。
下面介绍I2C通信的软件规定-----时序结构(一主多从)
下面的这些操作的主语都是“主机”。
- 起始条件:SCL高电平期间,SDA从高电平切换到低电平。
- 终止条件:SCL高电平期间,SDA从低电平切换到高电平。
- 发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
- 接收一个字节:主机在接收之前释放SDA,只控制SCL变化。SCL低电平期间,从机将数据位依次放到SDA线上(高位先行,且一般贴着SCL下降沿变化);SCL高电平期间,主机读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节。
- 发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。若从机没有收到主机的应答,就会完全释放SDA的控制权,回到待机模式。
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。
2: MPU6050简介
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角(欧拉角),常应用于平衡车、飞行器等需要检测自身姿态的场景。以飞机为例,欧拉角就是飞机机身相对于初始3个轴的夹角——俯仰(Pitch)、滚转(Roll)、偏航(Yaw)。但是单一的加速度计、陀螺仪、磁力计都不能获得精确且稳定的欧拉角,只有综合多种传感器进行数据融合、取长补短,才能获得精确且稳定的欧拉角。常见的数据融合算法,一般有互补滤波、卡尔曼滤波等(惯性导航领域——姿态解算)。 下面给出MPU6050芯片的一些参数:
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度。本质上就是弹簧测力计:F = m a F=maF=ma。测量的值是合加速度,由于系统运动时有运动加速度的影响,所以只有在静态时才表示系统的姿态。也就是说,加速度计静态稳定,而动态不稳定。
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度。若想得到角度,只需要对角速度进行积分即可。陀螺仪的缺点是,物体静止时,会由于噪声导致角速度无法完全归零,于是就会导致积分得到的角度产生缓慢的漂移,积分时间越长漂移越明显,但当物体运动时就可以忽略漂移的大小。也就是说,陀螺仪动态稳定,而静态不稳定。
3轴磁场传感器(选修):测量X、Y、Z轴的磁场强度,某些芯片会有此功能。
气压传感器(选修):测量气压大小,一般气压值反映高度信息(海拔高度),某些芯片会有此功能。
注:于是姿态解算的大题思路就是:将“加速度计”、“陀螺仪”进行互补滤波,融合得到静态和动态都稳定的姿态角。
注:上图所示的机械结构只是便于理解,实际MPU6050芯片中不存在图示的机械结构。16位ADC采集传感器的模拟信号,量化范围:-32768~32767(有符号数)。
加速度计满量程选择:±2、±4、±8、±16(g,1g=9.8m/s2)。
陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)。
可配置的数字低通滤波器,使输出数据更加平缓。
可配置的时钟源、可配置的采样分频。时钟源经过分频后,为内部的ADC提供转换时钟
I2C从机地址(手册可以查看到):MPU6050的设备号固定为0x68(某些批次可能为0x98),所以可以用于验证I2C读出协议是否正常。对于出厂地址为0x68的芯片来说,调整AD0引脚电压就可以改变其从机地址:1101000(AD0=0)、1101001(AD0=1)。
实验1:软件读写MPU6050
通过I2C通信协议,对MPU6050内部的寄存器进行读写。写入到配置寄存器,就可以对这个外挂模块进行配置。读出数据寄存器,就可以获取外挂模块的数据。最终将读出的数据显示在OLED屏幕上。要求显示:设备的ID号、加速度传感器的三个输出数据、陀螺仪传感器的三个输出数据(角速度)。
注:串口是异步通信,时序严格,所以一般不用软件模拟;I2C等同步通信,对时序要求不严格,可以很方便的进行软件模拟。
注:软件模拟时,SCL和SDA都是普通的GPIO口,所以可以随便接。
注:SCL和SDA都应该有一个上拉电阻,在模块的电路中已经设计了这个上拉电阻,所以可以跳线直连GPIO口。
main.c
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "MPU6050.h"int main(void){MPU6050_DataStruct SensorData;//存放6轴传感器数据OLED_Init(); //OLED初始化 MPU6050_Init();//MPU6050初始化//显示一些基本信息OLED_ShowString(1,1,"ID:");OLED_ShowHexNum(1,4,MPU6050_GetID(),2);
// OLED_ShowString(1,1,"Acce: | Gyro:");OLED_ShowString(2,1," |");OLED_ShowString(3,1," |");OLED_ShowString(4,1," |");//不断获取传感器值并显示while(1){MPU6050_GetData(&SensorData);//原始数值
// OLED_ShowSignedNum(2,1,SensorData.Acce_X,5);
// OLED_ShowSignedNum(3,1,SensorData.Acce_Y,5);
// OLED_ShowSignedNum(4,1,SensorData.Acce_Z,5);
// OLED_ShowSignedNum(2,9,SensorData.Gyro_X,5);
// OLED_ShowSignedNum(3,9,SensorData.Gyro_Y,5);
// OLED_ShowSignedNum(4,9,SensorData.Gyro_Z,5);//转换成小数的数值OLED_ShowFloat(2,1,(float)SensorData.Acce_X/32768*20,3,1);OLED_ShowFloat(3,1,(float)SensorData.Acce_Y/32768*20,3,1);OLED_ShowFloat(4,1,(float)SensorData.Acce_Z/32768*20,3,1);OLED_ShowFloat(2,9,(float)SensorData.Gyro_X/32768*500,3,1);OLED_ShowFloat(3,9,(float)SensorData.Gyro_Y/32768*500,3,1);OLED_ShowFloat(4,9,(float)SensorData.Gyro_Z/32768*500,3,1);};
}
I2C_User.h
#ifndef __I2C_USER_H
#define __I2C_USER_Hvoid I2C_User_Init(void);
void I2C_User_Start(void);
void I2C_User_Stop(void);
void I2C_User_SendByte(uint8_t send_byte);
uint8_t I2C_User_RecvByte(void);
void I2C_User_SendAck(uint8_t AckBit);
uint8_t I2C_User_RecvAck(void);#endif
I2C_User.c
//本文件 定义I2C的6个基本的时序单元,供其他模块调用
#include "stm32f10x.h" // Device header
#include "Delay.h"//引脚操作的封装和改名,方便移植——移植时仅需修改本部分即可
//
//定义I2C通信的两个引脚-PB10为SCL、PB11为SDA
#define I2C_User_SCL_Port GPIOB
#define I2C_User_SDA_Port GPIOB
#define I2C_User_SCL GPIO_Pin_10
#define I2C_User_SDA GPIO_Pin_11
//#define I2C_User_SCL_High GPIO_SetBits (I2C_User_SCL_Port, I2C_User_SCL)
//#define I2C_User_SCL_Low GPIO_ResetBits(I2C_User_SCL_Port, I2C_User_SCL)
//#define I2C_User_SDA_High GPIO_SetBits (I2C_User_SDA_Port, I2C_User_SDA)
//#define I2C_User_SDA_Low GPIO_ResetBits(I2C_User_SDA_Port, I2C_User_SDA)
//写SCL的操作
void I2C_User_W_SCL(uint8_t BitValue){GPIO_WriteBit(I2C_User_SCL_Port,I2C_User_SCL,(BitAction)BitValue);Delay_us(3);// 延迟一位(I2C最大通信速率400kHz-2.5us)
}//写SDA的操作
void I2C_User_W_SDA(uint8_t BitValue){GPIO_WriteBit(I2C_User_SDA_Port,I2C_User_SDA,(BitAction)BitValue);Delay_us(3);// 延迟一位(I2C最大通信速率400kHz-2.5us)
}//读SDA的操作
uint8_t I2C_User_R_SDA(void){uint8_t BitValue=0;BitValue = GPIO_ReadInputDataBit(I2C_User_SDA_Port,I2C_User_SDA);Delay_us(3);// 延迟一位(I2C最大通信速率400kHz-2.5us)return BitValue;
}//初始化两个GPIO口
void I2C_User_Init(void){// 开启APB2-GPIOB的外设时钟RCCRCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// 初始化PA的输出端口:定义结构体及参数GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = I2C_User_SCL | I2C_User_SDA;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出GPIO_Init(GPIOB, &GPIO_InitStructure);// 默认输出为高电平-释放总线GPIO_SetBits(GPIOB, I2C_User_SCL | I2C_User_SDA);
}
//// 下面是I2C的六个基本时序-移植时无需修改
// 注:除了终止条件,SCL以高电平结束;其他时序都以SCL低电平结束
//
//1. 发送起始位
void I2C_User_Start(void){// 调整SCL、SDA输出均为高电平-保险起见,先将SDA拉高I2C_User_W_SDA(1);I2C_User_W_SCL(1);// SDA输出下降沿I2C_User_W_SDA(0);// 拉低SCLI2C_User_W_SCL(0);
}//2. 发送终止位
void I2C_User_Stop(void){// 调整SCL、SDA输出均为低电平I2C_User_W_SCL(0);I2C_User_W_SDA(0);// 拉高SCLI2C_User_W_SCL(1);// 拉高SDAI2C_User_W_SDA(1);
}//3. 发送一个字节
void I2C_User_SendByte(uint8_t send_byte){uint8_t i=0;for(i=0;i<8;i++){//改变SDAI2C_User_W_SDA((0x80>>i) & send_byte);//拉高SCLI2C_User_W_SCL(1);//拉低SCLI2C_User_W_SCL(0);}
}//4. 接收一个字节
uint8_t I2C_User_RecvByte(void){uint8_t recv_byte=0x00;uint8_t i=0;//主机释放总线I2C_User_W_SDA(1);//接收数据for(i=0;i<8;i++){// 拉高SCLI2C_User_W_SCL(1);// 读取数据if(I2C_User_R_SDA()==1){recv_byte |= (0x80>>i);}// 拉低SCLI2C_User_W_SCL(0);}return recv_byte;
}//5. 发送应答位
void I2C_User_SendAck(uint8_t AckBit){// 将应答位放在SDA上I2C_User_W_SDA(AckBit);// 拉高SCLI2C_User_W_SCL(1);// 拉低SCLI2C_User_W_SCL(0);
}//6. 接收应答位
uint8_t I2C_User_RecvAck(void){uint8_t AckBit=0;//主机释放总线I2C_User_W_SDA(1);// 拉高SCLI2C_User_W_SCL(1);// 读取应答信号AckBit = I2C_User_R_SDA();// 拉低SCLI2C_User_W_SCL(0);return AckBit;
}
//
MPU6050.h
#ifndef __MPU6050_H
#define __MPU6050_H//定义MPU6050的6轴数据结构体
typedef struct{int16_t Gyro_X;int16_t Gyro_Y;int16_t Gyro_Z;int16_t Acce_X;int16_t Acce_Y;int16_t Acce_Z;int16_t Temp;
}MPU6050_DataStruct;//外部可调用函数
void MPU6050_WriteReg(uint8_t RegAddr, uint8_t wData);
uint8_t MPU6050_ReadReg(uint8_t RegAddr);
void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(MPU6050_DataStruct* MPU6050_Data);#endif
MPU6050.c
#include "stm32f10x.h" // Device header
#include "I2C_User.h"
#include "MPU6050.h"//定义从机地址、寄存器地址
#define MPU6050_ADDRESS 0xD0
#define MPU6050_REG_SMPLRT_DIV 0x19
#define MPU6050_REG_CONFIG 0x1A
#define MPU6050_REG_GYRO_CONFIG 0x1B
#define MPU6050_REG_ACCEL_CONFIG 0x1C#define MPU6050_REG_ACCEL_XOUT_H 0x3B
#define MPU6050_REG_ACCEL_XOUT_L 0x3C
#define MPU6050_REG_ACCEL_YOUT_H 0x3D
#define MPU6050_REG_ACCEL_YOUT_L 0x3E
#define MPU6050_REG_ACCEL_ZOUT_H 0x3F
#define MPU6050_REG_ACCEL_ZOUT_L 0x40
#define MPU6050_REG_TEMP_OUT_H 0x41
#define MPU6050_REG_TEMP_OUT_L 0x42
#define MPU6050_REG_GYRO_XOUT_H 0x43
#define MPU6050_REG_GYRO_XOUT_L 0x44
#define MPU6050_REG_GYRO_YOUT_H 0x45
#define MPU6050_REG_GYRO_YOUT_L 0x46
#define MPU6050_REG_GYRO_ZOUT_H 0x47
#define MPU6050_REG_GYRO_ZOUT_L 0x48#define MPU6050_REG_PWR_MGMT_1 0x6B
#define MPU6050_REG_PWR_MGMT_2 0x6C
#define MPU6050_REG_WHO_AM_I 0x75//MPU6050指定地址写寄存器
void MPU6050_WriteReg(uint8_t RegAddr, uint8_t wData){I2C_User_Start();I2C_User_SendByte(MPU6050_ADDRESS);I2C_User_RecvAck();//暂时不对应答位进行处理I2C_User_SendByte(RegAddr);I2C_User_RecvAck();I2C_User_SendByte(wData);I2C_User_RecvAck();I2C_User_Stop();
}//MPU6050指定地址读寄存器
uint8_t MPU6050_ReadReg(uint8_t RegAddr){uint8_t rData=0x00;I2C_User_Start();I2C_User_SendByte(MPU6050_ADDRESS);I2C_User_RecvAck();//暂时不对应答位进行处理I2C_User_SendByte(RegAddr);I2C_User_RecvAck();I2C_User_Start();I2C_User_SendByte(MPU6050_ADDRESS | 0x01);I2C_User_RecvAck();//暂时不对应答位进行处理rData = I2C_User_RecvByte();I2C_User_SendAck(1);I2C_User_Stop();return rData;
}//MPU6050初始化
void MPU6050_Init(void){//I2C初始化I2C_User_Init();//不复位、解除睡眠、不开启循环模式、温度传感器失能、选择陀螺仪x轴时钟MPU6050_WriteReg(MPU6050_REG_PWR_MGMT_1,0x01);//没有开启循环模式MPU6050_WriteReg(MPU6050_REG_PWR_MGMT_2,0x00);//采样率10分频MPU6050_WriteReg(MPU6050_REG_SMPLRT_DIV,0x09);//不使用外部同步、DLPF设置等级6MPU6050_WriteReg(MPU6050_REG_CONFIG,0x06);//陀螺仪:自测失能、满量程±500°/s-000_01_000MPU6050_WriteReg(MPU6050_REG_GYRO_CONFIG,0x08);//加速度计:自测失能、满量程±2g、失能运动检测-000_00_000MPU6050_WriteReg(MPU6050_REG_ACCEL_CONFIG,0x00);
}//获取MPU6050的ID号
uint8_t MPU6050_GetID(void){return MPU6050_ReadReg(MPU6050_REG_WHO_AM_I);
}//获取MPU6050的传感器数据
void MPU6050_GetData(MPU6050_DataStruct* MPU6050_Data){uint16_t sensor_byte_L, sensor_byte_H;//获取加速度计数据sensor_byte_H = MPU6050_ReadReg(MPU6050_REG_ACCEL_XOUT_H);sensor_byte_L = MPU6050_ReadReg(MPU6050_REG_ACCEL_XOUT_L);MPU6050_Data->Acce_X = (int16_t)((sensor_byte_H<<8) | sensor_byte_L);sensor_byte_H = MPU6050_ReadReg(MPU6050_REG_ACCEL_YOUT_H);sensor_byte_L = MPU6050_ReadReg(MPU6050_REG_ACCEL_YOUT_L);MPU6050_Data->Acce_Y = (int16_t)((sensor_byte_H<<8) | sensor_byte_L);sensor_byte_H = MPU6050_ReadReg(MPU6050_REG_ACCEL_ZOUT_H);sensor_byte_L = MPU6050_ReadReg(MPU6050_REG_ACCEL_ZOUT_L);MPU6050_Data->Acce_Z = (int16_t)((sensor_byte_H<<8) | sensor_byte_L);//获取陀螺仪数据sensor_byte_H = MPU6050_ReadReg(MPU6050_REG_GYRO_XOUT_H);sensor_byte_L = MPU6050_ReadReg(MPU6050_REG_GYRO_XOUT_L);MPU6050_Data->Gyro_X = (int16_t)((sensor_byte_H<<8) | sensor_byte_L);sensor_byte_H = MPU6050_ReadReg(MPU6050_REG_GYRO_YOUT_H);sensor_byte_L = MPU6050_ReadReg(MPU6050_REG_GYRO_YOUT_L);MPU6050_Data->Gyro_Y = (int16_t)((sensor_byte_H<<8) | sensor_byte_L);sensor_byte_H = MPU6050_ReadReg(MPU6050_REG_GYRO_ZOUT_H);sensor_byte_L = MPU6050_ReadReg(MPU6050_REG_GYRO_ZOUT_L);MPU6050_Data->Gyro_Z = (int16_t)((sensor_byte_H<<8) | sensor_byte_L);//获取温度传感器数据sensor_byte_H = MPU6050_ReadReg(MPU6050_REG_TEMP_OUT_H);sensor_byte_L = MPU6050_ReadReg(MPU6050_REG_TEMP_OUT_L);MPU6050_Data->Temp = (int16_t)((sensor_byte_H<<8) | sensor_byte_L);
}
IIC外设简介
总的来说,由于串口是异步时序,对时序要求极其严格,所以几乎不会软件模拟串口,基本上一边倒的采用硬件实现串口通信。而对于I2C通信来说,尽管用硬件电路可以减轻CPU负担,但是硬件I2C总线数量有限、引脚固定等,也有很多限制;而由于I2C是同步时序,软件模拟I2C足够简单且灵活,所以还是有许多场合都使用软件I2C进行通信。若只是简单应用I2C读写数据,使用软件模拟更加灵活;若对时序要求高,可以使用硬件I2C。本节介绍硬件实现I2C的原理。
STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担,还兼具可以实现完整的多主机通信、时序波形规整、通信速率快等优点,功能非常强大。具体参数如下:
- 支持多主机模型(固定多主机/可变多主机)。stm32是按照“可变多主机”的模式设置硬件电路的,也就是说,需要自己声明自己是主机。
- 支持7位/10位地址模式。10位地址发送两字节寻址,高5位固定为11110(这种开头不会在7位地址中出现),低10位作为地址(第一个字节的最低位还是R/W)。
- 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)。只要不超过最大频率,多少都可以。
- 支持DMA。多字节传输时可提高传输效率,如指定地址 读/写 多字节。若只有几个字节也没必要配置DMA。
- 兼容SMBus(System Management Bus)协议。SMBus是基于I2C总线改进而来的,主要用于电源管理系统中。本节用不到。
- STM32F103C8T6 硬件I2C资源:I2C1、I2C2。
SDA部分:
- 发送数据过程:首先将数据写入“数据寄存器DR”,当没有移位时,DR中的数据就被转运到“数据移位寄存器”中,并同时置状态寄存器的TXE位为1(发送寄存器空)。此时就可以继续向DR中写入数据。
- 接收数据流程:将数据一位一位的写入“数据移位寄存器”,当一个字节的数据收齐后,将会将数据整体从“数据移位寄存器”转运到“数据寄存器DR”,并同时置标志位RXNE(接收寄存器非空)。此时就可以将数据从DR中读出。
- 比较器和地址寄存器(用不到):从机模式使用。由于stm32的I2C是基于可变多主机模型设计的,不进行通信时默认为“从机”,于是“自身地址寄存器”就用于存放从机地址,可以由用户指定。“双地址寄存器”存储的也是从机地址,于是stm32就可以同时响应两个从机地址。
- 数据校验模块(用不到):当发送一个多字节的数据帧时,“帧错误校验计算”可以硬件自动执行CRC校验计算,得到一个字节的校验位附加在数据帧的最后。同样的,当接收的校验字节和CRC计算结果不匹配时,就会置校验错误标志位。
- 评价:整个数据收发流程类似于“串口通信”,只不过串口是全双工通信,收发电路独立;I2C是半双工通信,收发共用一个电路。
- SCL部分:
- 时钟控制:控制SCL线,具体细节无需了解。
- 时钟控制寄存器CCR:控制“时钟控制”电路执行相应的功能。
- 控制逻辑电路:写入“控制寄存器”,可以对整个电路进行控制;读取“状态寄存器”,可以得知整个电路的工作状态。
- 中断:内部某些事情比较紧急时,可以申请中断。
- DMA请求与响应:在进行很多字节的收发时,可以配合DMA来提高效率。
初始化I2C外设时注意四部分:
- RCC时钟:现实需要初始化GPIO、I2C两个外设的时钟。
- GPIO外设:都要配置成复用开漏输出模式。“复用”是交给片上外设来控制,“开漏输出”是I2C协议要求的端口配置。
- I2C外设:老套路了,先定义结构体,再调用一个I2C_Init即可。
- 开关控制:使能I2C外设。
- 注:上图简化成“一主多从模型”,所以SCL只有时钟输出。“多主机模型”时,SCL也会有输入。
相关文章:

10:IIC通信
1:IIC通信 I2C总线(Inter IC BUS) 是由Philips公司开发的一种通用数据总线,应用广泛,下面是一些指标参数: 两根通信线:SCL(Serial Clock,串行时钟线)、SDA&a…...

互联网上门洗衣洗鞋小程序优势有哪些?
互联网洗鞋店小程序相较于传统洗鞋方式,具有以下优势; 1. 便捷性:用户只需通过手机即可随时随地下单并查询,省去了许多不必要的时间和精力。学生们无需走出宿舍或校园,就能轻松预约洗鞋并取件。 2. 精准定位࿱…...
Java中如何优雅地根治null值引起的Bug问题
1. Java对象为null会引发的问题 NullPointerException:当你尝试调用或访问一个null对象的属性或方法时,Java会抛出NullPointerException异常。例如,如果你有一个名为person的变量,它被设置为null,然后你尝试调用perso…...

C# WPF上位机开发(子窗口通知父窗口更新进度)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 这两天在编写代码的时候,正好遇到一个棘手的问题,解决之后感觉挺有意义的,所以先用blog记录一下,后…...

XUbuntu22.04之跨平台容器格式工具:MKVToolNix(二百零三)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…...
vue中的生命周期和VueComponent实例对象
生命周期 生命周期又叫生命周期钩子,生命周期函数 生命周期是,Vue在关键的时刻帮我们调用的一些特殊名字的函数 生命周期的this指向vm或者组件实例对象 mounted会将初始化的Dom挂载到页面上 <template><div class"hello"><…...

Hooked协议掀起WEB3新浪潮
随着区块链技术和加密货币的兴起,币圈已经成为全球范围内的一个热门领域。在这个充满机遇与挑战的行业中,Hook机制正逐渐成为一种重要的技术手段,为投资者、开发者以及相关机构提供了更多的选择和可能性。本文将详细介绍币圈中的Hook机制&…...

【图文教程】windows 下 MongoDB 介绍下载安装配置
文章目录 介绍MySQL 之间的区别和适用场景差异数据模型:查询语言:可扩展性:数据一致性: 下载安装环境变量配置 介绍 MongoDB 是一种开源的、面向文档的 NoSQL 数据库管理系统。它使用灵活的文档模型来存储数据,这意味…...

算法复杂度-BigO表示法
1.时间复杂度--大O表示法 算法的渐进时间复杂度,T(n)O(f(n)) T(n)表示算法的渐进时间复杂度 f(n)表示代码执行的次数 O()表示正比例关系 2.常用的时间复杂度量级 3.举例 (1&am…...
测试理论知识五:功能测试、系统测试、验收测试、安装测试、测试的计划与控制
模块测试的目的是发现程序模块与其接口规格说明之间的不一致。 功能测试的目的是为了证明程序未能符合其外部规格说明。 系统测试的目的是为了证明软件产品与其初始目标不一致。 1. 功能测试 功能测试是一个试图发现程序与其外部规格说明之间存在不一致的过程。功能测试通…...

太阳能爆闪警示灯
适用场所: 适用于高压线,塔吊,路政,船舶,种植,塔机,航海航道等场所起警示作用。 产品特点: 光控无开关,白天不闪,昏暗环境自动闪烁,无需手动操作,省时省事; 采用红色LED作光源,亮度高&#…...

怎么为pdf文件添加水印?
怎么为pdf文件添加水印?PDF是一种很好用的文件格式,这种格式能够很有效的保护我们的文件,但有时可能还会被破解,这种时候在PDF上添加水印就是比较好的方法。 综上所述,PDF是保密性很强的文件,但添加水印能够…...

基于ssm医药信息管理系统论文
基于SSM的医药信息管理系统的设计与实现 摘要 当下,正处于信息化的时代,许多行业顺应时代的变化,结合使用计算机技术向数字化、信息化建设迈进。以前相关行业对于医药信息的管理和控制,采用人工登记的方式保存相关数据ÿ…...
Ceph存储体系架构?
Ceph体系架构主要由RADOS和RADOS GW和RBD以及CephFS构成。 RADOS(Reliable, Autonomic Distributed Object Store)是Ceph的底层核心,RADOS本身也是分布式存储系统,CEPH所有的存储功能都是基于RADOS实现。RADOS由两个组件组成&…...

详解现实世界资产(RWAs)
区块链中的现实世界资产(RWAs)是代表实际和传统金融资产的数字通证,如货币、大宗商品、股票和债券。 实际世界资产(RWA)的通证化是区块链行业中最大的市场机会之一,潜在市场规模可达数万万亿美元。理论上&…...

Windows漏洞利用开发——利用ROP绕过DEP保护
实验6 Windows漏洞利用开发 6.1实验名称 Windows漏洞利用开发 6.2实验目的 学习windows漏洞利用开发,使用kali linux相关工具对windows内目标程序进行漏洞利用 6.3实验步骤及内容 第三阶段:利用ROP绕过DEP保护 了解DEP保护理解构造ROP链从而绕过DEP…...

合并两个有序链表算法(leetcode第21题)
题目描述: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1:输入:l1 [1,2,4], l2 [1,3,4] 输出:[1,1,2,3,4,4] 示例 2:输入:l1 [], l2 [] 输…...

二维码初体验 com.google.zxing 实现续 - web api封装
文章目录 一、概述二、最终效果三、源码结构四、完整代码 一、概述 在 二维码初体验 com.google.zxing 实现 我们实现了二维码的生成,但是大部分情况下,二维码的相关功能是作为API接口来提供服务的。 我们下面便演示在springboot、Knife4j下封装api接口…...

Hadoop入门学习笔记——四、MapReduce的框架配置和YARN的部署
视频课程地址:https://www.bilibili.com/video/BV1WY4y197g7 课程资料链接:https://pan.baidu.com/s/15KpnWeKpvExpKmOC8xjmtQ?pwd5ay8 Hadoop入门学习笔记(汇总) 目录 四、MapReduce的框架配置和YARN的部署4.1. 配置MapReduce…...

list集合
List集合 List集合的概述 有序集合(也称之为序列),用户可以精确的控制列表中的每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素 与 Set 集合不同,列表通常允许重复的元素 List 集合的特点 有…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...