STM32F103的模拟I2C通讯

首先,写模拟I2C的需要知道的一点基础知识,让你能够知道有哪些模拟的信号,是干嘛用的。

  • 起始信号:时钟线SCL为高时,数据线SDA由高到低。

  • 停止信号:时钟线SCL为高时,数据线SDA由低到高。

  • 只有在SCL为低电平时,SDA才允许进行高低电平的转换

  • 应答信号:应答信号分为2类,一类是发送的应答信号,一类是接收的应答信号。发送的应答信号是在读数据结束后紧跟着应答或非应答信号来表示是否要继续读数据。接收的应答信号是在没发送数据后都需要接收一个应答信号来进行接下来的步骤。

  • 发送数据:向目标器件发送一个字节的数据。

  • 读取数据:向目标器件读取一个字节的数据。

其次,讲一下I2C写和读的相关操作顺序

写:

  1. 发送起始信号。
  2. 发送器件地址+写命令(器件地址为7位,写为0,读为1,占一位,所以加起来为8位)。
  3. 接收应答信号。
  4. 发送寄存器地址。
  5. 接收应答信号。
  6. 发送需要写入的数据。
  7. 接收应答信号。
  8. 如果需要连续写数据,则重复6,7步骤。
  9. 发送停止信号,结束。
    在这里插入图片描述
    在这里插入图片描述

读:

  1. 发送起始信号。
  2. 发送器件地址+写命令(器件地址为7位,写为0,读为1,占一位,所以加起来为8位)。
  3. 接收应答信号。
  4. 发送寄存器地址。
  5. 接收应答信号。
  6. 发送起始信号。
  7. 发送器件地址+读命令。
  8. 接收应答信号。
  9. 读取数据
  10. 若需要继续读数据,则发送应答信号。重复9的操作
  11. 若不需要继续读数据,则发送非应答信号
  12. 发送停止信号,结束。
    在这里插入图片描述
    在这里插入图片描述

接下来就是紧张刺激的代码环节

I2C_gpio.h

#ifndef _I2C_GPIO_H
#define _I2C_GPIO_H

#include "stm32f10x.h"
#include "delay.h"

#define SDA		GPIO_Pin_6		//根据自己的引脚进行修改
#define SCL		GPIO_Pin_7		//根据自己的引脚进行修改
#define I2C_port	GPIOA		//根据自己的引脚进行修改
	
#define SDA_H	GPIO_SetBits(I2C_port,SDA)	//SDA高电平
#define SCL_H	GPIO_SetBits(I2C_port,SCL)	//SCL高电平
#define SDA_L	GPIO_ResetBits(I2C_port,SDA)//SDA低电平
#define SCL_L	GPIO_ResetBits(I2C_port,SCL)//SCL低电平	

void I2C_GPIO_Config(void);	//GPIO模拟I2C的引脚初始化					
void I2C_Start(void);		//起始信号
void I2C_Stop(void);		//停止信号
uint8_t I2C_Wait_Ack(void);	//接收应答信号
void I2C_Ack(void);			//发送应答信号
void I2C_nAck(void);		//发送非应答信号
void I2C_Send_Byte(uint8_t Dat);	//发送一个字节
uint8_t I2C_Read_Byte(unsigned char Ack);	//读一个字节
uint8_t I2C_Write(uint8_t Add,uint8_t Reg,uint8_t Dat);		//I2C写一个数据
uint8_t I2C_Write_Len(uint8_t Add,uint8_t Reg,uint8_t Len,uint8_t *Buf);	//I2C写多个数据
uint8_t I2C_Read(uint8_t Add,uint8_t Reg);					//I2C读一个数据
uint8_t I2C_Read_Len(uint8_t Add,uint8_t Reg,uint8_t Len,uint8_t *Buf);		//I2C读多个数据

#endif	

I2C_gpio.c

开漏输出的引脚不能独立输出高电平,需要配合上拉电阻来完成输出高电平,他可以读取引脚状态。并且在I2C电路中本来就需要上拉电阻,所以简单来说,在I2C电路中,开漏输出既可以输出信号,又可以读取输入信号。不需要频繁切换GPIO的模式来进行相关操作,是最佳的引脚模式。

#include "I2C_gpio.h"

void I2C_GPIO_Config(void)	
{
	GPIO_InitTypeDef	GPIO_InitStruct;			//设置GPIO结构体
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能外设GPIOA时钟(GPIO口不是A的需要换)
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;		//设置GPIO口为开漏输出
	GPIO_InitStruct.GPIO_Pin = SDA|SCL;					//设置需要配置的引脚
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;		//设置GPIO的速率为50Mhz
	GPIO_Init(I2C_port,&GPIO_InitStruct);				//初始化GPIO
	GPIO_SetBits(I2C_port,SDA|SCL);						//初始化SDA和SCL为高电平
}

void I2C_Start(void)	//I2C的起始信号:当SCL为高期间,SDA由高到低的跳变
{						//间隔时间为大于4.7us,所以保险起见为5us。
	SDA_H;				
	delay_us(5);		//延时5微秒
	SCL_H;				
	delay_us(5);		
	SDA_L;				
	delay_us(5);		
	SCL_L;				//钳住I2C总线,准备发送或接收数据 
}

void I2C_Stop(void)		//I2C的停止信号:当SCL为高期间,SDA由低到高的跳变
{
	SCL_L;				//只有SCL为低电平是,SDA才允许改变改变。不先把SCL线拉低,SDA的低电平容易误判为起始信号。
	SDA_L;				
	delay_us(5);		
	SCL_H;				//SCL高电平
	delay_us(5);		
	SDA_H;				//SDA从低到高,触发终止信号
	delay_us(5);		
}

uint8_t I2C_Wait_Ack(void)	//等待I2C的应答
{
	uint8_t Time = 0;		
	SDA_H;
	delay_us(1);
	SCL_H;
	delay_us(1);
	while(GPIO_ReadInputDataBit(I2C_port,SDA))//如果SDA收到高电平信号
	{
		Time++;
		if(Time > 250)
		{
			I2C_Stop();
			return 1;//如果计数大于250,则发送停止信号,并返回1,表示接收应答信号失败
		}
	}
	//如果SDA收到低电平信号
	SCL_L;		//SCL拉低,结束第9位的脉冲
	//SDA_OUT();
	return 0;	//接收应答信号成功,返回0
}

void I2C_Ack(void)			//SDA发送低电平来表示继续读,应答信号
{
	SCL_L;
	SDA_L;
	delay_us(2);
	SCL_H;
	delay_us(2);
	SCL_L;
}

void I2C_nAck(void)			//SDA发送高电平来表示停止读,非应答信号
{
	SCL_L;
	SDA_H;
	delay_us(2);
	SCL_H;
	delay_us(2);
	SCL_L;
}

void I2C_Send_Byte(uint8_t Dat)//发送一个字节
{
	uint8_t i;
	SCL_L;
	for(i=0;i<8;i++)
	{
		if((Dat&0x80)>>7 == 1)	//dat与10000000,再右移7位,来判断最高位是否为1
		{
			SDA_H;				//如果最高位是1,则SDA发送高电平
		}
		else					//否则相反
			SDA_L;				
		Dat <<= 1;			//发送信号后,dat左移,进行下一位的数据发送
		delay_us(2);
		SCL_H;
		delay_us(2);
		SCL_L;
		delay_us(2);
	}
}

uint8_t I2C_Read_Byte(unsigned char Ack)//读一个字节
{
	unsigned char i,receive=0;
	for(i=0;i<8;i++)	
	{
		SCL_L;			
		delay_us(2);
		SCL_H;
		receive<<=1;	//receive左移一位
		if(GPIO_ReadInputDataBit(I2C_port,SDA) == 1)//如果得到了高电平信号
		{
			receive++;	//则+1
		}
		delay_us(1);
	}
	if(Ack == 1)		//如果ack为1,则发送应答信号
	{
		I2C_Ack();
	}
	else
		I2C_nAck();		//否则发送非应大信号
	return receive;		//返回得到的数据
}

uint8_t I2C_Write(uint8_t Add,uint8_t Reg,uint8_t Dat)
{
	I2C_Start();//在起始信号后必须发送一个7位从机地址+1位方向位,用“0”表示主机发送数据,“1”表示主机接收数据。
	I2C_Send_Byte((Add << 1) | 0);	//发送 器件地址+写的命令
	if(I2C_Wait_Ack() == 1)			//等待响应,如果失败,发送停止信号,返回1
	{
		I2C_Stop();					
		return 1;					
	}
	I2C_Send_Byte(Reg);				//发送 寄存器地址
	I2C_Wait_Ack();			//等待响应,如果失败,发送停止信号,返回1
	I2C_Send_Byte(Dat);				//发送8位数据的命令
	if(I2C_Wait_Ack() == 1)			//等待响应,如果失败,发送停止信号,返回1
	{
		I2C_Stop();
		return 1;
	}
	I2C_Stop();						//发送结束,发送停止信号
	return 0;						//返回 0,确定发送成功
}
uint8_t I2C_Write_Len(uint8_t Add,uint8_t Reg,uint8_t Len,uint8_t *Buf)//I2C的写数据
{
	uint8_t i;
	I2C_Start();//在起始信号后必须发送一个7位从机地址+1位方向位,用“0”表示主机发送数据,“1”表示主机接收数据。
	I2C_Send_Byte((Add << 1) | 0);	//发送 器件地址+写的命令
	if(I2C_Wait_Ack() == 1)			//等待响应,如果失败,发送停止信号,返回1
	{
		I2C_Stop();					
		return 1;					
	}
	I2C_Send_Byte(Reg);				//发送 寄存器地址
	if(I2C_Wait_Ack() == 1)			//等待响应,如果失败,发送停止信号,返回1
	{
		I2C_Stop();
		return 1;
	}
	for(i =0;i<Len;i++)				//循环 len 次写数据
	{
		I2C_Send_Byte(Buf[i]);		//发送第i位的8位数据
		if(I2C_Wait_Ack() == 1)		//等待响应,如果失败,发送停止信号,返回1
		{
			I2C_Stop();
			return 1;
		}
	}
	I2C_Stop();						//发送结束,发送停止信号
	return 0;						//返回 0,确定发送成功
}

uint8_t I2C_Read(uint8_t Add,uint8_t Reg)
{
	uint8_t Dat;
	I2C_Start();//在起始信号后必须发送一个7位从机地址+1位方向位,用“0”表示主机发送数据,“1”表示主机接收数据。
	I2C_Send_Byte((Add << 1) | 0);
	I2C_Wait_Ack();			//等待响应,如果失败,发送停止信号,返回1
	I2C_Send_Byte(Reg);				//发送 寄存器地址
	I2C_Wait_Ack();			//等待响应,如果失败,发送停止信号,返回1
	I2C_Start();					//发送开始信号
	I2C_Send_Byte((Add << 1) | 1);	//发送 器件地址+读的命令
	I2C_Wait_Ack();			//等待响应,如果失败,发送停止信号,返回1
	Dat = I2C_Read_Byte(0);
	I2C_Stop();						//发送停止信号
	return Dat;						//读取成功,返回0
}
uint8_t I2C_Read_Len(uint8_t Add,uint8_t Reg,uint8_t Len,uint8_t *Buf)//I2C读数据
{
	uint8_t i;
	I2C_Start();//在起始信号后必须发送一个7位从机地址+1位方向位,用“0”表示主机发送数据,“1”表示主机接收数据。
	I2C_Send_Byte((Add << 1) | 0);	//发送 器件地址+写的命令
	if(I2C_Wait_Ack() == 1)			//等待响应,如果失败,发送停止信号,返回1
	{
		I2C_Stop();					
		return 1;					
	}
	I2C_Send_Byte(Reg);				//发送 寄存器地址
	if(I2C_Wait_Ack() == 1)			//等待响应,如果失败,发送停止信号,返回1
	{
		I2C_Stop();
		return 1;
	}
	I2C_Start();					//发送开始信号
	I2C_Send_Byte((Add << 1) | 1);	//发送 器件地址+读的命令
	if(I2C_Wait_Ack() == 1)			//等待响应,如果失败,发送停止信号,返回1
	{
		I2C_Stop();
		return 1;
	}
	for(i=0;i<Len;i++)				//for循环 Len 次
	{
		if(i != Len-1)				//如果不是读到最后一次
		{
			Buf[i] = I2C_Read_Byte(1);	//保存第i次读到的数据到数组的第i位,并发送应答信号
		}
		else
			Buf[i] = I2C_Read_Byte(0);	//保存第i次读到的数据到数组的第i位,并发送非应答信号
	}
	I2C_Stop();						//发送停止信号
	return 0;						//读取成功,返回0
}

  • 7
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
STM32F767单片机开发板 标准例程寄存器版本65个软件工程源码合集: 实验0 新建工程实验 实验1 跑马灯实验 实验10 电容触摸按键实验 实验11 OLED实验 实验12 内存保护(MPU)实验 实验13 TFTLCD(MCU屏)实验 实验14 SDRAM实验 实验15 LTDC LCD(RGB屏)实验 实验16 USMART调试实验 实验17 RTC实验 实验18 硬件随机数实验 实验19 待机唤醒实验 实验2 按键输入实验 实验20 ADC实验 实验21 内部温度传感器实验 实验22 DAC实验 实验23 PWM DAC实验 实验24 DMA实验 实验25 IIC实验 实验26 IO扩展实验 实验27 光环境传感器实验 实验28 QSPI实验 实验29 485实验 实验3 串口通信实验 实验30 CAN实验 实验31 触摸屏实验 实验32 红外遥控器实验 实验33 DS18B20数字温度传感器实验 实验34 DHT11数字温湿度传感器实验 实验35 MPU9250九轴传感器实验 实验36 无线通信实验 实验37 FLASH模拟EEPROM实验 实验38 摄像头实验 实验39 内存管理实验 实验4 外部中断实验 实验40 SD卡实验 实验41 NAND FLASH实验 实验42 FATFS实验 实验43 汉字显示实验 实验44 图片显示实验 实验45 硬件JPEG解码实验 实验46 照相机实验 实验47 音乐播放器实验 实验48 录音机实验 实验49 SPDIF(光纤音频)实验 实验5 独立看门狗实验 实验50 视频播放器实验 实验51 FPU测试(Julia分形)实验 实验52 DSP测试实验 实验53 手写识别实验 实验54 T9拼音输入法实验 实验55 串口IAP实验 实验56 USB读卡器(Slave)实验 实验57 USB声卡(Slave)实验 实验58 USB虚拟串口(Slave)实验 实验59 USB U盘实验(Host) 实验6 窗口看门狗实验 实验60 USB鼠标键盘实验(Host) 实验61 网络通信实验 实验62 UCOSII实验1-任务调度 实验63 UCOSII实验2-信号量和邮箱 实验64 UCOSII实验3-消息队列、信号量集和软件定时器 实验65 综合测试实验 实验7 定时器中断实验 实验8 PWM输出实验 实验9 输入捕获实验
以下是 STM32F103 上使用 GPIO 模拟 I2C 从机的代码示例,供您参考: ```c #include "stm32f10x.h" // I2C GPIO 端口定义 #define I2C_SCL_PIN GPIO_Pin_6 #define I2C_SDA_PIN GPIO_Pin_7 #define I2C_GPIO_PORT GPIOB // I2C 状态定义 #define I2C_STA_IDLE 0 #define I2C_STA_START 1 #define I2C_STA_ADDR_R 2 #define I2C_STA_ADDR_W 3 #define I2C_STA_DATA_R 4 #define I2C_STA_DATA_W 5 #define I2C_STA_STOP 6 // I2C 从机状态定义 typedef struct { uint8_t State; // 当前状态 uint8_t Addr; // 从机地址 uint8_t Reg; // 寄存器地址 uint8_t Data; // 数据 } I2C_SwSlaveTypeDef; I2C_SwSlaveTypeDef SwSlaveI2C; // 设置 SCL 和 SDA 方向 #define SET_SCL_DIR(DIR) GPIO_WriteBit(I2C_GPIO_PORT, I2C_SCL_PIN, (DIR) ? Bit_SET : Bit_RESET) #define SET_SDA_DIR(DIR) GPIO_WriteBit(I2C_GPIO_PORT, I2C_SDA_PIN, (DIR) ? Bit_SET : Bit_RESET) // 读取 SDA 状态 #define READ_SDA_STATE() GPIO_ReadInputDataBit(I2C_GPIO_PORT, I2C_SDA_PIN) // 延时一段时间(单位:微秒) void DelayUs(uint32_t us) { uint32_t i; for (i = 0; i < us * 8; i++) {} // 根据 CPU 主频调整 } // 初始化 I2C 从机 void I2C_SwSlaveInit(void) { GPIO_InitTypeDef GPIO_InitStruct; // 使能 I2C GPIO 端口时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 配置 I2C GPIO 端口为推挽输出 GPIO_InitStruct.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStruct); // 初始化 I2C 从机状态 SwSlaveI2C.State = I2C_STA_IDLE; SwSlaveI2C.Addr = 0; SwSlaveI2C.Reg = 0; SwSlaveI2C.Data = 0; } // 处理 I2C 从机中断 void I2C_SwSlaveHandler(void) { uint8_t i; // 处理 I2C 从机状态 switch (SwSlaveI2C.State) { // 空闲状态 case I2C_STA_IDLE: // 等待 I2C 主机发起起始信号 if (READ_SDA_STATE() == RESET) { DelayUs(5); if (READ_SDA_STATE() == RESET) { SET_SCL_DIR(1); SwSlaveI2C.State = I2C_STA_START; } } break; // 起始状态 case I2C_STA_START: // SCL 保持高电平,SDA 下降沿表示起始信号 SET_SDA_DIR(0); DelayUs(5); SET_SCL_DIR(0); SwSlaveI2C.State = I2C_STA_ADDR_R; break; // 读取从机地址状态 case I2C_STA_ADDR_R: // 读取从机地址,并根据 R/W 位设置状态 for (i = 0; i < 8; i++) { SET_SCL_DIR(1); DelayUs(5); SwSlaveI2C.Addr <<= 1; SwSlaveI2C.Addr |= READ_SDA_STATE(); SET_SCL_DIR(0); DelayUs(5); } if (SwSlaveI2C.Addr & 0x01) { SwSlaveI2C.State = I2C_STA_DATA_R; } else { SwSlaveI2C.State = I2C_STA_DATA_W; } break; // 读取数据状态 case I2C_STA_DATA_R: // 读取数据,并

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值