(1)实验平台普中STM32F103朱雀、玄武开发板这一章我们来学习如何使用 STM32F1 的 IO 口模拟 I2C 时序 并实现与AT24C02EEPROM 之间的双向通信。 本章要实现的功能是 首先检测 AT24C02芯片是否存在 如果存在则输出提示信息 然后通过按键 KEY_UP 和 KEY1 控制AT24C02 数据读写 并输出写入和读取的数据信息 最后让 DS0 指示灯闪烁提示系统正常运行。 学习本章可以参考“6--芯片资料” 的 24C02 数据手册。 本章分为如下几部分内容35.1 I2C 介绍35.1.1 I2C 物理层35.1.2 I2C 协议层35.2 AT24C02 介绍35.3 硬件设计35.4 软件设计35.4.1 I2C 初始化函数35.4.2 I2C 读写字节函数35.4.3 AT24CXX 数据读写函数35.4.4 主函数35.5 实验现象课后作业35.1 I2C 介绍I2CInterIntegrated Circuit 总线是由 PHILIPS 公司开发的两线式串行总线 用于连接微控制器及其外围设备。 是微电子通信控制领域广泛采用的一种总线标准。 它是同步通信的一种特殊形式 具有接口线少 控制方式简单 器件封装形式小 通信速率较高等优点。 I2C 总线只有两根双向信号线。 一根是数据线 SDA 另一根是时钟线 SCL。 由于其管脚少 硬件实现简单 可扩展性强等特点 因此被广泛的使用在各大集成芯片内。 下面我们就从 I2C 的物理层与协议层来了解 I2C。35.1.1 I2C 物理层I2C 通信设备常用的连接方式如下图所示它的物理层有如下特点①它是一个支持多设备的总线。 “总线” 指多个设备共用的信号线。 在一个 I2C 通讯总线中 可连接多个 I2C 通讯设备 支持多个通讯主机及多个通讯从机。②一个 I2C 总线只使用两条总线线路 一条双向串行数据线(SDA) 一条串行时钟线(SCL)。 数据线即用来表示数据 时钟线用于数据收发同步。③每个连接到总线的设备都有一个独立的地址 主机可以利用这个地址进行不同设备之间的访问。④总线通过上拉电阻接到电源。 当 I2C 设备空闲时 会输出高阻态 而当所有设备都空闲 都输出高阻态时 由上拉电阻把总线拉成高电平。⑤多个主机同时使用总线时 为了防止数据冲突 会利用仲裁方式决定由哪个设备占用总线。⑥具有三种传输模式 标准模式传输速率为 100kbit/s 快速模式为400kbit/s 高速模式下可达 3.4Mbit/s 但目前大多 I2C 设备尚不支持高速模式。⑦连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。下面我们来了解下 I2C 总线常用的一些术语主机启动数据传送并产生时钟信号的设备从机被主机寻址的器件多主机同时有多于一个主机尝试控制总线但不破坏传输主模式用 I2CNDAT 支持自动字节计数的模式 位 I2CRM,I2CSTT,I2CSTP控制数据的接收和发送从模式发送和接收操作都是由 I2C 模块自动控制的仲裁是一个在有多个主机同时尝试控制总线但只允许其中一个控制总线并使传输不被破坏的过程同步两个或多个器件同步时钟信号的过程发送器发送数据到总线的器件接收器从总线接收数据的器件。35.1.2 I2C 协议层I2C 的协议定义了通信的起始和停止信号、 数据有效性、 响应、 仲裁、 时钟同步和地址广播等环节。 下面我们就来简单介绍下。1 数据有效性规定I2C 总线进行数据传送时 时钟信号为高电平期间 数据线上的数据必须保持稳定 只有在时钟线上的信号为低电平期间 数据线上的高电平或低电平状态才允许变化。 如下图每次数据传输都以字节为单位 每次传输的字节数不受限制。2 起始和停止信号SCL 线为高电平期间 SDA 线由高电平向低电平的变化表示起始信号 SCL线为高电平期间 SDA 线由低电平向高电平的变化表示终止信号。 如下图起始和终止信号都是由主机发出的 在起始信号产生后 总线就处于被占用的状态 在终止信号产生后 总线就处于空闲状态。3 应答响应每当发送器件传输完一个字节的数据后 后面必须紧跟一个校验位 这个校验位是接收端通过控制 SDA数据线 来实现的 以提醒发送端数据我这边已经接收完成 数据传送可以继续进行。 这个校验位其实就是数据或地址传输过程中的响应。 响应包括“应答(ACK)” 和“非应答(NACK)” 两种信号。 作为数据接收端时 当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后 若希望对方继续发送数据 则需要向对方发送“应答(ACK)” 信号即特定的低电平脉冲发送方会继续发送下一个数据 若接收端希望结束数据传输 则向对方发送“非应答(NACK)” 信号即特定的高电平脉冲 发送方接收到该信号后会产生一个停止信号 结束信号传输。 应答响应时序图如下每一个字节必须保证是 8 位长度。 数据传送时 先传送最高位MSB 每一个被传送的字节后面都必须跟随一位应答位即一帧共有 9 位 。由于某种原因从机不对主机寻址信号应答时如从机正在进行实时性的处理工作而无法接收总线上的数据 它必须将数据线置于高电平 而由主机产生一个终止信号以结束总线的数据传送。如果从机对主机进行了应答 但在数据传送一段时间后无法继续接收更多的数据时 从机可以通过对无法接收的第一个数据字节的“非应答” 通知主机 主机则应发出终止信号以结束数据的继续传送。当主机接收数据时 它收到最后一个数据字节后 必须向从机发出一个结束传送的信号。 这个信号是由对从机的“非应答” 来实现的。 然后 从机释放 SDA线 以允许主机产生终止信号。这些信号中 起始信号是必需的 结束信号和应答信号都可以不要。4 总线的寻址方式I2C 总线寻址按照从机地址位数可分为两种 一种是 7 位 另一种是 10 位。采用 7 位的寻址字节寻址字节是起始信号后的第一个字节 的位定义如下D7D1 位组成从机的地址。 D0 位是数据传送方向位 为“0” 时表示主机向从机写数据 为“1” 时表示主机由从机读数据。10 位寻址和 7 位寻址兼容 而且可以结合使用。 10 位寻址不会影响已有的7 位寻址 有 7 位和 10 位地址的器件可以连接到相同的 I2C 总线。 我们就以 7位寻址为例进行介绍。当主机发送了一个地址后 总线上的每个器件都将头 7 位与它自己的地址比较 如果一样 器件会判定它被主机寻址 其他地址不同的器件将被忽略后面的数据信号。 至于是从机接收器还是从机发送器 都由 R/W 位决定的。从机的地址由固定部分和可编程部分组成。 在一个系统中可能希望接入多个相同的从机 从机地址中可编程部分决定了可接入总线该类器件的最大数目。 如一个从机的 7 位寻址位有 4 位是固定位 3 位是可编程位 这时仅能寻址 8 个同样的器件 即可以有 8 个同样的器件接入到该 I2C 总线系统中。5 数据传输I2C 总线上传送的数据信号是广义的 既包括地址信号 又包括真正的数据信号。 在起始信号后必须传送一个从机的地址7 位 第 8 位是数据的传送方向位R/W 用“0” 表示主机发送写 数据W “1” 表示主机接收数据R 。 每次数据传送总是由主机产生的终止信号结束。 但是 若主机希望继续占用总线进行新的数据传送 则可以不产生终止信号 马上再次发出起始信号对另一从机进行寻址。在总线的一次数据传送过程中 可以有以下几种组合方式a、 主机向从机发送数据 数据传送方向在整个传送过程中不变注意 有阴影部分表示数据由主机向从机传送 无阴影部分则表示数据由从机向主机传送。 A 表示应答 A 非表示非应答 高电平 。 S 表示起始信号 P表示终止信号。b、 主机在第一个字节后 立即从从机读数据c、 在传送过程中 当需要改变传送方向时 起始信号和从机地址都被重复产生一次 但两次读/写方向位正好反相到这里我们就介绍完了 I2C 总线 现如今大部分的 MCU 都自带 I2C 总线接口 STM32F1 芯片也不例外 STM32F1 芯片自带 2 个 I2C 接口 I2C1 和 I2C2 但是本章实验我们不使用 STM32F1 自带的硬件 I2C 而采用软件模拟 I2C。 主要原因是STM32F1 的硬件 IIC 设计的比较复杂 而且稳定性不怎么好 程序移植比较麻烦而用软件模拟 IIC 最大的好处就是移植方便 同一个代码兼容所有单片机 任何一个单片机只要有 IO 口不需要特定 IO 都可以很快的移植过去。 有兴趣的朋友 可以结合 STM32F1 中文参考手册学习下硬件 I2C。下面我们再来了解下开发板上的 AT24C02 芯片。35.2 AT24C02 介绍AT24C01/02/04/08/16...是一个 1K/2K/4K/8K/16K 位串行 CMOS 内部含有128/256/512/1024/2048 个 8 位字节 AT24C01 有一个 8 字节页写缓冲器 AT24C02/04/08/16 有一个 16 字节页写缓冲器。 该器件通过 I2C 总线接口进行操作 它有一个专门的写保护功能。 我们开发板上使用的是 AT24C02EEPROM 芯片 此芯片具有 I2C 通信接口 芯片内保存的数据在掉电情况下都不丢失 所以通常用于存放一些比较重要的数据等。 AT24C02 芯片管脚及外观图如下图所示芯片管脚说明如下图所示AT24C02 器件地址为 7 位 高 4 位固定为 1010 低 3 位由 A0/A1/A2 信号线的电平决定。 因为传输地址或数据是以字节为单位传送的 当传送地址时 器件地址占 7 位 还有最后一位最低位 R/W 用来选择读写方向 它与地址无关。其格式如下我们开发板已经将芯片的 A0/A1/A2 连接到 GND 所以器件地址为 1010000即 0x50未计算最低位 。 如果要对芯片进行写操作时 R/W 即为 0 写器件地址即为 0XA0 如果要对芯片进行读操作时 R/W 即为 1 此时读器件地址为 0XA1。开发板上我们也将 WP 引脚直接接在 GND 上 此时芯片允许数据正常读写。I2C 总线时序如下图所示关于 AT24C02EEPROM 的更多信息 可参考“\6--芯片资料” 内 24C02 数据手册来了解。35.3 硬件设计本实验使用到硬件资源如下1 DS0 指示灯2 KEY_UP 和 KEY1 按键3 串口 14 AT24C02DS0 指示灯、 KEY_UP 和 KEY1 按键、 串口 1 电路在前面章节都介绍过 这里就不多说 AT24C02EEPROM 模块电路图如下图所示从电路图中可以看到 24C02 芯片的 SCL 和 SDA 管脚连接在 STM32F1 芯片的PB6 和 PB7 管脚上 并且都上拉了一个 4.7K 的电阻。 通过这两个管脚模拟 I2C时序与 24C02 通信 从 STM32F1 芯片管脚功能图中可以看到这两个管脚本身也是STM32F1 自带的硬件 I2C1 接口 所以如果要使用 STM32F1 硬件 I2C 与 24C02 芯片进行通信 也就不需要修改电路。DS0 指示灯用来提示系统运行状态 KEY_UP 按键用来控制 24C02 数据的写入 KEY1 按键用来控制 24C02 数据的读取 数据的写入与读取信息通过串口 1 将打印出来。35.4 软件设计本章所要实现的功能是 首先检测 AT24C02 芯片是否存在 如果存在则输出提示信息 然后通过按键 KEY_UP 和 KEY1 控制 AT24C02 数据读写 并输出写入和读取的数据信息 最后让 DS0 指示灯闪烁提示系统正常运行。 程序框架如下1 使能所用 GPIO 端口时钟 初始化 GPIO2 使用软件模拟 I2C 通信时序 包含起始和停止信号、 应答信号等3 编写 AT24C02 读写函数4 编写主函数前面的实验章节都有 GPIO 的初始化 现在对大家来说应该不是问题 所以本章软件的重点在 I2C 时序的模拟及数据的读写上。 下面我们打开“\4--实验程序\1--基础实验\27-I2C-EEPROM 实验” 工程 在 APP 工程组中可以看到添加了iic.c 和 24cxx.c 文件iic.c 里面包含了 I2C 驱动程序 24cxx.c 里面包含了AT24CXX 驱动程序 并且此程序支持 AT24C01/02/04/08/16/32/64/128/256 芯片 还要包含对应的头文件路径。这里我们分析几个重要函数 其他部分程序大家可以打开工程查看。35.4.1 I2C 初始化函数我们是使用 STM32F1 的 PB6 和 PB7 模拟 I2C 所以需要使能其端口时钟 并初始化 GPIO 初始化代码如下/******************************************************************************* * 函 数 名 : IIC_Init * 函数功能 : IIC初始化 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC,ENABLE); GPIO_InitStructure.GPIO_PinIIC_SCL_PIN; GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz; GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP; GPIO_Init(IIC_SCL_PORT,GPIO_InitStructure); GPIO_InitStructure.GPIO_PinIIC_SDA_PIN; GPIO_Init(IIC_SDA_PORT,GPIO_InitStructure); IIC_SCL1; IIC_SDA1; }由于我们使用的是软件模拟 I2C 所以在配置管脚为推挽输出即可 如果配置为开漏输出也是可以的 因为 PB6 和 PB7 上都外接了上拉电阻。 但是如果使用STM32F1 硬件 I2C 就必须配置为开漏输出。 初始化函数内的 IIC_SCL 和 IIC_SDA是我们定义的宏 其实就是对 PB6 和 PB7 位带的封装 可以在 iic.h 头文件查看到。35.4.2 I2C 读写字节函数通过前面 I2C 的介绍 我们知道 要进行 I2C 通信 需要编写起始信号、 停止信号、 应答和非应答信号。 因为 I2C 通信是以字节为单位进行传输的 所以还需要编写 I2C 读写字节的函数。 数据的读写是在 SDA 线上完成 读数据的时候要配置此管脚为输入模式 写数据的时候要配置为输出模式 具体代码如下/******************************************************************************* * 函 数 名 : SDA_OUT * 函数功能 : SDA输出配置 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void SDA_OUT(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_PinIIC_SDA_PIN; GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz; GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP; GPIO_Init(IIC_SDA_PORT,GPIO_InitStructure); } /******************************************************************************* * 函 数 名 : SDA_IN * 函数功能 : SDA输入配置 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void SDA_IN(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_PinIIC_SDA_PIN; GPIO_InitStructure.GPIO_ModeGPIO_Mode_IPU; GPIO_Init(IIC_SDA_PORT,GPIO_InitStructure); } /******************************************************************************* * 函 数 名 : IIC_Start * 函数功能 : 产生IIC起始信号 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void IIC_Start(void) { SDA_OUT(); //sda线输出 IIC_SDA1; IIC_SCL1; delay_us(5); IIC_SDA0;//START:when CLK is high,DATA change form high to low delay_us(6); IIC_SCL0;//钳住I2C总线准备发送或接收数据 } /******************************************************************************* * 函 数 名 : IIC_Stop * 函数功能 : 产生IIC停止信号 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void IIC_Stop(void) { SDA_OUT();//sda线输出 IIC_SCL0; IIC_SDA0;//STOP:when CLK is high DATA change form low to high IIC_SCL1; delay_us(6); IIC_SDA1;//发送I2C总线结束信号 delay_us(6); } /******************************************************************************* * 函 数 名 : IIC_Wait_Ack * 函数功能 : 等待应答信号到来 * 输 入 : 无 * 输 出 : 1接收应答失败 0接收应答成功 *******************************************************************************/ u8 IIC_Wait_Ack(void) { u8 tempTime0; IIC_SDA1; delay_us(1); SDA_IN(); //SDA设置为输入 IIC_SCL1; delay_us(1); while(READ_SDA) { tempTime; if(tempTime250) { IIC_Stop(); return 1; } } IIC_SCL0;//时钟输出0 return 0; } /******************************************************************************* * 函 数 名 : IIC_Ack * 函数功能 : 产生ACK应答 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void IIC_Ack(void) { IIC_SCL0; SDA_OUT(); IIC_SDA0; delay_us(2); IIC_SCL1; delay_us(5); IIC_SCL0; } /******************************************************************************* * 函 数 名 : IIC_NAck * 函数功能 : 产生NACK非应答 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void IIC_NAck(void) { IIC_SCL0; SDA_OUT(); IIC_SDA1; delay_us(2); IIC_SCL1; delay_us(5); IIC_SCL0; } /******************************************************************************* * 函 数 名 : IIC_Send_Byte * 函数功能 : IIC发送一个字节 * 输 入 : txd发送一个字节 * 输 出 : 无 *******************************************************************************/ void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL0;//拉低时钟开始数据传输 for(t0;t8;t) { if((txd0x80)0) //0x80 1000 0000 IIC_SDA1; else IIC_SDA0; txd1; delay_us(2); //对TEA5767这三个延时都是必须的 IIC_SCL1; delay_us(2); IIC_SCL0; delay_us(2); } } /******************************************************************************* * 函 数 名 : IIC_Read_Byte * 函数功能 : IIC读一个字节 * 输 入 : ack1时发送ACKack0发送nACK * 输 出 : 应答或非应答 *******************************************************************************/ u8 IIC_Read_Byte(u8 ack) { u8 i,receive0; SDA_IN();//SDA设置为输入 for(i0;i8;i ) { IIC_SCL0; delay_us(2); IIC_SCL1; receive1; if(READ_SDA)receive; delay_us(1); } if (!ack) IIC_NAck();//发送nACK else IIC_Ack(); //发送ACK return receive; }SDA_OUT 函数功能是配置 SDA 管脚为输出模式 SDA_IN 函数功能是配置 SDA 管脚为输入模式 这两个函数我们是通过初始化 GPIO 方式完成 当然也可以直接配置寄存器来修改管脚模式 如果这个寄存器的配置看不懂 可以查找下中文参考手册内 GPIO 寄存器介绍。35.4.3 AT24CXX 数据读写函数编写好 I2C 数据读写函数 下面就可以编写 AT24CXX 的读写函数 代码如下/******************************************************************************* * 函 数 名 : AT24CXX_ReadOneByte * 函数功能 : 在AT24CXX指定地址读出一个数据 * 输 入 : ReadAddr:开始读数的地址 * 输 出 : 读到的数据 *******************************************************************************/ u8 AT24CXX_ReadOneByte(u16 ReadAddr) { u8 temp0; IIC_Start(); if(EE_TYPEAT24C16) { IIC_Send_Byte(0XA0); //发送写命令 IIC_Wait_Ack(); IIC_Send_Byte(ReadAddr8);//发送高地址 } else { IIC_Send_Byte(0XA0((ReadAddr/256)1)); //发送器件地址0XA0,写数据 } IIC_Wait_Ack(); IIC_Send_Byte(ReadAddr%256); //发送低地址 IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(0XA1); //进入接收模式 IIC_Wait_Ack(); tempIIC_Read_Byte(0); IIC_Stop();//产生一个停止条件 return temp; } /******************************************************************************* * 函 数 名 : AT24CXX_WriteOneByte * 函数功能 : 在AT24CXX指定地址写入一个数据 * 输 入 : WriteAddr :写入数据的目的地址 DataToWrite:要写入的数据 * 输 出 : 无 *******************************************************************************/ void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite) { IIC_Start(); if(EE_TYPEAT24C16) { IIC_Send_Byte(0XA0); //发送写命令 IIC_Wait_Ack(); IIC_Send_Byte(WriteAddr8);//发送高地址 } else { IIC_Send_Byte(0XA0((WriteAddr/256)1)); //发送器件地址0XA0,写数据 } IIC_Wait_Ack(); IIC_Send_Byte(WriteAddr%256); //发送低地址 IIC_Wait_Ack(); IIC_Send_Byte(DataToWrite); //发送字节 IIC_Wait_Ack(); IIC_Stop();//产生一个停止条件 delay_ms(10); } /******************************************************************************* * 函 数 名 : AT24CXX_WriteLenByte * 函数功能 : 在AT24CXX里面的指定地址开始写入长度为Len的数据 用于写入16bit或者32bit的数据 * 输 入 : WriteAddr :写入数据的目的地址 DataToWrite:要写入的数据 Len :要写入数据的长度2,4 * 输 出 : 无 *******************************************************************************/ void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len) { u8 t; for(t0;tLen;t) { AT24CXX_WriteOneByte(WriteAddrt,(DataToWrite(8*t))0xff); } } /******************************************************************************* * 函 数 名 : AT24CXX_ReadLenByte * 函数功能 : 在AT24CXX里面的指定地址开始读出长度为Len的数据 用于读出16bit或者32bit的数据 * 输 入 : ReadAddr :开始读出的地址 Len :要读出数据的长度2,4 * 输 出 : 读取的数据 *******************************************************************************/ u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len) { u8 t; u32 temp0; for(t0;tLen;t) { temp8; tempAT24CXX_ReadOneByte(ReadAddrLen-t-1); } return temp; } /******************************************************************************* * 函 数 名 : AT24CXX_Check * 函数功能 : 检查AT24CXX是否正常 * 输 入 : 无 * 输 出 : 1:检测失败0:检测成功 *******************************************************************************/ u8 AT24CXX_Check(void) { u8 temp; tempAT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX if(temp0x36)return 0; else//排除第一次初始化的情况 { AT24CXX_WriteOneByte(255,0X36); tempAT24CXX_ReadOneByte(255); if(temp0X36)return 0; } return 1; } /******************************************************************************* * 函 数 名 : AT24CXX_Read * 函数功能 : 在AT24CXX里面的指定地址开始读出指定个数的数据 * 输 入 : ReadAddr :开始读出的地址 对24c02为0~255 pBuffer :数据数组首地址 NumToRead:要读出数据的个数 * 输 出 : 无 *******************************************************************************/ void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead) { while(NumToRead) { *pBufferAT24CXX_ReadOneByte(ReadAddr); NumToRead--; } } /******************************************************************************* * 函 数 名 : AT24CXX_Write * 函数功能 : 在AT24CXX里面的指定地址开始写入指定个数的数据 * 输 入 : WriteAddr :开始写入的地址 对24c02为0~255 pBuffer :数据数组首地址 NumToRead:要读出数据的个数 * 输 出 : 无 *******************************************************************************/ void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite) { while(NumToWrite--) { AT24CXX_WriteOneByte(WriteAddr,*pBuffer); WriteAddr; pBuffer; } }这部分函数在 24cxx.c 文件内 这个文件支持 AT24CXX 系列的很多芯片 可通过 24cxx.h 文件内的 EE_TYPE 宏来修改 可修改的宏定义如下#define AT24C01 127 #define AT24C02 255 #define AT24C04 511 #define AT24C08 1023 #define AT24C16 2047 #define AT24C32 4095 #define AT24C64 8191 #define AT24C128 16383 #define AT24C256 32767由于我们开发板上使用的是 AT24C02 所以宏定义默认是#define EE_TYPE AT24C02如果你自己设计的电路板上使用的是其他的 AT24CXX 芯片 那么可以修改这个宏 实现不同芯片数据的读取和写入操作。35.4.4 主函数编写好 I2C 和 AT24CXX 驱动函数后 接下来就可以编写主函数了 代码如下#include system.h #include SysTick.h #include led.h #include usart.h #include key.h #include 24cxx.h int main() { u8 i0; u8 key; u8 k0; SysTick_Init(72); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组 LED_Init(); USART1_Init(115200); KEY_Init(); AT24CXX_Init(); while(AT24CXX_Check()) //检测AT24C02是否正常 { printf(AT24C02检测不正常!\r\n); delay_ms(500); } printf(AT24C02检测正常!\r\n); while(1) { keyKEY_Scan(0); if(keyKEY_UP_PRESS) { k; if(k255) { k255; } AT24CXX_WriteOneByte(0,k); printf(写入的数据是%d\r\n,k); } if(keyKEY1_PRESS) { kAT24CXX_ReadOneByte(0); printf(读取的数据是%d\r\n,k); } i; if(i%200) { LED1!LED1; } delay_ms(10); } }主函数实现的功能很简单 首先调用之前编写好的硬件初始化函数 包括SysTick 系统时钟 LED 初始化等。 然后调用我们前面编写的 AT24CXX_Init 函数初始化 I2C 端口 接着调用 AT24CXX_Check 函数检测是否存在 AT24CXX 芯片 如果芯片存在就会输出“AT24C02 检测正常!” 信息。 最后进入 while 循环 调用KEY_Scan 函数 不断检测 KEY_UP 和 KEY1 按键是否按下 如果 KEY_UP 按键按下将变量 k 值加 1 写入到 24C02 的地址 0 内 并通过打印输出写入的值 当 KEY1按键按下 将 24C02 的地址 0 内数据读取出来保存到变量 k 中 并通过打印输出读取的值。 整个过程中 DS0 指示灯会间隔 200ms 闪烁 提示系统正常运行。35.5 实验现象将工程程序编译后下载到开发板内 串口会输出 AT24C02 检测正常信息 同时可以看到 DS0 指示灯不断闪烁 表示程序正常运行。 当按下 KEY_UP 按键后将数据写入到 24C02 芯片内 同时串口打印输出写入的值 当按下 KEY1 按键后读取 24C02 芯片内的值 同时串口打印输出读取的值。 如果想在串口调试助手上看到输出信息 可以打开“\5--开发工具\4-常用辅助开发软件\串口调试助手\串口调试助手丁丁” 内串口调试助手 实验现象如下 前提一定要连接好线路 USB 线一端连接电脑 另一端连接开发板“USB 转串口模块” 上的 USB 下载口 并且在“USB 转 TTL电源” 模块上 P4 端子短接片已插上实验说明 存储在 24C02EEPROM 芯片内的值 掉电时都不会丢失。 大家可以使用一条导线将 PB6 和 PB7 短接 重启下系统 这个时候就会看到初始化时会报“AT24C02 检测不正常!” 注意还是要按照前面操作串口助手的方法才能看到 。课后作业1 使用 KEY_UP 按键写入多个字节数据到 AT24C02 内 比如写入“www.prechin.cn” 字符串 使用 KEY1 按键读取上次写入的数据 通过串口输出。 温馨提示 调用 AT24CXX_Write 与 AT24CXX_Read 函数即可