本帖最后由 FPGA课程 于 2024-9-25 14:37 编辑
软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述我们知道I2C总线具备广泛的用途,比如寄存器的配置,EEPROM的使用,更重要的是I2C总线上可以挂多种外设。 对于一些低速器件的访问非常节省IO资源,由于是标准的总线接口,使用起来非常方便。I2C总线是OC开路,支持双向传输,所以总线上需要上拉电阻,如下图。
实验目的: 1:学习I2C总线协议 2:熟悉ZYNQ I2C控制器的资源 3:掌握vitis-sdk下中断和polled两种模式的使用 4:掌握EEPROM的I2C访问时序 5:掌握vitis-sdk下对EEPROM的存储空间读写 2系统框图
3I2C串行总线及EEPROM介绍
3.1I2C串行总线协议由于节课讲解的I2C是基于ZYNQ的I2C控制器,实际上可以不需要非常清楚I2C的详细时序,但是作为初学者,如果第一次学习I2C总线的,还是有必要学习下。
I2C协议把传输的消息分为两种类型的帧: 一个地址帧 :用于master指明消息发往哪个slave; 一个或多个数据帧: 由master发往slave的数据(或由slave发往master),每一帧是8-bit的数据。 注:协议要求每次放到SDA上的字节长度必须为8位,并且每个字节后须跟一个ACK位,在下面会讲到。 数据在SCL处于低电平时放到SDA上,并在SCL变为高电平后进行采样。读写数据和SCL上升沿之间的时间间隔是由总线上的设备自己定义的,不同芯片可能有差异。 I2C数据传输的时序图如下:
开始条件(start condition): 为了标识传输正式启动,master设备会将SCL置为高电平(当总线空闲时,SDA和SCL都处于高电平状态),然后将SDA拉低,这样,所有slave设备就会知道传输即将开始。如果两个master设备在同一时刻都希望获得总线的所有权,那么谁先将SDA拉低,谁就赢得了总线的控制权。在整个通信期间,可以存在多个start来开启每一次新的通信序列(communication sequence),而无需先放弃总线的控制权,后面会讲到这种机制。
地址帧(address frame): 地址帧总是在一次通信的最开始出现。一个7-bit的地址是从最高位(MSB)开始发送的,这个地址后面会紧跟1-bit的操作符,1表示读操作,0表示写操作。 接下来的一个bit是NACK/ACK,当这个帧中前面8bits发送完后,接收端的设备获得SDA控制权,此时接收设备应该在第9个时钟脉冲之前回复一个ACK(将SDA拉低)以表示接收正常,如果接收设备没有将SDA拉低,则说明接收设备可能没有收到数据(如寻址的设备不存在或设备忙)或无法解析收到的消息,如果是这样,则由master来决定如何处理(stop或repeated start condition)。
数据帧(data frames): 在地址帧发送之后,就可以开始传输数据了。Master继续产生时钟脉冲,而数据则由master(写操作)或slave(读操作)放到SDA上。每个数据帧8bits,数据帧的数量可以是任意的,直到产生停止条件。每一帧数据传输(即每8-bit)之后,接收方就需要回复一个ACK或NACK(写数据时由slave发送ACK,读数据时由master发送ACK。当master知道自己读完最后一个byte数据时,可发送NACK然后接stop condition)。
停止条件(stop condition): 当所有数据都发送完成时,master将产生一个停止条件。停止条件定义为:在SDA置于低电平时,将SCL拉高并保持高电平,然后将SDA拉高。 注意,在正常传输数据过程中,当SCL处于高电平时,SDA上的值不应该变化,防止意外产生一个停止条件。
重复开始条件(repeated start condition): 有时master需要在一次通信中进行多次消息交换(例如与不同的slave传输消息,或切换读写操作),并且期间不希望被其他master干扰,这时可以使用“重复开始条件” —— 在一次通信中,master可以产生多次start condition,来完成多次消息交换,最后再产生一个stop condition结束整个通信过程。由于期间没有stop condition,因此master一直占用总线,其他master无法切入。 为了产生一个重复的开始条件,SDA在SCL低电平时拉高,然后SCL拉高。接着master就可以产生一个开始条件继续新的消息传输(按照正常的7-bit/10-bit地址传输时序)。重复开始条件的传输时序如下图所示:
时钟拉伸(clock stretching): 有时候,低速slave可能由于上一个请求还没处理完,尚无法继续接收master的后续请求,即master的数据传输速率超过了slave的处理能力。这种情况下,slave可以进行时钟拉伸来要求master暂停传输数据 —— 通常时钟都是由master提供的,slave只是在SDA上放数据或读数据。而时钟拉伸则是slave在master释放SCL后,将SCL主动拉低并保持,此时要求master停止在SCL上产生脉冲以及在SDA上发送数据,直到slave释放SCL(SCL为高电平)。之后,master便可以继续正常的数据传输了。可见时钟拉伸实际上是利用了时钟同步的机制(见下文),只是时钟由slave产生。 如果系统中存在这种低速slave并且slave实现了clock stretching,则master必须实现为能够处理这种情况,实际上大部分slave设备中不包含SCL驱动器的,因此无法拉伸时钟。 所以更完整的I2C数据传输时序图为:
10-bit地址空间: 上面讲到I2C支持10-bit的设备地址,此时的时序如下图所示:
在10-bit地址的I2C系统中,需要两个帧来传输slave的地址。第一个帧的前5个bit固定为b11110,后接slave地址的高2位,第8位仍然是R/W位,接着是一个ACK位,由于系统中可能有多个10-bit slave设备地址的高2bit相同,因此这个ACK可能由多有slave设备设置。第二个帧紧接着第一帧发送,包含slave地址的低8位(7:0),接着该地址的slave回复一个ACK(或NACK)。 注意,10-bit地址的设备和7-bit地址的设备在一个系统中是可以并存的,因为7-bit地址的高5位不可能是b11110。实际上对于7-bit的从设备地址,合法范围为b0001XXX-b1110XXX,’X’表示任意值,因此该类型地址最多有112个(其他为保留地址[1])。 两个地址帧传输完成后,就开始数据帧的传输了,这和7-bit地址中的数据帧传输过程相同。
时钟同步和仲裁: 如果两个master都想在同一条空闲总线上传输,此时必须能够使用某种机制来选择将总线控制权交给哪个master,这是通过时钟同步和仲裁来完成的,而被迫让出控制权的master则需要等待总线空闲后再继续传输。在单一master的系统上无需实现时钟同步和仲裁。
时钟同步: 时钟同步是通过I2C接口和SCL之间的线“与”(wired-AND)来完成的,即如果有多个master同时产生时钟,那么只有所有master都发送高电平时,SCL上才表现为高电平,否则SCL都表现为低电平。
总线仲裁: 总线仲裁和时钟同步类似,当所有master在SDA上都写1时,SDA的数据才是1,只要有一个master写0,那此时SDA上的数据就是0。一个master每发送一个bit数据,在SCL处于高电平时,就检查看SDA的电平是否和发送的数据一致,如果不一致,这个master便知道自己输掉仲裁,然后停止向SDA写数据。也就是说,如果master一直检查到总线上数据和自己发送的数据一致,则继续传输,这样在仲裁过程中就保证了赢得仲裁的master不会丢失数据。 输掉仲裁的master在检测到自己输了之后也不再产生时钟脉冲,并且要在总线空闲时才能重新传输。 仲裁的过程可能要经过多个bit的发送和检查,实际上两个master如果发送的时序和数据完全一样,则两个master都能正常完成整个的数据传输。 3.2EEPROM 24C02芯片经过上面对I2C总线的介绍,下面我们介绍EEPROM 24C02的I2C总线读写控制。 24C02的容量位2Kbit=256Bytes,每1页16Bytes因此又16页。对于写操作一次最多写16Bytes,对于读操作可以一次全部读完256Bytes. 如下图,A0-A2是EEPROM I2C器件地址,SDA和SCL是EEPROM I2C总线SLAVE接口,WP是保护脚,一般接VCC。
24LXX 器件地址如下图
我们看下24C02的写时序,可以看到,支持单个字节的写,以及多个字节的写。首先发送器件的地址,然后发送需要写EEPROM存储空间的地址,之后就是数据,对于读操作一次可以写1个字节或者多个字节。
我们看下24C02的读时序,可以看到,支持单个字节的读,以及多个字节的读。以下支持3种读的方式: 第一种:CURRENT ADDRESS READ 只要发送器件地址就能读数据 第二种:RANDOM READ 需要发送器件地址,然后发送内存地址,之后再发送器件地址并且读取到数据,支持连续读取。 第三种:SEQUENTIAL CURRENT READ 只要发送器件地址,就能连续读取当前地址的数据,支持连续读取。 教程代码中采用的是第二种方法。
4ZYNQ I2C控制器介绍
4.1ZYNQ I2C控制器框图
ZYNQ的PS自带2个I2C控制器,I2C的IO可以映射到MIO或者EMIO上。ZYNQ的I2C控制器支持Master模式或者Slave模式,一般都是使用master模块,本实验也是基于Master模式访问EEPROM.
4.2ZYNQ I2C控制器寄存器XIICPS_CR寄存器XIICPS_CR_OFFSET (0x00U) I2C控制寄存器 Field Name | Bits | Type | Reset Value | Description | divisor_a | 15:14 | WR | 0x0 | 预分频A:对应的值位N+1,N的大小为0~3。 | divisor_b | 13:8 | WR | 0x0 | 预分频B:对应的值为N+1,N的大小为0~63 | 保留 | 7 | RO | 0x0 | 保留 | CLR_FIFO | 6 | WR | 0x0 | 清FIFO和传输大小寄存器 1-清除FIFO和传输大小寄存器,该位下一个APB 时钟自动归零 | SLVMON | 5 | WR | 0x0 | 从Monitor模式(本实验用不到) 1-Monitor模式 0-普通模式 | HOLD | 4 | WR | 0x0 | 总线保持 1-保持scl时钟低电平,这样总线上不再继续传输数据 0-当数据传输完毕可以立即停止传输 | ACK_EN | 3 | WR | 0x0 | ACK/NACK 1-发送ACK 0-发送NACK | NEA | 2 | WR | 0x0 | 地址模式 1-7bit地址模式 0-预留 | MS | 1 | WR | 0x0 | 主/从模式 1-Master模式 0-Slave模式 | RW | 0 | WR | 0x0 | 方向控制 1-Master接收 0- Master发送 |
XIICPS_SR寄存器XIICPS_SR_OFFSET (0x04U) I2C状态寄存器 Field Name | Bits | Type | Reset Value | Description | 保留 | 15:0 | RO | 0x0 | 保留 | BA | 8 | RO |
| 总线激活 1-数据正在传输 | RXOVF | 7 | RO |
| 接收FIFO溢出 1-接收FIFO已经满,继续有数据收到,FIFO的数据保持不变 | TXDV | 6 | RO |
| 数据有效 1-有1Byte需要发送,不能用该位判断数据传输完毕。 | RXDV | 5 | RO |
| 接收数据有效 1-接收端口数据需要被读走 | 保留 | 4 | RO |
| 保留 | RXRW | 3 | RO |
| RX读/写(仅工作SLAVE模式有效,所以这里用不到) 1-从主机接收到传输模式 | 保留 | 2:0 | RO |
| 2~0bit:保留 |
XIICPS_ADDR寄存器XIICPS_ADDR_OFFSET (0x08U) I2C地址寄存器 Field Name | Bits | Type | Reset Value | Description | 保留 | 15:10 | RO | 0x0 | 保留 | ADD | 9:0 | RW | 0x0 | 地址 普通地址模式6:0-7bits地址 扩展地址模式9:0-10bits地址 |
XIICPS_DATA寄存器XIICPS_DATA_OFFSET (0x0CU) I2C数据寄存器 Field Name | Bits | Type | Reset Value | Description | 预留 | 15:8 | RW | 0x0 | 预留 | DATA | 7:0 |
| 0x0 | 数据 发送或者接收的数据 |
XIICPS_ISR寄存器XIICPS_ISR_OFFSET (0x10U) I2C中断状态寄存器 Field Name | Bits | Type | Reset Value | Description | 保留 | 15:10 | WTC | 0x0 | 保留 | ARB_LOST | 9 | WTC | 0x0 | 仲裁丢失 1-当工作于多主机模式代表仲裁丢失 | 保留 | 8 | WTC | 0x0 | 保留 | RX_UNF | 7 | WTC | 0x0 | FIFO下溢 1-读FIFO已空,Master多读了接收FIFO导致了,FIFO下溢 | TX_OVF | 6 | WTC | 0x0 | FIFO溢出 1-写FIFO已满,Master多写了发送FIFO导致了,FIFO溢出 | RX_OVF | 5 | WTC | 0x0 | 接收溢出 1-接收FIFO已满,但是继续接收到了数据,多出的数据不会被应答,FIFO数据保持不变 | SLV_RDY | 4 | WTC | 0x0 | Monitored Slave 准备好 1-寻址从机返回ACK | TO | 3 | WTC | 0x0 | 传输超时 1-SCLK保持低电平时间过长导致超时 | NACK | 2 | WTC | 0x0 | 传输NACK 1-SLAVE相应NACK或者Master提前停止传输 | DATA | 1 | WTC | 0x0 | 数据是否传输完毕 1-数据未传输完毕 | COMP |
| WTC | 0x0 | 传输完毕 1-I2C传输完毕 |
XIICPS_TRANS_SIZE寄存器XIICPS_TRANS_SIZE_OFFSET (0x14U) I2C发送大小寄存器 Field Name | Bits | Type | Reset Value | Description | TRANS_SIZE | 7:0 | WR | 0x0 | 7~0bit:传输大小 Master发送模式:剩余未传输的数据 Master接收模式:剩余需要被读走的数据 Slave发送模式,当主机结束传输后剩余的未传输的数据 Slave接收模式,FIFO中有效的数据 |
XIICPS_SLV_PAUSE寄存器XIICPS_SLV_PAUSE_OFFSET (0x18U) Field Name | Bits | Type | Reset Value | Description | XIICPS_SLV_PAUSE | 7:0 | WR | 0x0 | 7~4bit:保留位 3~0bit:暂停时间 暂停间隔0~7 |
XIICPS_TIME_OUT寄存器XIICPS_TIME_OUT_OFFSET (0x1CU) I2C超时寄存器 Field Name | Bits | Type | Reset Value | Description | XIICPS_TIME_OUT | 7:0 | WR | 0x1F | 255~31:超时寄存器 |
XIICPS_IMR寄存器XIICPS_IMR_OFFSET (0x20U) 该寄存器对应于XIICPS_ISR中断状态寄存器中的中断是否被屏蔽了,是只读寄存器,当相应位的状态是1代表该中断被屏蔽了。 Field Name | Bits | Type | Reset Value | Description | 保留 | 15:20 | RO | 0x0 | 保留 | ARB_LOST | 9 | RO | 0x0 | 仲裁丢失中断掩码状态 | 保留 | 8 | RO | 0x0 | 保留 | RX_UNF | 7 | RO | 0x0 | FIFO下溢中断掩码状态 | TX_OVF | 6 | RO | 0x0 | FIFO溢出中断掩码状态 | RX_OVF | 5 | RO | 0x0 | 接收溢出中断掩码状态 | SLV_RDY | 4 | RO | 0x0 | Monitored Slave 准备好中断掩码状态 | TO | 3 | RO | 0x0 | 传输超时中断掩码状态 | NACK | 2 | RO | 0x0 | 传输NACK中断掩码状态 | DATA | 1 | RO | 0x0 | 数据是否传输完毕中断掩码状态 | COMP | 0 | RO | 0x0 | 传输完毕中断掩码状态 |
XIICPS_IER寄存器XIICPS_IER_OFFSET (0x24U) 该寄存器对应于XIICPS_ISR中断状态寄存器中的中断是否被使能了,当相应位设置位1代表该中断被使能。 Field Name | Bits | Type | Reset Value | Description | 保留 | 15:20 | RO | 0x0 | 保留 | ARB_LOST | 9 | WO | 0x0 | 仲裁丢失中断使能 | 保留 | 8 | RO | 0x0 | 保留 | RX_UNF | 7 | WO | 0x0 | FIFO下溢中断使能 | TX_OVF | 6 | WO | 0x0 | FIFO溢出中断使能 | RX_OVF | 5 | WO | 0x0 | 接收溢出中断使能 | SLV_RDY | 4 | WO | 0x0 | Monitored Slave 准备好中断使能 | TO | 3 | WO | 0x0 | 传输超时中断使能 | NACK | 2 | WO | 0x0 | 传输NACK中断使能 | DATA | 1 | WO | 0x0 | 数据是否传输完毕中断使能 | COMP | 0 | WO | 0x0 | 传输完毕中断使能 |
XIICPS_IDR寄存器XIICPS_IDR_OFFSET (0x28U) 该寄存器对应于XIICPS_ISR中断状态寄存器中的中断是否被禁用了,当相应位设置位1代表该中断被禁用,同时会设置中断掩码寄存器XIICPS_IMR中相应的寄存器位位1。 Field Name | Bits | Type | Reset Value | Description | 保留 | 15:20 | RO | 0x0 | 保留 | ARB_LOST | 9 | WO | 0x0 | 仲裁丢失中断禁用 | 保留 | 8 | RO | 0x0 | 保留 | RX_UNF | 7 | WO | 0x0 | FIFO下溢中断禁用 | TX_OVF | 6 | WO | 0x0 | FIFO溢出中断禁用 | RX_OVF | 5 | WO | 0x0 | 接收溢出中断禁用 | SLV_RDY | 4 | WO | 0x0 | Monitored Slave 准备好中断禁用 | TO | 3 | WO | 0x0 | 传输超时中断禁用 | NACK | 2 | WO | 0x0 | 传输NACK中断禁用 | DATA | 1 | WO | 0x0 | 数据是否传输完毕中断禁用 | COMP | 0 | WO | 0x0 | 传输完毕中断禁用 | 5硬件原理图
如上图所示,I2C总线上挂了2个外设,分别是EEPROM 24LC02以及RTC S1337
6搭建SOC系统工程详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“01Vitis Soc开发入门”这篇文章。 6.1SOC系统工程
ZYNQ IP中设置I2C0
6.2编译并导出平台文件以下步骤简写,有不清楚的看第一篇文章。 1:单击Block文件à右键àGenerate the Output ProductsàGlobalàGenerate。 2:单击Block文件à右键à Create a HDL wrapper(生成HDL顶层文件)àLet vivado manager wrapper and auto-update(自动更新)。 3:添加配套工程路径下uisrc/04_pin/fpga_pin.xdc约束文件 4:生成Bit文件。 5:导出到硬件: FileàExport HardwareàInclude bitstream 6:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。
7搭建Vitis-sdk工程创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。 7.1创建SDK Platform工程
右击soc_base编译,编译的时间可能会有点长 7.2创建APP工程以下应用程序中给出了polled方式,以及中断方式两种方案。
8Polled模式读写EEPROM
8.1eeprom_polled读写模式- #include "I2cPs_Polled.h"
- #include "sleep.h"
- extern XIicPs I2cInst0;
- I2C_ADDR8 *I2C_WINST_8 = (void*)0x0800000; //定义写数据的地址
- I2C_ADDR8 *I2C_RINST_8 = (void*)0x0810000; //定义读数据的地址
- int main(void)
- {
- u32 i=0;
- u32 j=0;
- i2cps_init(&I2cInst0,IIC_DEVICE_ID0);//initial ps i2c controller
- while(1)
- {
- //24c02 2kbits write
- for(i=0; i<16*2; i++)//24cl02 有16个页,每个页有16Bytes,这里每次写8bytes 共需要32次写完
- {
- I2C_WINST_8->reg_addr[0]=i*8; //EEPROM的内存地址,需要确保写的数据不能跨页
- for(j=0; j<8; j++)//每次写8bytes,单独的一次写数据必须确保在同一页内
- I2C_WINST_8->reg_buf[j] = j + i*8;
- i2cps_wr8(&I2cInst0,I2C_WINST_8,8,0x50); //调用驱动程序写数据
- usleep(4000); //等待一段时间,确保EEPROM数据完成更新
- }
- //以下程序一次性读完256Bytes,读操作可以一次性全部读完
- I2C_RINST_8->reg_addr[0]=0; //设置读EEPROM的地址,从0开始
- i2cps_rd8(&I2cInst0,I2C_RINST_8,256,0x50);//一次性读完256bytes
- for(i=0;i<256;i++)//比较读写的数据是否正确
- if(i!=I2C_RINST_8->reg_buf[i])
- xil_printf("error addr%d=%d\r\n",i,I2C_RINST_8->reg_buf[i]);
- xil_printf("i2c test successfully!\r\n");
- sleep(1);
- }
- return 0;
- }
复制代码
1:polled 模式eeprom写数据首先我们定义数据结构: - typedef struct
- {
- u8 reg_addr[2];
- u8 reg_buf[2048];
- }I2C_ADDR16;
- typedef struct
- {
- u8 reg_addr[1];
- u8 reg_buf [2048];
- }I2C_ADDR8;
复制代码
这两个数据结构分别支持8bit的内存寻址,和16bit的内存寻址。 对于EEPROM 只需要使用8bit的内存寻址方式。 我们在程序中定义了I2C_ADDR8 I2C_WINST_8,I2C_RINST_8两个变量用于分别读和写EEPROM. 比如当我们需要写入一个只位1的数据到EEPROM的第0页第一个地址,只需要设置: I2C_WINST_8.reg_addr[0]=0; I2C_WINST_8.reg_buf[0] = 1; i2cps_wr8(&I2cInst0,&I2C_WINST_8,1,0x50);
再比如当我们需要写入8个递增的数据到EEPROM的第0页第一个地址,只需要设置: I2C_WINST_8.reg_addr[0]=0*8;
for(j=0; j<8; j++)
I2C_WINST_8.reg_buf[j] = j + i*8; i2cps_wr8(&I2cInst0,&I2C_WINST_8,8,0x50);
这里读者需要特别注意,写EEPROM 一次最多写一页,对于24LC02就一页最多是16bytes所以最多写入16bytes,但是实际发现连续写入16个Bytes总是高8Bytes无法写入,改成每次写入8Bytes 正常。 for(i=0; i<16*2; i++)//24cl02 有16个页,每个页有16Bytes,这里每次写8bytes 共需要32次写完 { I2C_WINST_8->reg_addr[0]=i*8; //EEPROM的内存地址,需要确保写的数据不能跨页 for(j=0; j<8; j++)//每次写8bytes,单独的一次写数据必须确保在同一页内 I2C_WINST_8->reg_buf[j] = j + i*8; i2cps_wr8(&I2cInst0,I2C_WINST_8,8,0x50); //调用驱动程序写数据 usleep(4000); //等待一段时间,确保EEPROM数据完成更新 } } 2:polled 模式eeprom读数据写需要注意页写,跨页,而且ZYNQ一次只能写EEPROM 8Bytes,但是对于读操作,没有这个要求,所以我们可以一次读取所有的页 I2C_RINST_8.reg_addr[0]=0;
i2cps_rd8(&I2cInst0,&I2C_RINST_8,256,0x50);//read all 256 bytes 8.2.polled驱动程序分析
1:i2cps_polled.c驱动程序
为了让操作I2C控制器更加方便,我们编写了i2cps_polled.c驱动程序,使用该驱动程序调用SDK库函数。该驱动程序支持读写8bits的数据和16bits的数据,在SDK应用中,这种模式一般用于一些外设的寄存器初始化。
- #include "i2cps_polled.h"
- XIicPs I2cInst0;
- int i2cps_init(XIicPs *I2C_Ptr,u16 DeviceId) //初始化I2C外设
- {
- int Status;
- XIicPs_Config *Config;
- /*
- * Initialize the IIC driver so that it's ready to use
- * Look up the configuration in the config table, then initialize it.
- */
- Config = XIicPs_LookupConfig(DeviceId);
- if (NULL == Config) {
- return XST_FAILURE;
- }
- Status = XIicPs_CfgInitialize(I2C_Ptr, Config, Config->BaseAddress);
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- /*
- * Set the IIC serial clock rate.
- */
- XIicPs_SetSClk(I2C_Ptr, IIC_SCLK_RATE);
- return XST_SUCCESS;
- }
- //8bit数据写数据函数
- void i2cps_wr8(XIicPs *I2C_Ptr, I2C_ADDR8 *MsgPtr , u16 byte_cnt ,u16 slave_addr)
- {
- XIicPs_MasterSendPolled(I2C_Ptr, (u8 *)MsgPtr, byte_cnt + 1, slave_addr); //SDK 库函数polled 模式写数据
- while (XIicPs_BusIsBusy(I2C_Ptr)) ; //SDK 库函数返回1代表总线忙
- }
- //8bit数据写数据函数
- void i2cps_rd8(XIicPs *I2C_Ptr, I2C_ADDR8 *MsgPtr , u16 byte_cnt ,u16 slave_addr)
- {
- XIicPs_MasterSendPolled(I2C_Ptr, MsgPtr->reg_addr, 1, slave_addr); //SDK 库函数polled 模式写数据
- while (XIicPs_BusIsBusy(I2C_Ptr)) ; //SDK 库函数返回1代表总线忙
- XIicPs_MasterRecvPolled(I2C_Ptr, MsgPtr->reg_buf, byte_cnt, slave_addr); //SDK 库函数polled 模式读数据
- while (XIicPs_BusIsBusy(I2C_Ptr)) ; //SDK 库函数返回1代表总线忙
- }
- //16bit数据写数据函数
- void i2cps_wr16(XIicPs *I2C_Ptr, I2C_ADDR16 *MsgPtr , u16 byte_cnt ,u16 slave_addr)
- {
- XIicPs_MasterSendPolled(I2C_Ptr, (u8 *)MsgPtr, byte_cnt + 2, slave_addr); //SDK 库函数polled 模式写数据
- while (XIicPs_BusIsBusy(I2C_Ptr)) ; //SDK 库函数返回1代表总线忙
- }
- //16bit数据读数据函数
- void i2cps_rd16(XIicPs *I2C_Ptr, I2C_ADDR16 *MsgPtr , u16 byte_cnt ,u16 slave_addr)
- {
- XIicPs_MasterSendPolled(I2C_Ptr, MsgPtr->reg_addr, 2, slave_addr); //SDK 库函数polled 模式写数据
- while (XIicPs_BusIsBusy(I2C_Ptr)) ; //SDK 库函数返回1代表总线忙
- XIicPs_MasterRecvPolled(I2C_Ptr, MsgPtr->reg_buf, byte_cnt, slave_addr); //SDK 库函数polled 模式读数据
- while (XIicPs_BusIsBusy(I2C_Ptr)) ; //SDK 库函数返回1代表总线忙
- }
复制代码
其中数据结构I2C_ADDR8用于读写8bits的数据,定于如下 - typedef struct
- {
- u8 reg_addr[1];
- u8 reg_buf [2048];
- }I2C_ADDR8;
复制代码
其中数据结构I2C_ADDR16用于读写16bit的数据,定于如下 - typedef struct
- {
- u8 reg_addr[2];
- u8 reg_buf[2048];
- }I2C_ADDR16;
复制代码
以下是针对驱动程序的分析,比较繁琐,只是关注应用的可以不看。 2:I2cPs_init函数
2.1:XIicPs_LookupConfig(DeviceId)函数
首先依然是通过查找配置程序来获取I2C的硬件配置。我们跟踪这个程序,看看他获取的配置是什么。
右击参数打开参数定义
可以看到,这个数组里存放的是I2C设备ID、I2C控制器的基地址,I2C APB总线时钟频率。
可以继续右击这些参数定位到这些参数的定义
在xparameters.h中找到如下定义,可以看到I2C0的基地址和 APB总线的时钟
2.2:XIicPs_CfgInitialize(I2C_Ptr, Config, Config->BaseAddress)函数
2.2.1:XIicPs *InstancePtr结构体 XiicPs结构体定义了包括I2C数据收发缓存指针,收发数据的数量,I2C数据传输方向是发送还是接收,是7bit寻址还是10bit寻址,I2C中断的回调函数。
2.2.2:XIicPs_Reset(InstancePtr)函数
这个函数中对3个寄存器进行了操作,分别是I2C控制寄存器、I2C超时寄存器、I2C中断寄存器 各个寄存器的偏移地址如下:
2.2.3I2C控制寄存器地址偏移XIICPS_CR_OFFSET=0x00
写入XIICPS_CR_RESET_VALUE=0 2.2.4:I2C超时寄存器地址偏移XIICPS_TIME_OUT_OFFSET=0x 1C
写入XIICPS_TO_RESET_VALUE =0x000000FF 2.2.5I2C中断禁止寄存器地址偏移XIICPS_IDR_OFFSET= 0x28
写入之XIICPS_IXR_ALL_INTR_MASK = 0x 0x000002FF 2.3:XIicPs_SetSClk(I2C_Ptr, IIC_SCLK_RATE)
这个函数设置I2C的时钟参数相关的寄存器,计算出来的参数最终会写入I2C控制寄存器。这里需要注意一点,代码中有这么一段,I2C总线的速度实际要么工作在低速90Khz要么高速384.6Khz
3:i2cps_wr8函数
3.1:XIicPs_MasterSendPolled(I2C_Ptr, MsgPtr, ByteCount, SlaveAddr)函数
3.1.1设置控制寄存器 这个函数比较长,我们只拿其中关键的代码分析,以下代码中设置首先判断是否有RepeatedStart或者需要发送的数据量是否大于I2C控制器FIFO的大小,如果是的,那么设置SCL为高电平(通过设置I2C控制寄存器的(XIICPS_CR_HOLD_MASK=0x00000010)。
之后执行XIicPs_SetupMaster函数,设置I2C控制器,使能TX发送ACK检测、清空FIFO、地址为7bit寻址、Master模式、发送模式。 这个控制寄存器每个位的功能已经在前面有介绍。
以下是相关的寄存器控制位操作
3.1.2设置中断寄存器
以上代码中清除的中断包括:仲裁丢失中断、发送溢出中断、NACK中断
3.1.3 TransmitFifoFill(InstancePtr) 此函数首先完成一次FIFO数据的填充,I2C控制器发送数据先要把数据填充到FIFO.
读取发什么大小寄存器,判断本次传输能传输多少数据,这个寄存器的偏移地址XIICPS_TRANS_SIZE_OFFSET = 0x14,寄存器功能描述如下:
我们知道I2C的FIFO最大深度是16,如果I2C控制器的FIFO没有填满,则继续填写数据。 这里还有另外一个数据寄存器是我们第一次遇到,这个寄存器的偏移地址XIICPS_DATA_OFFSET= 0x0C 3.1.4:XIicPs_WriteReg(BaseAddr, XIICPS_ADDR_OFFSET, (u32)SlaveAddr) 往I2C控制器地址寄存器里面写入从机地址,这里我们又遇到一个新的寄存器,这个寄存器的偏移地址XIICPS_ADDR_OFFSET = 0x08
3.1.5发送剩余的数据
发送剩余数据需要先判断之前一次的发送是否有错误。通过读取中断状态寄存器的仲裁丢失位、发送溢出位、NACK位判断之前一次传输是否有错误。如果没有错误再进行下次一传输,直到数据发送完毕。 这里我们看下中断状态寄存器,这个寄存器的偏移地址XIICPS_ISR_OFFSET= 0x10,寄存器说明如下:
接下来还要通过判断以上中断状态寄存器的FIFO数据位是否已经空,如果空了就可以继续准备发送数据。 3.1.6数据发送结束 通过判单中断状态寄存器判断数据是否发送完毕,如果发送完毕,需要设置控制寄存器的HOLD位为0结束本次传输。
3.2:XIicPs_BusIsBusy(I2C_Ptr)函数
通过判断I2C状态寄存器,读取忙标志位判断I2C是否还在传输中。
I2C状态寄存器偏移地址XIICPS_SR_OFFSET = 0x04寄存器描述如下:
4:i2cps_rd8函数
对于读数据先要发送器件地址和寄存器地址,之后再发起读操作。 4.1:XIicPs_MasterRecvPolled(I2C_Ptr, MsgPtr, ByteCount, SlaveAddr)函数
前面我们分析过I2C发送数据部分的函数,这部分接收的代码操作非常类似。以下代码中设置首先判断是否有RepeatedStart或者需要发送的数据量是否大于I2C控制器FIFO的大小,如果是的,那么设置SCL为高电平(通过设置I2C控制寄存器的(XIICPS_CR_HOLD_MASK=0x00000010)。 4.1.1 XIicPs_SetupMaster(InstancePtr, RECVING_ROLE)函数
之后执行XIicPs_SetupMaster函数,设置I2C控制器,使能TX发送ACK检测、清空FIFO、地址为7bit寻址、Master模式、接收模式。
这个控制寄存器每个位的功能已经在前面有介绍,以下是相关的寄存器控制位操作:
4.1.2清除I2C中断状态寄存器
4.1.3设置需要接收的数据量 以下代码设置I2C控制器需要接收的数据量,最大的数据传输量: XIICPS_MAX_TRANSFER_SIZE=252,所以大于这个数据量
4.1.4接收剩余的数据
这段代理理解起来需要把握以下关键几点: 1`I2C控制器的RX 接收FIFO最大深度16 bytes,所以以下代码判断接收的数据是否大于16字节,如果时,则设置IsHlod=1
2`I2C控制器的RX接收数据一次最大时252bytes,所以以下代码首先判断需要接收的数据是否大于XIICPS_MAX_TRANSFER_SIZE,如果时则设置第一次接收的最大数据为ByteCountVar = (s32)XIICPS_MAX_TRANSFER_SIZE,并且设置UpdateTxSize=1,用以标识当第一次数据传输完成后还要继续传输。
3`这个大while循环就是负责把所有需要接收的数据都接收了。 在这个大while循环中,代码分析需要分3个部分。分为:1个小while循环判断数据是否有效、1个if分支代码硬件是ZYNQ、1个else分支代码硬件是MSPSOC。之所以要这么设计,因为ZYNQ和MSPOC的I2C控制器还是有所差异。我们先分析if...else...这两种分支 4`这段代码中处理的机制是一样的,差异在于针对ZYNQ平台
if分支ZYNQ平台的处理:从代码可以猜出,每次最大接收数据的最后阶段如果满足以下条件,即:硬件平台是ZYNQ&UpdateTxSize=1&ByteCountVar == (XIICPS_FIFO_DEPTH + 1)则需要对ZYNQ的I2C接收部分重新设置。
所以有了以下代码,这部分的代码中,首先会等待ZYNQ的I2C控制器等待接收FIFO满,之后再去判断下一次的传输。
else分支MPSOC平台的处理:相比ZYNQ平台处理的复杂程度,对于MPSOC就简单多了,只要继续判断剩余传输的数据是否大于XIICPS_MAX_TRANSFER_SIZE,如果时则设置再一次接收的最大数据为ByteCountVar = (s32)XIICPS_MAX_TRANSFER_SIZE,并且设置UpdateTxSize=1,用以标识当本次数据传输完成后还要继续传输
1个小while循环接收有效数据:小while循环中接收数据到内存中,通过XIicPs_RecvByte(InstancePtr)读取数据。
4.2:XIicPs_BusIsBusy(I2C_Ptr)函数
通过判断I2C状态寄存器,读取忙标志位判断I2C是否还在传输中。
这个函数的寄存器介绍在I2cPs_wirte部分有介绍。 9中断模式读写EEPROM
9.1eeprom_intr读写模式- #include "i2cps_intr.h"
- #include "sys_intr.h"
- #include "sleep.h"
- extern XIicPs I2cInst0;
- extern XScuGic Intc;
- extern volatile u32 SendStatu;
- extern volatile u32 RevStatu;
- I2C_ADDR8 *I2C_WINST_8 = (void*)0x0800000;
- I2C_ADDR8 *I2C_RINST_8 = (void*)0x0810000;
- void init_intr_sys(void)
- {
- Init_Intr_System(&Intc);//initial system interrupt
- i2cps_init(&I2cInst0 ,IIC_DEVICE_ID0); //initial i2cps controller
- i2cps_setup_IntrSystem(&Intc, &I2cInst0,IIC_INT_VEC_ID0); //setup i2c ps interrupt
- Setup_Intr_Exception(&Intc);//setup system interrupt
- }
- int main(void)
- {
- u32 i=0;
- u32 j=0;
- init_intr_sys();
- while(1)
- {
- //24c02 2kbits write(256 bytes)
- for(i=0; i<16*2; i++)//24cl02 has 16 pages as each page 16bytes
- {
- I2C_WINST_8->reg_addr[0]=i*8;
- for(j=0; j<8; j++)//every time write 8bytes data
- I2C_WINST_8->reg_buf[j] = j + i*8;
- SendStatu = 1;
- while(SendStatu != S_IIC_TSUCCESS) //wait i2c controller not busy
- {
- if(SendStatu == S_IIC_TERROR) break;
- i2cps_wr8(&I2cInst0,I2C_WINST_8,8,0x50); //write 1byte address 8byes data
- }
- usleep(4000);
- }
- //24c02 2kbits read all
- I2C_RINST_8->reg_addr[0]=0;
- RevStatu = 1;
- while(RevStatu != S_IIC_RSUCCESS)//wait i2c controller not busy
- {
- if(RevStatu == S_IIC_RERROR) break;
- i2cps_rd8(&I2cInst0,I2C_RINST_8,256,0x50); //read all 256 bytes
- }
- for(i=0;i<256;i++)//compare write buffer and read buffer
- if(i!=I2C_RINST_8->reg_buf[i])
- xil_printf("error addr%d=%d\r\n",i,I2C_RINST_8->reg_buf[i]);
- xil_printf("i2c test successfully!\r\n");
- sleep(1);
- }
- return 0;
- }
复制代码
9.2中断驱动程序分析
1:i2cps_intr.c驱动程序
Polled模式下,程序需要等待I2C总线操作完后才能进行其他的操作,这样会导致其他程序无法运行。相比polled模式,中断模式,可以几乎不影响其他程序的运行,所以需要多任务执行,并且需要尽量不影响其他程序的运行的方案中需要使用中断模式。
- #include "i2cps_intr.h"
- XIicPs I2cInst0;
- volatile u32 SendStatu;
- volatile u32 RevStatu;
- volatile u32 SendComplete;
- volatile u32 RecvComplete;
- volatile u32 TotalErrorCount;
- int i2cps_init(XIicPs *I2C_Ptr,u16 DeviceId) //初始化I2C外设
- {
- int Status;
- XIicPs_Config *Config;
- /*
- * Initialize the IIC driver so that it's ready to use
- * Look up the configuration in the config table, then initialize it.
- */
- Config = XIicPs_LookupConfig(DeviceId);
- if (NULL == Config) {
- return XST_FAILURE;
- }
- Status = XIicPs_CfgInitialize(I2C_Ptr, Config, Config->BaseAddress);
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- /*
- * Set the IIC serial clock rate.
- */
- XIicPs_SetSClk(I2C_Ptr, IIC_SCLK_RATE); //设置I2C的总线时钟
- return XST_SUCCESS;
- }
- void I2cPs_Handler(void *CallBackRef, u32 Event) //中断回调函数
- {
- if (0 != (Event & XIICPS_EVENT_COMPLETE_RECV)){ //数据接收完毕
- RecvComplete = TRUE;
- } else if (0 != (Event & XIICPS_EVENT_COMPLETE_SEND)) {//数据发送完毕
- SendComplete = TRUE;
- } else if (0 == (Event & XIICPS_EVENT_SLAVE_RDY)){ //如果是其他中断,并且不是SLAVE准备好中断代表错误
- TotalErrorCount++;
- }
- }
- int i2cps_setup_IntrSystem(XScuGic * GicInstancePtr , XIicPs *I2C_Ptr ,u16 I2cIntrId) //设置中断回调函数
- {
- int Status;
- Status = XScuGic_Connect(GicInstancePtr, I2cIntrId,
- (Xil_InterruptHandler)XIicPs_MasterInterruptHandler,
- (void *)I2C_Ptr);
- if (Status != XST_SUCCESS) {
- return Status;
- }
- XIicPs_SetStatusHandler(I2C_Ptr, (void *)I2C_Ptr, I2cPs_Handler);
- XScuGic_Enable(GicInstancePtr, I2cIntrId);
- return XST_SUCCESS;
- }
- //中断方式写数据8bits数据
- void i2cps_wr8(XIicPs *I2C_Ptr, I2C_ADDR8 *MsgPtr , u16 byte_cnt ,u16 slave_addr)
- {
- switch(SendStatu)
- {
- case S_IIC_TEN://write address
- if(XIicPs_BusIsBusy(I2C_Ptr))
- {
- }
- else
- {
- SendStatu = S_IIC_TBUSY;
- SendComplete = FALSE;
- XIicPs_MasterSend(I2C_Ptr, (u8 *)MsgPtr, byte_cnt + 1, slave_addr);
- }
- break;
- case S_IIC_TBUSY:
- if(SendComplete == FALSE)
- {
- if (0 != TotalErrorCount) {
- xil_printf("I2C tx error! %d\r\n", TotalErrorCount);
- SendStatu = S_IIC_TERROR ;
- }
- }
- else
- SendStatu = S_IIC_TSUCCESS;
- break;
- default:
- break;
- }
- return ;
- }
- //中断方式读数据8bits数据
- void i2cps_rd8(XIicPs *I2C_Ptr, I2C_ADDR8 *MsgPtr , u16 byte_cnt ,u16 slave_addr)
- {
- switch(RevStatu)
- {
- case S_IIC_TEN://write address
- if(XIicPs_BusIsBusy(I2C_Ptr))
- {
- }
- else
- {
- RevStatu = S_IIC_TBUSY;
- SendComplete = FALSE;
- XIicPs_MasterSend(I2C_Ptr, (u8 *)MsgPtr, 1, slave_addr);
- }
- break;
- case S_IIC_TBUSY:
- if(SendComplete == FALSE)
- {
- if (0 != TotalErrorCount) {
- xil_printf("I2C rx error! %d\r\n", TotalErrorCount);
- RevStatu = S_IIC_RERROR ;
- }
- }
- else
- {
- RevStatu = S_IIC_RCV;
- }
- break;
- case S_IIC_RCV:
- if (XIicPs_BusIsBusy(I2C_Ptr))// busy nop
- {
- }
- else
- {
- XIicPs_MasterRecv(I2C_Ptr, MsgPtr->reg_buf, byte_cnt, slave_addr);//recv data
- RevStatu = S_IIC_RBUSY;// read busy
- RecvComplete = FALSE;
- }
- break;
- case S_IIC_RBUSY://wait
- if(RecvComplete == FALSE)
- {
- if (0 != TotalErrorCount) {
- xil_printf("I2C rx error! %d\r\n", TotalErrorCount);
- RevStatu = S_IIC_RERROR ;
- }
- }
- else
- RevStatu = S_IIC_RSUCCESS;
- break;
- default:
- break;
- }
- return ;
- }
复制代码
其中数据结构I2C_ADDR8用于读写8bits的数据,定于如下 - typedef struct
- {
- u8 reg_addr[1];
- u8 reg_buf [2048];
- }I2C_ADDR8;
复制代码
以下内容是对I2C中断驱动程序底层部分的分析。
2:init_intr_sys函数- void init_intr_sys(void)
- {
- Init_Intr_System(&Intc);
- I2cPs_init(&Iic ,IIC_DEVICE_ID);
- I2cPs_Setup_IntrSystem(&Intc, &Iic,IIC_INT_VEC_ID);
- Setup_Intr_Exception(&Intc);
- }
复制代码
此函数在程序文件eeprom_test.c中通过调用相关中断函数实现中断功能。为了实现多类型中断,用户必须根据以下方式设置中断。 3:Init_Intr_System(&Intc)函数- int Init_Intr_System(XScuGic * IntcInstancePtr)
- {
- int Status;
- XScuGic_Config *IntcConfig;
- /*
- * Initialize the interrupt controller driver so that it is ready to
- * use.
- */
- IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
- if (NULL == IntcConfig) {
- return XST_FAILURE;
- }
- Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
- IntcConfig->CpuBaseAddress);
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- return XST_SUCCESS;
- }
复制代码
3.1:XScuGic_LookupConfig(INTC_DEVICE_ID)
右击XScuGic_ConfigTable查看参数定义:
可可以继续右击以下参数查看其定义
可以看到这里定义了GIC中断控制器的基地址
3.2:XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,IntcConfig->CpuBaseAddress)
中断部分主要内容是回调函数的设置,以下回调函数相关参数定义如下:
以下代码对所有为定义的全局中断进行定义回调函数和回调参数。
StubHandler函数定义如下:
4:Setup_Intr_Exception函数- void Setup_Intr_Exception(XScuGic * IntcInstancePtr)
- {
- /* Enable interrupts from the hardware */
- Xil_ExceptionInit();
- Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
- (Xil_ExceptionHandler)XScuGic_InterruptHandler,
- (void *)IntcInstancePtr);
- Xil_ExceptionEnable();
- }
复制代码
这个函数中对设置全局中断回调函数,以及使能全局中断 4.1:Xil_ExceptionInit()函数
这个函数目前说明都没做,只是未来保留兼容性,预留在这里。
4.2:Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,(void *)IntcInstancePtr)函数
4.2.1XIL_EXCEPTION_ID_INT定义
4.2.2: XExc_VectorTableEntry定义 任何的中断都可以理解位异常处理,这里定义了MPSOC或者ZYNQ支持的异常处理类型。
4.2.3XScuGic_InterruptHandler函数 可以看到这个函数会根据读取到的中断号调用相应的回调函数,这个回调函数会在具体的外设中断初始化中设置。
4.2.4中断异常回调函数
由于XIL_EXCEPTION_ID_INT=2所以Xil_ExceptionNullHandler在中断产生的时候会被调用。
4.3:Xil_ExceptionEnable()函数
这条函数最终指向了汇编指令:
5:I2cPs_init函数5.1:XIicPs_LookupConfig(DeviceId)函数
此函数在polled模式中已经详细介绍,这里不再重复。 5.2:XIicPs_CfgInitialize(I2C_Ptr, Config, Config->BaseAddress)函数
此函数在polled模式中已经详细介绍,这里不再重复。 5.3:XIicPs_SetSClk(I2C_Ptr, IIC_SCLK_RATE)
此函数在polled模式中已经详细介绍,这里不再重复。 5.4:XIicPs_SetStatusHandler(I2C_Ptr, (void *)I2C_Ptr, I2cPs_Handler)函数
此函数用于设置I2C的中断回调函数,通过以下定义可以确定回调函数是通过无符号的指针函数定义。
6:I2cPs_Setup_IntrSystem(&Intc, &Iic,IIC_INT_VEC_ID)6.1:XScuGic_Connect
(GicInstancePtr, I2cIntrId ,(Xil_InterruptHandler)XIicPs_MasterInterruptHandler, (void *)I2C_Ptr) 这里把GIC的49号中断回调函数XIicPs_MasterInterruptHandler和回调参数I2C_Ptr,这样当XIicPs_MasterInterruptHandler函数回调的时候,可以通过参数I2C_Ptr继续回调。
读者可以看前面Setup_Intr_Exception函数中关于Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,(void *)IntcInstancePtr)函数的说明再看这里就能明白了。
6.2: XIicPs_MasterInterruptHandler
这个函数初步一看有点复杂,当I2C中断产生后,这个函数被调用。虽然有点复杂,但是当拆开分析并且结合前面的polled模式的代码分析,反而变的简单了,我们把这个函数代码分3个部分分析:1)发生部分;2)接收部分;3)中断回调部分 1)发生部分:只要数据没有发送完就会继续发送,如果发送完,设置StatusEvent |= XIICPS_EVENT_COMPLETE_SEND
2)接收部分:接收部分的代码和I2C 采用polled模式类似,可以阅读polled模式部分分析
3)中断回调部分:当所有数据发送或者接收完,回调函数I2cPs_Handler 被执行以及StatusEvent参数被传递。
7:I2cPs_Handler中断回调函数这个函数是最终的回调函数,主要判断数据是否发送完毕,以及统计错误次数 - void I2cPs_Handler(void *CallBackRef, u32 Event)
- {
- /*
- * All of the data transfer has been finished.
- */
- //xil_printf("Event %d \r\n", Event);
- if (0 != (Event & XIICPS_EVENT_COMPLETE_RECV)){
- RecvComplete = TRUE;
- } else if (0 != (Event & XIICPS_EVENT_COMPLETE_SEND)) {
- SendComplete = TRUE;
- } else if (0 == (Event & XIICPS_EVENT_SLAVE_RDY)){
- /*
- * If it is other interrupt but not slave ready interrupt, it is
- * an error.
- * Data was received with an error.
- */
- TotalErrorCount++;
- }
- }
复制代码
10方案演示
10.1硬件准备
实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2P 模式开关设置到 JTAG 模式,即ON ON(注意新版本的 MLK_H3_CZ08-7100-MZ7100FC),支持 JTAG 模式,对于老版本的核心板,JTAG 调试的时候 一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF)
10.2实验结果
|