首页 > 其他分享 >STM32F1之I2C通信·软件I2C代码编写

STM32F1之I2C通信·软件I2C代码编写

时间:2024-05-26 09:30:10浏览次数:28  
标签:SCL MyI2C void 高电平 SDA GPIO 编写 I2C STM32F1

目录

1.  软件I2C代码编写 

1.1  I2C起始

方法一

方法二

方法三

方法四

1.2  I2C终止

1.3  发送一个字节 

1.4  接收一个字节

1.5  发送应答

1.6  接收应答


1.  软件I2C代码编写 

        由于软件I2C不受引脚限制,随便找两个普通的GPIO口就可以使用,首先我们随机找两个引脚对其进行初始化:

void MyI2C_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//开启GPIOB的时钟
	
	/*GPIO初始化*/
	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);					//将PB10和PB11引脚初始化为开漏输出
	
	/*设置默认电平*/
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);			//设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}

        然后根据I2C通讯的时序进行配置软件I2C,了解I2C通讯:

STM32F1之I2C通信-CSDN博客

1.1  I2C起始

        首先配置起始和终止条件,根据I2C通讯的时基单元我们可以了解到,起始条件下,SCL高电平期间,SDA从高电平切换到低电平;终止条件下,SCL高电平期间,SDA从低电平切换到高电平。

方法一

        我们可以通过GPIO_ResetBits()和GPIO_SetBits()的方式来控制引脚的电平,假如我们要将,PB10配置成SCL引脚,PB11配置成SDA引脚,则我们可以编写代码:

void MyI2C_Start(void)
{
	GPIO_SetBits(GPIOB, GPIO_Pin_11);			//释放SDA,确保SDA为高电平
	GPIO_SetBits(GPIOB, GPIO_Pin_10);			//释放SCL,确保SCL为高电平
	GPIO_ResetBits(GPIOB, GPIO_Pin_11);			//在SCL高电平期间,拉低SDA,产生起始信号
	GPIO_ResetBits(GPIOB, GPIO_Pin_10);			//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
方法二

        或者我们可以采用宏定义的方法,先对PB10和PB11的高低电平状态进行宏定义:

#define SDA_H()	GPIO_SetBits(GPIOB, GPIO_Pin_11);			
#define SCL_H() GPIO_SetBits(GPIOB, GPIO_Pin_10);			
#define SDA_L() GPIO_ResetBits(GPIOB, GPIO_Pin_11);			
#define SCL_L() GPIO_ResetBits(GPIOB, GPIO_Pin_10);			

        然后使用定义:

void MyI2C_Start(void)
{
	SDA_H();			//释放SDA,确保SDA为高电平
	SCL_H();			//释放SCL,确保SCL为高电平
	SDA_L();			//在SCL高电平期间,拉低SDA,产生起始信号
	SCL_L();			//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
		
方法三

        另一种有参宏定义:

#define	MyI2C_W_SDA(x);	 GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)(x));						
#define	MyI2C_W_SCL(x);	 GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)(x));						
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);							//释放SDA,确保SDA为高电平
	MyI2C_W_SCL(1);							//释放SCL,确保SCL为高电平
	MyI2C_W_SDA(0);							//在SCL高电平期间,拉低SDA,产生起始信号
	MyI2C_W_SCL(0);							//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
方法四

        对于方法三的有参宏,要是移植到别的库或者往别的单片机移植不容易修改,并且要是将其换到一些主频很高的单片机中需要进行延时操作,不方便进行修改,我们可以基于以上方法进行函数封装:

I2C写SCL引脚电平:

        其中,BitValue 协议层传入的当前需要写入SCL的电平,范围0~1,此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平:

void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);		//根据BitValue,设置SCL引脚的电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
}

 I2C写SDA引脚电平:

        其中,BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF,此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平:

void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);		//根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
	Delay_us(10);												//延时10us,防止时序频率超过要求
}

        调用以上封装函数:

void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);							//释放SDA,确保SDA为高电平
	MyI2C_W_SCL(1);							//释放SCL,确保SCL为高电平
	MyI2C_W_SDA(0);							//在SCL高电平期间,拉低SDA,产生起始信号
	MyI2C_W_SCL(0);							//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}

1.2  I2C终止

        这里我们可以直接调用方法四中的封装:

void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);							//拉低SDA,确保SDA为低电平
	MyI2C_W_SCL(1);							//释放SCL,使SCL呈现高电平
	MyI2C_W_SDA(1);							//在SCL高电平期间,释放SDA,产生终止信号
}

1.3  发送一个字节 

        发送一个字节: SCL低电平期间,主机将数据位依次放到SDA线上(高位先行) ,然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。

        Byte 要发送的一个字节数据,范围:0x00~0xFF,通过与0x80按位与的方式取出最高位数据,由于我们在方法四的封装函数void MyI2C_W_SDA(uint8_t BitValue);其中BitValue的取值为非0即1的特性,因此我们可以直接使用MyI2C_W_SDA(Byte & 0x80);通过与上不同的位得到一个字节数据,代码如下:

void MyI2C_SendByte(uint8_t Byte)
{
		MyI2C_W_SDA(Byte & 0x80);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x40);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x20);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x10);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x08);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x04);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x02);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x01);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);						

}

        我们可以通过for循环简化上述操作,通过对0x80右移实现上述操作:

void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++)				//循环8次,主机依次发送数据的每一位
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));	//使用掩码的方式取出Byte的指定一位数据并写入到SDA线
		MyI2C_W_SCL(1);						//释放SCL,从机在SCL高电平期间读取SDA
		MyI2C_W_SCL(0);						//拉低SCL,主机开始发送下一位数据
	}
}

1.4  接收一个字节

        SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。

I2C读SDA引脚电平:

        我们需要先创建一个读SDA电平的函数封装:

uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);		//读取SDA电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
	return BitValue;											//返回SDA电平
}

        此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1。

uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	for (i = 0; i < 8; i ++)				//循环8次,主机依次接收数据的每一位
	{
		MyI2C_W_SCL(1);						//释放SCL,主机机在SCL高电平期间读取SDA
		if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}	//读取SDA数据,并存储到Byte变量
														//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
		MyI2C_W_SCL(0);						//拉低SCL,从机在SCL低电平期间写入SDA
	}
	return Byte;							//返回接收到的一个字节数据
}

1.5  发送应答

        发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。

void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);					//主机把应答位数据放到SDA线
	MyI2C_W_SCL(1);							//释放SCL,从机在SCL高电平期间,读取应答位
	MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
}

1.6  接收应答

        接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。

uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;							//定义应答位变量
	MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	MyI2C_W_SCL(1);							//释放SCL,主机机在SCL高电平期间读取SDA
	AckBit = MyI2C_R_SDA();					//将应答位存储到变量里
	MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
	return AckBit;							//返回定义应答位变量
}

STM32F1之I2C通信-CSDN博客

STM32F1之RS485通讯协议·MODBUS-RTU超详细解析-CSDN博客

STM32F1之FLASH闪存-CSDN博客

标签:SCL,MyI2C,void,高电平,SDA,GPIO,编写,I2C,STM32F1
From: https://blog.csdn.net/MANONGDKY/article/details/139142429

相关文章

  • STM32F1之SPI通信·软件SPI代码编写
    目录1. 简介2. 硬件电路移位示意图3. SPI时序基本单元3.1  起始条件3.2 终止条件3.3  交换一个字节(模式0)3.4 交换一个字节(模式1)3.5 交换一个字节(模式2)3.6 交换一个字节(模式3)4. 代码编写4.1 引脚初始化4.2 引脚置高低电平封装4.2.1  S......
  • 用 Python 编写网络爬虫:从网页获取数据并存储到 Excel 文件
    在本篇博客中,我们将介绍如何使用Python编写一个简单的网络爬虫,用于从网页中提取数据,并将这些数据存储到Excel文件中。我们将使用Python中的一些库来实现这个功能,包括urllib.request、BeautifulSoup和openpyxl。1.网络爬虫的基本原理网络爬虫是一种程序,可以自动访问......
  • STM32F103C8T6独立看门狗学习笔记
    /*------------操作说明-------------------键寄存器IWDG_PR寄存器地址0x40003000初始值0X000000000X00005555解除对IWDG_PR、IWDG_RLR和IWDG_WINR寄存器的写入访问保护0X0000CCCC启动独立看门狗0X0000AAAA喂狗......
  • 编写C语言计算器:探索挑战与优化之路
    如果你对C语言编程充满兴趣,那么构建一个简易计算器可能是一个很好的练习机会。在本文中,我们将探讨如何使用C语言实现一个基本的计算器,并分享我们在这个过程中遇到的挑战及其解决方案。版本1.0如下:#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>voidmenu(){ p......
  • 单片机HC32系列IO模拟I2C 延时调试记录
    一. SysTick_Config和delay冲突因为 SysTick_Config 被用于设置SysTick为操作系统计时,而 delay 函数又使用了SysTick来实现延时,导致两者对SysTick的配置不一致。导致 SysTick_Config无法再次进入SysTick_IRQHandler()函数。 解决方法:将delay改为for循环延时。delay1......
  • CubeMX离线安装stm32f1固件包
    一.打开CubeMX软件点击Help选择Manageembededsoftwarepackages二、找到STM32F1版本最新的固件包,点击install 三、登录账号 四、等待下载完成五、下载完成......
  • 说说Loader和Plugin的区别?编写Loader,Plugin的思路?
    一、区别前面两节我们有提到Loader与Plugin对应的概念,先来回顾下loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中plugin赋予了webpack各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决loader......
  • [os] xx.serive文件编写 -- systemctl
    [os]  xx.serive文件编写--systemctl    一、基本信息 1、操作系统:Linuxrocky5.14.0-427.16.1.el9_4.x86_64#1SMPPREEMPT_DYNAMICWedMay817:48:14UTC2024x86_64x86_64x86_64GNU/Linux   二、c语言程序可运行文件(systest) ......
  • i2ctools工具使用
    用i2cdetect检测有几组i2c总线在系统上,输入:./i2cdetect-l用i2cdetect检测挂载在i2c总线上器件,输入./i2cdetect-r-y0我们可以看到有0x50,0x51,0x56三个地址可以用用i2cdump查看器件所有寄存器的值,这个很有用,输入./i2cdump-f-y00x56用i2cget读取单个寄存器值用i2cs......
  • 在一次渗透中学会编写Tamper脚本
    拿到这个网站,通过对比查询,我们发现闭合参数finsh时,查询出的内容更多经过进一步判断,确实存在漏洞不过在测试的时候发现存在一定的过滤但是可以通过内联注释进行绕过。这里也是加深了解了内联注释的知识点,之前只会简单的利用 /*!50000UniONSeLeCt*/ /*!12345union*/......