本文说明i2c的ssd1306驱动要点,帮助理解zephyr的ssd3306驱动。
地址
ssd1306的i2c地址和一般的i2c slave地址一样是7bit,在7bit后跟1bit读写标志,如下图:
其中SA0可以通过硬件选择是0还是1对应地址0x3c和0x3d,一般我们购买的模块都是选择的0,所以ssd1306的i2c salve地址是0x3c。
需要注意的是一些ssd1306的示例程序按照另外一种情况将1bit读写标志都算入地址,代码中就会写成0x78和0x7A。来至于买的模块上也这样丝印,如下图
在zephyr中使用的标准的i2c slave地址,也就是0x3c
命令
当主机发送地址ssd1306回复ACK后就可以发送命令或数据了,如下:
对于命令数据发送的格式是一个Control byte对应一个命令数据,如下
[Control byte+1 byte data]+[Control byte+1 byte data0]….
对应图像数据发送的格式是一个Control byte对应多个图像数据,如下
Control byte+1 byte data+1 byte data+1 byte data…
Control Byte
Co位,1表示后面还有数据,0表示是最后一个数据
D/C位, 1表示传输的图像数据,0表示的传输的是命令数据
剩余的6bit为全0
因此在Zephyr的ssd1306_reg中有如下定义
1 | #define SSD1306_CONTROL_LAST_BYTE_CMD 0x00 |
写命令
发多条命令
[Control byte+1 byte data]+[Control byte+1 byte data0]…. 在zephyr的sd1306中就体现为
1 | u8_t cmd_buf[] = { |
发一条命令
Control byte直接使用SSD1306_CONTROL_LAST_BYTE_CMD,然后发命令数据即可
1 | return i2c_reg_write_byte(driver->i2c, DT_INST_0_SOLOMON_SSD1306FB_BASE_ADDRESS, |
写数据
因为ssd1306在写数据后一般不会再执行其它命令,所以大多都是使用SSD1306_CONTROL_LAST_BYTE_DATA,下面是zephyr填一个page的代码
1 | u8_t cmd_buf[] = { |
SSD1306命令
本文只是帮助理解zephyr的SSD1306驱动,具体命令意义请直接参考SSD1306规格书
http://www.gabotronics.com/download/datasheets/ssd1306.pdf
显存
ssd1306是128 64的单色显示屏,其内部显存大小为128 64 bit
显存结构
128 * 64的大小,将其在垂直方向分为8个page,每个page8行,将其在水平方向分为128列,如下图:
当一个数据字节被写入显存时,当前列的同一页面的所有行图像数据被填充。数据位D0写入顶行,而数据位D7写入底行,如下图:
显存填充方式
ssd1306有3中填充方式,无论那种填充方式,起点地址都是已page和列来指定,可以任意指定page和列,但不能指定page内的某一行。
page模式
在page模式下,读取/写入显示RAM后,列地址指针自动增加1. 如果列地址指针到达列结束地址,则列地址指针将复位为列起始地址,而page地址指针则不会改变。 用户必须设置新的page和列地址才能访问下一页RAM内容。
水平模式
在水平寻址模式下,读/写显示RAM后,列地址指针自动增加1.如果列地址指针到达列结束地址,则列地址指针复位为列起始地址,页地址指针增加 当列和页地址指针都到达结束地址时,指针将重置为列起始地址和页面起始地址,如下图:
垂直模式
在垂直寻址模式下,在读/写显示RAM后,页面地址指针自动增加1.如果页面地址指针到达页面结束地址,则页面地址指针被重置为页面起始地址,列地址指针为 增加1. 当列和页地址指针都到达结束地址时,指针将重置为列起始地址和页面起始地址,如下图:
Zephyr display driver 模型
接口
zephyr的dirsplayer driver模型参看文件display.h,对上层使用的接口定义如下
1 | //写framebuffer |
实现
参考ssd1306.c,实现的方法就是在驱动中按照ssd1306的硬件特性实现struct display_driver_api 内函数指针对应的函数即可,如下
1 | static struct display_driver_api ssd1306_driver_api = { |
由于display硬件的不同除了write和get_capabilities是必须的外,其它实现函数内可以直接留空函数不支援对应功能。对于ssd1306的write函数我们再详细展开一下
write
write函数指针原型如下
1 | struct display_buffer_descriptor { |
从write的参数可以看到希望能写任意矩形区域x,y为起点 desc->width和desc->height为宽高, 再来看下Zephyr的实现:
1 | int ssd1306_write(const struct device *dev, const u16_t x, const u16_t y, |
很遗憾,限制了只能从0,0开始写,大多数情况下ssd1306会在驱动外部在建立一个1K的全framebuffer缓存,每次都写全屏。但我想在zephyr上让lvgl能跑在ssd1306就必须支援写任意矩形,改造如下:
1 | if ((y & 0x7) != 0U) { |
这样可以写指定的矩形了,不过可能你已经看到了代码里还是要求了page对齐(y&0x7),这会对lvgl工作造成影响吗? lvgl已经考虑了display driver的特殊性,可以看zephyr的lib/gui/lvgl/lvgl_display.c, 下面两个函数就是解决这个问题的,这里不做详细分析了,有兴趣可以看看代码
1 | disp_drv->rounder_cb = lvgl_rounder_cb_mono; |