cuter

一步步艰难搞定OLED

1
阅读(14991) 评论(28)

凌晨1点半,OLED终于有显示了,唉,SDK总是出现各种错误,导致我反反复复开关SDK,甚至是重启电脑,大多数时候连RUN和Debug都没有办法进行,让我从下午6点一直熬到现在……最后发现是由于OLED复位后没有给足够的延时,导致无法写OLED寄存器,进而导致SDK无法单步调试,估计这一点也影响了SDK的稳定性。

    先上图吧,明天再补OLED软件移植的整个过程~图片效果不是很好,手机不给力啊~

IMG_20121217_012711

关于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内部。截个图看一下:

image             具体如何操作,可以参考官方的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、方案实现

先给出方案的整体框图。

image

        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"

#define OLED_BASE_ADDR 0X7a800000

// 下面几个宏定义和引脚分配有直接关系,编写ucf文件时要注意
#define OLED_DC 0
#define OLED_RES 1
#define OLED_SCLK 2
#define OLED_SDIN 3
#define OLED_VBAT 4
#define OLED_VDD 5

// DC
#define Set_OLED_DC  (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_DC)))
#define Clr_OLED_DC  (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_DC))))


// RES
#define Set_OLED_RES  (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_RES)))
#define Clr_OLED_RES  (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_RES))))

// SCLK
#define Set_OLED_SCLK  (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_SCLK)))
#define Clr_OLED_SCLK  (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_SCLK))))


// SDIN
#define Set_OLED_SDIN  (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_SDIN)))
#define Clr_OLED_SDIN  (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_SDIN))))


// OLED_VBAT #define Set_OLED_VBAT (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_VBAT))) #define Clr_OLED_VBAT (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_VBAT)))) 



// OLED_VDD
#define Set_OLED_VDD  (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)|(1<<OLED_VDD)))
#define Clr_OLED_VDD  (Xil_Out32(OLED_BASE_ADDR,Xil_In32(OLED_BASE_ADDR)&(~(1<<OLED_VDD))))

       这里用到了BSP中的两个函数分别为Xil_In32()和Xil_Out32(),这两个函数分别实现对某个地址的读写,置位和清零的操作,大家自己看一下吧,用到了移位和取反,为了不影响其他位,才用了Xil_In32()读取寄存器的原状态,按位操作完毕后再送回寄存器。

除此之外,比较重要的就是OLED的初始化了,先看一下OLED的工作流程:

image

我在编写初始化代码的时候,POWER ON和初始化代码都放到一个oled_init的函数里了,所做的工作有:

1)POWER ON

image         在这里,我没有找到可以用程序控制的Vcc,我猜测是Vbat,因为Vbat是电荷泵的电源,Vcc的升压是通过电荷泵实现的,所以,勉强可以用Vbat代替Vcc,想不到其他解释了。

        也就是这张时序图害苦了我,在这里它说将RES#引脚拉低3us之后就可以给Vcc上电,所以我也就没给很久的延时,最后发现问题就出在这里,估计是没有足够的时间完成复位操作,导致软硬件无法交互。

2) SoftWare Initialization

image

上面这两幅图都出自SSD1306的datasheet 。

在PowerOn的时候要注意看电路图:

image

pin7 VDD和pin5 VBAT是高电平有效的,所以上电的时候,需要将OLED_VBAT和OLED-VDD清零,3.3V的电压才会送到VBAT和VDD。 网上大多数电路都是直接给VDD和VBAT加电压,而zed选择用软件控制,虽然稍微增加了软件的工作,但却达到了降低功耗的目的,这个细节要赞一下!

        其他接口函数的编写我就不一一讲了,1.3里的教程讲得很清楚了,我暂时还没有修改这些函数,待会把这些接口函数修改成128*32的。

        后面还是有工作要做的,比如现在显示的字符是翻转180°显示的,看着不舒服,要翻过来等等。

        附件里会把所有源文件打包发上来,附件能自己命名吗,谁知道?


版权声明:

本文由博主“cuter”发布。欢迎转载,但不得擅自更改博文内容,也不得用于任何盈利目的。转载时不得删除作者简介和版权声明。如有盗用而不说明出处引起的版权纠纷,由盗用者自负。

博客官方地址:

ChinaAET:http://blog.chinaaet.com/cuter521

EDN China: http://bbs.ednchina.com/BLOG_cuter521_356737.HTM

  1. 请问在哪里可以看到附件?

  2. @qq136983728   

    附件在哪?

    我也没找到你找到了吗?

  3. 那两个应该是低电平有效吧


  4. 附件在那啊?怎么看不到啊


  5. 请问您能发一下我这个的工程吗?我只找到了oled.c和oled.h,没找到主函数,你能发一份工程给我吗,或者主函数,qq: 1685255081 谢谢楼主。


  6. ***此内容已被管理员屏蔽***

  7. 附件在哪?

  8. 你好cuter大神,请问oled中的显示是反的,并且字体很小,很难看,如何修改呢?您博客中说道:“后面还是有工作要做的,比如现在显示的字符是翻转180°显示的,看着不舒服,要翻过来等等"这个在哪里实现了?我没找到谢谢。
  9. @cuter

    你好,今天我尝试了下用bootgen生成boot.bin文件,在sd卡上直接启动,发现bit流一直无法加载,但是jtag-usb加载是没问题的,bootgen的用法,我用别的一些demo(比如helloworld)都尝试过,都能启动。但是唯独oled这个不行?请问您知道这是怎么回事吗?



  10. @cuter
    哈哈,按照你的方法,终于弄好了,之前的generate bitstream 也通过在xdc 添加约束 IOSTANDARD LVCMOS33解决了。太感谢cuter啦!

  11. @yyiyy

    你这问题有些奇怪了,如果是zedboard的话而且软件没错的话,下载我的程序应该不会有问题,这个方案有一些网友应用了的。首先确认OLED没有问题吧,然后再按照我评论里说的,检查那三条是否有问题 port有默认值,这个情况我没遇到过,不好多说…

    • @cuter

      你好,我也出现了与@smilencezq类似的情况,主要有2个问题:

      1,产生bitstream的时候 开始总是报错,说是OLED[5:0] port 是default值,需要设定什么的,最后在网上找到了解决方法,就是在bitstream setting 哪里添加一个tcl文件。这个问题您遇到过么?会不会对后面产生影响?
      2,烧到板子上没有OLED没有反应,怎么破?是不是添加的OLED驱动文件有问题?我是直接下载“一步步艰难搞定OLED”中附件的程序的。我之前产生bitstream 时的 约束文件时自己创建的xdc文件,内容就是
      #   U11 <-> OLED-VBAT

      #   U12 <-> OLED-VDD
      #   U9 <-> OLED-RES
      #   U10 <-> OLED-DC
      #   AB12 <-> OLED-SCLK
      #   AA12 <-> OLED-SDIN


      #   OLED_DC 0     OLED[0] <-> U10
      #   OLED_RES 1     OLED[1] <-> U9
      #   OLED_SCLK 2    OLED[2] <-> AB12
      #   OLED_SDIN 3    OLED[3] <-> AA12
      #   OLED_VBAT 4    OLED[4] <-> U11
      #   OLED_VDD 5     OLED[5] <-> U12

      现在陷入极度郁闷中,2个晚上都没弄出来所以然啊

  12. 匿名用户匿名用户
    @cuter

    你好,我也出现了与@smilencezq类似的情况,主要有2个问题:

    1,产生bitstream的时候 开始总是报错,说是OLED[5:0] port 是default值,需要设定什么的,最后在网上找到了解决方法,就是在bitstream setting 哪里添加一个tcl文件。这个问题您遇到过么?会不会对后面产生影响?
    2,烧到板子上没有OLED没有反应,怎么破?是不是添加的OLED驱动文件有问题?我是直接下载“一步步艰难搞定OLED”中附件的程序的。我之前产生bitstream 时的 约束文件时自己创建的xdc文件,内容就是
    #   U11 <-> OLED-VBAT

    #   U12 <-> OLED-VDD
    #   U9 <-> OLED-RES
    #   U10 <-> OLED-DC
    #   AB12 <-> OLED-SCLK
    #   AA12 <-> OLED-SDIN


    #   OLED_DC 0     OLED[0] <-> U10
    #   OLED_RES 1     OLED[1] <-> U9
    #   OLED_SCLK 2    OLED[2] <-> AB12
    #   OLED_SDIN 3    OLED[3] <-> AA12
    #   OLED_VBAT 4    OLED[4] <-> U11
    #   OLED_VDD 5     OLED[5] <-> U12

    现在陷入极度郁闷中,2个晚上都没弄出来所以然啊

  13. 这里再补充一下,原文没有说的很清楚,在做pin的约束时,需要注意电路连接关系、控制寄存器每bit的作用。

    #   电路图连接关系:
    #   U11 <-> OLED-VBAT
    #   U12 <-> OLED-VDD
    #   U9 <-> OLED-RES
    #   U10 <-> OLED-DC
    #   AB12 <-> OLED-SCLK
    #   AA12 <-> OLED-SDIN


    #   驱动程序pin分配:
    #   OLED_DC 0     OLED[0] <-> U10
    #   OLED_RES 1     OLED[1] <-> U9
    #   OLED_SCLK 2    OLED[2] <-> AB12
    #   OLED_SDIN 3    OLED[3] <-> AA12
    #   OLED_VBAT 4    OLED[4] <-> U11
    #   OLED_VDD 5     OLED[5] <-> U12


    考虑到上述两个方面,所做约束如下:
    # DC
    set_property PACKAGE_PIN U10 [get_ports {OLED[0]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {OLED[0]}]

    # RES
    set_property PACKAGE_PIN U9 [get_ports {OLED[1]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {OLED[1]}]

    # SCLK
    set_property PACKAGE_PIN AB12 [get_ports {OLED[2]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {OLED[2]}]

    # SDIN
    set_property PACKAGE_PIN AA12 [get_ports {OLED[3]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {OLED[3]}]

    # "BAT"
    set_property PACKAGE_PIN U11 [get_ports {OLED[4]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {OLED[4]}]
      
    # "VDD"
    set_property IOSTANDARD LVCMOS33 [get_ports {OLED[5]}]
    set_property PACKAGE_PIN U12 [get_ports {OLED[5]}]
     

  14. 在cuter的大力帮助下,终于找到了问题,是之前约束文件写错了,和驱动里面(oled.h)的不匹配造成的~~~,现在总算成功了~~特别感谢!!~~~