實作了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: [email protected] {
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("[email protected]");
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;
}