STM32---I2C

一、I2C总线协议

定义:两线式串行总线

        两线式:说明处理器和外设之间只需要两根信号线,分别是SCL时钟控制信号线和SDA 数据线

        SCL:时钟控制信号线,永远只能由CPU控制,用于实现数据的同步,就四个字:低放高取

                SCL为低电平时将数据放在SDA数据线上

                SCL为高电平时从数据线SDA上获取数据

        SDA:数据线,用于传输数据,双方都可以控制        

                如果处理器给外设发送数据,SDA由处理器控制

                如果外设给处理器发送数据,SDA由外设控制

        SCL和SDA必须要分别连接一个上拉电阻,所以他们默认的电平都是高电平 

        I2C数据传输从高位开始,I2C数据传输一次传输一个字节,如果传输多个字节,需要分拆着来传

        SCL和SDA上可以连接多个外设,也可以连接多个CPU,(理论上可以连接多个CPU)

I2C协议规定:总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作
为传输的停止条件。起始和结束信号总是由主设备产生。总线在空闲状态时,SCL和SDA
都保持着高电平。
起始信号:当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件
结束信号:当SCL为高而SDA由低到高的跳变,表示产生一个停止条件

数据传输:数据传输以字节为单位,主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,数据在时钟的高电平被采样,一个字节按数据位从高位到低位的顺序进行传输
主设备在传输有效数据之前要先指定从设备的地址,一般为7位,然后再发生数据传输的方向位,0表示主设备向从设备写数据,1表示主设备向从设备读数据
应答信号:接收数据的器件在接收到8bit数据后,向发送数据的器件发出低电平的应答信号,表示已收到数据。这个信号可以是主控器件发出,也可以是从动器件发出。总之,由接收数据的器件发出。

二、I2C总线读写操作

主设备往从设备写数据

主设备读从设备数据

主设备读从设备的某个寄存器

软件模拟I2C时序

//iic.h
#ifndef __IIC_H
#define __IIC_H

//包含总头文件
#include "stm32f10x.h"
#include "system.h"

//定义SCL引脚的信息
#define IIC_SCL_PORT				GPIOB
#define IIC_SCL_PIN					GPIO_Pin_6
#define IIC_SCL_PORT_RCC		RCC_APB2Periph_GPIOB

//定义SDA引脚的信息
#define IIC_SDA_PORT				GPIOB
#define IIC_SDA_PIN					GPIO_Pin_7
#define IIC_SDA_PORT_RCC		RCC_APB2Periph_GPIOB

//定义输入输出的位带操作信息
#define IIC_SCL							PBout(6)
#define IIC_SDA							PBout(7)
#define READ_SDA						PBin(7)	

extern void IIC_Init(void);			//IIC初始化
extern void IIC_Start(void);		//发送START信号
extern void IIC_Stop(void);			//发送STOP信号
extern void IIC_Send_Byte(u8 TxData);	//发送1字节数据
extern u8	IIC_Read_Byte(u8 Ack);			//读1字节数据
extern u8 IIC_Wait_Ack(void);					//等待ACK信号
extern void IIC_Ack(void);						//CPU发送ACK信号
extern void IIC_NAck(void);						//CPU发送NACK信号
#endif

//iic.c
#include "iic.h"
#include "systick.h"

//定义IIC初始化函数
void IIC_Init(void) {
	GPIO_InitTypeDef GPIO_Config;
	
	//使能SCL和SDA引脚的GPIO时钟
	RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC, ENABLE);
	
	//初始化SCL引脚的工作参数
	GPIO_Config.GPIO_Pin = IIC_SCL_PIN;
	GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP;	//GPIO模拟I2C时序,采用推挽输出
	GPIO_Init(IIC_SCL_PORT, &GPIO_Config);
	
	//初始化SDA引脚的工作参数
	GPIO_Config.GPIO_Pin = IIC_SDA_PIN;
	GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP;	//GPIO模拟I2C时序,采用推挽输出
	GPIO_Init(IIC_SDA_PORT, &GPIO_Config);
	
	//默认拉高SCL和SDA
	IIC_SCL = 1;
	IIC_SDA = 1;
}

//定义配置SDA为输出的函数,CPU发送数据的时候需要配置为输出
void SDA_OUT(void) {
	GPIO_InitTypeDef GPIO_Config;
	
	GPIO_Config.GPIO_Pin = IIC_SDA_PIN;
	GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(IIC_SDA_PORT, &GPIO_Config);
}

//定义配置SDA为输入的函数,当CPU从外设读取数据时,需要配置为输入
void SDA_IN(void) {
	GPIO_InitTypeDef GPIO_Config;
	
	GPIO_Config.GPIO_Pin = IIC_SDA_PIN;
	GPIO_Config.GPIO_Mode = GPIO_Mode_IPU; //输入上拉
	GPIO_Init(IIC_SDA_PORT, &GPIO_Config);
}

//定义发送START信号函数
/*
SCL:
1		-----------------------------

0

SDA: tsu:sta>4.7us
1		--------------
									|  thd:sta>4us
0							    ---------------
*/
void IIC_Start(void) {
	SDA_OUT();		//由于要发送START信号,所有要将SDA配置为输出
	IIC_SCL = 1;	//时钟拉高
	IIC_SDA = 1;	//数据线也拉高
	delay_us(5);	//SCL和SDA都会延时5us的高电平
	IIC_SDA = 0; 	//SDA拉低,SCL还是高
	delay_us(6);	//SDA拉低6us,SCL还是拉高6us
	IIC_SCL = 0;	//准备开始传输设备地址信息了
}

//定义发送STOP信号函数
/*
SCL:
1		---------------------------------

0

SDA: 									tbuf>4us
1									------------------
	  tsu:sto>4.7us	 |					
0	  ---------------				   
*/
void IIC_Stop(void) {
	SDA_OUT();	//将SDA配置为输出
	IIC_SDA = 0;  //SDA拉低
	IIC_SCL = 1;  //SCL拉高
	delay_us(6);  //延时>4.7us
	IIC_SDA = 1;  //SDA拉高
	delay_us(6);  //延时>4us
}

//定义等待ACK信号函数
u8 IIC_Wait_Ack(void) {
	u8 tempTime = 0; //判断接收ACK信号是否超时
	
	IIC_SCL = 0;	//为了让外设在SCL为低电平的时候将ACK信号放到SDA上
	delay_us(6);	//在此期间外设将ACK信号放到数据线SDA上
	SDA_IN(); 	  //SDA配置为输入,为了获取到外设发送的ACK
	IIC_SCL = 1;  //拉高SCL
	delay_us(6);
	while(READ_SDA) {
		tempTime++;
		if(tempTime > 250) {
			IIC_Stop(); //没有收到有效ACK超时,停止后续的数据传输
			return 1;
		}
	}
	IIC_SCL = 0; //既然接收到了ACK信号了,可以继续传输数据了
	return 0;
}

//定义CPU发送ACK信号函数
void IIC_Ack(void) {
	IIC_SCL = 0;	//拉低的目的是将ACK数据放到SDA上
	SDA_OUT();	
	IIC_SDA = 0;	//发送一个有效的低电平ACK信号
	delay_us(6);
	IIC_SCL = 1;  //让外设在SCL为高电平获取ACK信号
	delay_us(6);
	IIC_SCL = 0;
}

//定义CPU发送NACK信号函数
void IIC_NAck(void) {
	IIC_SCL = 0;	//拉低的目的是将ACK数据放到SDA上
	SDA_OUT();	
	IIC_SDA = 1;	//发送一个无有效的ACK信号,高电平
	delay_us(6);
	IIC_SCL = 1;  //让外设在SCL为高电平获取ACK信号
	delay_us(6);
	IIC_SCL = 0;
}

//定义发送1字节数据的函数
void IIC_Send_Byte(u8 TxData) {
	u8 i;
	SDA_OUT(); 	//为了发送数据
	IIC_SCL = 0; //为了将数据放到SDA上
	for(i = 0; i < 8; i++) {  //发送8个bit位,一位一位的传输
		if(TxData & 0x80) //从高位开始传输,此时需要判断高位的bit位值到底是1还是0
			IIC_SDA = 1; //发送1
		else 
			IIC_SDA = 0; //发送0
		TxData <<= 1; //将低位往bit7挪动
		delay_us(6);  
		IIC_SCL = 1;  //为了让外设获取数据
		delay_us(6);
		IIC_SCL = 0; //开始传输下一个bit位
	}
}

//定义读取1字节数据的函数
u8 IIC_Read_Byte(u8 ack) {
	u8 i, data = 0;
	SDA_IN();	//首先将SDA配置为输入,为了获取数据
	for(i = 0; i < 8; i++) { //读取8个bit位
		IIC_SCL = 0; //将SCL拉低目的是让外设将数据放到SDA上
		delay_us(6); 
		IIC_SCL = 1; //将SCL拉高目的是让CPU开始获取数据
		data |= READ_SDA << (7 - i); //由于I2C数据传输从高位开始,所以这么干
		delay_us(6);
	}
	//读取1字节数据完毕,判断是否需要让CPU发送ACK还是NACK
	if(!ack) 	
		IIC_NAck();
	else
		IIC_Ack();
	return data; //返回读取到的数据
}











  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值