用I2C实现两单片机联机,接收程序怎么写?

用I2C实现两单片机联机,接收程序怎么写?,第1张

发送和接收差不多,

这里是个例子,有问题欢迎一起讨论

/*****************************************************

/* 文件名: I2C.h

/* 描述 : I2C.c的头文件

/* 编写环境 : Keil uVision 3 V3.51

/* 作者 : XX

/* 学校 : 广东XX大学

/* Email : lanhaospider@163.com

/* 版本 : V1.0

/* 编写日期 : 2008-3-30

/* 仅供学习参考

/* 芯片 : MCS-51 AT89S52

/* 晶振 : 11.0592MHz

/* 功能描述 : 模拟I2C总线的接口程序库,主机的程序

/* 应用 : 发送n个字节: 起始位->发送控制字节(类型标识符4位->

片选3位->读写位最后1位)->应答位->数据->应答...........应答->终止位

高位先到达,低位后到达

/****************************************************/

#include "reg51.h"/*根据不同主控芯片型号改写该套入*/

#include "intrins.h"

sbit SCL = P1^6 /*定义SCL线所在口,根据实践需要改写该定义*/

sbit SDA = P1^7 /*定义SDA线所在口,根据实践需要改写该定义*/

unsigned char idata error/*错误提示,全局变量*/

extern void Start_I2C(void)

extern void Stop_I2C(void)

extern void Ack_I2C(void)

extern void Send_Ack(void)

extern void Send_Not_Ack(void)

extern void Send_I2C(unsigned char send_byte)

extern unsigned char Receive_I2C(void)

/*****************************************************/

/* 文件名: I2C.c

/* 描述 : I2C通信程序

/* 编写环境 : Keil uVision 3 V3.51

/* 作者 : XX

/* 学校 : 广东XX大学

/* Email : lanhaospider@163.com

/* 版本 : V1.0

/* 编写日期 : 2008-3-30

/* 仅供学习参考

/* 芯片 : MCS-51 AT89S52

/* 晶振 : 11.0592MHz

/* 功能描述 : 模拟I2C总线的接口程序库,为主机的程序

/*****************************************************/

#include "I2C.h"

/**************************************************

调用方式 : void Start_I2C(void)

函数说明: 启动I2C总线

**************************************************/

void Start_I2C(void)

{

EA = 0 /*关总中断*/

SDA = 1 /*发送启动总线的数据信号*/

SCL = 1 /*发送启动总线的时钟信号*/

_nop_() /*保持数据线高,延时*/

_nop_()

_nop_()

_nop_()

_nop_()

SDA = 0 /*发送起始信号*/

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

SCL = 0 /*时钟线高低跳变一次,I2C通信开始*/

}

/**************************************************

调用方式 : void Stop_I2C(void)

函数说明: 关闭I2C总线

**************************************************/

void Stop_I2C(void)

{

SCL = 0/*发送关闭总线的时钟信号*/

SDA = 0/*发送关闭总线的数据信号*/

_nop_()

_nop_()

_nop_() /*保持数据线低,延时*/

_nop_()

_nop_()

SCL = 1 /*时钟线一次低高跳变,I2C通信停止*/

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

SDA = 1 /*发送I2C总线停止数据信号*/

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

EA = 1 /*开总中断*/

}

/**************************************************

调用方式 : void Ask_I2C(void)

函数说明: 主控程序等待从器件接收方式应答

**************************************************/

void Ack_I2C(void)

{

unsigned char errtimes = 0xFF

SDA = 1

SCL = 1

error = 0x10

while(SDA)

{

errtimes--

if(!errtimes)

{

Stop_I2C()

error = 0x11

return

}

}

SCL = 0

}

/**************************************************

调用方式 : void Send_Ask(void)

函数说明: 主控程序为接收方,从器件为发送方时,从

器件等待主器件应答

**************************************************/

void Send_Ack(void)

{

SDA = 0 /*保持数据线低,时钟线发生一次高低跳变 发送一个应答信号*/

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

SCL = 1 /*时钟线保持低电平*/

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

SCL = 0

}

/**************************************************

调用方式 : void Send_Not_Ask(void)

函数说明: 主控程序为接收方,从器件为发送方时,非应答信号

**************************************************/

void Send_Not_Ack(void)

{

SDA = 1 /*保持数据线高,时钟线发生一次高低跳变 没有应答*/

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

SCL = 1 /*时钟线保持高电平*/

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

SCL = 0

}

/**************************************************

调用方式 : void Send_I2C(unsigned char send_byte)

函数说明: 总线发送一个字节

**************************************************/

void Send_I2C(unsigned char send_byte)

{

unsigned char send_bit

for(send_bit = 8send_bit <= 0send_bit--)

{

SCL = 0

_nop_()

SDA = (send_byte &0x80)

send_byte<<=1

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

SCL = 1

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

}

SCL = 0

}

/**************************************************

调用方式 : unsigned char Receive_I2C(void)

函数说明: 从I2C总线上接收一个字节

**************************************************/

unsigned char Receive_I2C(void)

{

unsigned char receive_bit , receive_byte = 0

for(receive_bit = 8receive_bit <= 0receive_bit--)

{

SCL = 0

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

SCL = 1

_nop_()

_nop_()

_nop_()

_nop_()

_nop_()

receive_byte <<=1

receive_byte |= SDA

}

SCL = 0

return receive_byte

}

I2C总线是各种总线中使用信号线最少,并具有自动寻址、多主机时钟同步和仲裁等功能的总线。因此,使用I2C总线设计计算机系统十分方便灵活,体积也小,因而在各类实际应用中得到广泛应用。下面举二个应用示例。

I2C的运用比如在铁电存储器中,用铁电存储数据就是用的I2C总线协议。 目前,51、96系列的单片机应用很广,但是由于它们都没有I2C总线接口,从而限制了在这些系统中使用具有I2C总线接口的器件。通过对I2C总线时序的分析,可以用51单片机的两根I/O线来实现I2C总线的功能。接I2C总线规定:SCL线和SDA线是各设备对应输出状态相“与”的结果,任一设备都可以用输出低电平的方法来延长SCL的低电平时间,以迫使高速设备进入等待状态,从而实现不同速度设备间的时钟同步。因此,即使时钟脉冲的高、低电平时间长短不一,也能实现数据的可靠传送,可以用软件控制I/O口做I2C接口。下面就是用GMS97C2051的通用I/O口来作为I2C总线接口,并由软件控制实现数据传送的例子,图6为其连线图。

在单主控器的系统中,时钟线仅由主控器驱动,因此可以用51系列的一根I/O线作为SCL的信号线,将其设置为输出方式,并由软件控制来产生串行时钟信号。在实际系统中使用了P1.3。另一根I/O线P1.2作为I2C总线的串行数据线,可在软件控制下在时钟的低电平期间读取或输出数据。系统传输数据的过程如下:先由单片机发出一个启始数据信号,接着送出要访问器件的7位地址数据,并等待被控器件的应答信号。当收到应答信号后,根据访问要求进行相应的 *** 作。如果是读入数据,则数据线可一直设为输入方式,中间不需要改变SDA线的工作方式,每读入一个字节均应依次检测应答信号;如果是输出数据,则首先将SDA设置为输出方式,当发送完一个字节后,需要改变SDA线为输入方式,此时读入被控器件的应答信号就完成了一个字节的传送。当所有数据传输完毕后,应向SDA发出一个停止信号,以结束该次数据传输。 下面给出51系列用汇编语言实现启始、停止、读、写、应答的程序,读者也可以根据I2C总线时序在96系列或其它单片机上实现I2C总线接口。

a.启动位程序

ACK:CLR P1.3

NOP

NOP

SETB P1.2

NOP

NOP

NOP

CPL P1.3 ;P1.3=1

NOP

NOP

NOP

DENGDAI:JB P1.2,DENGDAI

RET

b.读数据程序

读字节可以在当前地址读(CURRENT

READ),也可以随机读(RANDOM READ),读出数据的最后一个字节后不用加应答信号。

READ:PUSH 0EH

CLR P1.4

LCALL BSTART ;START

MOV A,#0A0H ;SEND THE CNOTROL BYTE

LCALL SENDBYTE

LCALL ACK

MOV A,R1 ;SEND THE ADDRESS

LCALL SENDBYTE

LCALL ACK

LCALL BSTART ;START

MOV A,#0A1H ;SEND THE CNOTROL BYTE

LCALL SENDBYTE

LCALL ACK

LCALL READBYTE

LCALL BSTOP

POP 0EH

RET

送字节程序:

SENDBYTE:PUSH 0EH

PUSH 00H

MOV R0,#08H

LOOP1:CLR P1.3

NOP

NOP

RLC A

MOV P1.2,C

CPL P1.3 P1.3=1

NOP

NOP

DJNZ R0,LOOP1

POP 00H

POP 0EH

RET

读字节子程序:

READBYTE:PUSH 0EH

PUSH 00H

MOV R0,#08H;READ THE CONTENT

CLR A

LOOP4:CLR P1.3

NOP

NOP

NOP

SETB P1.3 ;P1.3=1

MOV C,P1.2

RLC A

DJNZ R0,LOOP4

MOV R2,A

POP 00H

POP 0EH

RET

c.写数据程序:

WRITE:PUSH 0EH

CLR P1.4

LCALL BSTART

MOV A,#0A0H

CLALL SENDBYTE ;SEND THE CONTROL BYTE

LCALL ACK

MOV A,R1 ;SEND THE ADDRESS

LCALL SENDBYTE

LCALL ACK

MOV A,R2 ;WRITE THE CONTENT

LCALL SENDBYTE

LCALL ACK

LCALL BSTOP

POP 0EH

RET

连续写的两个字节之间最好是有10ms的延时。当然,也可以进行页写(PAGE

WRITE),即一次性连续写8个字节,但采用页写方式时每个字节后要有一个应答信号。

d.停止位程序:

BSTOP:CLR P1.3

NOP

NOP

CLR P1.2

NOP

NOP

NOP

SETB P1.3

NOP

NOP

NOP

SETB P1.2

RET // IIC开始

void Start()

{

SDA=1SCL=1NOP4()SDA=0NOP4()SCL=0

}

// IIC 结束

void Stop()

{

SDA=0SCL=0NOP4()SCL=1NOP4()SDA=1

}

// IIC 读取应答

void RACK()

{

SDA=1NOP4()SCL=1NOP4()SCL=0

}

// IIC 发送非应答

void NO_ACK()

{

SDA=1SCL=1NOP4()SCL=0SDA=0

}

// IIC向从设备写入一字节数据

void Write_A_Byte(uchar b)

{

uchar i

for(i=0i<8i++)

{

b<<=1SDA=CY_nop_()SCL=1NOP4()SCL=0

}

RACK()

}

// IIC 向从设备的指定地址写入数据

void Write_IIC(uchar addr,uchar dat)

{

Start()

Write_A_Byte(0xa0)

Write_A_Byte(addr)

Write_A_Byte(dat)

Stop()

DelayMS(10)

}

// IIC 从从设备读取数据

uchar Read_A_Byte()

{

uchar i,b

for(i=0i<8i++)

{

SCL=1b<<=1B|=SDASCL=0

}

return b

}

// IIC 从从设备的当前地址读取数据

uchar Read_Current()

{

uchar d

Start()

Write_A_Byte(0xa1)

d=Read_A_Byte()

NO_ACK()

Stop()

return d

}

// IIC 从从设备的任意地址读取数据

uchar Random_Read(uchar addr)

{

Start()

Write_A_Byte(0xa0)

Write_A_Byte(addr)

Stop()

return Read_Current()

}

假设手上有一块从淘宝上买来的开发板,我要在开发板的I2C总线上增加一个从设备(如at24c08),那么我要怎样写这个“I2C设备驱动”,让

应用程序可以访问at24c08呢?

先来看一个最简单的i2c设备驱动:

static struct i2c_board_info at24cxx_info = { //所支持的i2c设备的列表

I2C_BOARD_INFO("at24c08", 0x50), //一项代表一个支持的设备,它的名字叫做“at24c08”,器件地址是0x50

}

static struct i2c_client *at24cxx_client

static int at24cxx_dev_init(void)

{

struct i2c_adapter *i2c_adap //分配一个适配器的指针

i2c_adap = i2c_get_adapter(0) //调用core层的函数,获得一个i2c总线。这里我们已经知道新增的器件挂接在编号为0的i2c总线上

at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info) // 把i2c适配器和新增的I2C器件关联起来,这个用了i2c总线0,地址是0x50。这就组成了一个客户端

at24cxx_client i2c_put_adapter(i2c_adap)

return 0

}

static void at24cxx_dev_exit(void)

{

i2c_unregister_device(at24cxx_client)

}

module_init(at24cxx_dev_init)

module_exit(at24cxx_dev_exit)

从上面的程序可以看到,写一个i2c设备驱动程序,与写普通的字符驱动基本一样。特别之处是它调用了i2c的core层的函数,以获得对i2c总线的控制。因为用的是开发板,板上的与soc芯片(一般来说就是arm的芯片)i2c总线驱动一般都做好了,直接调用core层的函数就可以控制soc的i2c模块了。也就是说,写i2c设备驱动不需要关注arm内部的i2c模块的寄存器,我们需要关注的是设备(at24c08)的寄存器以及它的datasheet对时序的要求。

其实,添加i2c设备的方法很灵活。根据Linux的官方文档《linux-3.4.2\Documentation\i2c\instantiating-devices》,添加i2c设备的方法总结有4种:

1. i2c_register_board_info:根据总线编号、设备名字(“at24c08”)、设备地址(0x50)注册一个字符驱动。这种方法最简单、最粗暴,最贴近平时在开片机上开发i2c器件的。

2. i2c_new_device:根据i2c总线的编号,声明一个i2c设备:这种方法就是上面例子用的方法。这种方法也简单,但是需要事先知道器件挂接在哪条总线上。对于设备,还实现知道了设备地址0x50,总线适配器也支持名字为“at24c08”的设备

3. i2c_new_probed_device:

4.从用户空间实例化一个器件:这个方法相当智能快速,如下输入指令,即可增加一个i2c设备,同时增加了对应的设备文件。

# echo eeprom 0x50 >/sys/bus/i2c/devices/i2c-3/new_device

根据英文文档的标题,添加i2c设备有称之为“i2c设备的实例化”。

从上述可以知道,在实例化一个i2c设备之前,除了有对应的驱动支持总线外(这里是总线0),还需要有一个驱动使用了总线0发送时序,支持名字为"at24c08"的器件。这个驱动用总线驱动的函数,配置了at24c08的寄存器。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/11097583.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-13
下一篇2023-05-13

发表评论

登录后才能评论

评论列表(0条)

    保存