先上图吧,明天再补OLED软件移植的整个过程~图片效果不是很好,手机不给力啊~
关于OLED这块,已经有两篇文章走在前面了,不过,虽然有这两篇文章,我的调试过程还是一波三折,所以我才打算再给出一篇详细教程。
1、资料准备:
1.1、通过前辈们的调试经验可以知道,Zed的OLED的驱动芯片是SSD1306,所以SSD1306的datasheet必不可少;
1.2、ZedBoard开发板的原理图;
1.3、ZedBoard用的OLED是128*32的UG-2832HSWEG04;网上比较流行的、比较相近的是128*64的一块OLED,型号我也没有细看,MCU是ARM的STM32,OLED的驱动芯片也是SSD1306,网上有很多帖子都在讨论它,其中有一个比较详细的教程,应该是一个STM32开发板的配套资料,有比较详细的程序,基本上可以直接使用。
这3份文档差不多足够了,ZedBoard的原理图我就不上传了,另外两个资料我会用附件上传上来。
2、方案选择:
2.1、使用ARM核的SPI接口
优点:初始化SPI成功后,就不用关心SPI的并串转换等问题;缺点:ARM核支持的是标准四线SPI,和OLED接口不完全吻合,不仅要控制时钟和数据,同时要控制D/C#,所以即使使用SPI,还是需要其他GPIO控制D/C#的时序,并且要和SPI接口协同工作。
2.2、老莫老师的方案,使用老莫老师的方法:通过EMIO实现
具体实现是在XPS中加入一个Xilinx提供的GPIO IP核,然后使能EMIO接口,将PS的GPIO连接到PL内部。截个图看一下:
具体如何操作,可以参考官方的ug873《Zynq-7000 EPP Concepts, Tools, and Techniques》,ZedBoard也有一个类似的文档,我一时找不到在哪了,只好给官方的了。
这个不知道咋说,老莫老师都吐槽过了。这个方法我也试了一下,感觉提供的BSP函数比较复杂,不是太好用。我需要的功能很简单,只需要6个GPIO,输出SSD1306需要的时序就够了,再加上以前有过新建IP核的经验,打算自己新建一个IP核,也就是方案三。
2.3、新建一个基于AXI-Lite总线的IP核,这个IP核有一个可读写的寄存器slv_reg0,PS软件产生需要的时序,送给slv_reg0,PL直接输出slv_reg0的低6位至SSD1306。这个方案最大的优点就是操作寄存器slv_reg0非常简单,只需要读写IP核的基地址就可以读写slv_reg0。
3、方案实现
先给出方案的整体框图。
3.1、PL部分
PL内部的OLED表示我新建的IP核,其实他是一个GPIO的IP核,但是既然是用来控制OLED的,我这里把他命名为OLED。新建IP核的详细教程,前面已经给出了,这里我把OLED的用户逻辑的代码稍微说一下。很简单,只有一行:
leds <= slv_reg0(5 downto 0);//将slv_reg0的值付给leds
这里没有使用PL产生时序,时序和数据都是由PS控制,PL仅起到了传输作用,其实PL部分所有的工作加起来充当了导线的角色——实现简单的代价是浪费了很多资源。
3.2、PS部分
PS部分部分实际上也没有多少好讲的,就把slv_reg0当成一个32bits的IO就可以了,对slv_reg0的低6位进行位操作,就可以写出控制时序了,这里稍微讲一下PS的位操作如何实现吧。
位操作相关代码#include"xil_io.h"
#defineOLED_BASE_ADDR0X7a800000
//下面几个宏定义和引脚分配有直接关系,编写ucf文件时要注意
#defineOLED_DC0
#defineOLED_RES1
#defineOLED_SCLK2
#defineOLED_SDIN3
#defineOLED_VBAT4
#defineOLED_VDD5
//DC
#defineSet_OLED_DC(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_DC)))
#defineClr_OLED_DC(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_DC))))
//RES
#defineSet_OLED_RES(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_RES)))
#defineClr_OLED_RES(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_RES))))
//SCLK
#defineSet_OLED_SCLK(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_SCLK)))
#defineClr_OLED_SCLK(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_SCLK))))
//SDIN
#defineSet_OLED_SDIN(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_SDIN)))
#defineClr_OLED_SDIN(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_SDIN))))
//OLED_VBAT#defineSet_OLED_VBAT(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_VBAT)))#defineClr_OLED_VBAT(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_VBAT))))
//OLED_VDD
#defineSet_OLED_VDD(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_VDD)))
#defineClr_OLED_VDD(Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_VDD))))
这里用到了BSP中的两个函数分别为Xil_In32()和Xil_Out32(),这两个函数分别实现对某个地址的读写,置位和清零的操作,大家自己看一下吧,用到了移位和取反,为了不影响其他位,才用了Xil_In32()读取寄存器的原状态,按位操作完毕后再送回寄存器。
除此之外,比较重要的就是OLED的初始化了,先看一下OLED的工作流程:
我在编写初始化代码的时候,POWER ON和初始化代码都放到一个oled_init的函数里了,所做的工作有:
1)POWER ON
在这里,我没有找到可以用程序控制的Vcc,我猜测是Vbat,因为Vbat是电荷泵的电源,Vcc的升压是通过电荷泵实现的,所以,勉强可以用Vbat代替Vcc,想不到其他解释了。
也就是这张时序图害苦了我,在这里它说将RES#引脚拉低3us之后就可以给Vcc上电,所以我也就没给很久的延时,最后发现问题就出在这里,估计是没有足够的时间完成复位操作,导致软硬件无法交互。
2) SoftWare Initialization
上面这两幅图都出自SSD1306的datasheet 。
在PowerOn的时候要注意看电路图:
pin7 VDD和pin5 VBAT是高电平有效的,所以上电的时候,需要将OLED_VBAT和OLED-VDD清零,3.3V的电压才会送到VBAT和VDD。 网上大多数电路都是直接给VDD和VBAT加电压,而zed选择用软件控制,虽然稍微增加了软件的工作,但却达到了降低功耗的目的,这个细节要赞一下!
其他接口函数的编写我就不一一讲了,1.3里的教程讲得很清楚了,我暂时还没有修改这些函数,待会把这些接口函数修改成128*32的。
后面还是有工作要做的,比如现在显示的字符是翻转180°显示的,看着不舒服,要翻过来等等。