基于Apollo2 SDK上OLED屏幕的实现

描述

上海润欣科技股份有限公司创研社


导读: 本文详细介绍了,如何在Apollo2 SDK-1.2.12平台上点亮并使用Heletc 1.3寸12864-OLED屏幕。本文将阐述,如何通过硬件SPI与模拟SPI模式,分别实现外设OLED屏的驱动代码和实现步骤。

1. SPI通信原理

SPI是串行外设接口(Serial Peripheral Interface)的缩写。是 Motorola 公司推出的一 种同步串行接口技术,是一种高速的,全双工,同步的通信总线。

SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。

1MOSI/SDI – SerialData In,串行数据输入;

2MISO/SDO – SerialDataOut,串行数据输出;

3SCLK – Serial Clock,时钟信号,由主设备产生;

4CS – Chip Select,从设备使能信号,由主设备控制。

其中,CS是从芯片是否被主芯片选中的控制信号,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),主芯片对此从芯片的操作才有效。这就使在同一条总线上连接多个SPI设备成为可能。

接下来就负责通讯的3根线了。通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCLK时钟线存在的原因,由SCLK提供时钟脉冲,SDISDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。因此,至少需要8次时钟信号的改变(上沿和下沿为一次),才能完成8位数据的传输。

OLED屏幕

(图1SPI通信结构图 

OLED屏幕

(图2SPI常规读操作 

OLED屏幕

(图3SPI常规写操作

2.  SPI的四种模式

根据SPI时钟极性(CPOL)和时钟相位(CPHA)配置的不同可分为4种模式。

时钟极性是指SPI通信设备处于空闲状态时(或SPI通信开始时,即SS为低电平时),SCK的电平信号CPOL=0时,SCK空闲状态为低电平,CPOL=1时则相反。

时钟相位是指数据采样的时刻,当CPHA=0时,MOSIMISO数据线会在时钟线第一个边沿开始采样(奇数边沿)。

CPHA=1时,MOSIMISO数据线会在时钟线第二个边沿开始采样(偶数边沿)。

详细如下:

1CPOL=0CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是 SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。

2CPOL=0CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是 SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。

3CPOL=1CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是 SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。

4CPOL=1CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是 SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。

OLED屏幕

(图4SPICPOLCPHA

3.硬件SPI与模拟SPI的区别

在模拟SPI的模式下,我们需要使用IO口去模拟SPI的时序,这个模拟的全部过程,都需要CPU全程负责,但在获取或者发送数据的时候,可能会使用软件延时,这个时间在数据交互量不大的情况下并明显,但是如果数据量大,可能会打乱SPI的时序。

对于硬件SPI来说,我们只需要开启相应的寄存器配置和对应的中断。数据的交互就不需要CPU参与。当传输中断产生的时候,CPU只需要从中断中搬运数据就好了,省下了软件模拟IO的存取时间。让CPU省下更多时间去运行其他代码。

4.硬件SPI的配置

首先我们需要确定OLED屏幕上面的引脚,如图:

(图5OLED硬件管脚图

GND - 接地                     VCC – 3.3V

SCL – SCK5脚)        SDA – MOSI7脚)

RST – 42脚(可修改) DC – 43脚(可修改)

作为Master模式下,提供有6IO口供用户选择,而作为Slave1组。在Master模式下,Apollo提供一个128-byte local RAM作为双向FIFO的传输容量。Apollo2的管脚复用具体如下:

(图6Apollo2全部引脚寄存器配置图 

                    OLED屏幕

 (图7Apollo2 引脚颜色比对图

第一步,我们选择Master 0 Signals 也就是相对于的 567引脚。

具体通过am_hal_gpio_pin_config()函数进行引脚配置

OLED屏幕

OLED屏幕

 

第二步,配置iom_config

OLED屏幕

SPI_g_sIOMConfig里面配置的是IOM的一些常规参数:

 

OLED屏幕

模式我们选择为AM_HAL_IOM_SPIMODE 传输速率为 100KHZ,相位和极性都是0 。写数据的阈值是4bit,读取是60bit。这两个是生产中断的条件。

最后记得开启IO Master

OLED屏幕

第三步,对屏幕进行复位操作,而复位操作主要是改变RST引脚的高低电平。

OLED屏幕

通过Apollo2 SDK提供的API去修改IO口状态。

 am_hal_gpio_out_bit_clear()置0    am_hal_gpio_out_bit_set()置1

OLED屏幕

第四步,通过SPI通信,将指令或者数据传输到OLED屏幕中。OLED屏幕判断指令还是数据,是通过DC引脚的高低电平实现的。所以需要有一个参数去控制 43引脚的状态。代码如下:

 

OLED屏幕

数据先传输进Buffer,通过判断cmd 控制DC_Set/ DC_Clr

然后通过am_hal_iom_spi_write()函数,将数据直接传输到外设。

am_hal_iom_spi_write(uint32_t ui32Module, uint32_t ui32ChipSelect,

                     uint32_t *pui32Data, uint32_t ui32NumBytes,

                     uint32_t ui32Options)

{

    am_hal_iom_status_e ui32Status;

    //

    // Validate parameters

    //

    if ( ui32Module >= AM_REG_IOMSTR_NUM_MODULES )

    {

        return AM_HAL_IOM_ERR_INVALID_MODULE;

    }

    // Reset the error status

    ui32Status = g_iom_error_status[ui32Module] = AM_HAL_IOM_SUCCESS;

    if (ui32NumBytes == 0)

    {

        g_iom_error_status[ui32Module] = ui32Status = AM_HAL_IOM_ERR_INVALID_PARAM;

        return ui32Status;

    }

 

    //

    // Check to see if queues have been enabled. If they are, we'll actually

    // switch to the queued interface.

    //

    if ( g_psIOMQueue[ui32Module].pui8Data != NULL )

    {

        //

        // If the queue is on, go ahead and add this transaction to the queue.

        //

        ui32Status = am_hal_iom_queue_spi_write(ui32Module, ui32ChipSelect, pui32Data,

                                   ui32NumBytes, ui32Options, 0);

 

        if (ui32Status == AM_HAL_IOM_SUCCESS)

        {

            //

            // Wait until the transaction actually clears.

            //

            am_hal_iom_queue_flush(ui32Module);

            // g_iom_error_status gets set in the isr handling

            ui32Status = g_iom_error_status[ui32Module];

        }

 

        //

        // At this point, we've completed the transaction, and we can return.

        //

    }

    else

    {

        //

        // Otherwise, we'll just do a polled transaction.

        //

        ui32Status = am_hal_iom_spi_write_nq(ui32Module, ui32ChipSelect, pui32Data,

                                ui32NumBytes, ui32Options);

    }

    return ui32Status;

}

该函数的几个参数定义分别如下:

1ui32Module – IOMMaster编号选择

2ui32ChipSelect – 外设编号选择

3pui32Data – 传输的数据

4ui32NumBytes –传输数据的大小

5ui32Options – 寄存器偏移量

这里可以根据实际情况去配置各个参数,从而达到传输数据的目的。至此,硬件SPI模式基本配置完成。

5.模拟SPI配置

模拟SPI基本与硬件SPI类似。使用任意两个IO口模拟通信,不需要使用指定的SPI接口,也不需要响应的SPI配置。

第一步,IO口的配置大概如下:

OLED屏幕

 

四个引脚如下:SCL – 8脚; SDA – 9脚;RES – 42脚;DC – 43 同样需要配置各个IO口的状态:

OLED屏幕

模拟SPI与硬件SPI的最主要区别是在写函数里面。使用一个引脚模拟时钟,另外一个引脚发送数据。

第二步,发送函数:

OLED屏幕

 

判断cmd的操作是必不可少的,接着判断(dat & 0x80) 判断高位是否为‘1’。dat 高位为‘1’,MOSI就输出‘1’;否则输出‘0’。然后移位,次高位变为最高位。就是把dat的数据从MOSI脚输出。

 

6.了解OLED屏幕

 有机发光显示OLEDOrganicLight EmittingDisplay)是比液晶显示技术更为先进的新一代平板显示技术,是被业界公认为最具发展前景的下一代显示技术。

OLED12864128*64行点阵的OLED单色,字符,图形显示模块,模块内有64*64的显示数据RAM,其中的每位数据对应于OLED屏上的每一个点的亮,暗状态。

12864OLED的像素矩阵的划分是比较特殊的。 整个屏幕水平方向划分为8page, 垂直方向则是按像素划分为128 column. 每个page-column包含8个像素, 通过一个十六进制数(其实就是一个字节, 8bit)来控制, 每个bit控制一个像素。

(图8-1OLED屏幕像素矩阵

OLED屏幕

(图8-2OLED屏幕像素矩阵

7. OLED屏幕配置

 与大部分12864 OLED屏幕一样,需要提前给屏幕输入特定的显示指令。代码如家,需要注意的是硬件SPI与模拟SPI选择自己响应的OLED_WR_Byte()即可。配置过程如下:

OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel

                   OLED_WR_Byte(0x00,OLED_CMD);//---set low column address

                   OLED_WR_Byte(0x10,OLED_CMD);//---set high column address

                   OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)

                   OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register

                   OLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current Brightness

                   OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0×óÓÒ·´ÖÃ 0xa1Õý³£

                   OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0ÉÏÏ·´Öà 0xc8Õý³£

                   OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display

                   OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)

                   OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty

                   OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset        Shift Mapping RAM Counter (0x00~0x3F)

                   OLED_WR_Byte(0x00,OLED_CMD);//-not offset

                   OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency

                   OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec

                   OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period

                   OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock

                   OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration

                   OLED_WR_Byte(0x12,OLED_CMD);

                   OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh

                   OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level

                   OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)

                   OLED_WR_Byte(0x02,OLED_CMD);//

                   OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable

                   OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable

                   OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)

                   OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)

                   OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel

                   OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/

                   OLED_Clear();

                   OLED_Set_Pos(0,0);       

将上述代码发送给OLED屏幕之后,屏幕的初始化也基本完成

8. OLED屏幕文字显示

下面是使用OLED显示中文汉字的范例。代码如下

OLED屏幕

OLED_Set_Pos()函数是用来定位文字在屏幕上显示的位置。文字的大小是16*16个像素点,所以for()循环里面的t16次。Hzk[ ][ ]数组里面存储的是通过取模软件,将文字转换成16进制的数据。

取模工具选用PCtolCD2002 完美版本,这里选择字符模式。输入文字的时候,可以点击生成字模,在下方就会显示出各个文字相对应的16进制数组。

OLED屏幕

(图9)文字取模

 

将各个数组分别添加到Hzk数组里面之后,就可以在主函数里面通过OLED_ShowCHinese()进行显示。

OLED屏幕

显示效果如下图:

(图10)文字显示效果

 

9. OLED屏幕图片显示

照片显示和文字显示原理相同,也是点亮相对应的像素点。

OLED屏幕

该函数当中,x0 y0表示的是图片所显示的的起始坐标,x1 表示的是图片像素的x轴所占的像素,y1表示的是页数(0-7

将想要显示的图片,转换成BMP格式之后,通过PCtolCD2002的图片模式进行转换。设置如下:选择阴码,逆向,十六进制输出。

OLED屏幕

(图11)图片取模选项设置

OLED屏幕

(图12)图片取模界面

将所生成的十六进制数据进行,修改对齐后,装到BMP数组当中。在主函数当中就可以直接显示了,效果如下图。

(图13)图片显示效果

 

后记:    

       在调试当中遇到的一些需要注意的点:

1、  freeRTOS下,使用OLED屏幕的话,需要预先启用SPI相对于的IOM 口。

OLED屏幕

需要用到函数am_hal_pwrctrl_periph_enable(uint32_t ui32Peripheral)进行相应的配置。同理在进行低功耗处理的时候,可以关闭SPI接口。

am_hal_pwrctrl_periph_enable(uint32_t ui32Peripheral)

{

 

    am_hal_debug_assert_msg(ONE_BIT(ui32Peripheral),

                        "Cannot enable more than one peripheral at a time.");

 

    //

    // Begin critical section.

    //

    AM_CRITICAL_BEGIN_ASM

 

    //

    // Enable power control for the given device.

    //

    AM_REG(PWRCTRL, DEVICEEN) |= ui32Peripheral;

 

    //

    // End Critical Section.

    //

    AM_CRITICAL_END_ASM

 

    //

    // Wait for the power to stablize.  Using a simple delay loop is more

    // power efficient than a polling loop.

    //

    am_hal_flash_delay(AM_HAL_PWRCTRL_DEVICEEN_DELAYCYCLES / 3);

 

    //

    // Quick check to guarantee we're good (should never be more than 1 read).

    //

    POLL_PWRSTATUS(ui32Peripheral);

}

2、  OLED屏幕在进行Clear的时候,屏幕不干净

(图14)屏幕刷新有残留

原因是:写到寄存器第一列和第二列的数据被驱动芯片当做乱码并在 显示屏的最后一列显示出来。修改如下:

把程序中所有 X”轴的值改成 132,如下图定义的是 X 轴与 Y 轴。

OLED屏幕

同时,clear函数里面,循环发送数据的次数,也应该从n<128 改为 n<132

OLED屏幕

3、  图片显示函数

void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1,

         unsigned char y1,unsigned char BMP[])

{      

 unsigned int j=0;

 unsigned char x,y;

 

  if(y1%8==0) y=y1/8;     

  else y=y1/8+1;

         for(y=y0;y

         {

                   OLED_Set_Pos(x0,y);

    for(x=x0;x

             {     

                      OLED_WR_Byte(BMP[j++],OLED_DATA);                    

             }

         }

}

在使用该函数的时候,首先要注意的是,y1表示的是页数(0-7)也就是前文指的page,而不是你所给照片取模的Y轴的像素。其次在照片取模当中,尽量控制为128*64的大小。如果图片大小无法满足,则在使用OLED_DrawBMP()的时候,xx1的数据差应该就是你图片的像素大小。

例如:

我的照片大小为 (92 X 60 ),如果选择屏幕显示的起始坐标点为(0,0

则函数为:OLED_DrawBMP(0,0,92,7,BMP);

但是当你想移动图片的位置,修改为OLED_DrawBMP(18,0,92,7,BMP);显示的结果会出现乱码。

正确的方式是改为OLED_DrawBMP(18,0,110,7,BMP);

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分