网站制作成功案例,胖小七网站建设,一般app需要多少钱,qq小程序开放平台STM32快速入门#xff08;总线协议之I2C一主多从#xff08;软件实现 硬件实现#xff09;#xff09;
前言
支持一对多#xff08;一主多从#xff09;、多对多传输#xff08;多主多从#xff09;#xff0c;只支持半双工#xff0c;一般有两根数据线…STM32快速入门总线协议之I2C一主多从软件实现 硬件实现
前言
支持一对多一主多从、多对多传输多主多从只支持半双工一般有两根数据线SCLSerial Clock Line、SDA(Serial Data Line)
I2C有两种实现方式一种是GPIO软件模拟另一种是直接使用I2C硬件。
I2C软件实现
软件实现I2C的好处就是我们可以任意的选取GPIO口作为SDA、SCL线。
硬件电路的设计
硬件连接图如下 SDA是数据线SCL是时钟线。时钟线完全由主机控制。而从机只有对SDA的短暂控制权。通常使用I2C通通信SCL和SDA线上都会接一个上拉电阻要么是在外设的内部要么是在外面可以直接通过外设的硬件接线图看到。接下来讨论外接电阻存在的意义。
如下 在配置成推挽输出模式下可以直接读输入寄存器从而获取引脚状态。但推挽输出配置外接电阻是没有任何意义的因为输出0或输出1完全由IO决定。可以说只要IO口是推挽输出的状态那么SDA线的控制权就不可能移交给从机。想要放弃对SDA的控制就必须将IO口配置成输出的模式。甚至在极端情况下在主输出1从输出0会造成短路。 在配置成开漏输出模式下也可以直接读输入寄存器从而获取引脚状态通过置输出寄存器为1这样引脚处于高阻态引脚悬空主机可以方便的释放SDA的控制权。不需要像推挽输出那样配置成输入模式。通过读取输入寄存器可以方便的接收数据。主机、外设引脚都处于开漏模式需要输出0就向输出寄存器写0将引脚拉低需要输出1就向输出寄存器写1将引脚断开让上拉电阻提供一个弱上拉。并且也不存在短路的风险即使在主输出1从输出0的情况下因为高电平是上拉电阻提供中间怎么都会存在电阻。
所以一因为推挽输出输出的是强高低电平在同一时刻主机上拉输出1某一个从机下拉输出0这样就造成了短路所以为了防止这种情况所有机器的SCL、SDA都被配置成开漏输出只能输出强低电平0同时外接一个弱的上拉电阻以输出弱的高电平杜绝了短路去危险情况。同时外接上拉电阻 开漏输出不需要频繁切换输入输出模式。
所以我们实现的软件I2C也会将SCL、SDA引脚置为开漏输出的模式。
I2C的起始位 停止位
起始位和停止位的波形如下 简述一下起始位就是在SCL高电平期间将SDA拉低停止位就是在SCL为高电平期间将SDA拉高。
这样设计的目的和I2C数据位的传输有关I2C中一个数据位的传输遵循在SCL低电平期间写数据的一方向SDA写1释放SDA或者写0拉低SDA同时读的一方不允许去读数据SDA。在SCL高电平期间读数据的一方在SDA上进行读数据同时写的一方不想允许更改数据SDA。也就是所谓的‘低电平放高电平取’原则。
而起始位和停止位恰好违背上面的原则让通信双方能够很好的辨识出一个通信的周期。
此外系统空闲时两根总线都是被弱上拉拉高的高电平状态。至于为什么是弱上拉我们后面讨论。
首部
紧跟起始位的是设备地址 寄存器地址
对于7位设备地址
第一个字节必须是
| 设备地址7位 | 读写位1位|# 读写位为0代表写为1代表读。对于10位设备地址
前两个字节必须是
| 1111 0 | 设备地址2位 | 读写位1位 | | 设备地址8位 |# 读写位为0代表写为1代表读。本博客主要讨论7位地址的情况。
在写设备地址之后的一个字节是要写的寄存器地址寄存器地址就是要写的设备的寄存器的物理地址一般是8位地址。I2C中只有写才有寻址的能力第二个发送的字节为寄存器地址读只能顺序去读不能直接发送寄存器地址。要想实现随机读就需要在发送一个‘幽灵’写设备地址后重新发送一个起始位 读设备地址从而实现了随机读。简单讲随机读就是一种复合模式。也即随机读 随机写 直接读。
在帧头传输完毕后就是正式读写数据的传输。
I2C一个Byte的发送
写遵循低放高取原则因为是写操作在传输数据时从机不具备操作SDA的权利从机的SDA引脚保持高组态SDA的高低电平由主机决定主机通过操作SCL控制数据的传输进度。但是当一个Byte位传输完毕主机会短暂释放SDA等待从机回复一个bit位的ACK此时从机短暂拥有SDA的控制权。多次写入的数按最开始的寄存器地址依次排列。主机发送停止位代表传输周期结束。 主机发送起始位 主机发送写设备地址 主机获取从机的ack 主机发送寄存器地址 主机获取从机的ack 主机发送待写数据 主机获取从机的ack重复6~7步可以重复写 主机发送停止位
注意1 I2C是高位先行。
注意2 连续的写会纯在单页回滚问题eeprom默认1页是8Byte写到8的整数倍会导致指针混滚这点需要注意。
I2C一个Byte的读取
读取同样遵循低放高取原则因为是读操作在传输数据时主机不具备操作SDA的权利主机的SDA引脚保持高组态SDA的高低电平由从机决定主机通过操作SCL控制数据的传输进度。但是当一个Byte位接收完毕从机会短暂释放SDA等待主机回复一个bit位的ACK此时主机短暂拥有SDA的控制权当主机回复0ACK从机会继续发送数据当主机回复1NACK从机会停止发送数据。一般情况下主机回复NACK后会接着回复一个停止位。
直接顺序读 主机发送起始位 主机发送读设备地址 主机获取从机的ack 主机读取从机发来的数据 主机发送NACK结束传输ACK可以回到4继续读 主机送停止位
顺序读地址只能从从设备的0开始每次读从设备的地址指针会自增1。不存在混滚问题。
随机读 主机发送起始位 主机发送写设备地址 主机获取从机的ack 主机发送寄存器地址 主机获取从机的ack 主机发送起始位 Restart 主机发送读设备地址 主机获取从机的ack 主机读取从机发来的数据 主机发送NACK结束传输ACK可以回到9继续读 主机送停止位
总结一下读写特点写可以寻址寻址从设备的寄存器读只能顺序读随机读 随机写 顺序读。
软件实现核心代码
需要另外注意的是如果要对EEPROM外设进行读写在写后不能直接进行读需要延时至少5ms给EEPROM一点操作时间否则直接读出来的数据我测的是一直为0xff
代码如下
/*i2c.h
*/#define SCL_PORT GPIOB
#define SCL_PIN GPIO_Pin_6#define SDA_PORT GPIOB
#define SDA_PIN GPIO_Pin_7// .../*i2c.c
*//*** description: I2C端口GPIO初始化* return {*}*/
void Lunar_I2CInit(void) {GPIO_InitTypeDef GPIOB_Cfg;// PARCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// 两个引脚都配置为开漏输出GPIOB_Cfg.GPIO_Mode GPIO_Mode_Out_OD;GPIOB_Cfg.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7;GPIOB_Cfg.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOB, GPIOB_Cfg);// 最开始都为释放状态的高电平Lunar_SetSCL(Bit_SET);Lunar_SetSDA(Bit_SET);
}/*** description: 控制SCL引脚的电平状态完全由主机控制* param {uint8_t} BitVal* return {*}*/
void Lunar_SetSCL(uint8_t BitVal) {GPIO_WriteBit(SCL_PORT, SCL_PIN, (BitAction)BitVal);// 来一点点延时Delay_Us(10);
}/*** description: 控制SDA引脚的状态可由主机控制也可由从机控制* param {uint8_t} BitVal* return {*}*/
void Lunar_SetSDA(uint8_t BitVal) {GPIO_WriteBit(SDA_PORT, SDA_PIN, (BitAction)BitVal);// 来一点点延时Delay_Us(10);
}/*** description: 获取SDA电平状态* return {uint8_t}*/
uint8_t Lunar_GetSDA(void) {uint8_t rt GPIO_ReadInputDataBit(SDA_PORT, SDA_PIN);// 来一点点延时Delay_Us(10);return rt;
}/*************************************************************************************************//*** description: 发送一个起始位* return {void}*/
void Lunar_Start(void) {// 先将SCL、SDA拉高Lunar_SetSCL(1);Lunar_SetSDA(1);if (Lunar_GetSDA() 0) {printf(error slave hold the SDA!!!\r\n);}// 再将SDA拉低Lunar_SetSDA(0);// 再将SCL拉低、准备发送数据Lunar_SetSCL(0);
}/*** description: 发送一个停止位* return {void}*/
void Lunar_Stop(void) {// 确保SDA为0Lunar_SetSDA(0);// 将SCL拉高Lunar_SetSCL(1);// 将SDA拉高Lunar_SetSDA(1);if (Lunar_GetSDA() 0) {printf(error slave hold the SDA!!!\r\n);}// Lunar_SetSCL(0);
}/*** description: 写状态下等待从设备ACK的回复* return {uint8_t}读到ACK还是NACK*/
uint8_t Lunar_ReadACK(void) {uint8_t rt 0;// 释放SDALunar_SetSDA(1);// 拉高SCLLunar_SetSCL(1);// 读SDArt Lunar_GetSDA();Lunar_SetSCL(0);return rt;}/*** description: 读状态下向从设备回复一个ACK* return {*}*/
void Lunar_WriteACK(void) {// SDA置0Lunar_SetSDA(0);// SCL置1Lunar_SetSCL(1);// SCL置0Lunar_SetSCL(0);// 释放SDA拥有权Lunar_SetSDA(1);
}/*** description: 读状态下向从设备回复一个NACK* return {*}*/
void Lunar_WriteNACK(void) {// SDA置1Lunar_SetSDA(1);// SCL置1Lunar_SetSCL(1);// SCL置0Lunar_SetSCL(0);// 释放SDA拥有权Lunar_SetSDA(1); // ???
}/*** description: 读一个Byte* return {uint8_t}返回读到的Byte*/
uint8_t Lunar_ReadByte(void) {uint8_t rt 0;// 此出主机不应该拥有SDA的控制权Lunar_SetSDA(1);for (int i 0; i 8; i) {// 先拉高SCLLunar_SetSCL(1);// 读SDArt | Lunar_GetSDA() (7 - i);printf(%d, , rt);// 拉低SCLLunar_SetSCL(0);}printf(\r\n);return rt;
}/*** description: 写一个Byte* param {uint8_t} data要写入的数据* return {*}*/
void Lunar_WriteByte(uint8_t data) {// 此处主机应该拥有SDA的控制权for (int i 0; i 8; i) {// 写一个bitLunar_SetSDA((data (7 - i)) 1);// 拉高SCLLunar_SetSCL(1);// 拉低SCLLunar_SetSCL(0);}
}/*** description: 获取指定设备指定寄存器地址的值* param {uint8_t} device_addr设备地址是7位* param {uint8_t} registry_addr* return {uint8_t}*/
uint8_t Lunar_GetRegistryByte(uint8_t device_addr, uint8_t registry_addr) {uint8_t rt 0;uint8_t tag 0;// 发送起始位Lunar_Start();// 发送设备地址Lunar_WriteByte((device_addr 1) | 0); // 写设备地址// 获取从机的acktag tag || Lunar_ReadACK();// 发送寄存器地址Lunar_WriteByte(registry_addr);// 获取从机的acktag tag || Lunar_ReadACK();// Restart// 发送起始位Lunar_Start();// 发送设备地址Lunar_WriteByte((device_addr 1) | 1); // 读设备地址// 获取从机的acktag tag || Lunar_ReadACK();// 读取从机发来的数据rt Lunar_ReadByte();// 发送NACK结束传输Lunar_WriteNACK();// 发送停止位Lunar_Stop();if (tag) {printf(in Lunar_GetRegistryByte Lunar_ReadACK faild!\r\n);}return rt;
}/*** description: 设置指定设备指定寄存器地址的值* param {uint8_t} device_addr* param {uint8_t} registry_addr* param {uint8_t} data* return {*}*/
void Lunar_SetRegistryByte(uint8_t device_addr, uint8_t registry_addr, uint8_t data) {uint8_t tag 0;// 发送起始位Lunar_Start();// 发送设备地址Lunar_WriteByte((device_addr 1) | 0); // 写设备地址// 获取从机的acktag tag || Lunar_ReadACK();// 发送寄存器地址Lunar_WriteByte(registry_addr);// 获取从机的acktag tag || Lunar_ReadACK();// 发送待写数据Lunar_WriteByte(data);// 获取从机的acktag tag || Lunar_ReadACK();// 发送停止位Lunar_Stop();if (tag) {printf(in Lunar_SetRegistryByte Lunar_ReadACK faild!\r\n);}
}
I2C硬件实现
导航
图242 I2C框图 实现细节
从图242可以看到和USART类似I2C也配备了一组移位寄存器 数据寄存器的组合只不过I2C只有一根数据线所以只有一组寄存器组合。
对于于比较器和自身地址寄存器I2C硬件可以在主从两种模式下工作当作为从模式时设备就会拥有一个设备地址自身地址寄存器就是存储该从设备地址的地方比较器就是完成设备地址匹配的功能。此外I2C可以通过帧错误校验计算模块完成CRC校验的功能。
然后就是SCL时钟线的时钟控制模块其作用就是控制SCL线时钟的具体实现细节我没有去深究但它的作用肯定是根据I2C规定的协议去控制SCL引脚上的时钟。
最后左下部分就是一些可以让用户控制的寄存器这些寄存器的作用都可以在中文手册中查阅。
这里贴一张来自江协科技的I2C简化框图 使用库函数实现硬件I2C的套路
写流程 发送起始位。 等待EV5事件也就是等待BUSY(SCL被拉低) MSL处于主模式在start被发出硬件自动置位 SBstart被发出 被置位。 发送写设备地址。 等待EV6发送事件也就是BUSYSCL被拉低 MSL处于主模式在start被发出硬件自动置位 ADDR设备地址已经发送并且收到从机的ACK TXE发送寄存器为空 TRA发送模式依据发送设备地址最后一位设置 被置位。 这里有一个理论上的EV8_1事件但是库函数没有对应的宏大体意思就是发送寄存器和发送移位寄存器同时为空。 发送寄存器地址。 等待EV8事件也就是TRA发送模式依据发送设备地址最后一位设置 BUSYSCL被拉低 MSL处于主模式在start被发出硬件自动置位 TXE发送寄存器为空收到从机的ACK回复才会置位被置位。 发送待写数据。 等待EV8_2事件也就是在EV8事件基础上多了一个BTF标志位代表发送寄存器和发送移位寄存器都空了数据线上也没有需要传输的数据了该发的数据发干净了。 发送停止位请求停止传输。
重复执行8~9可实现多次写。
这里可以配合中文手册的图理解写的套路 读流程 发送起始位。 等待EV5事件也就是等待BUSY(SCL被拉低) MSL处于主模式在start被发出硬件自动置位 SBstart被发出 被置位。 发送写设备地址。 等待EV6发送事件也就是BUSYSCL被拉低 MSL处于主模式在start被发出硬件自动置位 ADDR设备地址已经发送并且收到从机的ACK TXE发送寄存器为空 TRA发送模式依据发送设备地址最后一位设置 被置位。 这里有一个理论上的EV8_1事件但是库函数没有对应的宏大体意思就是发送寄存器和发送移位寄存器同时为空。起提示作用。 发送寄存器地址。 等待EV8_2事件也就是TRA发送模式依据发送设备地址最后一位设置 BUSYSCL被拉低 MSL处于主模式在start被发出硬件自动置位 TXE发送寄存器为空收到从机的ACK回复才会置位 BTF数据寄存器和移位寄存器都为空被置位。 发送起始位。 等待EV5事件也就是等待BUSY(SCL被拉低) MSL处于主模式在start被发出硬件自动置位 SBstart被发出 被置位。 发送读设备地址。 等待EV6接收事件也就是BUSYSCL被拉低 MSL处于主模式在start被发出硬件自动置位 ADDR设备地址已经发送并且收到从机的ACK 被置位。 EV6_1 / EV7_1 需要提前设置ack位和停止产生位。如果想继续读数据就不执行该步循环执行13~14即可实现多次读。 等待EV7事件也就是BUSYSCL被拉低 MSL处于主模式在start被发出硬件自动置位 RXNE接收数据寄存器非空被置位。 接收从机发来的数据。
这里可以配合中文手册的图理解读的套路手册提供的是直接顺序读的时序但是前面软件部分说过需要实现随机读可以使用‘幽灵’写 顺序读的方式实现上述步骤也正是这样做的。
这里可以配合中文手册的图理解读的套路 这里提供I2C_CR1寄存器一些相关位的描述方便读者分析 这里提供I2C_SR1寄存器一些相关位的描述方便读者分析 这里提供I2C_SR2寄存器一些相关位的描述方便读者分析 库函数实现代码
在实现硬件I2C时需要将SCL、SDA所在GPIO的引脚都配置为开漏复用输出的模式复用是为了将IO口控制权交给片上外设开漏是为了遵循协议。开漏的具体原因在软件部分也进行过深入探讨这里就不过多赘述。 在写硬件实现I2C的代码的时候出现了一个非常奇怪的BUG在第一次执行Lunar_GetRegistryByte时程序一直卡在Lunar_GetRegistryByte函数的第一个while循环中查了半天发现是START位发不出去因为BUSY一直是被置位总线被拉低一直处于通信状态的状态。然后检测Lunar_I2CInit函数发现只要一开启I2C1时钟BUSY就是被置位的状态。经过百度在I2C被初始化前使用I2C_SoftwareResetCmd库函数进行一次复位才得以解决。
最后需要注意的是使用I2C_Send7bitAddress函数发送设备地址需要注意提前将地址左移一位因为I2C_Send7bitAddress函数的实现它是直接在你提供的设备地址末尾置读写位的。
硬件实现核心代码如下
/*** description: I2C端口GPIO初始化* return {*}*/
void Lunar_I2CInit(void) {GPIO_InitTypeDef GPIOB_Cfg;I2C_InitTypeDef I2C1_Cfg;// PARCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// 两个引脚都配置为复用开漏输出GPIOB_Cfg.GPIO_Mode GPIO_Mode_AF_OD;GPIOB_Cfg.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7;GPIOB_Cfg.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOB, GPIOB_Cfg);// 开启I2C1时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);I2C1_Cfg.I2C_Ack I2C_Ack_Enable; // 默认回复ACKI2C1_Cfg.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; // 7bit设备地址I2C1_Cfg.I2C_ClockSpeed 50000; // 50KHZI2C1_Cfg.I2C_DutyCycle I2C_DutyCycle_2; // 该位仅对100KHZ ~ 400KHZ的高速频率有效我们设置的50KHZ其实不起作用I2C1_Cfg.I2C_Mode I2C_Mode_I2C;I2C1_Cfg.I2C_OwnAddress1 0x00; // 随便自定义一个不冲突的主机设备地址// 必须使用软件对I2C进行复位不然I2C1的BUSY位一直会处于置位的状态I2C_SoftwareResetCmd(I2C1, ENABLE);I2C_SoftwareResetCmd(I2C1, DISABLE);I2C_Init(I2C1, I2C1_Cfg);I2C_Cmd(I2C1, ENABLE); // 使能// I2C_GenerateSTOP(I2C1, ENABLE);
}/*** description: 获取指定设备指定寄存器地址的值* param {uint8_t} device_addr设备地址是7位* param {uint8_t} registry_addr* return {uint8_t}*/
uint8_t Lunar_GetRegistryByte(uint8_t device_addr, uint8_t registry_addr) {uint8_t rt 0;device_addr device_addr 1;// 发送起始位I2C_GenerateSTART(I2C1, ENABLE);// 等待EV5事件// 也就是等待BUSY(SCL被拉低) // MSL处于主模式在start被发出硬件自动置位 // SBstart被发出 被置位while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) ! SUCCESS);// 发送设备地址I2C_Send7bitAddress(I2C1, device_addr, I2C_Direction_Transmitter); // 写设备地址// 等待EV6发送事件// 也就是BUSYSCL被拉低 // MSL处于主模式在start被发出硬件自动置位// ADDR设备地址已经发送并且收到从机的ACK// TXE发送寄存器为空 // TRA发送模式依据发送设备地址最后一位设置 被置位。while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) ! SUCCESS);// 这里有一个理论上的EV8_1事件但是库函数没有对应的宏大体意思就是发送寄存器和发送移位寄存器同时为空。起提示作用。// 发送寄存器地址I2C_SendData(I2C1, registry_addr);// 等待EV8_2事件// 也就是TRA发送模式依据发送设备地址最后一位设置 // BUSYSCL被拉低 // MSL处于主模式在start被发出硬件自动置位 // TXE发送寄存器为空收到从机的ACK回复才会置位 // BTF数据寄存器和移位寄存器都为空被置位。while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) ! SUCCESS);// Restart// 发送起始位I2C_GenerateSTART(I2C1, ENABLE);// 等待EV5事件// 也就是等待BUSY(SCL被拉低) // MSL处于主模式在start被发出硬件自动置位 // SBstart被发出 被置位while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) ! SUCCESS);// 发送设备地址I2C_Send7bitAddress(I2C1, device_addr, I2C_Direction_Receiver); // 读设备地址// 等待EV6接收事件// 也就是BUSYSCL被拉低 // MSL处于主模式在start被发出硬件自动置位 // ADDR设备地址已经发送并且收到从机的ACK 被置位。while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) ! SUCCESS);// EV6_1 / EV7_1 需要提前设置ack位和停止产生位。I2C_AcknowledgeConfig(I2C1, DISABLE);I2C_GenerateSTOP(I2C1, ENABLE);// 等待EV7也就是BUSYSCL被拉低 // MSL处于主模式在start被发出硬件自动置位 // RXNE接收数据寄存器非空被置位。while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED) ! SUCCESS);// 接收从机发来的数据rt I2C_ReceiveData(I2C1);// 恢复ACK确认I2C_AcknowledgeConfig(I2C1, ENABLE);return rt;
}/*** description: 设置指定设备指定寄存器地址的值* param {uint8_t} device_addr* param {uint8_t} registry_addr* param {uint8_t} data* return {*}*/
void Lunar_SetRegistryByte(uint8_t device_addr, uint8_t registry_addr, uint8_t data) {device_addr device_addr 1;// 发送起始位I2C_GenerateSTART(I2C1, ENABLE);// 等待EV5事件// 也就是等待BUSY(SCL被拉低) // MSL处于主模式在start被发出硬件自动置位 // SBstart被发出 被置位while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) ! SUCCESS);// 发送设备地址I2C_Send7bitAddress(I2C1, device_addr, I2C_Direction_Transmitter); // 写设备地址// 等待EV6发送事件// 也就是BUSYSCL被拉低 // MSL处于主模式在start被发出硬件自动置位 // ADDR设备地址已经发送并且收到从机的ACK // TXE发送寄存器为空 // TRA发送模式依据发送设备地址最后一位设置 被置位。while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) ! SUCCESS);// 这里有一个理论上的EV8_1事件但是库函数没有对应的宏大体意思就是发送寄存器和发送移位寄存器同时为空。起提示作用// 发送寄存器地址I2C_SendData(I2C1, registry_addr);// 等待EV8事件// 也就是TRA发送模式依据发送设备地址最后一位设置 // BUSYSCL被拉低 // MSL处于主模式在start被发出硬件自动置位 // TXE发送寄存器为空收到从机的ACK回复才会置位被置位。while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING) ! SUCCESS);// 发送待写数据I2C_SendData(I2C1, data);// 等待EV8_2事件也就是在EV8事件基础上多了一个BTF标志位代表发送寄存器和发送移位寄存器都空了数据线上也没有需要传输的数据了该发的数据发干净了。while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) ! SUCCESS);// 发送停止位请求停止传输I2C_GenerateSTOP(I2C1, ENABLE);
}感谢江协科技提供的STM32教学视频。
感谢为STM32 f100ZET6中硬件I2C的问题提供解决方案的作者原帖如下https://blog.csdn.net/jatamatadada/article/details/40860619 本章完结