Stm32之I2C通信的使用(基于PCF8591)
I2C协议简介
I2C (Inter-Integrated Circuit),是一种串行通信总线,用于连接微控制器及其外围设备,达到主控制器和从器件间的主从双向通信,是一种同步半双工通信(两端时钟频次一致,双向通信,但不能同时进行数据收发)。
///插播一条:我自己在今年年初录制了一套还比较系统的入门单片机教程和毕业设计指导,想要的同学找我拿就行了免費的,私信我就可以哦~点我头像白色字体加我也能领取哦,记得口令陈老师///
I2C通信属于串行通信,具有两根串行信号线:数据线(SDA),时钟线(SCL)。如下图所示,主控制器与从器件(一个或多个)都通过两根信号线连接,信号线上主机和从机都能够扮演发送器和接管器的角色。为确保传输过程的指向精确性,每个接到I2C总线上的器件都有唯一的地址(7位从器件专用地址码),可达到制定从机的定向传输与群发传输。
物理层
IIC物理层
其具有以下特性:
一个 I2C 总线只运用两条总线线路,一条双向串行数据线(SDA),一条串行时钟线(SCL)。数据线即拿来表示数据,时钟线用于数据收发同步。
每个连接到总线的设备都有一个独立的地址,主机能够利用这个地址进行不同设备之间的访问。
它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
总线通过上拉电阻接到电源,当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平,能够参见GPIO的开漏输出模式。
协议层
IIC协议层
如图为I2C通信的流程,能够总结如下(概要,详细细节还得看手册):
主机(MCU)的I2C接口发出一个起始信号(S),告诉从机(SLAVE,奴隶,很形象哈哈)我要初始传输数据啦
此时所有从机都会收到这个信号,但是,到底是哪个从机要进行数据的读写呢?,这个时候主机就会广告从机地址(SLAVE ADDRESS,注意每个从机都有唯一的地址),告诉指定的从机要和你进行通信,以及是读数据还是写数据(1为读,0为写)
被选定的从机此时要给出一个应答信号,告诉主机到底要不要进行通信
0000四.假如从机容许的话就能够进行通信了,假如要停下,主时机发送一个停下信号(P),告诉从机到此完毕
STM32的I2C架构
架构就没什么好说的了,图里一看就明白。
基于PCF8591的I2C通讯程序分析
PCF8591是一款由Philips公司开发的8-bit A/D and D/A converter,采用I2C协议通信,虽然STM32内置有ADC,但是为了练习I2C的通讯方式,还是采用这款芯片。
我们查阅其datesheet,怎么去查阅呢,抓重要关键字,address byte,确定其地址,手册上是这样写的:
The address always has to be sent as the first byte after the start condition in the I2C-bus protocol. The last bit of the address byte is the read/write-bit which sets the direction of the following data transfer
从芯片原理图得知A0,A1,A2接地,1001000X,那么其地址就是0x90或者是0x91,看是读还是写来决定,接下来看Control byte,手册上是这样写的:
The second byte sent to a PCF8591 device will be stored in its control register and is required to control the device function.
假设我需要从通道0单端输出A/D转换数据,那么control byte就是01000000 = 0x40,万事大吉 ,开写通信协议:
首先看i2c.h:
#include"stm32f10x.h"#include"delay.h"//应答信号externu8ack;
//初始化函数externvoidI2c_Init(void);
//起动总线函数externvoidStart_I2c(void);
//结束总线函数 externvoidStop_I2c(void);
//应答子函数externvoidAck_I2c(u8a);
//字节数据发送函数externvoidSendByte(unsignedcharc);
//有子地址发送多字节数据函数externu8ISendStr(unsignedcharsla,unsignedcharsuba,unsignedchar*s,unsignedcharno);
//无子地址发送多字节数据函数externu8ISendStrExt(unsignedcharsla,unsignedchar*s,unsignedcharno);
//无子地址读字节数据函数externunsignedcharRcvByte(void);
//有子地址读取多字节数据函数externu8IRcvStr(unsignedcharsla,unsignedcharsuba,unsignedchar*s,unsignedcharno);
//无子地址读取多字节数据函数externu8IRcvStrExt(unsignedcharsla,unsignedchar*s,unsignedcharno);
然后看i2c.c,实际上的i2c协议很复杂,下面使用操作GPIO的方式来模拟I2C通信(不使用库函数操作),不是想要了解其中的原理可直接复制网络上现成的资源,每个芯片的i2C通信方式也略有差别,具体如何实现还是要看datesheet怎么写的。
/*************************此部分为I2C总线的驱动程序*************************************/
#include"i2c.h" //变量定义和函数声明全在此文件中/************************************硬件接口******************************/#define i2c_port GPIOB#define SData GPIO_Pin_7 //PB7; //I2C 时钟 #define SCLK GPIO_Pin_6 //PB6; //I2C 数据 /********************************宏定义*******************************************/#define SCL(x) x ? GPIO_SetBits(GPIOB , SCLK) : GPIO_ResetBits(GPIOB , SCLK)#define SDA(x) x ? GPIO_SetBits(GPIOB , SData) : GPIO_ResetBits(GPIOB , SData)/********************************变量*******************************************/u8ack;
/******************************************************************* 初始化函数 函数原型: void I2c_Init(); 功能: 初始化引脚 ********************************************************************/
voidI2c_Init(void){
GPIO_InitTypeDefGPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStructure.GPIO_Pin=SData|SCLK;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;//设置为开漏输出,实现线与功能GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(i2c_port,&GPIO_InitStructure);}
/******************************************************************* 起动总线函数 函数原型: void Start_I2c(); 功能: 启动I2C总线,即发送I2C起始条件. ********************************************************************/voidStart_I2c(){
SDA(1);//SDA=1; 发送起始条件(S)的数据信号delay_us(1);
SCL(1);//SCL=1;delay_us(5);//起始条件建立时间大于4.7us,延时,关于建立时间与保持时间,这些是数字电路中的重要概 念,不再解释。SDA(0);//SDA=0; /*发送起始信号*/delay_us(5);// 起始条件锁定时间大于4μsSCL(0);//SCL=0; /*钳住I2C总线,准备发送或接收数据 */delay_us(2);}
/******************************************************************* 结束总线函数 函数原型: void Stop_I2c(); 功能: 结束I2C总线,即发送I2C结束条件. ********************************************************************/voidStop_I2c(){
SDA(0);//SDA=0;发送结束条件的数据信号delay_us(1);//发送结束条件的时钟信号SCL(1);//SCL=1;结束条件建立时间大于4μsdelay_us(5);
SDA(0);//SDA=1;发送I2C总线结束信号delay_us(4);}
/******************************************************************* 字节数据发送函数 函数原型: void SendByte(UCHAR c);功能: 将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对 此状态位进行操作.(不应答或非应答都使ack=0) 发送数据正常,ack=1; ack=0表示被控器无应答或损坏。********************************************************************/voidSendByte(unsignedcharc){
unsignedcharBitCnt;
for(BitCnt=0;BitCnt<8;BitCnt++)//要传送的数据长度为8位{
if((c<<BitCnt)&0x80)SDA(1);//SDA=1; 判断发送位elseSDA(0);// SDA=0 delay_us(1);
SCL(1);//SCL=1 置时钟线为高,通知被控器开始接收数据位delay_us(5);//保证时钟高电平周期大于4μsSCL(0);//SCL = 0}
delay_us(2);
SDA(1);//SDA=1 位发送完后释放数据线,准备接收应答位delay_us(2);
SCL(1);//SCL=1delay_us(3);
if(GPIO_ReadInputDataBit(GPIOB,SData)==1)ack=0;
elseack=1;//判断是否接收到应答信号SCL(0);//SCL=0;delay_us(3);}
/******************************************************************* 字节数据接收函数 函数原型: UCHAR RcvByte();功能: 用来接收从器件传来的数据,并判断总线错误(不发应答信号), 发完后请用应答函数应答从机。 ********************************************************************/unsignedcharRcvByte(){
unsignedcharretc;
unsignedcharBitCnt;
retc=0;
SDA(1);//SDA=1 置数据线为输入方式for(BitCnt=0;BitCnt<8;BitCnt++)
delay_us(1);
SCL(0);//SCL=0 置时钟线为低,准备接收数据位delay_us(5);//时钟低电平周期大于4.7μsSCL(1);//SCL=1 置时钟线为高使数据线上数据有效delay_us(2);
retc=retc<<1;
if(GPIO_ReadInputDataBit(GPIOB,SData)==1)retc=retc+1;// SDA == 1读数据位,接收的数据位放入retc中 delay_us(2);
SCL(0);// SCL=0;delay_us(2);
return(retc);}
/******************************************************************** 应答子函数函数原型: void Ack_I2c(bit a);功能: 主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)********************************************************************/voidAck_I2c(u8a){
if(a==0)
SDA(0);//SDA=0;在此发出应答或非应答信号 else
SDA(1);//SDA=1;delay_us(3);
SCL(1);//SCL=1; delay_us(4);//时钟低电平周期大于4μsSCL(0);//SCL=0; 清时钟线,钳住I2C总线以便继续接收delay_us(2);}
pcf8591.h:
#ifndef __pcf8591_H__#define __pcf8591_H__#include"sys.h"#include"i2c.h"u8DACconversion(unsignedcharsla,unsignedcharc,unsignedcharVal);u8ISendByte(unsignedcharsla,unsignedcharc);u8IRcvByte(unsignedcharsla);
#endif
pcf8591.c:
#include"pcf8591.h"/*******************************************************************DAC 变换, 转化函数 *******************************************************************/u8DACconversion(unsignedcharsla,unsignedcharc,unsignedcharVal){
Start_I2c();//启动总线SendByte(sla);//发送器件地址if(ack==0)return(0);
SendByte(c);//发送控制字节if(ack==0)return(0);
SendByte(Val);//发送DAC的数值 if(ack==0)return(0);
Stop_I2c();//结束总线return(1);}
/*******************************************************************ADC发送字节[命令]数据函数 *******************************************************************/u8ISendByte(unsignedcharsla,unsignedcharc){
Start_I2c();//启动总线SendByte(sla);//发送器件地址if(ack==0)return(0);//应答信号SendByte(c);//发送数据if(ack==0)return(0);
Stop_I2c();//结束总线return(1);}
/*******************************************************************ADC读字节数据函数 *******************************************************************/unsignedcharIRcvByte(unsignedcharsla){unsignedcharc;
Start_I2c();//启动总线SendByte(sla+1);//发送器件地址,由于是读取,所以加1if(ack==0)return(0);
c=RcvByte();//读取数据0
Ack_I2c(1);//发送非就答位Stop_I2c();//结束总线return(c);}
最后看main.c的操作
#include"sys.h"#include"delay.h"#include"i2c.h"#include"usart.h"#include"pcf8591.h"#define PCF8591 0x90 vu16date;
intmain(void){
I2c_Init();
uart_init(115200);
delay_init();
while(1)
ISendByte(PCF8591,0x40);
date=IRcvByte(PCF8591);//printf("Send:%d \n\r",date);
delay_ms(500);
以后会更新更多基于IIC协议的器件通信,也会给出库函数版本的操作
总结
实现I2C通信,以下几个要点:
·datasheet中找到address byte,以及相关寄存器的规则
·配置I2C端口以及相关时钟
·根据I2C协议编写通信代码
需要学习单片机的朋友 ,做毕业设计的同学,参加竞赛,关注我们,口令陈老师,与导师一起学习成长,共同进步,还有更多资料领取。
说了这么多,大家记得留意下方评论第一条(或者私信我)有干货~
-END-
*本文系网络转载,版权归原作者所有,如有侵权请联系删除
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.