在之前Mini2440裸机开发之SPI(OLED SSD1306)中我们介绍了关于OLED SSD1306相关的知识,这里我们将会学习以内核驱动的方式去控制OLED。
一、OLED128x64(SSD1306)
1.1 引脚说明
当SSD1306选定4-wire serial interface接口方式,SPI引脚定义:
- CS:片选信号;连接S3C2440的GPG3引脚;
- DC::命令数据选择引脚;连接S3C2440的GPG10引脚;
- RES:模块复位引脚,低电平有效;连接S3C2440的GPG9引脚;
- D1:MOSI,SPI数据线,主设备输出从设备输入引脚;连接S3C2440的GPG6引脚;
- D0:SCLK,SPI时钟线;连接S3C2440的GPG7引脚;
- VCC:电源正极3.3~5V;
- GND:电源地;
上一节在SPI控制器驱动中我们配置片选引脚为GPG3,除了MOSI、MISO、片选引脚以外,我们还需要将GPG10了解到OLED的DC引脚上。Mini240 GPIO资源如下:
17 | EINT9 | EINT9/GPG1 | 18 | EINT11 | EIT11/GPG3/nSS1 |
19 | EINT13 | EINT13/GPG5/SPIMISO1 | 20 | EINT14 | EINT14/GPG6/SPIMOSI1 |
21 | EINT15 | EINT15/GPG7/SPICLK1 | 22 | EINT17 | EINT17/GPG9/nRST1 |
23 | EINT18 | EINT18/GPG10/nCTS1 | 24 | EINT19 | ENIT19/GPG11 |
1.2 spi_write
我们通过spi_write进行数据的传输:
/************************************************************* * * Function : 通过SPI协议写数据 * Input : buf 发送缓冲区 * len 发送缓冲区长度 * Return : 写入字节长度 负数写入失败 * **************************************************************/ static int oled_spi_write(unsigned char *buf, uint16_t len) { int status; #if 0 struct spi_message msg; struct spi_transfer xfer = { .len = len, .tx_buf = buf, }; spi_message_init(&msg); /* 初始化spi_message */ spi_message_add_tail(&xfer, &msg); /* 添加到传输队列 */ status = spi_sync(oled_dev->spi, &msg); /* 同步发送 */ #else status = spi_write(oled_dev->spi, buf, len); #endif return status; }
1.3 写命令
SPI通信时,写命令需要将DC设置为低电平,然后发送一个字节的命令即可;
/************************************************************* * * Function : 通过SPI协议写命令 * Input : cmd 命令 * Return : 写入字节长度 负数写入失败 * **************************************************************/ static int oled_write_cmd(unsigned char cmd) { gpio_set_value(oled_dev->dc_gpio, OLED_CMD); /* 拉低,表示写入的是指令 */ if(oled_spi_write(&cmd, 1) == 1) return 1; else return ERROR; }
1.3 写数据
SPI通信时,写数据需要将DC设置为高电平,然后发送一个字节的数据即可;
/************************************************************* * * Function : 通过SPI协议写数据 * Input : data 数据 * Return : 写入字节长度 负数写入失败 * **************************************************************/ static int oled_write_data(unsigned char data) { gpio_set_value(oled_dev->dc_gpio, OLED_DATA); /* 拉高,表示写入的是数据 */ if(oled_spi_write(&data, 1) == 1) return 1; else return ERROR; }
1.5 其它
针对OLED的其他操作,比如清屏、初始化、设置坐标等,这里也就不重复介绍了。
二、OLED设备驱动
2.1 项目结构
我们在/work/sambashare/drivers路径下创建项目22.oled_dev。
2.2 SPI驱动编写流程
2.2.1 模块入口函数
声明spi_driver结构体变量:调用spi_register_driver注册SPI驱动;
- 设置成员probe:当驱动和设备信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中;
- 设置成员remove:设备被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中;
- 设置成员driver:设置驱动名称、owner等;
2.2.2 模块出口函数
调用spi_unregister_driver卸载SPI驱动;
2.2.3 probe函数
一般就是字符设备的注册流程:
- 从设备树加载资源,设置命令数据选择、复位引脚;(演示代码并没有使用设备树)
- 动态分配字符设备编号;
- 字符设备初始化以及注册;
- 创建类、以及在/dev路径下生成设备节点;
- 保存当前spi_device;
2.2.4 remove函数
一般就是字符设备的卸载流程:
- 注销类、以及类下设备;
- 移除字符设备;
- 注销设备编号;
2.2.5 字符设备读写等函数
由于我们为SPI从设备注册了字符设备驱动,这样我们通过对字符设备节点读写,就可实现对硬件的读写操作。
我们需要实现字符设备的操作集合。这里由于我们的设备是OLED,也就是对各种SPI设备的写操作,这一部分是通过控制SPI适配器来实现的,我们按照芯片写指令时序进行SPI传输控制即可。
当然如果我们想对OLED设备进行一些其它的控制,比如清屏、设置当前坐标,则可以使用ioctl函数来实现。具体可以参考Linux驱动之字符设备ioctl接口使用。
2.3 SPI从设备注册流程
- 定义板级SPI从设备信息;
- 在注册SPI控制器之前调用spi_register_board_info进行SPI从设备的注册;
2.4 oled_drv.c
#include <linux/init.h> #include <linux/module.h> #include <linux/stat.h> #include <linux/gpio.h> #include <linux/device.h> #include <linux/kdev_t.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/spi/spi.h> #include <linux/uaccess.h> #include <linux/slab.h> #include <mach/gpio-samsung.h> /* 全局静态变量:希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在 定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用 */ #define OK (0) #define ERROR (-1) /* 写命令、数据 */ enum { OLED_CMD = 0x00, OLED_DATA = 0x01, }; /* oled设备 */ struct oled_device { int dc_gpio; /* 命令/数据引脚 */ int rst_gpio; /* 重置引脚*/ struct class *oled_class; /* 设备class */ dev_t devid; /* 设备编号 */ struct cdev spi_cdev; /* oled字符设备 */ struct spi_device *spi; /* spi 从设备 */ }; static struct oled_device * oled_dev; /***************************************************************************************************** * * 从ASCII0x20开始 取模方向:逐列式 每列1个字节,共6列 即一个字节占8行6列 * 取模走向:低位在前 * *****************************************************************************************************/ static const u8 ASCII6x8[][6] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // sp ASCII 0x20 { 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 }, // ! { 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 }, // " { 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 }, // # { 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 }, // $ { 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 }, // % { 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 }, // & { 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 }, // ' { 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 }, // ( { 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 }, // ) { 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 }, // * { 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 }, // + { 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 }, // , { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 }, // - { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, // . { 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 }, // / { 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0 { 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1 { 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2 { 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3 { 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4 { 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5 { 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6 { 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7 { 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8 { 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9 { 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 }, // : { 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 }, // ; { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, // < { 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 }, // = { 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 }, // > { 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 }, // ? { 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E }, // @ { 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C }, // A { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C { 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F { 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A }, // G { 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H { 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 }, // I { 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J { 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K { 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L { 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M { 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 }, // P { 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q { 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R { 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 }, // S { 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T { 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U { 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V { 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F }, // W { 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 }, // X { 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 }, // Y { 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z { 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 }, // [ { 0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 }, // 55 { 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 }, // ] { 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^ { 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 }, // _ { 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 }, // ' { 0x00, 0x20, 0x54, 0x54, 0x54, 0x78 }, // a { 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 }, // b { 0x00, 0x38, 0x44, 0x44, 0x44, 0x20 }, // c { 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F }, // d { 0x00, 0x38, 0x54, 0x54, 0x54, 0x18 }, // e { 0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 }, // f { 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C }, // g { 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h { 0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 }, // i { 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 }, // j { 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k { 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 }, // l { 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 }, // n { 0x00, 0x38, 0x44, 0x44, 0x44, 0x38 }, // o { 0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 }, // p { 0x00, 0x18, 0x24, 0x24, 0x18, 0xFC }, // q { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 }, // r { 0x00, 0x48, 0x54, 0x54, 0x54, 0x20 }, // s { 0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 }, // t { 0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C }, // u { 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v { 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w { 0x00, 0x44, 0x28, 0x10, 0x28, 0x44 }, // x { 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C }, // y { 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z { 0x14, 0x14, 0x14, 0x14, 0x14, 0x14 } // horiz lines }; /************************************************************* * * Function : 通过SPI协议写数据 * Input : buf 发送缓冲区 * len 发送缓冲区长度 * Return : 写入字节长度 负数写入失败 * **************************************************************/ static int oled_spi_write(unsigned char *buf, uint16_t len) { int status; #if 0 struct spi_message msg; struct spi_transfer xfer = { .len = len, .tx_buf = buf, }; spi_message_init(&msg); /* 初始化spi_message */ spi_message_add_tail(&xfer, &msg); /* 添加到传输队列 */ status = spi_sync(oled_dev->spi, &msg); /* 同步发送 */ #else status = spi_write(oled_dev->spi, buf, len); #endif return status; } /************************************************************* * * Function : 通过SPI协议写命令 * Input : cmd 命令 * Return : 写入字节长度 负数写入失败 * **************************************************************/ static int oled_write_cmd(unsigned char cmd) { gpio_set_value(oled_dev->dc_gpio, OLED_CMD); /* 拉低,表示写入的是指令 */ if(oled_spi_write(&cmd, 1) == 1) return 1; else return ERROR; } /************************************************************* * * Function : 通过SPI协议写数据 * Input : data 数据 * Return : 写入字节长度 负数写入失败 * **************************************************************/ static int oled_write_data(unsigned char data) { gpio_set_value(oled_dev->dc_gpio, OLED_DATA); /* 拉高,表示写入的是数据 */ if(oled_spi_write(&data, 1) == 1) return 1; else return ERROR; } /************************************************************* * * Function : oled复位信号 GPG9引脚 * Input : val 0 复位 1 取消复位 * **************************************************************/ void oled_set_res(char val) { if (val) /* 1 取消复位 */ gpio_set_value(oled_dev->rst_gpio, 1); else gpio_set_value(oled_dev->rst_gpio, 0); /* 0 复位 */ } /************************************************************* * * Function : 坐标设定 * Input : x x坐标 0x00~0x7F * y y坐标 0~7 * **************************************************************/ static void oled_pos(u8 x,u8 y) { oled_write_cmd(0xB0+y); oled_write_cmd(((x&0xF0)>>4)|0x10); /* 设置列地址高四位 */ oled_write_cmd(x&0x0F); /* 设置列地址低四位 */ } /************************************************************* * * Function : 清屏 * **************************************************************/ static void oled_clear(void) { u8 x; u8 y; for(y=0;y<8;y++) { oled_write_cmd(0xB0+y); /* 选择页 */ oled_write_cmd(0x00); /* 设置列地址低四位 */ oled_write_cmd(0x10); /* 设置列地址高四位 */ for(x=0;x<0x80;x++) oled_write_data(0x00); /* 每次清1列 */ } } /*************************************************************************************************** * * Function : 写入一组标准ASCII字符串 一个字节占8行6列 * Input : x 设置列地址0~0X7F * y 设置页地址0~7 * str 要显示的字符 * **************************************************************************************************/ static void oled_p6x8str(u8 x,u8 y,u8 *str) { u8 i=0; u8 j=0; u8 k=0; while (str[j]!='