模块一共由7个引脚,采用SPI模式时引脚定义如下:
GND:电源地
VCC:2.2V~5.5V
SCL(D0):CLK 时钟 (高电平 2.2V~5.5V)
SDA(D1):MOSI 数据(高电平 2.2V~5.5V)
RST:复位(高电平 2.2V~5.5V)
D/C:数据/命令(高电平 2.2V~5.5V)
CS:SPI片选
注意:没有MISO引脚,因为主控只能向OLED写数据,不能读取OLED的数据
要操作OLED,只需使用SPI接口发送数据,并不需要使用SPI接口读取数据。除此之外,还需要控制D/C引脚:
OLED上有128*64个像素(128列,64行),每个像素只有2种状态:亮、灭。
OLED内部有一块显存GDDRAM(Graphic Display Data RAM),显存中每位对应一个像素,入下图所示
显存被分为8页、128列,要写某个字节时,需要先指定地址(哪页、哪列),然后写入1字节的数据。
OLED有三种寻址模式:
spi1_pins_a: spi1-0 {
pins1 {
pinmux = <STM32_PINMUX('Z', 0, AF5)>, /* SPI1_SCK */
<STM32_PINMUX('Z', 2, AF5)>; /* SPI1_MOSI */
bias-disable;
drive-push-pull;
slew-rate = <3>;
};
pins2 {
pinmux = <STM32_PINMUX('Z', 1, AF5)>; /* SPI1_MISO */
bias-disable;
drive-push-pull;
slew-rate = <3>;
};
};
spi1_sleep_pins_a: spi1-sleep-0 {
pins {
pinmux = <STM32_PINMUX('Z', 0, ANALOG)>, /* SPI1_SCK */
<STM32_PINMUX('Z', 1, ANALOG)>, /* SPI1_MISO */
<STM32_PINMUX('Z', 2, ANALOG)>; /* SPI1_MOSI */
};
};
&spi1 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&spi1_pins_a>;
pinctrl-1 = <&spi1_sleep_pins_a>;
cs-gpios = <&gpioz 3 GPIO_ACTIVE_LOW>, <&gpioa 14 GPIO_ACTIVE_LOW>;
status = "okay";
/* OLED屏幕 */
oled@1 {
compatible = "atk,oled";
reg = <1>; /* CS #1 */
spi-max-frequency = <1000000>;
dc-gpios = <&gpioi 3 GPIO_ACTIVE_LOW>;
rst-gpios = <&gpioi 11 GPIO_ACTIVE_LOW>;
};
};
内核中使能 SPI 控制器驱动, ST 默认将SPI控制器驱动编译为模块,使能步骤如下:
Device Drivers
SPI support (SPI [=y])
<*> STMicroelectronics STM32 SPI controller //编译进内核
OLED驱动程序基于SPI总线驱动框架和缓冲帧驱动框架编写,有关缓冲帧的内容参考8.1缓冲帧(Framebuffer)驱动框架和8.2LCD-TFT显示控制器驱动 (LCD驱动)部分,驱动代码主要包括以下几个部分:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#define OLED_DISPLAY_RAM_SIZE (8*128)
struct oled_handle{
struct spi_device *spi; //oled所属spi设备
int rst_gpio; //复位引脚
int dc_gpio; //数据命令选择引脚
struct task_struct *kthread; //用于将显存内容更新到OLED的内核线程
uint8_t (*oled_buffer)[128]; //oled buffer,将缓冲帧中的数据转换为OLED格式后在通过SPI总线发送到OLED
struct fb_info *fb; //缓冲帧句柄
unsigned int pseudo_palette[16]; //调色板
uint8_t (*fb_buffer)[16]; //缓冲帧
dma_addr_t phy_addr; //缓冲帧物理地址
uint8_t (*old_fb_buffer)[16]; //缓冲帧上一次更新时的状态
};
//初始化OLED的复位引脚和数据命令选择引脚
static int devm_pin_init(struct oled_handle *oled)
{
int result;
//获取RST GPIO号
oled->rst_gpio = of_get_named_gpio(oled->spi->dev.of_node, "rst-gpios", 0);
if(oled->rst_gpio < 0)
{
printk("get rst_gpio failed\r\n");
return oled->rst_gpio;
}
//申请RST GPIO
result = devm_gpio_request(&oled->spi->dev, oled->rst_gpio, "oled,rst_gpio");
if(result < 0)
{
printk("request rst_gpio failed\r\n");
return result;
}
//设置复位引脚输出高电平
gpio_direction_output(oled->rst_gpio, 1);
//获取DC GPIO号
oled->dc_gpio = of_get_named_gpio(oled->spi->dev.of_node, "dc-gpios", 0);
if(oled->dc_gpio < 0)
{
printk("get dc_gpio failed\r\n");
return oled->dc_gpio;
}
//申请DC GPIO
result = devm_gpio_request(&oled->spi->dev, oled->dc_gpio, "oled,dc_gpio");
if(result < 0)
{
printk("request dc_gpio failed\r\n");
return result;
}
//设置数据/命令选择引脚输出高电平
gpio_direction_output(oled->dc_gpio, 1);
return 0;
}
//数据命令选择引脚拉高,表示发送数据
static void dc_high(struct oled_handle *oled)
{
gpio_direction_output(oled->dc_gpio, 1);
}
//数据命令选择引脚拉低,表示发送命令
static void dc_low(struct oled_handle *oled)
{
gpio_direction_output(oled->dc_gpio, 0);
}
//复位引脚拉高
static void rst_high(struct oled_handle *oled)
{
gpio_direction_output(oled->rst_gpio, 1);
}
//复位引脚拉低
static void rst_low(struct oled_handle *oled)
{
gpio_direction_output(oled->rst_gpio, 0);
}
//复位OLED屏幕
static void oled_reset(struct oled_handle *oled)
{
//暂时拉高复位引脚
rst_high(oled);
msleep_interruptible(200);
//拉低复位引脚,进行复位
rst_low(oled);
msleep_interruptible(200);
//拉高复位引脚,复位结束
rst_high(oled);
msleep_interruptible(200);
}
//通过SPI总线向OLED设备发送数据
static int oled_write(struct oled_handle *oled, const uint8_t *data, uint32_t lenght)
{
int result;
uint8_t *buffer;
struct spi_message message;
struct spi_transfer transfer;
//分配发送缓存
buffer = kzalloc(lenght, GFP_KERNEL);
if(!buffer)
return -ENOMEM;
//将数据拷贝到buffer中
memcpy(buffer, data, lenght);
//初始化spi_message
spi_message_init(&message);
//复位spi_transfer
memset(&transfer, 0, sizeof(transfer));
//发送缓存
transfer.tx_buf = buffer;
//接收缓存
transfer.rx_buf = NULL;
//传输的长度
transfer.len = lenght;
//将spi_transfer添加到spi_message队列
spi_message_add_tail(&transfer, &message);
//同步传输
result = spi_sync(oled->spi, &message);
//释放发送缓存
kfree(buffer);
return result;
}
//向OLED屏幕发送命令
static int oled_write_cmd(struct oled_handle *oled, uint8_t *command, uint32_t lenght)
{
//拉低数据命令选择引脚,表示发送命令
dc_low(oled);
//通过SPI发送数据
return oled_write(oled, command, lenght);
}
//向OLED屏幕发送数据
static int oled_write_data(struct oled_handle *oled, uint8_t *data, uint32_t lenght)
{
//拉高数据命令选择引脚,表示发送数据
dc_high(oled);
//通过SPI发送数据
return oled_write(oled, data, lenght);
}
//将oled_buffer中的数据显示在OLED屏幕上
static int oled_update(struct oled_handle *oled)
{
int result;
uint8_t command[3];
//设置地址
command[0] = 0xB0 + 0; //设置页地址
command[1] = 0x10 + 0; //设置显示位置—列高地址高4位
command[2] = 0x00 + 0; //设置显示位置—列低地址低4位
result = oled_write_cmd(oled, command, 3);
if(result != 0)
return result;
//发送显示数据
result = oled_write_data(oled, oled->oled_buffer[0], 128*8);
if(result != 0)
return result;
return 0;
}
//初始化OLED屏幕
static int oled_init(struct oled_handle *oled)
{
int result;
uint8_t command[28];
//复位OLED
oled_reset(oled);
//发送初始化命令
command[0 ] = 0xAE; //--turn off oled panel
command[1 ] = 0x00; //---set low column address
command[2 ] = 0x10; //---set high column address
command[3 ] = 0x40; //--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
command[4 ] = 0x81; //--set contrast control register
command[5 ] = 0xCF; // Set SEG Output Current Brightness
command[6 ] = 0xA1; //--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
command[7 ] = 0xC8; //Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
command[8 ] = 0xA6; //--set normal display
command[9 ] = 0xA8; //--set multiplex ratio(1 to 64)
command[10] = 0x3F; //--1/64 duty
command[11] = 0xD3; //-set display offset Shift Mapping RAM Counter (0x00~0x3F)
command[12] = 0x00; //-not offset
command[13] = 0xD5; //--set display clock divide ratio/oscillator frequency
command[14] = 0x80; //--set divide ratio, Set Clock as 100 Frames/Sec
command[15] = 0xD9; //--set pre-charge period
command[16] = 0xF1; //Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
command[17] = 0xDA; //--set com pins hardware configuration
command[18] = 0x12;
command[19] = 0xDB; //--set vcomh
command[20] = 0x40; //Set VCOM Deselect Level
command[21] = 0x20; //-Set Addressing Mode (0x00/0x01/0x02)
command[22] = 0x00; //
command[23] = 0x8D; //--set Charge Pump enable/disable
command[24] = 0x14; //--set(0x10) disable
command[25] = 0xA4; // Disable Entire Display On (0xa4/0xa5)
command[26] = 0xA6; // Disable Inverse Display On (0xa6/a7)
command[27] = 0xAF; //--turn on oled panel
result = oled_write_cmd(oled, command, 28);
if(result != 0)
return result;
//更新OLED显示
return oled_update(oled);
}
//将缓冲帧中的像素转换成OLED格式
static void convert_fb_to_oled(struct oled_handle *oled)
{
int i, j, k;
//一共8*8行,其中每8行1byte
for(i=0; i<8; i++) {
//一个128列
for(j=0; j<16; j++) {
for(k=0; k<8; k++) {
oled->oled_buffer[i][j*8+k] = (((oled->fb_buffer[i*8+0][j] >> k) & 0x01) << 0) |
(((oled->fb_buffer[i*8+1][j] >> k) & 0x01) << 1) |
(((oled->fb_buffer[i*8+2][j] >> k) & 0x01) << 2) |
(((oled->fb_buffer[i*8+3][j] >> k) & 0x01) << 3) |
(((oled->fb_buffer[i*8+4][j] >> k) & 0x01) << 4) |
(((oled->fb_buffer[i*8+5][j] >> k) & 0x01) << 5) |
(((oled->fb_buffer[i*8+6][j] >> k) & 0x01) << 6) |
(((oled->fb_buffer[i*8+7][j] >> k) & 0x01) << 7);
}
}
}
}
//内核线程,用于周期性刷新OLED显示屏
static int oled_thread(void *arg)
{
struct oled_handle *oled;
oled = (struct oled_handle*)arg;
while(!kthread_should_stop())
{
//缓冲帧内容改变才刷新OLED
if(memcmp(oled->old_fb_buffer, oled->fb_buffer, OLED_DISPLAY_RAM_SIZE))
{
//应用层可能正在进行写操作,这里延时休眠600~700us等待应用层写完
usleep_range(600, 700);
//显存格式转换
convert_fb_to_oled(oled);
//记录缓冲帧状态
memcpy(oled->old_fb_buffer, oled->fb_buffer, OLED_DISPLAY_RAM_SIZE);
//更新显示
oled_update(oled);
}
else
{
//休眠
msleep_interruptible(2);
}
}
return 0;
}
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
static int oled_setcolreg(unsigned regno, unsigned red,
unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
unsigned int val;
unsigned int *pseudo_palette;
if (regno >= 16)
return -EINVAL;
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
pseudo_palette = info->pseudo_palette;
pseudo_palette[regno] = val;
return 0;
}
//缓冲帧操作函数集合
static struct fb_ops oled_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = oled_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static int oled_probe(struct spi_device *spi)
{
int result;
struct oled_handle *oled;
printk("%s\r\n", __FUNCTION__);
//设置SPI设备的DMA寻址范围,不然dma_alloc会执行失败
spi->dev.coherent_dma_mask = DMA_BIT_MASK(32);
//分配OLED句柄
oled = devm_kmalloc(&spi->dev, sizeof(struct oled_handle), GFP_KERNEL);
if(!oled)
{
printk("alloc oled_buffer failed\r\n");
return -ENOMEM;
}
memset(oled, 0x00, sizeof(struct oled_handle));
//分配oled缓存,缓冲帧中的数据经过格式转换后拷贝到oled_buffer中,然后在显示到屏幕
oled->oled_buffer = devm_kmalloc(&spi->dev, OLED_DISPLAY_RAM_SIZE, GFP_KERNEL);
if(!oled->oled_buffer)
{
printk("alloc oled_buffer failed\r\n");
return -ENOMEM;
}
memset(oled->oled_buffer, 0x00, OLED_DISPLAY_RAM_SIZE);
//分配old_fb缓存,用于存储上一次更新显示器时缓冲帧中的状态
oled->old_fb_buffer = devm_kmalloc(&spi->dev, OLED_DISPLAY_RAM_SIZE, GFP_KERNEL);
if(!oled->old_fb_buffer)
{
printk("alloc old_fb_buffer failed\r\n");
return -ENOMEM;
}
memset(oled->old_fb_buffer, 0x00, OLED_DISPLAY_RAM_SIZE);
//分配缓冲帧
oled->fb_buffer = dma_alloc_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, &oled->phy_addr, GFP_KERNEL);
if(!oled->fb_buffer)
{
printk("alloc fb_buffer failed\r\n");
return -ENOMEM;
}
memset(oled->fb_buffer, 0x00, OLED_DISPLAY_RAM_SIZE);
//设置SPI设备的驱动私有数据
spi->dev.driver_data = (void*)oled;
//给oled句柄绑定SPI设备
oled->spi = spi;
//设置SPI模式,也可以在设备树中进行配置
/*MODE3(CPOL=1,CPHA=1)*/
oled->spi->mode = SPI_MODE_3;
spi_setup(oled->spi);
//初始化OLED的GPIO
result = devm_pin_init(oled);
if(result < 0)
{
dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
printk("init gpio failed\r\n");
return result;
}
//初始化OLED屏幕
result = oled_init(oled);
if(result < 0)
{
dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
printk("oled_init failed\r\n");
return result;
}
//分配缓冲帧句柄
oled->fb = framebuffer_alloc(0, &spi->dev);
if(!oled->fb)
{
dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
printk("alloc fb failed\r\n");
return -ENOMEM;
}
//设置fb
//显存虚拟地址和大小
oled->fb->screen_base = (char*)oled->fb_buffer;
oled->fb->screen_size = OLED_DISPLAY_RAM_SIZE;
//LCD分辨率、颜色格式
oled->fb->var.xres = 128;
oled->fb->var.yres = 64;
oled->fb->var.xres_virtual = 128;
oled->fb->var.yres_virtual = 64;
oled->fb->var.bits_per_pixel = 1;
//ID
strcpy(oled->fb->fix.id, "atk,oled");
//显存大小和物理地址
oled->fb->fix.smem_len = OLED_DISPLAY_RAM_SIZE;
oled->fb->fix.smem_start = oled->phy_addr;
//一行的显存长度
oled->fb->fix.line_length = 16;
//显示器类型
oled->fb->fix.type = FB_TYPE_PACKED_PIXELS;
//像素格式
oled->fb->fix.visual = FB_VISUAL_MONO10;
//底层操作函数集合
oled->fb->fbops = &oled_ops;
//颜色表
oled->fb->pseudo_palette = oled->pseudo_palette;
//注册缓冲帧驱动
result = register_framebuffer(oled->fb);
if(result < 0)
{
framebuffer_release(oled->fb);
dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
printk("register fb failed\r\n");
return result;
}
//创建内核线程,更新OLED
oled->kthread = kthread_create(oled_thread, (void*)oled, "oled_thread%d,%d", oled->spi->controller->bus_num, oled->spi->chip_select);
if(IS_ERR(oled->kthread))
{
unregister_framebuffer(oled->fb);
framebuffer_release(oled->fb);
dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
printk("create oled_thread failed\r\n");
return PTR_ERR(oled->kthread);
}
wake_up_process(oled->kthread);
return 0;
}
//设备或驱动卸载时执行
static int oled_remove(struct spi_device *spi)
{
struct oled_handle *oled;
printk("%s\r\n", __FUNCTION__);
oled = (struct oled_handle*)spi->dev.driver_data;
if(!oled)
{
printk("verification failed\r\n");
return -EINVAL;
}
//停止内核线程
kthread_stop(oled->kthread);
//注销缓冲帧驱动
unregister_framebuffer(oled->fb);
//释放缓冲帧句柄
framebuffer_release(oled->fb);
//释放缓冲帧
dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
return 0;
}
//匹配列表,用于设备树和平台驱动匹配
static const struct of_device_id oled_of_match[] = {
{.compatible = "atk,oled"},
{ /* Sentinel */}
};
//传统匹配方式ID列表
static const struct spi_device_id oled_id[] = {
{}
};
//SPI驱动
static struct spi_driver oled_drv = {
.driver = {
.name = "oled",
.owner = THIS_MODULE,
.pm = NULL,
.of_match_table = oled_of_match,
},
.id_table = oled_id,
.probe = oled_probe,
.remove = oled_remove,
};
static int __init oled_drv_init(void)
{
int result = 0;
printk("%s\r\n", __FUNCTION__);
//注册SPI设备驱动
result = spi_register_driver(&oled_drv);
if(result < 0)
{
printk("add cdev failed\r\n");
return result;
}
return 0;
}
static void __exit oled_drv_exit(void)
{
printk("%s\r\n", __FUNCTION__);
//注销SPI驱动
spi_unregister_driver(&oled_drv);
}
module_init(oled_drv_init);
module_exit(oled_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("CSDN");
MODULE_DESCRIPTION("oled_dev");
OLED应用程序开发步骤如下:
#include <unistd.h>
#include "oled_lib.h"
int main(int argc, char *argv[])
{
if(argc < 2)
{
printf("Error Usage!\r\n");
return -1;
}
oled_init(argv[1]);
while(1)
{
oled_clear();
usleep(100*1000);
display_line(0, 0, 127, 63);
usleep(100*1000);
display_line(0, 63, 127, 31);
usleep(100*1000);
display_rect(55, 5, 50, 20);
sleep(1);
}
return 0;
}