OLED即Organic Light-Emitting Diode,相比LCD的屏,LCD屏需要背光,而OLED不需要,因为它本身就是发光的。
开发板上的屏使用的驱动IC是SSD1306,支持并口、I2C和SPI等接口,可以根据自身需要进行灵活的配置。具体是使用的哪种接口,是通过芯片BS0、BS1、BS2三个引脚来控制的,如图所示:
另外,我拿到的屏是配置成四线SPI接口的,而不是像OLED教程所说的是I2C接口。
开发板上的屏有黄、蓝两种颜色显示,前16行为黄色,后48行为蓝色,并且中间有一行不能显示的间隔区。
既然是SPI的接口,我们先来看发送数据的时序图:
先传送高位数据,再传送低位数据,在每个时钟的上升沿进行数据的采样,CS是片选信号,DC是数据/命令控制信号,为高表示发送的是数据,为低是命令。我们先采用GPIO模拟SPI的方式来操作,当然NRF51822芯片是支持硬件SPI的,后面我们再做修改,发送一个字节数据代码如下:
SSD1306显存总共有128*64bit大小,显存总共分成了8页,如图所示:
每页总共128*8bit大小,写数据是按字节为单位写入的(在移植uCGUI时需要注意这一点)。
那么既然显存大小是128*64bit,那么在写入显示数据时首先要指定写入数据的位置。0xB0~0xB7命令用来设置页地址,共8页,0x00~0x0F命令用来指定列地址的低4位,0x10~0x1F命令用来指定列地址的高4位,细心的网友可能会发现16*16总共有256列了,虽然芯片可能支持这么多列,但实际屏上是不会显示的,也就是说列地址的高4位你只能设置为0~7。
这里也写了一个函数,根据x、y的坐标来设置写入数据的起始位置。
还有需要注意是指定坐标后,写入一个字节的数据,这个数据是体现先在那一列上的,并且是低位在上,高位在下这样显示的。什么意思呢,你比如在(0, 0)这个坐标写入一个字节数据0xF0,那么第0页、第0列是这样显示的(从上往下),[ ][ ][ ][ ][*][*][*][*],(方括号里的*号代表显示,空代表不显示)。
Ok,有了这个基础后,根据这个原理我们就显示一些任意数据了,如果需要显示字符,那么就需要用到字库,这里用到了8*16的ascii字库,其中8代表字符的宽度,16代表字符的高度,代码如下:
例如我要显示Hello world!,效果如下:
开发板上的屏使用的驱动IC是SSD1306,支持并口、I2C和SPI等接口,可以根据自身需要进行灵活的配置。具体是使用的哪种接口,是通过芯片BS0、BS1、BS2三个引脚来控制的,如图所示:
另外,我拿到的屏是配置成四线SPI接口的,而不是像OLED教程所说的是I2C接口。
开发板上的屏有黄、蓝两种颜色显示,前16行为黄色,后48行为蓝色,并且中间有一行不能显示的间隔区。
既然是SPI的接口,我们先来看发送数据的时序图:
先传送高位数据,再传送低位数据,在每个时钟的上升沿进行数据的采样,CS是片选信号,DC是数据/命令控制信号,为高表示发送的是数据,为低是命令。我们先采用GPIO模拟SPI的方式来操作,当然NRF51822芯片是支持硬件SPI的,后面我们再做修改,发送一个字节数据代码如下:
void spi_write_byte(unsigned char val)
{
char i;
for (i = 0; i < 8; i++) {
if (val & 0x80)
nrf_gpio_pin_set(SPI_SDA);
else
nrf_gpio_pin_clear(SPI_SDA);
val <<= 1;
nrf_gpio_pin_clear(SPI_CLK);
nrf_gpio_pin_set(SPI_CLK);
}
}
写数据还是命令只要控制DC信号就可以了,代码如下:
void spi_write_data(unsigned char data)
{
nrf_gpio_pin_set(SPI_DC);
spi_write_byte(data);
}
void spi_write_cmd(unsigned char cmd)
{
nrf_gpio_pin_clear(SPI_DC);
spi_write_byte(cmd);
}
然后再来看屏的初始化部分,屏的初始化主要是屏IC里面的寄存器初始化,这里我们直接使用开发板例程所提供的,另外还包括屏的上电和RESET部分。
void lcd_init(void)
{
nrf_gpio_cfg_output(SPI_CLK);
nrf_gpio_cfg_output(SPI_SDA);
nrf_gpio_cfg_output(SPI_CS);
nrf_gpio_cfg_output(SPI_DC);
nrf_gpio_cfg_output(LCD_PWR);
nrf_gpio_cfg_output(LCD_RST);
nrf_gpio_pin_set(LCD_PWR); /* power on */
nrf_gpio_pin_clear(LCD_RST); /* reset */
nrf_delay_ms(100);
nrf_gpio_pin_set(LCD_RST);
nrf_gpio_pin_clear(SPI_CS);
spi_write_cmd(0xAE); /* register init */
spi_write_cmd(0xD5);
spi_write_cmd(0x80);
spi_write_cmd(0xA8);
spi_write_cmd(0x3F);
spi_write_cmd(0xD3);
spi_write_cmd(0x00);
spi_write_cmd(0x40);
spi_write_cmd(0x8D);
spi_write_cmd(0x95);
spi_write_cmd(0xA1);
spi_write_cmd(0xC8);
spi_write_cmd(0xDA);
spi_write_cmd(0x12);
spi_write_cmd(0x81);
spi_write_cmd(0xFF);
spi_write_cmd(0xD9);
spi_write_cmd(0xF1);
spi_write_cmd(0xDB);
spi_write_cmd(0x30);
spi_write_cmd(0xAD);
spi_write_cmd(0x30);
spi_write_cmd(0xA4);
spi_write_cmd(0xA6);
spi_write_cmd(0xAF);
lcd_clear();
}
最后来看显示部分。
SSD1306显存总共有128*64bit大小,显存总共分成了8页,如图所示:
每页总共128*8bit大小,写数据是按字节为单位写入的(在移植uCGUI时需要注意这一点)。
那么既然显存大小是128*64bit,那么在写入显示数据时首先要指定写入数据的位置。0xB0~0xB7命令用来设置页地址,共8页,0x00~0x0F命令用来指定列地址的低4位,0x10~0x1F命令用来指定列地址的高4位,细心的网友可能会发现16*16总共有256列了,虽然芯片可能支持这么多列,但实际屏上是不会显示的,也就是说列地址的高4位你只能设置为0~7。
这里也写了一个函数,根据x、y的坐标来设置写入数据的起始位置。
#define PAGE_SIZE 8
void lcd_set_xy(unsigned char x, unsigned char y)
{
y = (y / PAGE_SIZE);
spi_write_cmd(0xB0 + y);
spi_write_cmd(((x & 0xF0) >> 4) | 0x10);
spi_write_cmd(x & 0x0F);
}
虽然这里参数y可以是0~63,但数据写入并不能从坐标的任意位置写入,即y你传0和传7的效果是一样的。
还有需要注意是指定坐标后,写入一个字节的数据,这个数据是体现先在那一列上的,并且是低位在上,高位在下这样显示的。什么意思呢,你比如在(0, 0)这个坐标写入一个字节数据0xF0,那么第0页、第0列是这样显示的(从上往下),[ ][ ][ ][ ][*][*][*][*],(方括号里的*号代表显示,空代表不显示)。
Ok,有了这个基础后,根据这个原理我们就显示一些任意数据了,如果需要显示字符,那么就需要用到字库,这里用到了8*16的ascii字库,其中8代表字符的宽度,16代表字符的高度,代码如下:
void lcd_disp_string(unsigned char x, unsigned char y, char *s)
{
unsigned char c;
int i;
while (*s != '\0') {
if (x > 120) {
x = 0;
y += 16;
}
lcd_set_xy(x, y);
c = *s - 32;
for (i = 0; i < 8; i++)
spi_write_data(F8x16[c * 16 + i]);
lcd_set_xy(x, y + 8);
for (i = 0; i < 8; i++)
spi_write_data(F8x16[c * 16 + i + 8]);
s++;
x += 8;
}
}
例如我要显示Hello world!,效果如下: