实现了SPI OLED外设驱动,OLED型号为SH1106。
1.主机驱动与外设驱动分离
Linux中的I2C、SPI、USB等总线驱动,都采用了主机(控制器)驱动与外设(设备)驱动分离的思想。主机端只负责实现总线协议相关的逻辑,总线上传输的数据主机并不关心,如主机的i2c控制器只负责实现i2c总线协议相关内容,如i2c起始结束信号、i2c应答信号、i2c时钟、发送和接收数据等,至于i2c总线上传输的数据,i2c控制器并不关心。外设驱动关心的是如何访问挂在总线上的设备,即总线传输的数据要符合外设的规定。这样才能被外设正确识别,如i2c从设备ap3216c驱动,需要根据ap3216c的寄存器等信息,设置和读取数据。主机和外设驱动在物理是分开的,但逻辑上是密切相关的,外设驱动要借助主机驱动提供的基础设施(API和数据结构)在总线上发起数据传输,以便访问自身。主机和外设驱动涉及了4个软件模块:
(1)主机端的驱动。根据具体的I2C、SPI、USB等控制器的硬件特性,操作具体的主机控制器,在总线上产生符合总线协议的时序。
(2)连接主机和外设的纽带。外设不直接操作主机端的驱动来访问外设,而是调用一个标准的API,由这个标准的API把请求转发给具体的主机端驱动。
(3)外设端的驱动。外设接在I2C、SPI、USB等总线上,但外设可以是触摸屏、网卡、声卡等任意类型的设备,这就需要编写具体的外设驱动,通过主机端的驱动来访问外设。即利用具体总线提供的xxx_driver
结构体中probe
函数注册具体的外设驱动类型,访问外设通过具体总线提供的标准API。
(4)板级逻辑。板级逻辑描述主机和外设是如何连接的,通俗的说就是什么总线上挂了什么设备,这部分信息需要在设备树中进行描述。
2.SPI驱动框架
2.1.主机端驱动-SPI控制器驱动
在Linux中,使用struct spi_master
结构体来描述SPI主机控制器驱动。主要成员有主机控制器的序号bus_num
、片选数量num_chipselect
、SPI模式mode_bits
及数据传输函数transfer
和transfer_one_message
等。transfer
和transfer_one_message
用于SPI控制器和SPI外设之间传输数据。SPI主机端驱动已经由SOC厂家实现,imx6ull SPI驱动文件路径为drivers/spi/spi-imx.c
,后续进行分析。
include <linux/spi/spi.h>
struct spi_master {
struct device dev;
struct list_head list;
// SOC上的SPI总线数量,即SPI控制器的数量
s16 bus_num;
u16 num_chipselect;
u16 mode_bits;
/* bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))
/* limits on transfer speed */
u32 min_speed_hz;
u32 max_speed_hz;
/* other constraints relevant to this driver */
u16 flags;
#define SPI_MASTER_HALF_DUPLEX BIT(0) /* can't do full duplex */
#define SPI_MASTER_NO_RX BIT(1) /* can't do buffer read */
#define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */
#define SPI_MASTER_MUST_RX BIT(3) /* requires rx */
#define SPI_MASTER_MUST_TX BIT(4) /* requires tx */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
bool bus_lock_flag;
int (*setup)(struct spi_device *spi);
// SPI控制器数据传输函数
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
// 和spi工作线程相关的定义,imx6ul默认spi传输数据时使用内核线程
bool queued;
struct kthread_worker kworker; // 管理内核线程,可以有多个线程工作在kworker上
struct task_struct *kworker_task;
struct kthread_work pump_messages; // 具体工作,由kworker管理的线程处理
spinlock_t queue_lock;
struct list_head queue;
struct spi_message *cur_msg;
int (*prepare_transfer_hardware)(struct spi_master *master);
// SPI控制器数据传输函数,一次传输一个spi_message信息
int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
int (*unprepare_transfer_hardware)(struct spi_master *master);
int (*prepare_message)(struct spi_master *master,struct spi_message *message);
int (*unprepare_message)(struct spi_master *master,struct spi_message *message);
void (*set_cs)(struct spi_device *spi, bool enable);
int (*transfer_one)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer);
void (*handle_err)(struct spi_master *master, struct spi_message *message);
};
2.2.连接主机驱动和外设驱动的纽带-SPI核心层
SPI核心层提供了SPI主机控制器驱动及SPI外设驱动注册、注销等API。
spi_alloc_master
用于分配一个struct spi_master
结构体,dev
为SPI控制器设备结构体指针,size
为额外分配内存空间的字节数,dev
结构体driver_data
指针指向额外的空间,返回值为NULL
表示失败,反之则表示成功。devm_spi_register_master
用于注册SPI主机控制器,dev
为设备结构体指针,master
为SPI主机控制器结构体指针,返回值为0表示注册成功,返回值为负值表示注册失败。spi_register_master
和spi_unregister_master
用于注册和注销SPI主机控制器。
include <linux/spi/spi.h>
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
int devm_spi_register_master(struct device *dev, struct spi_master *master)
int spi_register_master(struct spi_master *master)
void spi_unregister_master(struct spi_master *master)
使用spi_register_driver
和spi_unregister_driver
注册、注销SPI外设驱动。sdrv
为SPI外设驱动结构体指针。编写驱动的主要工作是实现SPI外设驱动。
include <linux/spi/spi.h>
int spi_register_driver(struct spi_driver *sdrv)
void spi_unregister_driver(struct spi_driver *sdrv)
// 完成module_init和module_exit的功能
#define module_spi_driver(__spi_driver) module_driver(__spi_driver, spi_register_driver,spi_unregister_driver)
2.3.外设端驱动-SPI外设驱动
Linux使用struct spi_driver
结构体描述SPI外设驱动。可以看出spi_driver
和platform_driver
结构体很相似,都有probe()
、remove()
等函数。当SPI设备和驱动匹配成功后,probe
函数就会执行。
include <linux/spi/spi.h>
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
在SPI外设驱动中,SPI总线数据传输时使用了一套与CPU无关的统一接口。接口的关键数据结构是struct spi_transfer
,用于描述SPI总线传输的数据,类似于struct i2c_msg
结构体,都是将传输的数据进行封装。在一次完整的SPI数据传输过程中,包含一个或多个spi_transfer
,spi_message
将多个spi_transfer
以双向链表的形式组织在一起。
struct spi_transfer {
const void *tx_buf;
void *rx_buf;
unsigned len;
dma_addr_t tx_dma;
dma_addr_t rx_dma;
struct sg_table tx_sg;
struct sg_table rx_sg;
unsigned cs_change:1;
unsigned tx_nbits:3;
unsigned rx_nbits:3;
#define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
u8 bits_per_word;
u16 delay_usecs;
u32 speed_hz;
struct list_head transfer_list;
};
struct spi_message {
struct list_head transfers;
struct spi_device *spi;
unsigned is_dma_mapped:1;
/* completion is reported through a callback */
void (*complete)(void *context);
void *context;
unsigned frame_length;
unsigned actual_length;
int status;
/* for optional use by whatever driver currently owns the
* spi_message ... between calls to spi_async and then later
* complete(), that's the spi_master controller driver.
*/
struct list_head queue;
void *state;
};
// 初始化spi_message结构体
void spi_message_init(struct spi_message *m)
// 将多个spi_transfe组织成spi_message形式,实质上是向链表中添加元素
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
// 删除spi_transfer中的spi_message
void spi_transfer_del(struct spi_transfer *t)
// 初始化spi_message,并将spi_message追加到spi_transfer的双向链表中
void spi_message_init_with_transfers(struct spi_message *m, struct spi_transfer *xfers, unsigned int num_xfers)
// 发起一次同步SPI数据传输,调用会被阻塞,直到数据传输完成或者出错
int spi_sync(struct spi_device *spi, struct spi_message *message)
// 发起一次异步SPI数据传输,调用不会被阻塞,总线被占用或者spi_message提交完成后返回,可以在spi_message的complete字段挂接一个回调函数,当消息处理完后会调用此回调函数
int spi_async(struct spi_device *spi, struct spi_message *message)
// 通用的SPI同步写入函数
int spi_write(struct spi_device *spi, const void *buf, size_t len)
// 通用的SPI同步读取函数
int spi_read(struct spi_device *spi, void *buf, size_t len)
// 同步SPI传输函数,可包含读写操作
int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers)
2.4.SPI设备树设备节点
引脚定义:
GPIO3_IO28 LCD_DATA23 MISO
GPIO3_IO27 LCD_DATA22 MOSI SDA
GPIO3_IO17 LCD_DATA12 RDY
GPIO3_IO25 LCD_DATA20 SCLK SCL
GPIO3_IO26 LCD_DATA21 SS0 D/C
GPIO3_IO10 LCD_DATA05 SS1
GPIO3_IO11 LCD_DATA06 SS2
GPIO3_IO12 LCD_DATA07 SS3 RST
// SPI pinctrl
pinctrl_spi1: spi1 {
fsl,pins = <
MX6UL_PAD_LCD_DATA23__ECSPI1_MISO 0x10b1
MX6UL_PAD_LCD_DATA22__ECSPI1_MOSI 0x10b1 // SDA
MX6UL_PAD_LCD_DATA20__ECSPI1_SCLK 0x10b1 // SCL
MX6UL_PAD_LCD_DATA21__GPIO3_IO26 0x10b0 // D/C
MX6UL_PAD_LCD_DATA07__GPIO3_IO12 0x10b0 // RST
>;
};
&ecspi1 {
// 片选数量
fsl,spi-num-chipselects = <1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi1>;
status = "okay";
// oled连接到spi1的第0个通道上
oled: ssh1106@0 {
compatible = "oled, ssh1106";
rst-gpio = <&gpio3 12 GPIO_ACTIVE_LOW>; // 低电平复位
dc-gpio = <&gpio3 26 GPIO_ACTIVE_LOW>; // 低电平表示命令,高电平表示数据
spi-max-frequency = <4000000>; // 最大频率4M
reg = <0>;
};
};
3.imx6ull spi主机控制器驱动
// 兼容属性
static const struct of_device_id spi_imx_dt_ids[] = {
{ .compatible = "fsl,imx1-cspi", .data = &imx1_cspi_devtype_data, },
{ .compatible = "fsl,imx21-cspi", .data = &imx21_cspi_devtype_data, },
{ .compatible = "fsl,imx27-cspi", .data = &imx27_cspi_devtype_data, },
{ .compatible = "fsl,imx31-cspi", .data = &imx31_cspi_devtype_data, },
{ .compatible = "fsl,imx35-cspi", .data = &imx35_cspi_devtype_data, },
{ .compatible = "fsl,imx51-ecspi", .data = &imx51_ecspi_devtype_data, },
{ .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, }, // 支持imx6ull的spi控制器
{ /* sentinel */ }
};
static struct platform_driver spi_imx_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = spi_imx_dt_ids, // 设备树匹配表
.pm = IMX_SPI_PM,
},
.id_table = spi_imx_devtype, // 传统的匹配表
.probe = spi_imx_probe, // probe函数
.remove = spi_imx_remove, // remove函数
};
// 注册平台驱动
module_platform_driver(spi_imx_driver);
// module_platform_driver完成了平台驱动的注册和注销
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
平台驱动的probe函数是最重要的,下面分析probe函数。
spi_imx_probe
->of_property_read_u32 // 读取fsl,spi-num-chipselects属性,即片选数量
->spi_alloc_master // 分配spi_master结构体
->of_get_named_gpio // 获取spi cs 引脚
->devm_gpio_request // 请求gpio
->spi_imx->bitbang.setup_transfer = spi_imx_setupxfer; // spi配置函数,传输数据时会调用此函数
->spi_imx->bitbang.txrx_bufs = spi_imx_transfer // 注册spi数据传输函数,spi设备传输数据最终调用此函数
->init_completion // 初始化等待完成队列
->platform_get_resource // 获取地址信息
->devm_ioremap_resource // 映射地址
->platform_get_irq // 获取虚拟中断号
->devm_request_irq // 注册中断,中断服务函数为spi_imx_isr
->devm_clk_get // 获取ipg per时钟
->clk_prepare_enable // 使能时钟
->spi_imx->devtype_data->reset(spi_imx) // 将spi接收FIFO中的数据读完
->spi_imx->devtype_data->intctrl(spi_imx, 0) // 关闭所有中断
->spi_bitbang_start
->spi_register_master // 注册spi主机驱动
->of_spi_register_master
->of_gpio_named_count // 获取spi片选引脚数量
->of_get_named_gpio // 获取spi片选gpio编号
->dev_set_name // 设置spi主机控制器设备名字
->device_add // 注册设备
->spi_master_initialize_queue // 初始化spi工作队列
->master->transfer = spi_queued_transfer // 设置spi工作队列传输函数
->master->transfer_one_message = spi_transfer_one_message // 设置transfer_one_message函数
->spi_init_queue
->init_kthread_worker // 初始化管理内核线程的kworker
->kthread_run // 运行内核线程,执行kthread_worker_fn函数,循环处理pump_messages
// 初始化pump_messages,设置内核线程处理spi messages的函数为spi_pump_messages,
// 所有的spi数据传输都在此函数中完成
->init_kthread_work
->spi_start_queue
->master->running = true; // 设置spi内核线程开始运行标记
->queue_kthread_work // 将spi工作pump_messages添加到创建的内核线程工作队列中
->of_register_spi_devices // 注册挂载在spi总线的设备,遍历spi设备树节点下的设备
->acpi_register_spi_devices // 注册电源相关设备
->clk_disable_unprepare // 关闭ipg时钟
->clk_disable_unprepare // 关闭per时钟
分析probe函数可知,imx6ul启用了内核线程来处理spi的工作任务,内核线程调用spi_pump_messages
函数进行spi数据的传输。下面分析spi_pump_messages
函数。
spi_pump_messages
->container_of // 获取spi_master结构体指针
->__spi_pump_messages(master, true) // spi_sync也会调用此函数,但其第二个参数为false
->master->prepare_message() // 使能时钟,真正调用的是spi_imx_prepare_message
->master->transfer_one_message() // 调用此函数传输数据,真正调用的是spi_bitbang_transfer_one
->list_for_each_entry // 遍历spi_message,传输所有数据
->bitbang->setup_transfer() // 配置spi数据传输环境,最终调用spi_imx_setupxfer
->spi_imx_setupxfer() // 配置时钟、极性、相位,根据位宽选择收发函数
->spi_imx->rx = spi_imx_buf_rx_u8 // 设置发送函数,以8位为例
->spi_imx->tx = spi_imx_buf_tx_u8; // 设置接收函数,以8位为例
->spi_imx->devtype_data->config() // 配置
->mx51_ecspi_config() // 最终调用此函数进行配置
->bitbang->chipselect() // 片选,真正调用的是spi_imx_chipselect
->bitbang->txrx_bufs() // 调用spi_imx_transfer
->spi_imx_transfer()
->spi_imx_pio_transfer
->spi_imx_push
->spi_imx->tx // 调用spi_imx_buf_tx_u8发送数据
->spi_imx->devtype_data->trigger // 发送完调用mx51_ecspi_trigger函数启动burst传输
->spi_imx->devtype_data->intctrl
->mx51_ecspi_intctrl // 使能spi发送fifo空中断
->wait_for_completion // 等待发送完成
启动传输后,剩下数据的收发在中断中,中断函数为spi_imx_isr
,下面分析中断函数:
spi_imx_isr
->spi_imx->devtype_data->rx_available() // 是否有数据接收,真正调用的是mx51_ecspi_rx_available
->spi_imx->rx() // 有数据就调用接收函数接收数据
->spi_imx_push() // 如果还有数据要发送,调用发送函数发送
->spi_imx->tx // 调用spi_imx_buf_tx_u8发送数据
->spi_imx->devtype_data->intctrl() // 如果还有数据要接收,使能接收中断
->spi_imx->devtype_data->intctrl() // 无数据接收和发送,关闭中断
->complete // 数据传输完成,发送信号,唤醒等待的线程
4.SPI设备驱动源码
/*===========================spi_sh1106.h================================*/
#ifndef __SPI_SH1106_H__
#define __SPI_SH1106_H__
#include <linux/spi/spi.h>
#include <linux/cdev.h>
#include <linux/of.h> // 设备树
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <uapi/linux/ioctl.h>
#define NAME "spi_sh1106"
#define DEV_NUM (1)
#define CMD (0x0)
#define DATA (0x1)
struct sh1106_cdev
{
struct cdev cdev;
dev_t devno;
struct class* sh1106_class;
struct device* sh1106_device;
struct device_node* node;
int rst_pin; // 复位引脚
int dc_pin; // 数据命令控制引脚
struct mutex mutex;
struct spi_device* spi;
};
#endif // __SPI_SH1106_H__
/*===========================spi_sh1106.c================================*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include "spi_sh1106.h"
static struct sh1106_cdev* sh1106 = NULL;
static int sh1106_open(struct inode* inode, struct file* filp)
{
struct sh1106_cdev* dev;
dev = container_of(inode->i_cdev, struct sh1106_cdev, cdev);
// 如果是非阻塞打开,尝试获取互斥体,获取失败直接返回,获取成功继续执行
if (filp->f_flags & O_NONBLOCK) {
// mutex_trylock和down_trylock的返回值意义相反
if (!mutex_trylock(&dev->mutex))
return -EBUSY;
}
else mutex_lock(&dev->mutex);
// 复位oled
gpio_set_value(dev->rst_pin, 0);
// 延迟50毫秒
mdelay(50);
gpio_set_value(dev->rst_pin, 1);
filp->private_data = dev;
return 0;
}
static ssize_t sh1106_write(struct file* filp, const char __user* buf,
size_t size, loff_t* ppos)
{
int ret = 0;
struct spi_message m;
struct spi_transfer t;
char buffer[512] = {0};
struct sh1106_cdev* dev = filp->private_data;
if (size > sizeof(buffer)) return -EINVAL;
ret = copy_from_user(buffer, buf, size);
if (ret != 0) return -EFAULT;
#if 0
ret = spi_write(dev->spi, buffer, size);
if (ret != 0) return ret;
#else
memset(&m, 0, sizeof(struct spi_message));
memset(&t, 0, sizeof(struct spi_transfer));
t.tx_buf = buffer; // 发送缓冲区地址
t.len = size; // 发送数据的长度
spi_message_init(&m); // 初始化spi_message
spi_message_add_tail(&t, &m); // 将spi_transfer添加到spi_message
ret = spi_sync(dev->spi, &m); // 同步传输
if (ret != 0) return -EFAULT;
#endif
return size;
}
static int sh1106_release(struct inode* inode, struct file* filp)
{
struct sh1106_cdev* dev;
dev = container_of(inode->i_cdev, struct sh1106_cdev, cdev);
mutex_unlock(&dev->mutex);
return 0;
}
// 控制spi传输的信息对oled来说是命令还是数据
static long sh1106_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
{
struct sh1106_cdev* dev = filp->private_data;
if (cmd == CMD)
gpio_set_value(dev->dc_pin, 0); // spi传输的信息是命令
else if(cmd == DATA)
gpio_set_value(dev->dc_pin, 1); // spi传输的信息是数据
else
return -EINVAL;
return 0;
}
static const struct file_operations sh1106_ops =
{
.owner = THIS_MODULE,
.open = sh1106_open,
//.read = sh1106_read,
.write = sh1106_write,
.unlocked_ioctl = sh1106_ioctl,
.release = sh1106_release
};
// 获取设备树中定义的复位、数据命令GPIO
static int get_gpio(struct sh1106_cdev* dev)
{
int ret = 0;
dev->rst_pin = of_get_named_gpio(dev->node, "rst-gpio", 0);
if (dev->rst_pin < 0) {
dev_err(&dev->spi->dev, "of_get_named_gpio rst-gpio failed, errno %d\n",
dev->rst_pin);
return dev->rst_pin;
}
ret = gpio_is_valid(dev->rst_pin);
if (ret == 0) {
dev_err(&dev->spi->dev, "gpio_is_valid rst-gpio failed, errno %d\n", ret);
return -ENODEV;
}
ret = gpio_request(dev->rst_pin, "rst-gpio");
if (ret < 0) {
dev_err(&dev->spi->dev, "gpio_request rst-gpio failed, errno %d\n", ret);
return ret;
}
// 设置rst引脚输出高点平
gpio_direction_output(dev->rst_pin, 1);
dev->dc_pin = of_get_named_gpio(dev->node, "dc-gpio", 0);
if (dev->dc_pin < 0) {
dev_err(&dev->spi->dev, "of_get_named_gpio dc-gpio failed, errno %d\n", ret);
ret = dev->dc_pin;
goto free_rst;
}
ret = gpio_is_valid(dev->dc_pin);
if (ret == 0) {
dev_err(&dev->spi->dev, "gpio_is_valid dc-gpio failed, errno %d\n", ret);
ret = -ENODEV;
goto free_rst;
}
ret = gpio_request(dev->dc_pin, "dc-gpio");
if (ret < 0) {
dev_err(&dev->spi->dev, "gpio_request dc-gpio failed, errno %d\n", ret);
goto free_rst;
}
// 设置dc引脚输出高点平
gpio_direction_output(dev->dc_pin, 1);
return 0;
free_rst:
gpio_free(dev->rst_pin);
return ret;
}
static int sh1106_probe(struct spi_device* spi)
{
int ret = 0;
sh1106 = kzalloc(sizeof(struct sh1106_cdev), GFP_KERNEL);
if (NULL == sh1106) {
dev_err(&spi->dev, "kzalloc failed\n");
return -ENOMEM;
}
sh1106->spi = spi;
sh1106->node = spi->dev.of_node;
ret = alloc_chrdev_region(&sh1106->devno, 0, DEV_NUM, NAME);
if (ret != 0) {
dev_err(&spi->dev, "alloc_chrdev_region error, errno %d\n", ret);
goto free_sh1106;
}
dev_info(&spi->dev, "device major numbers %d, minor numbers %d\n",
MAJOR(sh1106->devno), MINOR(sh1106->devno));
// 初始化字符设备和注册操作函数结构体
cdev_init(&sh1106->cdev, &sh1106_ops);
sh1106->cdev.owner = THIS_MODULE;
ret = cdev_add(&sh1106->cdev, sh1106->devno, DEV_NUM);
if (ret < 0) {
dev_err(&spi->dev, "cdev_add error, errno %d\n", ret);
goto unregister_devno;
}
// 创建类和设备,便于自动生成设备节点
sh1106->sh1106_class = class_create(THIS_MODULE, NAME);
if (IS_ERR(sh1106->sh1106_class)) {
ret = PTR_ERR(sh1106->sh1106_class);
dev_err(&spi->dev, "class_create failed %d\n", ret);
goto del_cdev;
}
sh1106->sh1106_device = device_create(sh1106->sh1106_class,
NULL, sh1106->devno, NULL, NAME);
if (IS_ERR(sh1106->sh1106_device)) {
ret = PTR_ERR(sh1106->sh1106_device);
dev_err(&spi->dev, "device_create failed %d\n", ret);
goto clean_class;
}
ret = get_gpio(sh1106);
if (ret != 0) goto clean_device;
mutex_init(&sh1106->mutex);
return 0;
clean_device:
device_destroy(sh1106->sh1106_class, sh1106->devno);
clean_class:
class_destroy(sh1106->sh1106_class);
del_cdev:
cdev_del(&sh1106->cdev);
unregister_devno:
unregister_chrdev_region(sh1106->devno, DEV_NUM);
free_sh1106:
kfree(sh1106);
sh1106 = NULL;
return ret;
}
static int sh1106_remove(struct spi_device* spi)
{
gpio_free(sh1106->dc_pin);
gpio_free(sh1106->rst_pin);
device_destroy(sh1106->sh1106_class, sh1106->devno);
class_destroy(sh1106->sh1106_class);
cdev_del(&sh1106->cdev);
unregister_chrdev_region(sh1106->devno, DEV_NUM);
kfree(sh1106);
sh1106 = NULL;
return 0;
}
// 传统的设备匹配表
static const struct spi_device_id sh1106_id[] = {
{"oled, ssh1106", 0},
{ }
};
// 设备树匹配表
static const struct of_device_id sh1106_of_match[] = {
{.compatible = "oled, ssh1106"},
{ } // 最后一项必须为空
};
// 外设驱动结构体
static struct spi_driver sh1106_driver = {
.id_table = sh1106_id,
.probe = sh1106_probe,
.remove = sh1106_remove,
.driver = {
.owner = THIS_MODULE,
.name = NAME,
.of_match_table = sh1106_of_match,
}
};
#if 1
static __init int sh1106_init(void)
{
int ret = 0;
ret = spi_register_driver(&sh1106_driver);
if (ret != 0)
pr_err("spi_sh1106 register failed %d\n", ret);
return ret;
}
static __exit void sh1106_exit(void)
{
spi_unregister_driver(&sh1106_driver);
}
module_init(sh1106_init);
module_exit(sh1106_exit);
#else
// 同时完成sh1106_init、sh1106_exit、module_init、module_exit的功能
module_spi_driver(sh1106_driver);
#endif
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liyang.plus@foxmail.com");
5.测试程序源码
/*===========================test.h================================*/
#define CMD (0x0)
#define DATA (0x1)
#define ERROR (-1)
#define OK (0)
#define PATH "/dev/spi_sh1106"
#define X_WIDTH 131
#define Y_WIDTH 64
typedef unsigned char byte
const byte F6x8[][6] =
{
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // sp
{ 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
};
const byte F8X16[]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 0
0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//!1
0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//"2
0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//#3
0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$4
0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//%5
0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//&6
0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//'7
0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//(8
0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//)9
0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//*10
0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+11
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//,12
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//-13
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//.14
0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,///15
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//016
0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//117
0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//218
0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//319
0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//420
0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//521
0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//622
0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//723
0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//824
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//925
0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//:26
0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//;27
0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//<28
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//=29
0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//>30
0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//?31
0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@32
0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A33
0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B34
0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C35
0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D36
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E37
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F38
0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G39
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H40
0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I41
0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J42
0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K43
0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L44
0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M45
0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N46
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O47
0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P48
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q49
0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R50
0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S51
0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T52
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U53
0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V54
0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W55
0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X56
0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y57
0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z58
0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[59
0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\60
0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//]61
0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^62
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_63
0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//`64
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a65
0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b66
0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c67
0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d68
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e69
0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f70
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g71
0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h72
0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i73
0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j74
0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k75
0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l76
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m77
0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n78
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o79
0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p80
0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q81
0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r82
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s83
0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t84
0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u85
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v86
0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w87
0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x88
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y89
0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z90
0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{91
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//|92
0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//}93
0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~94
};
/*===========================test.c================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "test.h"
static void write_data(int fd, byte val)
{
int ret;
ioctl(fd, DATA);
ret = write(fd, &val, sizeof(byte));
if (ret != sizeof(byte))
printf("write data error\n");
}
static void write_cmd(int fd, byte val)
{
int ret;
ioctl(fd, CMD);
ret = write(fd, &val, sizeof(byte));
if (ret != sizeof(byte))
printf("write cmd error\n");
}
static inline void Set_Display_On_Off(int fd, byte d)
{
write_cmd(fd, 0xAE | d);
}
static inline void Set_Display_Clock(int fd, byte d)
{
write_cmd(fd, 0xD5);
write_cmd(fd, d);
}
static inline void Set_Multiplex_Ratio(int fd, byte d)
{
write_cmd(fd, 0xA8);
write_cmd(fd, d);
}
static inline void Set_Display_Offset(int fd, byte d)
{
write_cmd(fd, 0xC0 | d);
}
static inline void SetStartLine(int fd, byte d)
{
write_cmd(fd, 0x40 | d);
}
static inline void Set_Charge_Pump(int fd, byte d)
{
write_cmd(fd, 0x8D);
write_cmd(fd, 0x10 | d);
}
static inline void SetAddressingMode(int fd, byte d)
{
write_cmd(fd, 0x20);
write_cmd(fd, d);
}
static inline void Set_Segment_Remap(int fd, byte d)
{
write_cmd(fd, 0xA0 | d);
}
static inline void Set_Common_Remap(int fd, byte d)
{
write_cmd(fd, 0xC0 | d);
}
static inline void Set_Common_Config(int fd, byte d)
{
write_cmd(fd, 0xDA);
write_cmd(fd, 0x02 | d);
}
static inline void SetContrastControl(int fd, byte d)
{
write_cmd(fd, 0x81);
write_cmd(fd, d);
}
static inline void Set_Precharge_Period(int fd, byte d)
{
write_cmd(fd, 0xD9);
write_cmd(fd, d);
}
static inline void Set_VCOMH(int fd, byte d)
{
write_cmd(fd, 0xDB);
write_cmd(fd, d);
}
static inline void Set_Entire_Display(int fd, byte d)
{
write_cmd(fd, 0xA4 | d);
}
static inline void Set_Inverse_Display(int fd, byte d)
{
write_cmd(fd, 0xA6 | d);
}
static inline void LCD_Fill(int fd, byte d)
{
byte y,x;
for(y = 0; y < 8; y++)
{
write_cmd(fd, 0xB0 + y);
write_cmd(fd, 0x01);
write_cmd(fd, 0x10);
for(x = 0; x < X_WIDTH; x++)
write_data(fd, d);
}
}
static inline void LCD_Set_Pos(int fd, byte x, byte y)
{
write_cmd(fd, 0xB0 + y);
write_cmd(fd, ((x & 0xF0) >> 4) | 0x10 );
write_cmd(fd, (x & 0x0F) | 0x01 );
}
static void oled_init(int fd)
{
Set_Display_On_Off (fd, 0x00); // Display Off (0x00/0x01)
Set_Display_Clock (fd, 0x80); // Set Clock as 100 Frames/Sec
Set_Multiplex_Ratio (fd, 0x3F); // 1/64 Duty (0x0F~0x3F)
Set_Display_Offset (fd, 0x00); // Shift Mapping RAM Counter (0x00~0x3F)
SetStartLine (fd, 0x00); // Set Mapping RAM Display Start Line (0x00~0x3F)
Set_Charge_Pump (fd, 0x04); // Enable Embedded DC/DC Converter (0x00/0x04)
SetAddressingMode (fd, 0x02); // Set Page Addressing Mode (0x00/0x01/0x02)
Set_Segment_Remap (fd, 0x01); // Set SEG/Column Mapping 0x00左右反置 0x01正常
Set_Common_Remap (fd, 0x08); // Set COM/Row Scan Direction 0x00上下反置 0x08正常
Set_Common_Config (fd, 0x10); // Set Sequential Configuration (0x00/0x10)
SetContrastControl (fd, 0xCF); // Set SEG Output Current
Set_Precharge_Period(fd, 0xF1); // Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
Set_VCOMH (fd, 0x40); // Set VCOM Deselect Level
Set_Entire_Display (fd, 0x00); // Disable Entire Display On (0x00/0x01)
Set_Inverse_Display (fd, 0x00); // Disable Inverse Display On (0x00/0x01)
Set_Display_On_Off (fd, 0x01); // Display On (0x00/0x01)
LCD_Fill (fd, 0x00); //初始清屏
LCD_Set_Pos (fd, 0, 0);
}
//其中x1、x2的范围0~127,y1,y2的范围0~63
void LCD_Rectangle(int fd, byte x1, byte y1, byte x2, byte y2, byte gif)
{
byte n;
LCD_Set_Pos(fd, x1, y1 >> 3);
for(n = x1; n <= x2; n++) {
write_data(fd, 0x01 << (y1 % 8));
if(gif == 1)
usleep(50 * 1000);
}
LCD_Set_Pos(fd, x1, y2 >> 3);
for(n = x1; n <= x2; n++) {
write_data(fd, 0x01 << (y2 % 8));
if(gif == 1)
usleep(50 * 1000);
}
}
//显示的位置(x,y),y为页范围0~7
void LCD_P6x8Str(int fd, byte x, byte y, byte ch[])
{
byte c = 0, i = 0, j = 0;
while (ch[j] != '\0') {
c = ch[j] - 32;
if(x > 126) {
x = 0;
y++;
}
LCD_Set_Pos(fd, x, y);
for(i = 0; i < 6; i++)
write_data(fd, F6x8[c][i]);
x += 6;
j++;
}
}
// 显示的位置(x,y),y为页范围0~7
void LCD_P8x16Str(int fd, byte x, byte y, byte ch[])
{
byte c = 0, i = 0, j = 0;
while (ch[j] != '\0') {
c = ch[j] - 32;
if( x > 120) {
x = 0;
y++;
}
LCD_Set_Pos(fd, x, y);
for(i = 0; i < 8; i++)
write_data(fd, F8X16[c * 16 + i]);
LCD_Set_Pos(fd, x, y + 1);
for(i = 0; i < 8; i++)
write_data(fd, F8X16[c * 16 + i + 8]);
x += 8;
j++;
}
}
int main(int argc, char* argv[])
{
int fd;
char str1[] = "imx6ull spi";
char str2[] = "oled driver";
fd = open(PATH, O_RDWR);
if (fd < 0) {
printf("open %s error\n", PATH);
return ERROR;
}
oled_init(fd);
//LCD_Rectangle(fd, 10, 10, 120, 60, 1);
//LCD_P6x8Str(fd, 20, 0, str1);
//LCD_P6x8Str(fd, 20, 4, str2);
LCD_P8x16Str(fd, 20, 1, str1);
LCD_P8x16Str(fd, 20, 3, str2);
close(fd);
return OK;
}