STM32快速复习(七)IIC通信

文章目录

  • 前言
  • 一、IIC是什么?
  • 二、标准库函数
  • 二、标准库示例代码
  • 总结


前言

IIC通信算是我在大学和面试中用的最多,问的最多的通信协议
工作后也经常用到,只是我负责的工作内容用的少,但是,一般项目中使用也是非常多的一个。
我大学记IIC,印象最突出的一点就是,一问一答。
上位机给下位机发指令,下位机一定要回复。
具体来讲:上位机发送地址,下位机回复应答信号,上位机发送读/写指令,下位机回复应答信号,上位机发送数据写入/读取当前地址指令,下位机回复应答信号,结束

因为会发送地址,所以IIC可以连接多个设备,每个设备会有不同的地址。(大白话)


一、IIC是什么?

I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步,半双工
带数据应答
支持总线挂载多设备(一主多从、多主多从)
高位先行。

关于STM32的IIC
STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
支持多主机模型
支持7位/10位地址模式
支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
支持DMA
兼容SMBus协议

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外设时注意四部分:

  1. RCC时钟:现实需要初始化GPIO、I2C两个外设的时钟。
  2. GPIO外设:都要配置成复用开漏输出模式。“复用”是交给片上外设来控制,“开漏输出”是I2C协议要求的端口配置。
  3. I2C外设:老套路了,先定义结构体,再调用一个I2C_Init即可。
  4. 开关控制:使能I2C外设。
    注:上图简化成“一主多从模型”,所以SCL只有时钟输出。“多主机模型”时,SCL也会有输入。

I2C外设初始化完成以后,还要考虑引脚的时序如何变化,才能使得硬件I2C真正的能收发数据。下面介绍硬件I2C的操作流程,编写I2C的读写时序需要参考下面由st公司给出的时序图。
与软件I2C中CPU可以实时控制引脚变化不同,硬件I2C使用片上外设I2C来控制引脚变化,CPU不具有引脚的直接控制权,所以只能靠读取标志位来判断当前时序进行到哪一步了。参考手册中给出了“从机发送”、“从机接收”、“主机发送”、“主机接收”。由于本节是“一主多从”模式,所以只介绍“主机发送”、“主机接收”:

STM32发送和接收
在这里插入图片描述

  1. “EVx”事件:刚才提到CPU靠读取标志位来获取当前的时序状态,但有的状态会产生多个标志位,所以EVx事件就是组合了多个标志位的一个大标志位。
  2. 关于应答位:I2C库函数发送数据自带接收应答、接收数据自带发送应答,所以用户想要发送应答就需要在发送数据时同时配置是否发送应答(有专门的库函数控制应答使能),而想要接收应答就直接读取相应的EVx事件(有专门的库函数)即可。
    下面介绍发送时序:
    起始位、终止位:有相应的库函数可以直接调用。
    EV5:起始位发送完毕,可以写入从机地址。
    地址:检测到EV5后,就将数据放到DR上(SB自动清零),发送完成后硬件自动检测应答,无应答则置应答失败的标志位,此时可以用中断来提醒。
    EV6:从机地址发送完毕,可以写入新的数据到DR寄存器中。
    EV8_1:只是表明了存在这么一个过渡状态,实际上用不到这个事件。
    EV8:连续发送数据时,一旦检测到EV8事件,就可以再次写入数据。
    EV8_2:发送最后一个字节数据之后,就等这个事件发生,就可以产生停止位了。

在这里插入图片描述
注意没有给出“指定地址读”的时序,需要用户自行组合,也就是先用“主机发送”时序来一段“哑写”,然后再接着进行上图所示的“主机接收”时序。
“哑写”时序:按照“主机发送”时序,发送完寄存器地址后,直接等待EV8_2事件发生,然后直接产生“起始位”,接上下面的时序。
起始位、终止位:有相应的库函数可以直接调用。
EV5:起始位发送完毕,可以写入从机地址。
EV6:注意检测到这个事件后,要配置是否发送应答,因为这决定了是否是连续接收数据。若不发送应答,还要直接产生停止条件(硬件I2C不会立即产生);若发送应答,则需等待EV7。
EV7事件:表示当前可以读取数据。若上述没有发送应答,那么到这里时序结束。

作为一个通信协议,就必须在硬件和软件上都做出规定:
硬件上规定电路如何连接、端口的输入输出模式等
软件上规定通信时序、字节如何传输、高位/低位先行等
硬件:
所有I2C设备的SCL连在一起,SDA连在一起
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
在这里插入图片描述
在这里插入图片描述

软件:II2C通信的软件规定——时序结构(一主多从):

起始条件: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)。
在这里插入图片描述

于是下面就可以拼凑出完整的数据帧:
指定地址写:对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)。
在这里插入图片描述

当前地址读:对于指定设备(Slave Address),在 当前地址指针 (上电默认0x00)指示的地址下,读取从机数据(Data)。
在这里插入图片描述

指定地址读(更换地址):对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)。
在这里插入图片描述

二、标准库函数

I2C_DeInit :将I2C外设的寄存器恢复成默认值。
I2C_Init 【必需】:初始化I2C。
I2C_StructInit :将指定的I2C初始化结构体中的成员变量设置为默认值。
I2C_Cmd 【必需】:开启I2C外设。
收发时序常用:
I2C_GenerateSTART :生成起始条件。
I2C_GenerateSTOP :生成终止条件。
I2C_AcknowledgeConfig :配置应答使能。也就是stm32作为主机,收到1个字节后,是否给从机应答。
I2C_SendData :发送数据。将数据写入到DR寄存器。
I2C_ReceiveData :接收数据。从DR寄存器中读取数据。
I2C_Send7bitAddress :发送7位地址的专用函数。当然也可以用 I2C_SendData 函数完成该功能。
I2C_FastModeDutyCycleConfig :用于配置I2C的快速模式(Fast-mode)占空比。这个一般再初始化的时候就配置好不变了。
状态监控函数:(更多细节内容可以查看源码注释)
上文提到EVx事件可能会包含多个标志位,如果一个一个判断可能非常麻烦,所以i2c的库函数还额外给出了“状态监控函数”,来辅助用户同时判断多个标志位,以确定某个EVx状态是否发生。下面有三种方法:
1. 基本状态监控—— I2C_CheckEvent 【推荐】。可以同时判断一个或多个标志位,来确定EVx状态是否发生,UP主推荐。
2. 高级状态监控—— I2C_GetLastEvent 。实际并不高级,只是将两个状态寄存器SR1、SR2拼接成16位数据发送给用户。不推荐。
3. 基于标志位的状态监控—— I2C_GetFlagStatus 。就是一次判断一个标志位。

I2C_ClearFlag :清除标志位。主函数调用。
I2C_GetITStatus :读取中断标志位。中断函数调用。
I2C_ClearITPendingBit :清除中断标志位。中断函数调用。
I2C_ITConfig :使能或禁用I2C的中断功能。
DMA配置:
I2C_DMACmd :使能或禁用I2C外设的DMA传输,也就是DMA的硬件触发源。默认禁用。
I2C_DMALastTransferCmd :控制I2C外设在DMA传输结束后是否自动停止传输。
广播通信模式/双地址模式:
I2C_OwnAddress2Config :用于配置I2C外设的第二个从设备地址,该地址用于从设备模式中的广播通信。
I2C_DualAddressCmd :用于使能或禁用I2C外设的双地址模式。
I2C_GeneralCallCmd :使能或禁用I2C总线的广播通信模式。
SMBus协议:
I2C_SMBusAlertConfig :用于配置SMBus Alert功能。
PEC校验:
I2C_TransmitPEC :启用或禁用I2C协议中的PEC(Packet Error Checking)校验。
I2C_PECPositionConfig :配置PEC校验码在I2C通信帧中的位置。
I2C_CalculatePEC :用于计算给定数据块的PEC校验码。
I2C_GetPEC :用于获取接收到的I2C数据帧的PEC校验码。
其他配置:
I2C_ReadRegister :通用的读取寄存器函数,用于读取I2C外设的寄存器值。
I2C_SoftwareResetCmd :用于向I2C总线发送软件复位命令,将I2C外设复位到初始状态,以便重新开始通信。当I2C外设出现异常或通信故障时,可以通过软件复位命令将其恢复到正常状态。
I2C_NACKPositionConfig :用于配置I2C外设在接收数据时的非应答位位置,在数据字节的哪个位置发送NACK位。
I2C_ARPCmd :用于使能或禁用I2C的自动重试机制(Automatic Retry Mechanism)。自动重试机制的原理是当发送方无法收到接收方的应答信号时,会自动重新发送数据帧,直到接收方成功应答为止。自动重试机制的次数可以通过软件编程进行配置。
I2C_StretchClockCmd :用于使能或禁用I2C时钟延长功能(Clock Stretching)。I2C从设备可以通过拉低时钟线的方式来向主设备表示其未准备好接收数据,主设备需要等待从设备准备好之后再继续传输数据。

二、标准库示例代码

代码如下(示例):

//本文件 定义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;}//
-----------------------------------------------------以上IIC程序(通用)-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);}
--------------------------------------------------------------------
- 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);};}

总结

  1. 关于应答位。I2C库函数发送数据自带接收应答、接收数据自带发送应答,所以用户想要发送应答就需要提前配置,想要接收应答也就是读取相应的标志位。
  2. 关于起始条件 I2C_GenerateSTART 。这个起始条件会等待前一个字节发送完毕后才产生,并不会直接截断传送。所以发送数据后,即使等待EV8事件发生,就发送起始条件,也没问
    题。但是保险起见,还是等待EV8_2事件,再发送起始条件。
  3. 超时退出机制。程序中有大量的等待事件的while循环,万一有某个事件没有产生,程序就会卡死,非常危险,所以要设置超时退出机制。也不用很高大上,就是一个简单的计数退出就
    行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1474480.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Redis 五大数据类型底层原理

0、前言 本文涉及的主题&#xff1a; redis 对象存储 底层数据结构&#xff1a;int、embstr、raw、ziplist、listpack、quicklist、skiplist、intset、hashtable redis 数据类型&#xff1a;string、list、set、zset、hash 1、对象存储、底层编码、数据类型 1.1 对象存储…

linux RTC时钟时间出现了明显的偏移

RTC时钟时间出现了明显的偏移 1、开发环境2、问题阐述3、验证问题3.1、首先去排查了硬件电路和芯片电压不稳定的问题。3.2、晶振的问题。3.3、芯片本身3.4、芯片寄存器 4、代码修改 1、开发环境 平台&#xff1a;imx6ul kernel版本&#xff1a;linux4.1.5 RTC芯片&#xff1a;…

xxl-job集成SpringBoot

安装xxl-job客户端一般有很多方式&#xff0c;我这里给大家提供两种安装方式&#xff0c;包含里面的各项配置等等。 前期需要准备好MySQL数据库。复制SQL到数据库里面。 # # XXL-JOB v2.4.2-SNAPSHOT # Copyright (c) 2015-present, xuxueli.CREATE database if NOT EXISTS x…

001uboot体验

1.uboot的作用&#xff1a; 上电->uboot启动->关闭看门狗、初始化时钟、sdram、uart等外设->把内核文件从flash读取到SDRAM->引导内核启动->挂载根文件系统->启动根文件系统的应用程序 2.uboot编译 uboot是一个通用的裸机程序&#xff0c;为了适应各种芯片&…

Redis常用命令——Set、Zset篇

文章目录 一、Set相关命令操作 SADD SMEMBERS SISMEMBER SCARD SPOP SMOVE SREM SINTER 与 SINTERSTORE SUNION 与 SUNIONSTORE SDIFF 与 SDIFFSTORE Set命令小结 二、Zset 相关命令操作 ZADD ZCARD ZCOUNT ZRANGE ZREVRANGE ZPOPMAX BZPOPMAX ZPOPMIN 与 BZPOPMIN ZRANK 与 …

【刷题汇总--字符串中找出连续最长的数字串、岛屿数量、拼三角】

C日常刷题积累 今日刷题汇总 - day0071、字符串中找出连续最长的数字串1.1、题目1.2、思路1.3、程序实现 -- 比较1.4、程序实现 -- 双指针 2、岛屿数量2.1、题目2.2、思路2.3、程序实现 - dfs 3、拼三角3.1、题目3.2、思路3.3、程序实现 -- 蛮力法3.4、程序实现 -- 巧解(单调性…

Matlab协方差矩阵分解法生成随机场

Matlab协方差矩阵分解法生成随机场 相关系数矩阵 % function outcohesion(x,y,mu,theta) % end % xyload(F:\Research-OUC\基于机器许学习模型的海底斜坡可靠度研究\基于comsol的斜坡稳定性分析\comsol网格操作\grid_operate-matlab.mphtxt); % xxy(:,1); % yxy(:,2); Xlinspac…

贪心 | Java | LeetCode 455, 376, 53 做题总结

贪心算法介绍 贪心算法&#xff1a;贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 说实话贪心算法并没有固定的套路。 一般解题步骤 贪心算法一般分为如下四步&#xff1a; ① 将问题分解为若干个子问题 ② 找出适合的贪心策略 ③ 求解每一个子问题的…

从打印到监测:揭秘3D生物打印中天然水凝胶的创新之路?

从打印到监测&#xff1a;揭秘3D生物打印中天然水凝胶的创新之路&#xff1f; 在组织工程和再生医学领域&#xff0c;生物墨水是构建3D组织结构的关键材料。传统上&#xff0c;生物墨水主要基于细胞外基质 (ECM) 水凝胶&#xff0c;例如胶原蛋白、明胶、脱乙酰壳多糖、海藻酸盐…

Matplotlib 学习

知识点 1.plot()&#xff1a;用于绘制线图和 散点图scatter() 函数&#xff1a;plot() 函数可以接受许多可选参数&#xff0c;用于控制图形的外观&#xff0c;例如&#xff1a;颜色: colorblue 控制线条的颜色。线型: linestyle-- 控制线条的样式&#xff0c;例如虚线。标记…

ssrf结合redis未授权getshell

目录 漏洞介绍 SSRF Redis未授权 利用原理 环境搭建 利用过程 rockylinux cron计划任务反弹shell 写公钥免密登录 ubuntu 写公钥免密登录 漏洞介绍 SSRF SSRF&#xff08;server side request forgrey&#xff09;服务端请求伪造&#xff0c;因后端未过滤用户输入&…

昇思25天学习打卡营第19天|LSTM+CRF序列标注

概述 序列标注指给定输入序列&#xff0c;给序列中每个Token进行标注标签的过程。序列标注问题通常用于从文本中进行信息抽取&#xff0c;包括分词(Word Segmentation)、词性标注(Position Tagging)、命名实体识别(Named Entity Recognition, NER)等。 条件随机场&#xff08…

【OJ】运行时错误(Runtime Error)导致递归爆栈问题

在进行OJ赛时&#xff0c; 题目&#xff1a;给你一个整数n&#xff0c;问最多能将其分解为多少质数的和。在第一行输出最多的质数数量k,下一行输出k个整数&#xff0c;为这些质数。 出现运行时错误 代码如下&#xff1a; def main():# code heren int(eval(input()))list …

Vatee万腾平台:创新科技,驱动未来

在科技日新月异的今天&#xff0c;每一个创新的火花都可能成为推动社会进步的重要力量。Vatee万腾平台&#xff0c;作为科技创新领域的佼佼者&#xff0c;正以其卓越的技术实力、前瞻性的战略眼光和不懈的探索精神&#xff0c;驱动着未来的车轮滚滚向前。 Vatee万腾平台深知&am…

flask使用定时任务flask_apscheduler(APScheduler)

Flask-APScheduler描述: Flask-APScheduler 是一个 Flask 扩展&#xff0c;增加了对 APScheduler 的支持。 APScheduler 有三个内置的调度系统可供您使用&#xff1a; Cron 式调度&#xff08;可选开始/结束时间&#xff09; 基于间隔的执行&#xff08;以偶数间隔运行作业…

Linux系统安装软件包的方法rpm和yum详解

起因&#xff1a; 本篇文章是记录学习Centos7的历程 关于rpm 常见命令 1&#xff09;查看已经安装的软件包 rpm -q 软件包名 2&#xff09;查看文件的相关信息 rpm -qi 软件包名 3&#xff09;查看软件包的依赖关系 就是说要想安装这个软件包&#xff0c;就必须把一些前…

【matlab】状态空间模型与传递函数模型的建立与转换

目录 SISO系统 MIMO系统 状态空间模型 状态空间模型到传递函数模型的转换 传递函数模型到状态空间模型的转换 (1) 转换函数ss() (2) 规范形转换函数canon() (3) 常微分方程(传递函数)转换为状态空间模型函数dif2ss() 状态空间模型的变换 特征值、特征向量与广义特征向量的计算…

使用自动化测试确保接口正确性的详细指南!

引言&#xff1a; 随着软件开发的迅速发展&#xff0c;接口的正确性成为了确保应用程序质量的关键要素之一。自动化测试是一种强大的工具&#xff0c;可以帮助开发人员和测试人员减少错误&#xff0c;提高测试覆盖率&#xff0c;并加快测试过程。本文将详细介绍从零开始如何使…

阶段三:项目开发---搭建项目前后端系统基础架构:QA:可能遇到的问题及解决方案

任务实现 常见问题1&#xff1a;文件监视程序的系统限制。 1、错误提示&#xff1a;如果在Vue项目中&#xff0c;使用【 npm run serve】运行kongguan_web项目时报以下错误&#xff1a; 2、产生原因&#xff1a;文件监视程序的系统产生了限制&#xff0c;达到了默认的上限&am…

ubuntu软件源的两种格式和环境变量

1. ubuntu的/etc是什么目录&#xff1f; 在Ubuntu操作系统中&#xff0c;/etc/是一个特殊的目录&#xff0c;它包含系统的配置文件。这些配置文件用于设置各种系统和应用程序的参数和选项。 一般来说&#xff0c;用户可以在这个目录下找到各种重要的配置文件&#xff0c;如网络…