本文主要介绍zynq 7000下的i2c ps 外设编程。实验是在vivado 2018.3 上完成的。
本实验的前提条件是你已经做过了zynq 7000的helloworld sdk 实验。一般开发板的厂家都提供了的,一些设置与所用硬件有关,也可参考我的博客 petalinux 2018.2 下的helloworld 实验
I2C总线简介
I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。I2C总线上分成主机和从机两种设备。
主机用于启动总线传送数据并产生时钟以同步从机,此时任何被寻址的器件均被认为是从器件.在总线上主机和从机、发送和接收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。
SDA(串行数据线)和SCL(串行时钟线)都是双向I/O线,接口电路为开漏输出,需通过上拉电阻接电源VCC。当总线空闲时.两根线都是高电平,连接总线的外同器件都是CMOS器件,输出级也是开漏电路.在总线上消耗的电流很小,因此,总线上扩展的器件数量主要由电容负载来决定,因为每个器件的总线接口都有一定的等效电容.而线路中电容会影响总线传输速度.当电容过大时,有可能造成传输错误.所以,其负载能力为400pF,因此可以估算出总线允许长度和所接器件数量。
zynq 7000的I2C
在zynq 7000中有2种方式可以控制iic(I2C)外设,一种是利用zynq 7000的 PS 外设i2c ,还有一种是axi4-i2c IP。 我认为前面简单一点,所以采用的前面那种方式。
在vivao 里打开以前设计的helloworld 工程,或者其他工程,没有就先做一个,打开原理图设计(open block design),双击zynq 打开zynq设置。
在设置界面里,选择peripheral I/OPins,在I2C0 行里,列里 EMIO。我之所以这样选是:我的开发板里没有直接的MIO 可选,只能选扩展输出口的引脚。也许你的情况不一样,那就不一样的选择。
其实这样选择就可以了,但我还是到MIO Configuration 查看下,这里有I2C0 对应EMIO
这样修改设置后回到原理图。这时可以看到zynq 多了IIC0,接下来将鼠标箭头移动到接口上,当看到箭头变为铅笔形状是点击鼠标右键,在弹出的菜单中选择 Make External 将接口修改为外部接口,可以看到外部接口名为IIC_0
保存原理图, 依次进行Generate Output Products和 Create HDL Warpper操作。就是在source 中选择.bd文件,右键出现菜单,选择他们。
在约束文件中添加如下内容,引脚的名字可以在***wrapper.v文件中看到。
set_property IOSTANDARD LVCMOS33 [get_ports IIC_0_scl_io]
set_property PACKAGE_PIN G17 [get_ports IIC_0_scl_io]
set_property IOSTANDARD LVCMOS33 [get_ports IIC_0_sda_io]
set_property PACKAGE_PIN C20 [get_ports IIC_0_sda_io]
set_property PULLUP true [get_ports IIC_0_scl_io]
set_property PULLUP true [get_ports IIC_0_sda_io]
约束文件其实也可交互式添加。点击左侧导航栏 RTL ANALYSIS->Open Elaborated Design,在弹出的对话框中点击“ OK” 打开电气设计窗口。在打开的窗口内点击箭头所指的 I/O Ports,会在下面打开配置引脚约束的窗口。在窗口里找到IIC 相关引脚并设置。我是这样得到上面脚本的。
在这里C20 G17 是引脚编号,我就是在输出扩展口中选2个脚,然后对应连接外设的相应引脚。
设计做好了,下面就是产生比特流,输出硬件(应该包含比特流)
SDK软件设计
在Vivado 里, File->Launch SDK。我们进入SDK 软件设计。
在SDK 里, 新建一个工程,选择模板为hello world , 可以运行下helloworld,看看显示等是否一切正常。
删除helloworld.c 里的所有内容, 然后复制如下代码:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xiicps.h"
// IIC device ID
#define IIC_DEV_ID XPAR_PS7_I2C_0_DEVICE_ID
#define IIC_RATE 200000
//7位设备地址
#define SLAVE_ADDR 0x40
#define MODE1 0
#define PRESCALE 254
static XIicPs IicPs;
static XIicPs_Config * IicPs_Cfg;
//测试配置列表
u8 configList[11][2]={
{0x00,0x10},
{0xfe,120},
{0x00,0x00},
{6,0x0},
{7,0x0},
{8,200},
{9,0},
{0x0A,0},
{0x0B,0},
{0x0c,150},
{0x0d,1}
};
// 初始化IIC,并设置IIC速率
int initIic()
{
int status;
// 1.查找IIC设备
IicPs_Cfg = XIicPs_LookupConfig(IIC_DEV_ID);
// 2.初始化
status = XIicPs_CfgInitialize(&IicPs, IicPs_Cfg, IicPs_Cfg->BaseAddress);
if(status != XST_SUCCESS)
{
print("initial IIC failed \n");
return XST_FAILURE;
}
//设置IIC速率
status = XIicPs_SetSClk(&IicPs, IIC_RATE);
if(status != XST_SUCCESS)
{
print("set IIC clock rate failed \n");
return XST_FAILURE;
}
return XST_SUCCESS;
}
/******************************************************************
* function IIC完成单个寄存器的配置
*
* @parameter : XIicPs * iicPs =====> IIC设备结构体
* @parameter : u16 slaveAddr =====> IIC从机设备地址
* @parameter : u8 * Cfg_Ptr ====> 配置寄存器的指针
*
* @return s32 XST_SUCCESS or XST_FAILURE
******************************************************************/
s32 writeReg(XIicPs * iicPs, u16 slaveAddr, u8 * Cfg_Ptr)
{
s32 status ;
//IIC写入数据,从机地址,寄存器地址和写入的数据
status = XIicPs_MasterSendPolled(iicPs, Cfg_Ptr, 2, SLAVE_ADDR );
if (status != XST_SUCCESS)
{
printf("configure register failed! \n");
return XST_FAILURE;
}
//两次IIC写入之间保持一定间隔
usleep(8000);
return status;
}
/******************************************************************
* function IIC从寄存器中读出数据
*
* @parameter : XIicPs * iicPs =====> IIC设备结构体
* @parameter : u16 slaveAddr =====> IIC从机设备地址
* @parameter : u8 * Cfg_Ptr ====> 配置寄存器的指针
*
* @return u8 registerData =====> 从寄存器中读出的数据
******************************************************************/
u8 readReg(XIicPs * iicPs, u16 slaveAddr, u8 * regAddr)
{
s32 status ;
u8 registerData;
//发送设备地址,寄存器地址
status = XIicPs_MasterSendPolled(iicPs, regAddr, 1, SLAVE_ADDR );
if (status != XST_SUCCESS)
{
printf("configure register failed! \n");
return XST_FAILURE;
}
//从寄存器中读出数据
status = XIicPs_MasterRecvPolled(iicPs, ®isterData, 1, SLAVE_ADDR);
if (status != XST_SUCCESS)
{
printf("configure register failed! \n");
return XST_FAILURE;
}
return registerData;
}
int main()
{
init_platform();
u8 dataBuf[31];
u8 addr,obj;
//初始化IIC
int status = initIic();
if(status != XST_SUCCESS)
{
printf("initialize IIC failed \n");
return XST_FAILURE;
}
print("Hello World1\n\r");
//依次写入配置列表
for(int i=0; i < 11; i++ )
{
writeReg(&IicPs, SLAVE_ADDR, configList[i]);
addr=configList[i][0];
obj=readReg(&IicPs, SLAVE_ADDR, &addr);
printf("i=%d, addr=%x, obj=%x\n",i,addr,obj);
}
//将值从寄存器中读出
print("Hello World2\n\r");
cleanup_platform();
return 0;
}
我这代码来自 ZYNQ基础----驱动IIC外设 ,然后对他进行了修改。
其实sdk 本身也有代码,并且还有编程说明,打开bsp 下的 system.mss,可以看到ps7_i2c_0 这里有document,还可以Import Examples。
在Import Examples 里可以找到例子存放的目录。就是点击 Examples Directory。
我开始测试了xiicps_polled_master_example.c,但是不成功,还是我上面例子比较好。
软件运行测试
排除软件错误,复制应该编译问题。
Xilinx->Program FPGA
然后就可以用 Debug As或者Run As 测试软件了。
要测试软件,有一些需要注意。
1:正确连接i2c 设备。定义为SCL 的脚连设备的SCL脚, SDA也是对应连接,还有GND VCC。
2:确定好I2C 设备的地址,我的是0x40
#define SLAVE_ADDR 0x40
你的不同就要修改这里,如果地址不对,就不会有响应。
3:明白I2C 寄存器地址的含义,并不是所有寄存器都是内存一样,读写含义可能不同。我测试的是PCA9685 舵机控制器。
//测试配置列表
u8 configList[11][2]={
{0x00,0x10},
{0xfe,120},
{0x00,0x00},
{6,0x0},
{7,0x0},
{8,200},
{9,0},
{0x0A,0},
{0x0B,0},
{0x0c,150},
{0x0d,1}
};
configList 里面的内容是(前面寄存器地址,后面写入字节) 这样一个表。你可以改变表的长度和内容,适应你设备的情况。
程序运行结果:
Hello World1
i=0, addr=0, obj=90
i=1, addr=fe, obj=78
i=2, addr=0, obj=80
i=3, addr=6, obj=0
i=4, addr=7, obj=0
i=5, addr=8, obj=c8
i=6, addr=9, obj=0
i=7, addr=a, obj=0
i=8, addr=b, obj=0
i=9, addr=c, obj=96
i=10, addr=d, obj=1
Hello World2
当然显示只是一方面,还要看I2C 运行功能状态。我上面代码可以控制舵机正常工作。
介绍到此!