Linux SPI设备驱动
实现了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_messagevoid 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 0x10b1MX6UL_PAD_LCD_DATA22__ECSPI1_MOSI 0x10b1 // SDAMX6UL_PAD_LCD_DATA20__ECSPI1_SCLK 0x10b1 // SCLMX6UL_PAD_LCD_DATA21__GPIO3_IO26 0x10b0 // D/CMX6UL_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>; // 最大频率4Mreg = <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);// 复位oledgpio_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 0ret = spi_write(dev->spi, buffer, size);if (ret != 0) return ret;#elsememset(&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_messagespi_message_add_tail(&t, &m); // 将spi_transfer添加到spi_messageret = spi_sync(dev->spi, &m); // 同步传输if (ret != 0) return -EFAULT;#endifreturn 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传输的信息是数据elsereturn -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};// 获取设备树中定义的复位、数据命令GPIOstatic 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 1static __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);#endifMODULE_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 64typedef unsigned char byteconst 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,// 00x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//!10x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//"20x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//#30x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$40xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//%50x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//&60x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//'70x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//(80x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//)90x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//*100x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+110x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//,120x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//-130x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//.140x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,///150x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0160x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1170x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2180x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3190x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4200x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5210x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6220x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7230x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8240x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9250x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//:260x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//;270x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//<280x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//=290x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//>300x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//?310xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@320x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A330x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B340xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C350x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D360x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E370x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F380xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G390x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H400x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I410x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J420x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K430x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L440x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M450x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N460xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O470x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P480xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q490x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R500x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S510x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T520x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U530x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V540xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W550x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X560x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y570x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z580x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[590x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\600x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//]610x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^620x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_630x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//`640x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a650x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b660x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c670x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d680x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e690x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f700x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g710x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h720x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i730x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j740x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k750x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l760x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m770x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n780x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o790x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p800x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q810x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r820x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s830x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t840x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u850x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v860x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w870x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x880x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y890x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z900x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{910x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//|920x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//}930x00,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/SecSet_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 CurrentSet_Precharge_Period(fd, 0xF1); // Set Pre-Charge as 15 Clocks & Discharge as 1 ClockSet_VCOMH (fd, 0x40); // Set VCOM Deselect LevelSet_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~63void 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~7void 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~7void 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;}
6.测试结果
Linux SPI设备驱动相关推荐
- linux spi屏驱动程序,65 linux spi设备驱动之spi LCD屏驱动
SPI的控制器驱动由平台设备与平台驱动来实现. 驱动后用spi_master对象来描述.在设备驱动中就可以通过函数spi_write, spi_read, spi_w8r16, spi_w8r8等函数 ...
- LINUX SPI设备驱动模型分析之二 SPI总线模块分析
上一篇文章我们简要介绍了SPI驱动模块,本章我们详细说明一下spi总线.设备.驱动模块的注册.注销以及这几个模块之间的关联. SPI总线的注册 spi模块也是基于LINUX设备-总线-驱动模型进行开发 ...
- linux probe函数调用,linux spi设备驱动中probe函数何时被调用
这两天被设备文件快搞疯了,也怪自己学东西一知半解吧,弄了几天总算能把设备注册理清楚一点点了.就以spi子设备的注册为例总结一下,免得自己忘记. 首先以注册一个spidev的设备为例: static s ...
- platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架
platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...
- Linux kernel SPI源码分析之SPI设备驱动源码分析(linux kernel 5.18)
SPI基础支持此处不再赘述,直接分析linux中的SPI驱动源码. 1.SPI设备驱动架构图 2.源码分析 本次分析基于kernel5.18,linux/drivers/spi/spidev.c 设备 ...
- 二十、SPI设备驱动及应用(一)
先给出Linux SPI子系统的体系结构图: SPI子系统体系结构 下面开始分析SPI子系统. Linux中SPI子系统的初始化是从drivers/spi/spi.c文件中的spi_init函数开始的 ...
- linux字符设备驱动的 ioctl 幻数
在Linux字符设备驱动入门(一)中,我们实现了字符设备的简单读写字符功能,接下来我们要在这个基础上加入ioctl功能.首先,我们先来看看3.0内核下../include/linux/fs.h中fil ...
- imx6ul spi 设备驱动开发
imx6ul spi 设备驱动开发 spi设备树格式 spi设备树配置 spi 驱动 设备树解析 spi设备驱动使用 spi通用设备驱动 spi测试工具 spi时序对比 spi api 接口 spi设 ...
- linux3.0字符设备驱动,linux字符设备驱动的 ioctl 幻数
在Linux字符设备驱动入门(一)中,我们实现了字符设备的简单读写字符功能,接下来我们要在这个基础上加入ioctl功能.首先,我们先来看看3.0内核下../include/linux/fs.h中fil ...
最新文章
- 软件测试:黑盒白盒与动态静态之间有必然联系吗
- linux /home recovering journal,linux报错:/dev/sdb2:recovering journal
- 向上取整的方法_瓷砖测量的方法有哪些?瓷砖尺寸一般是多少?
- 手把手教你部署一个最小化的 Kubernetes 集群
- 德国巴伐利亚山谷积雪遍地 汽车被大雪掩埋
- Virut样本取证特征
- android app应用签名生成工具,android应用签名详细步骤
- 为什么redhat6/centos6里看到的网卡是em*?
- JProfiler分析内存泄漏
- 2010年软考 考试日期安排
- jhipster使用简明教程
- 转载:肖知兴:管理到底是个什么鬼,以及怎么破
- 抖音网上如何赚钱变现,有哪些具体的方法
- java释放string_java – 释放stringbuilder内存的最快方法
- 屏蔽html查看源代码,禁止查看网页源代码方法
- 爬去东方财富网龙虎榜(wechat:15353378609)
- 今日芯声 | 微软 Xbox 老大:关闭游戏直播平台 Mixer,我没有遗憾
- 【python】基础七:编码问题
- 从零开始速通百度云网盘
- Java开发——JDK环境配置