📚前言
📑博客主页:@丘比特惩罚陆
💖欢迎关注:点赞收藏⭐留言✒
💬系列专栏:嵌入式、web前端、笔记专栏
🎮 加入社区: 灌水乐园
🥇人生格言:选对方向,每走一步都是进步!
✒️欢迎大佬指正,一起学习!一起加油!👏 希望大家能小手一动,帮忙点个赞!
😁资源邮箱:2237814512@qq.com
📕 I2C总线协议简介
I2C总线是英国的菲利普公司在八十年代初期退出来的一种串行的、半双工的总线,主要是用于一些近距离、低速的芯片之间的通信;I2C总线有两根双向的信号线,一根SDA用于收发数据、一根时钟线SCL用于通信双方时钟的同步。
在我们的嵌入式C语言当中,我们由其他的通信协议,我们分别还可以简单的将三种通信协议全部罗列出来进行一个大的比较一下,主要的区别我们可以通过一下的表格了解一下:
硬件 | 速度 | 工作模式 | 时钟线 | 独立时钟 | 数据发送 | 协议 | |
USART 异步串口 | TX/RX | 最慢 | 全双工 | 无 | 有 | 8/9位 | 帧格式(起始+数据+校验+停止) |
SPI 同步串口 | CS/SCL/MOSI/MISO | 最快 | 全双工 | 有 | 主机有从机无 | 8/16位 | 数据交互式 |
IIC 同步串口 | SCL/SDA | 一般 | 半双工 | 有 | 主机有从机无 | 8位 | 应答 |
我们从上面的表格,我们可以往下延伸一下,我们的IIC总线有四信号:分别可以是,起始信号+应答信号+非应答+停止信号,那么就简单了,这我们只需要稍微的了解一下他们之间的通信的过程,我们就好往下走了。
💬 IIC通信过程
我们的IIC通信的过程可以分成以下几部分,
1、主机发送起始信号到我们的启动的总线上面;
2、主机发送一个字节数据指明到从机地址和后续字节的传送方式;
3、被寻址的从机部分发送应答信号回到我们的主机;
4、发送器发送一个字节数据;
5、接收器发送应答信号回应我们的发送器;
6、发送完,通信完成主机发送停止信号释放我们的总线。
但是,在这里要提醒的是,我们的第四第五步当中说的发送器和接收器不是我们的主机和从机,这是由我们的第一个直接的最后以为决定主机给到我们发送还是从机发送!
📝 时序图
IIC总线传送的数据可以包括地址,亦可以是真正的数据,主机上面在发送起始信号后必须先发送一个字节的数据,该数据的高7位是从机的地址,最低位表示后续字节的发送方向,‘0’代表的是主机发送数据给我们的从机,‘1’代表的是从机发送数据回到我们的主机。
总线上面的所有的从机接收到的该字节数据后豆浆这7位地址和自己的地址进行一次就比较,相同的话,认为就是可以被主机寻到地址,然后再根据第八位将自己定为发送器或者接收器。
我们配置的功能就是根据上面的时序图来进行相应的配置就可以了,我们分别来大概的了解一下怎么配置,比如,我们可以看到起始信号,应该按照一下的方法进行一个功能的配置
✒️ 起始信号
//函 数 名 : IIC_Start
//功能说明 : 产生IIC起止信号
//形 参 : 无
//返 回 值 : 无
//备 注 : SCL为高电平期间,SDA由高电平向低电平的跳变。
void IIC_Start(void)
{
IIC_SCL = 1; //时钟线为高
IIC_SDA = 1; //数据线为高
Delay_Us(5); //保持,为了产生下沿的动作
IIC_SDA = 0; //高电平期间,拉低SDA,产生起止信号
Delay_Us(5);
IIC_SCL = 0; //占用总线,准备发送数据
Delay_Us(5);
}
✒️ 停止信号
函 数 名 : IIC_Stop
功能说明 : 产生IIC结束信号
形 参 : 无
返 回 值 : 无
备 注 : SCL为高电平期间,SDA由低电平向高电平的跳变。
void IIC_Stop(void)
{
IIC_SCL = 0; //低电平时才能改变SDA的状态
IIC_SDA = 0; //拉低SDA,为产生上沿做准备
//Delay_Us(5);
IIC_SCL = 1; //在SCL为高电平期间
Delay_Us(5);
IIC_SDA = 1; //SDA由低向高跳变
Delay_Us(5);
}
✒️ 非应答信号
函 数 名 : IIC_ACK_NACK
功能说明 : 作为接收方时,每个字节(8bit)传输完成后的下一个时钟信号,发起应答或非应答信号
形 参 : ack:0应答,1非应答
返 回 值 :
备 注 : 在SCL为高电平期间,SDA为低,则表示一个应答信号(ACK);SDA为高,则表示一个非应答信号(NACK)。
void IIC_ACK_NACK(unsigned char ack)
{
IIC_SCL = 0; //SCL为低电平时才能改变SDA的状态
Delay_Us(1);
if(!ack) IIC_SDA = 0; //产生应答信号
else IIC_SDA = 1; //产生非应答信号
Delay_Us(5);
IIC_SCL = 1; //拉高SCL,从机读取应答信号
Delay_Us(5);
IIC_SCL = 0; //拉低SCL,准备接收下一个数据
}
✒️ 应答信号
函 数 名 : IIC_Wait_ACK
功能说明 : 作为发送方时,检测从机返回的应答信号
形 参 : 无
返 回 值 : 0:应答,1:非应答
备 注 :
unsigned char IIC_Wait_ACK(void)
{
unsigned char ErrorCounter = 0;
IIC_SDA = 1; //释放数据线
IIC_SCL = 1; //拉高SCL
Delay_Us(5);
while(IIC_SDAIN) //SDA被拉低则认为从机给的应答信号
{
ErrorCounter++;
if(ErrorCounter > 250) //如果过了一段时间数据线还是为高,那么表示非应答信号
{
IIC_Stop();
return 1;
}
}
IIC_SCL = 0; //拉低SCL,准备下一个数据的接受
Delay_Us(5);
return 0;
}
⏰ SHT31温湿度传感器
稍微简单的介绍一下这个温湿度传感器,这主要是一款较为低成本的温湿度检测模块,一般的工作电压是2.4V-5.5V功耗比较低,体积比较小,它的工作原理是通过我们单片机的上面读取温湿度的数据,使用我们的IIC通信协议这个方式进行输出。
工作原理:
温度检测:主要的工作的原理是采用的是热电偶的方法,由两种不同的材料的金属丝进行组成,两种金属丝材料相互链接在一起,形成一个工作的端口,放在我们的被检测温度下米线;另一个端口就是我们的自由端口,和测量仪相互链接在一起,形成一个闭合回路,当两个端温度不一样的时候,回路就会出现热电动势,经过电路的转换就能将这个电压的变化送佛啊单片机。
湿度检测:是使用沉积在两个导电电极上的聚胺盐或醋酸纤维聚合物薄膜(—种高分子化合物)当薄膜吸水或失水后,会改变两个电极间的介电常数。进而引起电容器容量的变化,利用外部测量电路可将电容器的容量变化进行捕捉、转化处理,最终在输出端显示成易识别的信号.
将它转换成我们的代码就是:1主机多个从机,通过寻址地址的方式将每个从机有的每一个地址,将信号发送到对我们的单片机上显示出来就可以了。
代码奉上
sht31.c
#include "iic.h" //要包含IIC.h头文件
#include "sht3x.h"
#include "systick.h"
static unsigned char crc8(const unsigned char *data, int len);
/*********************************************************************************************************
* 函 数 名 : Sht3x_Init,设置为通用开漏输出,先看rcc章节
* 功能说明 : 初始化SHT3x端口
* 形 参 : 无
* 返 回 值 : 无
* 备 注 : IIC_SCL:PB6,IIC_SDA:PB7
*********************************************************************************************************/
void Sht3x_Init(void)//window命名规则,init要大写
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
//io口的配置就到gpio.c里面就好了,rcc那一栏
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
//时钟你不知道的话,你可以到rcc文件右键RCC_AHB1PeriphClockCmd就可以看到了
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //通用输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //开漏
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; //速度配置
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //浮空(无上下拉)
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化
//配置的内容我们直接到手册就可以就好了
//配置完全部都要拉高,强制处于空闲状态
IIC_SCL = 1;
IIC_SDA = 1;
}
/*********************************************************************************************************
* 函 数 名 : Sht3x_Read_TemperatureHumidity
* 功能说明 : 读取SHT3x温湿度数据
* 形 参 : HT:存储温湿度的结构体指针
* 返 回 值 : 无
* 备 注 : 无
*********************************************************************************************************/
int Sht3x_Read_TemperatureHumidity(Sht3x_TypeDef *temp)
{
unsigned char buff[6] = {0};
unsigned char i = 0;
IIC_Start(); //第一步,先调用起止信号
IIC_WriteByte(SHT3X_ADDR<<1 | IIC_WRITE); //器件地址+写
if(IIC_Wait_ACK() == ACK){ //对方应答了,继续执行,从机应答
IIC_WriteByte(SHT3X_READ_TEMP_HUM>>8); //命令的高位
if(IIC_Wait_ACK() == ACK){ //对方应答了,继续执行
IIC_WriteByte(SHT3X_READ_TEMP_HUM&0xff); //命令的低位
if(IIC_Wait_ACK() == ACK){ //对方应答了,继续执行
do{
if(++i > 20){
return 4;
}
IIC_Stop();
Delay_Ms(2); //等待对方采集好数据
IIC_Start();
IIC_WriteByte(SHT3X_ADDR<<1 | IIC_READ);//询问是否采集好了数据
}while(IIC_Wait_ACK() != ACK); //如果条件成立,表示传感器采集数据未完成
for(i=0; i<5; i++){
buff[i] = IIC_ReadByte(ACK);
}
buff[5] = IIC_ReadByte(NACK);
IIC_Stop();
//转换温湿度
if(crc8(buff, 2) == buff[2] && crc8(&buff[3], 2) == buff[5]){
temp->temperature = (buff[0]<<8|buff[1])/65535.0*175-45;
temp->humidity = (buff[3]<<8|buff[4])/65535.0*100;
}else return 5;
}else return 3;
}else return 2;
}else return 1;
return 0;
}
/*********************************************************************************************************
* 函 数 名 : Sht3x_Read_TemperatureHumidity
* 功能说明 : 读取SHT3x温湿度数据(第二种写法啊)
* 形 参 : HT:存储温湿度的结构体指针
* 返 回 值 : 无
* 备 注 : 无,24,0b设置时间采样的个数(数据),设置时钟的延伸
*********************************************************************************************************/
//void sht3x_read_T_H(void)
//{
// int i;
// IIC_Start();
// IIC_WriteByte((0x44<<1)|0);
// if(IIC_Wait_ACK()==1)
// {
// printf("没有响应,请你监测总线是否挂接有从机或从机损坏\r\n");
// return ;
// }
// IIC_WriteByte(0x24);
//}
unsigned char crc8(const unsigned char *data, int len)
{
const unsigned char POLYNOMIAL = 0x31;
unsigned char crc = 0xFF;
int j, i;
for (j=len; j; --j){
crc ^= *data++;
for ( i = 8; i; --i ){
crc = ( crc & 0x80 )
? (crc << 1) ^ POLYNOMIAL
: (crc << 1);
}
}
return crc;//这不都是一样的嘛
}
sht31.h,我们这里温湿度的话,我们在.h进行宏定义一个结构体就可以在main使用了。
#ifndef SHT30_H
#define SHT30_H
#include "io_bit.h"
#define SHT3X_ADDR 0X44 //从机地址
#define SHT3X_READ_TEMP_HUM 0x240b //非时钟延伸,中等重复性
typedef struct
{
float temperature;
float humidity;
}Sht3x_TypeDef;
void Sht3x_Init(void);
int Sht3x_Read_TemperatureHumidity(Sht3x_TypeDef *HT);
#endif
🎮 效果展示
总结
一键三连谢谢大家,写文章挺累的!最后就是完成的工程码源可以到私聊!