1、IIC总线的介绍
1. I2C总线是PHLIPS公司在八十年代初推出的一种"同步串行半双工"总线,
主要用于连接整体电路。
SOC和各自传感器外设之间的通信,一般使用的都是IIC或者SPI
两个设备之间的通信,一般使用UART,485,CAN,USB
2. I2C总线为"两线制",只有两根双向信号线。
一根是"数据线SDA",另一根是"时钟线SCL"。
3. I2C硬件结构简单,接口连接方便,成本较低。因此在各个领域得到了广泛的应用。
4. I2C总线的通信的速度一般为100k-400kbps之间。
5. 一根I2C总线上可以外接多个I2C的器件,I2C总线上需要外接两个"上拉电阻"。
1. I2C是具备多主机系统所需的包括总线裁决功能的高性能串行总线。
2. 每个接到I2C总线上的器件都有"唯一的地址"。
主机与其它器件进行数据传送时总线上发送数据的器件为"发送器",
总线上接收数据的器件则为"接收器"。
3. 主机:可以主动发起通信的器件称为主机,一般指SOC。
时钟信号一般由主机产生,作用给从机。
从机:只可以被动的进行数据收发的器件,程序从机,一般指传感器。
2、I2C总线的时序
2.1 起始信号时序/终止信号时序
1. SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号
2. SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号
3. 起始和终止信号都是由主机发出,起始信号产生后,总线就处于占用的态;
终止信号产生后,总线就处于空闲态。
2.2 数据传输时序
1. I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,
只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
2. 在时钟为低电平期间,发送器可以向数据线上写入数据,
因此数据线上的数据可以发生改变。
在时钟为高电平期间,接收器从数据线上读取数据,
因此要求数据线上的数据必须保持稳定。
一个时钟周期完成了一个bit位数据的收发。
2.3 应答信号
1. 每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),
每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
2. 发送器给接收器发送完成1个字节的数据之后,接收器需要在第九个时钟周期内,
给发送器返回一个应答信号或者非应答信号。
3. 在第九个时钟周期的低电平期间,接收器向数据线上写入数据,
在第九个时钟周期的高电平期间,发送器从数据线上读取数据,
如果读到的是低电平信号,表示接收到应答信号,
如果读到的是高电平信号,表示接收到非应答信号。
2.4 I2C的寻址
1. I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
2. 主机在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/W),
用“0”表示主机发送数据(W),“1”表示主机接收数据(R)。
总线上的每个从机都将这7位地址码与自己的地址进行比较,
如果相同,则认为自己被主机寻址,根据R/W位将自己定为发送器或接收器。
3、I2C总线的通信协议
3.1 主机给从机发生一个字节的通信协议
3.2 主机给从机发生连续的多个字节的通信协议
3.3 从机给主机发送一个字节的通信协议
3.4 从机给主机发送连续的多个字节的通信协议
从机给主机发送数据时,为什么主机在读最后一个字节数据时,返回的是非应答信号?
从机只能被动的进行数据的收发,当从机给主机发送数据时,
主机收到1个字节的数据之后,需要给从机返回一个应答信号或者非应答信号,
如果主机返回的是应答信号,则从机会发送下一个字节的数据,
如果主机返回的是非应答信号,则从机不会发送下一个字节的数据。
4、I2C总线实验实现
4.1 分析温湿度传感器电路图
4.2 分析芯片手册
4.2.1 分析2.5.2章节,确定GPIOF外设寄存器的基地址以及接到哪根总线上
略
4.2.2 分析RCC章节,使用GPIOF外设的时钟源
略
4.2.3 分析GPIO章节,设置PF14,PF15引脚为输入或者输出,输出高低电平
略
4.2.4 分析SI7006温湿度传感器的芯片手册
SI7006温湿度传感器的框图:
转换时间:跟测量的精度有关
I2C总线的时序图:
湿度的测量精度和误差
温度测量的精度和误差:
SI7006典型设计电路图:
SI7006的从机地址:
SI7006芯片的命令码:
命令码就是这个寄存器地址
测量温湿度保持主模式的通信协议:
数字量的湿度转换为模拟量的公式:
数字量的温度转换为模拟量的公式:
读写用户寄存器的通信协议:
用户寄存器的介绍:
4.3 编写驱动代码
4.3.1 I2C驱动代码
1. 开始信号函数
2. 停止信号函数
3. 主机给从机,写一个字节数据的函数
4. 主机从从机,读一个字节数据的函数
5. 主机给从机发送应答信号函数
6. 主机给从机发送非应答信号函数
7. 主机等待从机发送应答信号的函数
iic.h文件
将前面协议中的每一个小方块都封装成函数
#ifndef _IIC_H_
#define _IIC_H_
#include "../common/include/stm32mp1xx_i2c.h"
#include "gpio.h"
//宏定义
//时钟 SCL PF14
#define IIC_SCL_H \
do{ \
GPIOF->BSRR |= 1<<14; \
}while(0)
#define IIC_SCL_L \
do{ \
GPIOF->BRR |= 1<<14; \
}while(0)
//数据线 SDA PF15
//GPIO输出模式
#define SET_SDA_OUT \
do{ \
GPIOF->MODER &=~(0x3<<30); \
GPIOF->MODER |=(0x1<<30); \
}while(0)
//输入模式
#define SET_SDA_IN \
do{ \
GPIOF->MODER &=~(0x3<<30); \
}while(0)
//高低电平
#define IIC_SDA_H \
do{ \
GPIOF->BSRR |= 1<<15; \
}while(0)
#define IIC_SDA_L \
do{ \
GPIOF->BRR |= 1<<15; \
}while(0)
//读取输入内容
#define IIC_SDA_READ (GPIOF->IDR & (1<<15))
void delay_us();
void iic_init();
void iic_start();
void iic_stop();
void iic_write_1byte(uint8_t data);
uint8_t iic_read_1byte(uint8_t ack);
uint8_t iic_wait_ack();
void iic_send_ack();
void iic_send_nack();
#endif /*_IIC_H_*/
iic.c
根据每个函数中画好的时序图写,将图转换成代码语言
#include "../include/iic.h"
void delay_us(){
unsigned int i = 2000;
while (i--)
;
}
void iic_init(){
//使能GPIOF时钟
RCC->MP_AHB4ENSETR |=(1<<5);
//将PF14 PF15设置为输出
GPIOF->MODER &=~(0xF<<28);
GPIOF->MODER |= (0x5<<28);
//推挽
GPIOF->OTYPER &=~(0b11<<14);
//高速
GPIOF->OSPEEDR |= (0xF<<28);
//禁止上下拉
GPIOF->PUPDR &=~(0xF<<28);
//拉高时钟线和数据线
IIC_SCL_H;
IIC_SDA_H;
}
void iic_start(){
/*
* 开始信号:时钟在高电平期间,数据线从高到低的变化
* --------
* SCL \
* --------
* ----
* SDA \
* --------
* */
SET_SDA_OUT;//先将SDA设置为输出模式
IIC_SCL_H;
IIC_SDA_H;
delay_us();
IIC_SDA_L;
delay_us();
IIC_SCL_L;
}
void iic_stop(){
/*
* 停止信号 : 时钟在高电平期间,数据线从低到高的变化
* ----------
* SCL /
* --------
* --- -------
* SDA X /
* --- -------
* 为了确保停止信号是一个上升沿,因此在时钟为低电平期间
* 将数据线拉低,确保可以产生上升沿。
* */
SET_SDA_OUT;//先将SDA设置为输出模式
IIC_SCL_L;
delay_us();
IIC_SDA_L;//确保上升沿
delay_us();
IIC_SCL_H;
delay_us();
IIC_SDA_H;
delay_us();
}
uint8_t iic_wait_ack(){
/*
* 主机发送一个字节之后,从机给主机返回一个应答信号,主机接收应答信号
* -----------
* SCL / M:读 \
* ----------------- --------
* --- -------- --------------------
* SDA X X
* --- --------------------
* 主 释 设 从机 主机
* 机 放 置 向数据 读数据线
* 总 SDA 线写 上的数据
* 线 输 数据
* 入
* */
uint8_t ack;
IIC_SCL_L;
delay_us();
IIC_SDA_H;//释放总线
SET_SDA_IN;
delay_us();
delay_us(); // 等待从机向数据线上写入应答信号或者非应答信号
IIC_SCL_H;
delay_us();
if(IIC_SDA_READ){
ack=1;
}else{
ack=0;
}
delay_us();
IIC_SCL_L;
delay_us();
return ack;
}
void iic_send_ack(){
/* --------
* SCL / \
* ------- ------
* ---
* SDA X
* --- -------------
* 在第九个时钟周期的低电平期间,接收器向数据线写入数据,
* 在第九个时钟周期的高电平期间,发送器从数据线上读取数据,
* 如果读到低电平表示应答信号
* */
SET_SDA_OUT;
IIC_SCL_L;
delay_us();
IIC_SDA_L;
delay_us();
IIC_SCL_H;
delay_us();
delay_us();//等待接收器读走数据
IIC_SCL_L;//释放,可以继续写入数据
delay_us();
}
void iic_send_nack(){
/* --------
* SCL / \
* ------- ------
* --- ---------------
* SDA X
* ---
* 在第九个时钟周期的低电平期间,接收器向数据线写入数据,
* 在第九个时钟周期的高电平期间,发送器从数据线上读取数据,
* 如果读到高电平表示非应答信号
* */
SET_SDA_OUT;
IIC_SCL_L;
delay_us();
IIC_SDA_H;//非应答
IIC_SCL_H;
delay_us();
delay_us();
IIC_SCL_L;
delay_us();
}
void iic_write_1byte(uint8_t data){
/*
* 数据信号:时钟在低电平期间,发送器向数据线上写入数据
* 时钟在高电平期间,接收器从数据线上读取数据
* ---- --------
* SCL \ / \
* -------- --------
* -------- ------------------ ---
* SDA X X
* -------- ------------------ ---
* 先发送高位在发送低位
* */
int i=0;
SET_SDA_OUT;
//传数 先传数据高位再传低位
for(;i<8;i++){
IIC_SCL_L;
delay_us();
if(data & 0x80){//取出数据并为高电平
IIC_SDA_H;
}else{
IIC_SDA_L;
}
delay_us();
delay_us(); // 等待从机从数据线上读取数据
data=data<<1;
}
}
uint8_t iic_read_1byte(uint8_t ack){
uint8_t data=0;
int i=0;
SET_SDA_IN;
for(;i<8;i++){
IIC_SCL_L; // SCL拉低
delay_us();
delay_us(); // 等待从机向数据线上写入数据
IIC_SCL_H;
delay_us();
data <<=1;
if(IIC_SDA_READ){//读并为高电平
data |=1;
}else{
data |=0;
}
delay_us();
}
//主机根据情况发送应答或非应答
if(!ack){
iic_send_ack();
}else{
iic_send_nack();
}
return data;
}
建立芯片的文件,单独完成芯片的功能
4.3.2 SI7006的驱动代码
1. SI7006初始化的函数
2. 测量湿度并读取湿度的函数
3. 测量温度并读取温度的函数
si7006.h
将要用的命令码定义为宏,
精度按照寄存器里写的配置
#ifndef _SI7006_H
#define _SI7006_H
#define HULMI 0xE4 //测量湿度
#define TEMPRATURE 0xE5 //测量温度
#define WRTIE 0xE6 //向si7006写
#define READ 0xE7 //从si7006读
#define WRITE_USER_REG_VALUE 0x3A//设置精度,也是初始化用户寄存器需要向芯片中写入的值
#define slaveaddr 0x40
void si7006_init();
unsigned char si7006_read_hul(unsigned char Slaveaddr,unsigned char CMD);
unsigned char si7006_read_tem(unsigned char Slaveaddr,unsigned char CMD);
#endif /*_SI7006_H*/
si7006.c
芯片手册里已经写好了如何读取温湿度,按照手册里给的时序图写即可
向寄存器中写入
从寄存器读
#include "../include/si7006.h"
#include "../include/iic.h"
extern void delay_ms(unsigned int ms);
void si7006_init(){
//要设置精度,12位的温度,14位湿度,禁止加热器
//按照si7006芯片手册里的过程写
//不同模式代码不一样
iic_init();//初始化iic
iic_start();
iic_write_1byte(slaveaddr<<1);
iic_wait_ack();
iic_write_1byte(WRTIE);
iic_wait_ack();
iic_write_1byte(WRITE_USER_REG_VALUE);
iic_send_ack();
iic_stop();
}
unsigned char si7006_read_hul(unsigned char Slaveaddr,unsigned char CMD){
unsigned char dat_h,dat_l;
unsigned short data;
iic_start();
iic_write_1byte(slaveaddr<<1);
iic_wait_ack();
iic_write_1byte(CMD);
iic_wait_ack();
iic_start();
iic_write_1byte((slaveaddr<<1)|1);
iic_wait_ack();
delay_ms(100);
dat_h=iic_read_1byte(0);
dat_l=iic_read_1byte(1);
iic_stop();
data = dat_h;
data = data << 8 | dat_l;
return data;
}
unsigned char si7006_read_tem(unsigned char Slaveaddr,unsigned char CMD){
unsigned char dat_h,dat_l;
unsigned short data;
iic_start();
iic_write_1byte(slaveaddr<<1);
iic_wait_ack();
iic_write_1byte(CMD);
iic_wait_ack();
iic_start();
iic_write_1byte((slaveaddr<<1)|1);
iic_wait_ack();
delay_ms(100);
dat_h=iic_read_1byte(0);
dat_l=iic_read_1byte(1);
iic_stop();
data = dat_h;
data = data << 8 | dat_l;
return data;
}
main.c
main中用定时器实现一秒打印一次温湿度,
还需将芯片测得的模拟量通过攻是转换为数据量
#include "include/led.h"
#include "include/key.h"
#include "include/uart4.h"
#include "include/beep.h"
#include "include/fan.h"
#include "include/motor.h"
#include "include/interrupt.h"
#include "include/timer_it.h"
#include "include/si7006.h"
extern void printf(const char *fmt, ...);
extern int tim_flag;
void delay_ms(unsigned int ms){
int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 1800; j++)
;
}
int main(){
short temp;
unsigned short hum;
hal_timer_IT_init();
hal_gic_init(61, 10);
si7006_init();
while (1){
if(tim_flag==1){
tim_flag=0;
temp = si7006_read_tem(slaveaddr, TEMPRATURE);
hum = si7006_read_hul(slaveaddr,HULMI);
temp = (temp * 175.72 / 65536.0 - 46.85) * 100;
hum = (hum * 125.0 / 65536.0 - 6.0) * 100;
printf("temp = %d.%d\n", temp/100, temp%100);
printf("hum = %d.%d\n", hum/100, hum%100);
}
}
}