当前位置: 首页 > news >正文

【STM32单片机】#11.5 I2C通信(硬件读写)

主要参考学习资料:

B站@江协科技

STM32入门教程-2023版 细致讲解 中文字幕

开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwd=dspb

单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协

目录

  • I2C外设简介
    • I2C框图
    • I2C基本结构
    • 主机发送
    • 主机接收
  • 函数详解
    • I2C_DeInit函数
    • I2C_Init函数
      • I2C_InitTypeDef结构体
    • I2C_StructInit函数
    • I2C_Cmd函数
    • I2C_GenerateSTART函数
    • I2C_GenerateSTOP函数
    • I2C_AcknowledgeConfig函数
    • I2C_SendData函数
    • I2C_ReceiveData函数
    • I2C_Send7bitAddress函数
    • I2C_CheckEvent函数
    • 中断标志位函数
  • 实验25 硬件I2C读写MPU6050
    • MPU6050驱动

I2C外设简介

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

I2C框图

I2C外设框图左侧SDA、SCL为通信引脚,具体复用的GPIO口见引脚定义表,SMBALERT为SMBus所用引脚。数据控制部分中,收发数据的核心部分为数据寄存器(DR)和数据移位寄存器,流程与串口通信相似。发送数据时,可以将一个字节的数据写入DR,DR在移位寄存器空闲时直接将数据转移进去,并将标志位TXE置一,随后移位寄存器进行数据移位,DR可接收下一个字节的数据;接收数据时,输入数据从引脚移位到移位寄存器,当一个字节的数据移位完成后,数据从移位寄存器转移到DR,并将标志位RXNE置一,此时可以读取数据。起始条件、终止条件和应答位均由控制电路完成。

比较器、自身地址寄存器和双地址寄存器用于STM32作从机时的通信,自身地址寄存器存储STM32的从机地址。当STM32作为从机被寻址时,如果收到的寻址通过比较器判断和自身地址相同,则STM32做出响应。STM32还支持同时响应两个从机地址,因此拥有双地址寄存器。帧错误校验寄存器和帧错误校验计算自动执行CRC校验计算,进行数据有效性验证。本实验不涉及这些功能。下方SCL引脚部分,时钟控制和控制逻辑电路均可视为黑盒,暂时无需掌握原理细节。

I2C基本结构

本实验所需I2C结构简化如下:

主机发送

上图为主机发送流程图,只需关注7位发送。其中EVx为通信的不同阶段触发的事件,库函数中有对应的检查EVx是否发生的函数,可以将其理解为大标志位。

发送时,需通过在控制寄存器I2C_CR1的START位写1使STM32自动产生起始条件。检测到起始条件发送后,即可将从机地址写入DR中由硬件电路自动发送,随后硬件自动接收应答并判断,若无应答,硬件置应答失败标志位,可申请中断。寻址完成后相继触发EV6和EV8_1,此时需要我们写入DR进行数据发送。移位寄存器发送数据时产生EV8事件,EV8事件期间即可继续向DR写入数据并清除事件,因此图中的EV8结束在了下一次数据发送前的任意时刻。如果移位寄存器已经发送完数据,DR仍没有新数据,则表示字节发送结束,并产生EV8_2事件,可以通过在I2C_CR1的STOP位写1自动产生停止条件。

主机接收

接收时,将START位写1产生起始条件,EV5事件可写入地址,EV6事件表示寻址完成,EV6_1在接收数据第一个字节时产生。接收完每个字节的数据之前,需要在I2C_CR1的ACK配置好应答使能,写0无应答,写1应答。EV7在移位寄存器接收完数据转移到DR后产生,此时可读取数据并清除事件。若不需要再接收数据,需要在最后一个时序单元发生提前向ACK写0并设置STOP请求,即EV7_1事件。

函数详解

I2C_DeInit函数

简介:恢复缺省配置。

参数:I2C名称

I2C1/2

I2C_Init函数

简介:初始化I2C。

参数一:I2C名称

参数二:I2C_InitTypeDef结构体指针

I2C_InitTypeDef结构体

成员I2C_Mode:模式

I2C_Mode_I2C(I2C)
I2C_Mode_SMBusDevice(SMBus设备)
I2C_Mode_SMBusHost(SMBus主机)

成员I2C_ClockSpeed:时钟频率,标准速度0~100KHz,快速100~400KHz。

成员I2C_DutyCycle:时钟占空比,标准速度固定1:1,快速可选下列参数(低电平:高电平)。

I2C_DutyCycle_16_9(16:9)
I2C_DutyCycle_2(2:1)

由于I2C数据线在通信过程中为强下拉、弱上拉,因此SDA电平由低到高需要一定的翻转时间,在快速模式中需适当延长SCL低电平时间。

成员I2C_Ack:应答位配置

I2C_Ack_Enable/Disable

成员I2C_AcknowledgedAddress:STM32作为从机响应的地址位数

I2C_AcknowledgedAddress_10bit/7bit

成员I2C_OwnAddress1:STM32作为从机的自身地址

I2C_StructInit函数

简介:初始化I2C_InitTypeDef结构体。

参数:I2C_InitTypeDef结构体指针

I2C_Cmd函数

简介:I2C使能。

参数一:I2C名称

参数二:使能/失能

I2C_GenerateSTART函数

简介:生成起始条件。

参数一:I2C名称

参数二:使能/失能

I2C_GenerateSTOP函数

简介:生成起始条件。

参数一:I2C名称

参数二:使能/失能

I2C_AcknowledgeConfig函数

简介:配置ACK位。

参数一:I2C名称

参数二:使能/失能

I2C_SendData函数

简介:向DR写入数据。

参数一:I2C名称

参数二:数据

I2C_ReceiveData函数

简介:读取DR数据。

参数:I2C名称

I2C_Send7bitAddress函数

简介:发送7位地址。

参数一:I2C名称

参数二:地址

参数三:读写模式

I2C_Direction_Transmitter(写入)
I2C_Direction_Receiver(读取)

I2C_CheckEvent函数

简介:检测事件。

参数一:I2C名称

参数二:事件

返回值

SUCCESS, ERROR

中断标志位函数

I2C_GetFlagStatus函数

I2C_ClearFlag函数

参数一:I2C名称

参数二:I2C标志位

I2C_FLAG_TXE
I2C_FLAG_RXNE
其余暂时不用

I2C_GetITStatus函数

I2C_ClearITPendingBit函数

参数一:I2C名称

参数二:I2C中断源

实验25 硬件I2C读写MPU6050

接线图同实验24,引脚PB10/11对应I2C2。

将实验24MyI2C驱动删除,在MPU6050驱动中使用内置库函数。

MPU6050驱动

头文件不变,源文件将MPU6050中MyI2C函数全部替换为库函数,为方便参考改前代码均留作注释。

MPU6050.c

#include "stm32f10x.h"
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS 0xD0void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS);
//	MyI2C_ReceiveAck();
//	MyI2C_SendByte(RegAddress);
//	MyI2C_ReceiveAck();
//	MyI2C_SendByte(Data);
//	MyI2C_ReceiveAck();
//	MyI2C_Stop();I2C_GenerateSTART(I2C2, ENABLE);//I2C函数仅配置寄存器,其执行完毕不代表波形发送完毕,需检测对应事件//等待EV5while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);//函数根据传输方向自动调整地址所在字节的最低位I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//应答位由硬件自动完成,第一次发送地址等待EV6(发送模式)while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);I2C_SendData(I2C2, RegAddress);//中间连续发送等待EV8while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);I2C_SendData(I2C2, Data);//最后一次发送等待EV8_2while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);I2C_GenerateSTOP(I2C2, ENABLE);
}uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS);
//	MyI2C_ReceiveAck();
//	MyI2C_SendByte(RegAddress);
//	MyI2C_ReceiveAck();
//	
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
//	MyI2C_ReceiveAck();
//	Data = MyI2C_ReceiveByte();
//	MyI2C_SendAck(1);
//	MyI2C_Stop();//发送地址部分移用自WriteReg函数I2C_GenerateSTART(I2C2, ENABLE);while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);I2C_SendData(I2C2, RegAddress);//由于在从机地址之后只发送寄存器地址一个数据,此处EV8改为EV8_2while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);//重复起始条件I2C_GenerateSTART(I2C2, ENABLE);//等待EV5while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);//方向变为接收I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);//等待EV6(接收模式)while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);//单字节(最后一个字节)接收,需在EV6后立刻配置好寄存器的非应答和停止条件,否则收到数据时应答已发送I2C_AcknowledgeConfig(I2C2, DISABLE);I2C_GenerateSTOP(I2C2, ENABLE);//等待EV7while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);//读取DRData = I2C_ReceiveData(I2C2);//寄存器应答恢复默认配置以便接收多字节I2C_AcknowledgeConfig(I2C2, ENABLE);return Data;
}void MPU6050_Init(void)
{
//	MyI2C_Init();RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;//复用开漏输出,外设使用复用,I2C协议要求开漏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_InitStructure;//初始化默认给应答I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;I2C_InitStructure.I2C_ClockSpeed = 50000;//时钟占空比在标准速度下无效I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//非从机模式地址任意I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;I2C_InitStructure.I2C_OwnAddress1 = 0x00;I2C_Init(I2C2, &I2C_InitStructure);I2C_Cmd(I2C2, ENABLE);//I2C初始化完毕MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);MPU6050_WriteReg(MPU6050_CONFIG, 0x06);MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}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;
}

该代码为了检测事件使用了大量while死循环等待,一旦一个事件未发生,则整个程序都会卡死。可通过封装超时退出的while循环等待函数解决该问题:

//参数复制自I2C_CheckEvent函数
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{uint32_t Timeout;Timeout = 10000;while(I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS){Timeout --;if(Timeout == 0)break;}
}

随后将I2C_CheckEvent函数均替换为封装函数即可。

主程序不变。

http://www.xdnf.cn/news/239869.html

相关文章:

  • TM1668芯片学习心得三
  • Qwen3-32B的幻觉问题
  • Windows系统安装Docker(Win10系统升级,然后安装)
  • UE 像素和线框盒子 材质
  • 【AI图像创作变现】08 变现渠道—间接获客:让客户主动找上门
  • 广州创科——湖北房县汪家河水库除险加固信息化工程
  • 【Android】轻松实现实时FPS功能
  • [Survey] Image Segmentation in Foundation Model Era: A Survey
  • AI赋能烟草工艺革命:虫情监测步入智能化时代
  • MySQL中ROW_NUMBER() OVER的用法以及使用场景
  • 【Java函数式编程-58.1】深入理解Java中的Consumer函数式接口
  • 冠军之选,智启未来——解码威码逊的品牌传奇与冠军代言故事
  • 客户联络中心如何进行能力建设?
  • 【SAM2代码解析】数据集处理3--混合数据加载器(DataLoader)
  • 中国县级2m精度耕地分布数据(2020年)
  • 深度学习概述
  • Silo 科学数据工具库安装与使用指南
  • 【closerAI ComfyUI】开源社区炸锅!comfyUI原生支持Step1X-Edit 图像编辑!离简单免费高效又进一步
  • 关键词排名工具查到的位置和真实搜索差距大是什么原因?
  • SpringBoot优雅关机
  • MicroPython 开发ESP32应用教程 之 ADC及应用实例:电池电量检测并显示
  • HarmonyOS NEXT应用开发-Notification Kit(用户通知服务)notificationManager.cancelAll
  • ComfyUI
  • 国标GB28181平台EasyGBS未来研发方向在哪?
  • 数字中国开新篇,数智化为何需要新引擎
  • SLAM中的状态估计理论:从基础到前沿的完整解析
  • C++初阶:类和对象(二)
  • 机器学习|通过线性回归了解算法流程
  • spring 面试题
  • 智能 + 安全:婴幼儿托育管理实训基地标准化建设方案