上海润欣科技股份有限公司创研社
导读: 本文详细介绍了,如何在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(片选)。
(1)MOSI/SDI – SerialData In,串行数据输入;
(2)MISO/SDO – SerialDataOut,串行数据输出;
(3)SCLK – Serial Clock,时钟信号,由主设备产生;
(4)CS – Chip Select,从设备使能信号,由主设备控制。
其中,CS是从芯片是否被主芯片选中的控制信号,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),主芯片对此从芯片的操作才有效。这就使在同一条总线上连接多个SPI设备成为可能。
接下来就负责通讯的3根线了。通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCLK时钟线存在的原因,由SCLK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。因此,至少需要8次时钟信号的改变(上沿和下沿为一次),才能完成8位数据的传输。
(图1)SPI通信结构图
(图2)SPI常规读操作
(图3)SPI常规写操作
2. SPI的四种模式
根据SPI时钟极性(CPOL)和时钟相位(CPHA)配置的不同可分为4种模式。
时钟极性是指SPI通信设备处于空闲状态时(或SPI通信开始时,即SS为低电平时),SCK的电平信号CPOL=0时,SCK空闲状态为低电平,CPOL=1时则相反。
时钟相位是指数据采样的时刻,当CPHA=0时,MOSI或MISO数据线会在时钟线第一个边沿开始采样(奇数边沿)。
当CPHA=1时,MOSI或MISO数据线会在时钟线第二个边沿开始采样(偶数边沿)。
详细如下:
(1)CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是 SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。
(2)CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是 SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
(3)CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是 SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
(4)CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是 SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
(图4)SPI的CPOL和CPHA
3.硬件SPI与模拟SPI的区别
在模拟SPI的模式下,我们需要使用IO口去模拟SPI的时序,这个模拟的全部过程,都需要CPU全程负责,但在获取或者发送数据的时候,可能会使用软件延时,这个时间在数据交互量不大的情况下并明显,但是如果数据量大,可能会打乱SPI的时序。
对于硬件SPI来说,我们只需要开启相应的寄存器配置和对应的中断。数据的交互就不需要CPU参与。当传输中断产生的时候,CPU只需要从中断中搬运数据就好了,省下了软件模拟IO的存取时间。让CPU省下更多时间去运行其他代码。
4.硬件SPI的配置
首先我们需要确定OLED屏幕上面的引脚,如图:
(图5)OLED硬件管脚图
GND - 接地 VCC – 接3.3V
SCL – 接SCK(5脚) SDA – 接MOSI(7脚)
RST – 接 42脚(可修改) DC – 接43脚(可修改)
作为Master模式下,提供有6组IO口供用户选择,而作为Slave有1组。在Master模式下,Apollo提供一个128-byte 的local RAM作为双向FIFO的传输容量。Apollo2的管脚复用具体如下:
(图6)Apollo2全部引脚寄存器配置图
(图7)Apollo2 引脚颜色比对图
第一步,我们选择Master 0 Signals 也就是相对于的 5、6、7引脚。
具体通过am_hal_gpio_pin_config()函数进行引脚配置
第二步,配置iom_config
在SPI_g_sIOMConfig里面配置的是IOM的一些常规参数:
模式我们选择为AM_HAL_IOM_SPIMODE 传输速率为 100KHZ,相位和极性都是0 。写数据的阈值是4bit,读取是60bit。这两个是生产中断的条件。
最后记得开启IO Master
第三步,对屏幕进行复位操作,而复位操作主要是改变RST引脚的高低电平。
通过Apollo2 SDK提供的API去修改IO口状态。
am_hal_gpio_out_bit_clear()置0 am_hal_gpio_out_bit_set()置1
第四步,通过SPI通信,将指令或者数据传输到OLED屏幕中。OLED屏幕判断指令还是数据,是通过DC引脚的高低电平实现的。所以需要有一个参数去控制 43引脚的状态。代码如下:
数据先传输进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;
}
该函数的几个参数定义分别如下:
(1)ui32Module – IOM的Master编号选择
(2)ui32ChipSelect – 外设编号选择
(3)pui32Data – 传输的数据
(4)ui32NumBytes –传输数据的大小
(5)ui32Options – 寄存器偏移量
这里可以根据实际情况去配置各个参数,从而达到传输数据的目的。至此,硬件SPI模式基本配置完成。
5.模拟SPI配置
模拟SPI基本与硬件SPI类似。使用任意两个IO口模拟通信,不需要使用指定的SPI接口,也不需要响应的SPI配置。
第一步,IO口的配置大概如下:
四个引脚如下:SCL – 8脚; SDA – 9脚;RES – 42脚;DC – 43脚 同样需要配置各个IO口的状态:
模拟SPI与硬件SPI的最主要区别是在写函数里面。使用一个引脚模拟时钟,另外一个引脚发送数据。
第二步,发送函数:
判断cmd的操作是必不可少的,接着判断(dat & 0x80) 判断高位是否为‘1’。dat 高位为‘1’,MOSI就输出‘1’;否则输出‘0’。然后移位,次高位变为最高位。就是把dat的数据从MOSI脚输出。
6.了解OLED屏幕
有机发光显示OLED(OrganicLight EmittingDisplay)是比液晶显示技术更为先进的新一代平板显示技术,是被业界公认为最具发展前景的下一代显示技术。
OLED12864是128*64行点阵的OLED单色,字符,图形显示模块,模块内有64*64的显示数据RAM,其中的每位数据对应于OLED屏上的每一个点的亮,暗状态。
12864OLED的像素矩阵的划分是比较特殊的。 整个屏幕水平方向划分为8个page, 垂直方向则是按像素划分为128 column. 每个page-column包含8个像素, 通过一个十六进制数(其实就是一个字节, 8个bit)来控制, 每个bit控制一个像素。
(图8-1)OLED屏幕像素矩阵
(图8-2)OLED屏幕像素矩阵
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_Set_Pos()函数是用来定位文字在屏幕上显示的位置。文字的大小是16*16个像素点,所以for()循环里面的t为16次。Hzk[ ][ ]数组里面存储的是通过取模软件,将文字转换成16进制的数据。
取模工具选用PCtolCD2002 完美版本,这里选择字符模式。输入文字的时候,可以点击生成字模,在下方就会显示出各个文字相对应的16进制数组。
(图9)文字取模
将各个数组分别添加到Hzk数组里面之后,就可以在主函数里面通过OLED_ShowCHinese()进行显示。
显示效果如下图:
(图10)文字显示效果
9. OLED屏幕图片显示
照片显示和文字显示原理相同,也是点亮相对应的像素点。
该函数当中,x0 ,y0表示的是图片所显示的的起始坐标,x1 表示的是图片像素的x轴所占的像素,y1表示的是页数(0-7)
将想要显示的图片,转换成BMP格式之后,通过PCtolCD2002的图片模式进行转换。设置如下:选择阴码,逆向,十六进制输出。
(图11)图片取模选项设置
(图12)图片取模界面
将所生成的十六进制数据进行,修改对齐后,装到BMP数组当中。在主函数当中就可以直接显示了,效果如下图。
(图13)图片显示效果
后记:
在调试当中遇到的一些需要注意的点:
1、 在freeRTOS下,使用OLED屏幕的话,需要预先启用SPI相对于的IOM 口。
需要用到函数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 轴。
同时,clear函数里面,循环发送数据的次数,也应该从n<128 改为 n<132。
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()的时候,x与x1的数据差应该就是你图片的像素大小。
例如:
我的照片大小为 (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);
全部0条评论
快来发表一下你的评论吧 !