IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 模拟IIC基本代码和分析(基于STM32F103RCT6的HAL库) -> 正文阅读

[嵌入式]模拟IIC基本代码和分析(基于STM32F103RCT6的HAL库)

? ? ? ? 由于STM32的硬件IIC有点问题,且模拟IIC能够更好的帮助我们理解IIC的通信协议,所以下面给出了以STM32F103RCT6为硬件的模拟IIC基本代码(基于HAL库)。

目录

一、IIC通信的基本介绍

?二、模拟IIC的基本代码及其分析

1、基本us延时程序

2、切换SDA口的输入和输出状态

3、主机IIC起始、终止信号的发送

?4、主机等待应答

5、主机发送是否应答信号

6、主机发送一个字节

7、主机接收一个字节

三、总结

四、附录


一、IIC通信的基本介绍

? ? ? ? IICInter-Integrated Circuit)其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上(接线图如下)。所以IIC工作模式是一种半双工通信

?????????通信过程如下图(其中起始、终止、数据、应答等会在下面结合代码与时序图一起分析):

????????主机发送和接收数据流程如下图:

?二、模拟IIC的基本代码及其分析

1、基本us延时程序

????????由于STM32HAL库最小只提供了ms级的延时,所以需要自己创建一个us级延时的子程序以满足IIC时序要求。

void RCCdelay_us(uint32_t udelay)
{
  __IO uint32_t Delay = udelay * 72 / 8;       //72M主频
  do
  {
    __NOP();                                   //空语句
  }
  while (Delay --);
}

2、切换SDA口的输入和输出状态

????????由于IIC是半双工通信,SDA数据线对于主机来说有时候需要输出数据,有时候需要接受数据,故此处创建一个子函数方便调用切换SDA口状态。

void I2C_SDA_Mode(uint8_t addr) //输入参数:1表示配置SDA为输出模式,0表示配置SDA为输入模式
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	if(addr)                                                   //1 表示 out
	{
		GPIO_InitStruct.Pin = IIC_SDA_Pin;                     //IIC的SDA引脚
		GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;            //开漏输出模式
		GPIO_InitStruct.Pull = GPIO_PULLUP;                    //上拉
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;           //SDA口速度
		HAL_GPIO_Init(LM75A_SDA_GPIO_Port, &GPIO_InitStruct);  //设置SDA口
	}
	else                                                       //0 表示 input
	{
		GPIO_InitStruct.Pin = IIC_SDA_Pin;                      //IIC的SDA引脚
		GPIO_InitStruct.Mode = GPIO_MODE_INPUT;                 //输入模式
		GPIO_InitStruct.Pull = GPIO_PULLUP;                     //上拉
		HAL_GPIO_Init(LM75A_SDA_GPIO_Port, &GPIO_InitStruct);   //设置SDA口
	}
}

3、主机IIC起始、终止信号的发送

????????起始信号:在SCL高电平期间,SDA由高电平跳变为低电平,表示主机发送起始信号。(且要求SDA的下降沿到SCL的下降沿时间大于4us)。

????????终止信号:在SCL高电平期间,SDA由低电平跳变为高电平,表示主机发送终止信号。(且要求SCL的上升沿到SDA的上升沿时间大于4us)。

????????如下图分别所示为起始信号和终止信号的要求:

void I2C_Start(void)      //IIC起始信号
{
	I2C_SDA_Mode(OUT);    //先配置SDA为输出
	
	RESET_SCL;            //先拉低SCL为下面拉高SDA做准备(在SCL低时变化SDA信号可防止误操作)
	SET_SDA;              //拉高SDA
	SET_SCL;              //拉高SCL
	RCCdelay_us(5);       //延时5us(满足时序中在SCL高期间SDA高电平时间保持4.7us以上)
	RESET_SDA;            //拉低SDA,产生下降沿
	RCCdelay_us(5);       //延时5us(满足时序中在SCL高电平期间SDA低电平保持4us以上)
	RESET_SCL;            //拉低SCL
}

void I2C_Stop(void)       //IIC终止信号
{
	I2C_SDA_Mode(OUT);    //先配置SDA为输出
	
	RESET_SCL;            //先拉低SCL为下面拉低SDA做准备(在SCL低时变化SDA信号可防止误操作)
	RESET_SDA;            //拉低SDA
	SET_SCL;              //拉高SCL
	RCCdelay_us(5);       //延时5us(满足时序中在SCL高期间SDA低电平时间保持4us以上)
	SET_SDA;              //拉高SDA
	RCCdelay_us(5);       //延时5us(满足时序中在SCL高电平期间SDA高电平保持4.7us以上)
    RESET_SCL;            //拉低SCL
}

?4、主机等待应答

????????由于IIC每发送一个字节都会收到一个应答信号,所以主机需要有程序来判断是否接受到了这个应答信号。

????????应答信号:低电平表示应答,高电平表示无应答。

uint8_t I2C_Wait_Ack(void)         //主机等待应答
{
	uint8_t Time_Ack=0;            //定义变量用来标志应答超时
	
	I2C_SDA_Mode(IN);              //主机接收应答信号,此时应该设置SDA为输出模式
	
    RESET_SCL;                     //拉低SCL(因为从机发送应答信号是在SCL低电平期间允许改变)
	RCCdelay_us(4);                //延时4us确保SDA数据完全改变
	SET_SCL;                       //拉高SCL(因为从机发送的信号是在SCL高电平期间被采样接受)              
	RCCdelay_us(4);                //SCL为高期间SDA数据保持不变
	
	while(HAL_GPIO_ReadPin(LM75A_SDA_GPIO_Port, LM75A_SDA_Pin)) //当读取SDA为高电平时候进入
	{
		if(++Time_Ack > 250)//高电平也就是无应答,一直在这等待,直到状态变量大于250认为超时
		{
			I2C_Stop();return 1; //从机无应答,停止IIC,返回1
		}			
	}
	RESET_SCL;              
	RCCdelay_us(4);             //如果跳出了while循环就是有应答信号,拉低SCL
	
	return 0;                   //有应答返回0
	
}

5、主机发送是否应答信号

????????当主机读取从机数据时,有时候不止一个字节的数据,那么每读取一个字节就要求主机给从机发送一个应答信号。

void I2C_Send_Ack(uint8_t ack)//主机发送是否应答信号 1表示不应答 0表示应答
{
	if(ack)                                   // 1 表示不应答 NoAck
	{
		RESET_SCL;
		RCCdelay_us(5);
		SET_SDA;
		RCCdelay_us(5);
		SET_SCL;                           //SCL拉高,保持4us以上,此时SDA为高,表示不应答
		RCCdelay_us(5);
		RESET_SCL;                         //SCL拉低,表示不应答信号发送结束
		RCCdelay_us(5);
		RESET_SDA;
	}
	else                                      //0 表示应答 Ack
	{
		RESET_SCL;
		RCCdelay_us(5);
		RESET_SDA;
		RCCdelay_us(5);
		SET_SCL;                           //SCL拉高,保持4us以上,此时SDA为低,表示应答
		RCCdelay_us(5);
		RESET_SCL;                         //SCL拉低,表示应答信号发送结束
		RCCdelay_us(5);
		SET_SDA;
	}
}

6、主机发送一个字节

????????注意IIC是先发送高位,后发送低位。

void I2C_Write_Byte(uint8_t Data) //主机发送一个字节数据
{
	I2C_SDA_Mode(OUT);            //配置SDA为输出模式
	
	RESET_SCL;                    //先拉低SCL,为SDA第一个数据可变化做准备
	RCCdelay_us(1);               //延时1us,确保完全拉低SCL
	
	
	for(uint8_t i=0;i<8;i++)      //循环8此讲Data数据发送出去
	{
		if((Data<<i) & 0x80)     //Data循环次数左移,保证循环从最高位到最低位发送数据
			SET_SDA;
		else
			RESET_SDA;
		
        RCCdelay_us(4);          //延时4us,确保SDA的数据被稳定接收
		SET_SCL;                 //SCL拉高,准备发送数据
		RCCdelay_us(4);          //延时4us,确保SDA的数据被稳定接收
		RESET_SCL;               //SCL拉低,允许SDA数据变化
	}
}

7、主机接收一个字节

????????注意接收是先接收最高位,再接收最低位。

uint8_t I2C_Read_Data(void)      //主机接收一个字节
{
	uint8_t Data;                //定义一个变量接收数据
	
	I2C_SDA_Mode(IN);            //设置SDA为输入模式
	
	RESET_SCL;                   //拉低SCL,允许SDA的数据变化
	RCCdelay_us(4);              //延时4us,确保SDA数据稳定

	
	for(uint8_t i=0;i<8;i++)     //循环8次接收一个字节数据
	{
		SET_SCL;                  //拉高SCL,读取SDA数据
		RCCdelay_us(1);           //延时1us确保SCL被完全拉高
		Data = Data<<1;           //将得到的数据每次左移一位,满足第一个数据最终在最高位
		if(HAL_GPIO_ReadPin(LM75A_SDA_GPIO_Port, LM75A_SDA_Pin))//读SDA的电平
			Data |= 0x01;         //高电平则把最低为置1,低电平则不用操作(因为左移自动补0)
        
		RESET_SCL;                //拉低SCL,允许下一个SDA的数据变化
		RCCdelay_us(4);           //延时4us,确保SDA数据稳定
	}
	return Data;                  //返回读到的一个字节数据
}

三、总结

????????以上就是基于STM32F103RCT6的模拟IIC基础程序(基于HAL库),后面我会利用这个代码配合实际的IIC外设进行通信来更好的使用这组代码(比如与LM75A温度传感器通信)。当然上述代码有些地方可以改进,比如可以不切换SDA的输入输出模式,可以利用GPIO寄存器来读取SDA的状态,就可以省去SDA模式切换等等。

四、附录

????????上述代码是全部IIC.c文件的内容(还需自行添加iic.h的头文件),下面给出对应的IIC.h的代码。(注意,所有代码并没有SDA和SCL的GPIO口配置,因为在HAL库中会自动生成gpio.c,所以SDA和SCL的GPIO口的配置都在gpio.c中,只需要在CUBE中将其配置成GPIO_OUT即可)。

#ifndef _IIC_H_
#define _IIC_H_

#include "stdint.h"

/*表明SDA是输入还是输出*/
#define OUT 1
#define IN 0                                      

/*表明应答还是不应答*/
#define Ack 0
#define NoAck 1                                  

/*将IIC的SDA和SCL和你自己设定的IO口对应*/
#define PORT_SCL IIC_SCL_GPIO_Port
#define PIN_SCL  IIC_SCL_Pin

#define PORT_SDA IIC_SDA_GPIO_Port
#define PIN_SDA  IIC_SDA_Pin


#define SET_SCL     HAL_GPIO_WritePin(PORT_SCL, PIN_SCL, GPIO_PIN_SET)
#define RESET_SCL   HAL_GPIO_WritePin(PORT_SCL, PIN_SCL, GPIO_PIN_RESET)

#define SET_SDA     HAL_GPIO_WritePin(PORT_SDA, PIN_SDA, GPIO_PIN_SET)
#define RESET_SDA   HAL_GPIO_WritePin(PORT_SDA, PIN_SDA, GPIO_PIN_RESET)


/*函数声明*/
void I2C_Start(void);
void I2C_Stop(void);
uint8_t I2C_Wait_Ack(void);
void I2C_Send_Ack(uint8_t ack);
void I2C_Write_Byte(uint8_t Data);
uint8_t I2C_Read_Data(void);
void I2C_SDA_Mode(uint8_t addr);           


#endif

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-12-09 11:49:55  更:2021-12-09 11:51:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 12:25:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码