当前位置: 首页 > news >正文

SPI驱动学习五(如何编写SPI设备驱动程序)

目录

  • 一、SPI驱动程序框架
  • 二、怎么编写SPI设备驱动程序
    • 1. 编写设备树
    • 2. 注册spi_driver
    • 3. 怎么发起SPI传输
      • 3.1 接口函数
      • 3.2 函数解析
  • 三、示例1:编写SPI_DAC模块驱动程序
    • 1. 要做什么事情
    • 2. 硬件
      • 2.1 原理图
      • 2.2 连接
    • 3. 编写设备树
    • 4. 编写驱动程序
    • 5. 编写app层操作程序
  • 四、示例1:编写SPI_OLED模块驱动程序
    • 1. 硬件
      • 1.1 原理图
      • 1.2 连接
    • 2. 编写设备树
    • 3. 编写驱动程序
    • 3. 编写app层操作程序
    • 5. 使用Framebuffer改造OLED驱动
      • 5.1 思路
      • 5.2 编程
        • 5.2.1 Framebuffer编程
        • 5.2.2 数据搬移
        • 5.2.3 调试
      • 5.3 完整代码
        • 5.3.1 驱动 oled_drv.c
        • 5.3.2 APP spi_oled.c

参考资料:

  • 内核头文件:include\linux\spi\spi.h
  • 内核文档:Documentation\spi\spidev

一、SPI驱动程序框架

  在之前的文章《SPI驱动学习二(驱动框架)》中,我们已经讲解过该部分内容,这里我们再回顾下,如下图所示:
在这里插入图片描述
  SPI Master(或者说控制器) 通过platform总线设备驱动模型进行实现,SPI Device通过SPI driver驱动模型来实现。

二、怎么编写SPI设备驱动程序

1. 编写设备树

  • 查看原理图,确定这个设备链接在哪个SPI控制器下

  • 在设备树里,找到SPI控制器的节点

  • 在这个节点下,创建子节点,用来表示SPI设备

  • 示例如下:

    &ecspi1 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_ecspi1>;fsl,spi-num-chipselects = <2>;cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;status = "okay";dac: dac {compatible = "100ask,dac";reg = <0>;spi-max-frequency = <10000000>;};
    };
    

2. 注册spi_driver

  SPI设备的设备树节点,会被转换为一个spi_device结构体。我们需要编写一个spi_driver来支持它。

示例如下:

static const struct of_device_id dac_of_match[] = {{.compatible = "100ask,dac"},{}
};static struct spi_driver dac_driver = {.driver = {.name	= "dac",.of_match_table = dac_of_match,},.probe		= dac_probe,.remove		= dac_remove,//.id_table	= dac_spi_ids,
};

3. 怎么发起SPI传输

3.1 接口函数

接口函数都在这个内核文件里:include\linux\spi\spi.h

  • 简易函数

    /*** SPI同步写* @spi: 写哪个设备* @buf: 数据buffer* @len: 长度* 这个函数可以休眠** 返回值: 0-成功, 负数-失败码*/
    static inline int
    spi_write(struct spi_device *spi, const void *buf, size_t len);/*** SPI同步读* @spi: 读哪个设备* @buf: 数据buffer* @len: 长度* 这个函数可以休眠** 返回值: 0-成功, 负数-失败码*/
    static inline int
    spi_read(struct spi_device *spi, void *buf, size_t len);/*** spi_write_then_read : 先写再读, 这是一个同步函数* @spi: 读写哪个设备* @txbuf: 发送buffer* @n_tx: 发送多少字节* @rxbuf: 接收buffer* @n_rx: 接收多少字节* 这个函数可以休眠* * 这个函数执行的是半双工的操作: 先发送txbuf中的数据,在读数据,读到的数据存入rxbuf** 这个函数用来传输少量数据(建议不要操作32字节), 它的效率不高* 如果想进行高效的SPI传输,请使用spi_{async,sync}(这些函数使用DMA buffer)** 返回值: 0-成功, 负数-失败码*/
    extern int spi_write_then_read(struct spi_device *spi,const void *txbuf, unsigned n_tx,void *rxbuf, unsigned n_rx);/*** spi_w8r8 - 同步函数,先写8位数据,再读8位数据* @spi: 读写哪个设备* @cmd: 要写的数据* 这个函数可以休眠*** 返回值: 成功的话返回一个8位数据(unsigned), 负数表示失败码*/
    static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd);/*** spi_w8r16 - 同步函数,先写8位数据,再读16位数据* @spi: 读写哪个设备* @cmd: 要写的数据* 这个函数可以休眠** 读到的16位数据: *     低地址对应读到的第1个字节(MSB),高地址对应读到的第2个字节(LSB)*     这是一个big-endian的数据** 返回值: 成功的话返回一个16位数据(unsigned), 负数表示失败码*/
    static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd);/*** spi_w8r16be - 同步函数,先写8位数据,再读16位数据,*               读到的16位数据被当做big-endian,然后转换为CPU使用的字节序* @spi: 读写哪个设备* @cmd: 要写的数据* 这个函数可以休眠** 这个函数跟spi_w8r16类似,差别在于它读到16位数据后,会把它转换为"native endianness"** 返回值: 成功的话返回一个16位数据(unsigned, 被转换为本地字节序), 负数表示失败码*/
    static inline ssize_t spi_w8r16be(struct spi_device *spi, u8 cmd);
    
  • 复杂的函数

    /*** spi_async - 异步SPI传输函数,简单地说就是这个函数即刻返回,它返回后SPI传输不一定已经完成* @spi: 读写哪个设备* @message: 用来描述数据传输,里面含有完成时的回调函数(completion callback)* 上下文: 任意上下文都可以使用,中断中也可以使用** 这个函数不会休眠,它可以在中断上下文使用(无法休眠的上下文),也可以在任务上下文使用(可以休眠的上下文) ** 完成SPI传输后,回调函数被调用,它是在"无法休眠的上下文"中被调用的,所以回调函数里不能有休眠操作。* 在回调函数被调用前message->statuss是未定义的值,没有意义。* 当回调函数被调用时,就可以根据message->status判断结果: 0-成功,负数表示失败码* 当回调函数执行完后,驱动程序要认为message等结构体已经被释放,不能再使用它们。** 在传输过程中一旦发生错误,整个message传输都会中止,对spi设备的片选被取消。** 返回值: 0-成功(只是表示启动的异步传输,并不表示已经传输成功), 负数-失败码*/
    extern int spi_async(struct spi_device *spi, struct spi_message *message);/*** spi_sync - 同步的、阻塞的SPI传输函数,简单地说就是这个函数返回时,SPI传输要么成功要么失败* @spi: 读写哪个设备* @message: 用来描述数据传输,里面含有完成时的回调函数(completion callback)* 上下文: 能休眠的上下文才可以使用这个函数** 这个函数的message参数中,使用的buffer是DMA buffer** 返回值: 0-成功, 负数-失败码*/
    extern int spi_sync(struct spi_device *spi, struct spi_message *message);/*** spi_sync_transfer - 同步的SPI传输函数* @spi: 读写哪个设备* @xfers: spi_transfers数组,用来描述传输* @num_xfers: 数组项个数* 上下文: 能休眠的上下文才可以使用这个函数** 返回值: 0-成功, 负数-失败码*/
    static inline int
    spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers,unsigned int num_xfers);
    

3.2 函数解析

  在SPI子系统中,用spi_transfer结构体描述一个传输,用spi_message管理多个传输。

SPI传输时,发出N个字节,就可以同时得到N个字节

  • 即使只想读N个字节,也必须发出N个字节:可以发出0xff
  • 即使只想发出N个字节,也会读到N个字节:可以忽略读到的数据。

spi_transfer结构体如下图所示:

  • tx_buf:不是NULL的话,要发送的数据保存在里面
  • rx_buf:不是NULL的话,表示读到的数据不要丢弃,保存进rx_buf里
    在这里插入图片描述
      可以构造多个spi_transfer结构体,把它们放入一个spi_message里面。
    spi_message结构体如下图所示:
    在这里插入图片描述
      SPI传输示例:
    在这里插入图片描述

三、示例1:编写SPI_DAC模块驱动程序

参考资料: DAC芯片手册TLC5615.pdf

1. 要做什么事情

  • 查看原理图,编写设备树
  • 编写驱动程序,注册一个spidrv
  • 编写测试程序

2. 硬件

2.1 原理图

原理图:

2.2 连接

在这里插入图片描述

3. 编写设备树

确认SPI时钟最大频率:

T = 25 + 25 = 50ns
F = 20000000 = 20MHz

设备树如下:

    dac: dac {compatible = "100ask,dac";reg = <0>;spi-max-frequency = <20000000>;};

DAC模块接在这个插座上,那么要在设备树里spi1的节点下创建子节点。如下:

&ecspi1 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_ecspi1>;fsl,spi-num-chipselects = <2>;cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;status = "okay";dac: dac {compatible = "100ask,dac";reg = <0>;spi-max-frequency = <20000000>;};
};

  将该部分内容添加到主控板的设备树文件中。

4. 编写驱动程序

  上一篇文章《SPI驱动学习四(通过SPI操作外设模块)》中,我们基于spidev编写过DAC的应用程序,可以参考它:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>#include <linux/uaccess.h>#define SPI_IOC_WR 123/*-------------------------------------------------------------------------*/static struct spi_device *dac;
static int major;static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{int val;int err;unsigned char tx_buf[2];	unsigned char rx_buf[2];	struct spi_message	msg;struct spi_transfer	xfer[1];int status;memset(&xfer[0], 0, sizeof(xfer));/* copy_from_user */err = copy_from_user(&val, (const void __user *)arg, sizeof(int));printk("spidev_ioctl get val from user: %d\n", val);/* 发起SPI传输:     *//* 1. 把val修改为正确的格式 */val <<= 2;     /* bit0,bit1 = 0b00 */val &= 0xFFC;  /* 只保留10bit */tx_buf[1] = val & 0xff;tx_buf[0] = (val>>8) & 0xff;	/* 2. 发起SPI传输同时写\读 *//* 2.1 构造transfer* 2.2 加入message* 2.3 调用spi_sync*/xfer[0].tx_buf = tx_buf;xfer[0].rx_buf = rx_buf;xfer[0].len = 2;spi_message_init(&msg);spi_message_add_tail(&xfer[0], &msg);status = spi_sync(dac, &msg);/* 3. 修改读到的数据的格式 */val = (rx_buf[0] << 8) | (rx_buf[1]);val >>= 2;/* copy_to_user */err = copy_to_user((void __user *)arg, &val, sizeof(int));return 0;
}static const struct file_operations spidev_fops = {.owner =	THIS_MODULE,/* REVISIT switch to aio primitives, so that userspace* gets more complete API coverage.  It'll simplify things* too, except for the locking.*/.unlocked_ioctl = spidev_ioctl,
};/*-------------------------------------------------------------------------*//* The main reason to have this class is to make mdev/udev create the* /dev/spidevB.C character device nodes exposing our userspace API.* It also simplifies memory management.*/static struct class *spidev_class;static const struct of_device_id spidev_dt_ids[] = {{ .compatible = "100ask,dac" },{},
};/*-------------------------------------------------------------------------*/static int spidev_probe(struct spi_device *spi)
{/* 1. 记录spi_device */dac = spi;/* 2. 注册字符设备 */major = register_chrdev(0, "100ask_dac", &spidev_fops);spidev_class = class_create(THIS_MODULE, "100ask_dac");device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac");	return 0;
}static int spidev_remove(struct spi_device *spi)
{/* 反注册字符设备 */device_destroy(spidev_class, MKDEV(major, 0));class_destroy(spidev_class);unregister_chrdev(major, "100ask_dac");return 0;
}static struct spi_driver spidev_spi_driver = {.driver = {.name =		"100ask_spi_dac_drv",.of_match_table = of_match_ptr(spidev_dt_ids),},.probe =	spidev_probe,.remove =	spidev_remove,/* NOTE:  suspend/resume methods are not necessary here.* We don't do anything except pass the requests to/from* the underlying controller.  The refrigerator handles* most issues; the controller driver handles the rest.*/
};/*-------------------------------------------------------------------------*/static int __init spidev_adc_init(void)
{int status;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);status = spi_register_driver(&spidev_spi_driver);if (status < 0) {}return status;
}
module_init(spidev_adc_init);static void __exit spidev_adc_exit(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_adc_exit);MODULE_LICENSE("GPL");

  将该驱动编译进内核或者编译为ko文件,即可使用,该操作方法属于基本操作,此处不再赘述!

5. 编写app层操作程序

/* 参考: 内核源码根目录下tools\spi\spidev_fdx.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>#include <linux/types.h>#define SPI_IOC_WR 123/* dac_test /dev/100ask_dac <val> */int main(int argc, char **argv)
{int fd;unsigned int val;int status;unsigned char tx_buf[2];	unsigned char rx_buf[2];	if (argc != 3){printf("Usage: %s /dev/100ask_dac <val>\n", argv[0]);return 0;}fd = open(argv[1], O_RDWR);if (fd < 0) {printf("can not open %s\n", argv[1]);return 1;}val = strtoul(argv[2], NULL, 0);status = ioctl(fd, SPI_IOC_WR, &val);if (status < 0) {printf("SPI_IOC_WR\n");return -1;}/* 打印 */printf("Pre val = %d\n", val);return 0;
}

四、示例1:编写SPI_OLED模块驱动程序

1. 硬件

1.1 原理图

在这里插入图片描述

1.2 连接

  把OLED模块接到扩展板的SPI_A插座上,如下:
在这里插入图片描述

2. 编写设备树

在这里插入图片描述
  DC引脚(决定传输数据还是命令)使用GPIO4_20,也需在设备树里指定。设备树如下:

&ecspi1 {pinctrl-names = "default";pinctrl-0 = <&pinctrl_ecspi1>;fsl,spi-num-chipselects = <2>;cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;status = "okay";oled: oled {compatible = "100ask,oled";reg = <0>;spi-max-frequency = <10000000>;dc-gpios = <&gpio4 20 GPIO_ACTIVE_HIGH>; };
};

3. 编写驱动程序

//spi_oled_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>#include <linux/uaccess.h>
#include <linux/gpio/consumer.h>#define OLED_IOC_INIT 			123
#define OLED_IOC_SET_POS 		124//为0 表示命令,为1表示数据
#define OLED_CMD 	0
#define OLED_DATA 	1/*-------------------------------------------------------------------------*/
static struct spi_device *oled;
static int major;
static struct gpio_desc *dc_gpio;static void dc_pin_init(void)
{gpiod_direction_output(dc_gpio, 1);
}static void oled_set_dc_pin(int val)
{gpiod_set_value(dc_gpio, val);
}static void spi_write_datas(const unsigned char *buf, int len)
{spi_write(oled, buf, len);
}/*********************************************************************** 函数名称: oled_write_cmd* 功能描述: oled向特定地址写入数据或者命令* 输入参数:@uc_data :要写入的数据@uc_cmd:为1则表示写入数据,为0表示写入命令* 输出参数:无* 返 回 值: 无* 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/04		 V1.0	  芯晓		  创建***********************************************************************/
static void oled_write_cmd_data(unsigned char uc_data,unsigned char uc_cmd)
{if(uc_cmd==0){oled_set_dc_pin(0);}else{oled_set_dc_pin(1);//拉高,表示写入数据}spi_write_datas(&uc_data, 1);//写入
}/*********************************************************************** 函数名称: oled_init* 功能描述: oled_init的初始化,包括SPI控制器得初始化* 输入参数:无* 输出参数: 初始化的结果* 返 回 值: 成功则返回0,否则返回-1* 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/15		 V1.0	  芯晓		  创建***********************************************************************/
static int oled_init(void)
{oled_write_cmd_data(0xae,OLED_CMD);//关闭显示oled_write_cmd_data(0x00,OLED_CMD);//设置 lower column addressoled_write_cmd_data(0x10,OLED_CMD);//设置 higher column addressoled_write_cmd_data(0x40,OLED_CMD);//设置 display start lineoled_write_cmd_data(0xB0,OLED_CMD);//设置page addressoled_write_cmd_data(0x81,OLED_CMD);// contract controloled_write_cmd_data(0x66,OLED_CMD);//128oled_write_cmd_data(0xa1,OLED_CMD);//设置 segment remapoled_write_cmd_data(0xa6,OLED_CMD);//normal /reverseoled_write_cmd_data(0xa8,OLED_CMD);//multiple ratiooled_write_cmd_data(0x3f,OLED_CMD);//duty = 1/64oled_write_cmd_data(0xc8,OLED_CMD);//com scan directionoled_write_cmd_data(0xd3,OLED_CMD);//set displat offsetoled_write_cmd_data(0x00,OLED_CMD);//oled_write_cmd_data(0xd5,OLED_CMD);//set osc divisionoled_write_cmd_data(0x80,OLED_CMD);//oled_write_cmd_data(0xd9,OLED_CMD);//ser pre-charge periodoled_write_cmd_data(0x1f,OLED_CMD);//oled_write_cmd_data(0xda,OLED_CMD);//set com pinsoled_write_cmd_data(0x12,OLED_CMD);//oled_write_cmd_data(0xdb,OLED_CMD);//set vcomholed_write_cmd_data(0x30,OLED_CMD);//oled_write_cmd_data(0x8d,OLED_CMD);//set charge pump disable oled_write_cmd_data(0x14,OLED_CMD);//oled_write_cmd_data(0xaf,OLED_CMD);//set dispkay onreturn 0;
}		  			 		  						  					  				 	   		  	  	 	  //坐标设置
/*********************************************************************** 函数名称: OLED_DIsp_Set_Pos* 功能描述:设置要显示的位置* 输入参数:@ x :要显示的column address@y :要显示的page address* 输出参数: 无* 返 回 值: * 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/15		 V1.0	  芯晓		  创建***********************************************************************/
static void OLED_DIsp_Set_Pos(int x, int y)
{ 	oled_write_cmd_data(0xb0+y,OLED_CMD);oled_write_cmd_data((x&0x0f),OLED_CMD); oled_write_cmd_data(((x&0xf0)>>4)|0x10,OLED_CMD);
}   	      	   			 static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{int x, y;/* 根据cmd操作硬件 */switch (cmd){case OLED_IOC_INIT: /* init */{dc_pin_init();oled_init();break;}case OLED_IOC_SET_POS: /* set pos */{x = arg & 0xff;y = (arg >> 8) & 0xff;OLED_DIsp_Set_Pos(x, y);break;}}return 0;
}static ssize_t
spidev_write(struct file *filp, const char __user *buf,size_t count, loff_t *f_pos)
{char *ker_buf;int err;ker_buf = kmalloc(count, GFP_KERNEL);err = copy_from_user(ker_buf, buf, count);oled_set_dc_pin(1);//拉高,表示写入数据spi_write_datas(ker_buf, count);kfree(ker_buf);return count;
}static const struct file_operations spidev_fops = {.owner =	THIS_MODULE,/* REVISIT switch to aio primitives, so that userspace* gets more complete API coverage.  It'll simplify things* too, except for the locking.*/.write =	spidev_write,.unlocked_ioctl = spidev_ioctl,
};/*-------------------------------------------------------------------------*//* The main reason to have this class is to make mdev/udev create the* /dev/spidevB.C character device nodes exposing our userspace API.* It also simplifies memory management.*/static struct class *spidev_class;static const struct of_device_id spidev_dt_ids[] = {{ .compatible = "100ask,oled" },{},
};/*-------------------------------------------------------------------------*/static int spidev_probe(struct spi_device *spi)
{/* 1. 记录spi_device */oled = spi;/* 2. 注册字符设备 */major = register_chrdev(0, "100ask_oled", &spidev_fops);spidev_class = class_create(THIS_MODULE, "100ask_oled");device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_oled");	/* 3. 获得GPIO引脚 */dc_gpio = gpiod_get(&spi->dev, "dc", 0);return 0;
}static int spidev_remove(struct spi_device *spi)
{gpiod_put(dc_gpio);/* 反注册字符设备 */device_destroy(spidev_class, MKDEV(major, 0));class_destroy(spidev_class);unregister_chrdev(major, "100ask_oled");return 0;
}static struct spi_driver spidev_spi_driver = {.driver = {.name =		"100ask_spi_oled_drv",.of_match_table = of_match_ptr(spidev_dt_ids),},.probe =	spidev_probe,.remove =	spidev_remove,/* NOTE:  suspend/resume methods are not necessary here.* We don't do anything except pass the requests to/from* the underlying controller.  The refrigerator handles* most issues; the controller driver handles the rest.*/
};/*-------------------------------------------------------------------------*/static int __init spidev_oled_init(void)
{int status;status = spi_register_driver(&spidev_spi_driver);if (status < 0) {}return status;
}
module_init(spidev_oled_init);static void __exit spidev_oled_exit(void)
{spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_oled_exit);MODULE_LICENSE("GPL");

3. 编写app层操作程序

//font.h文件,字符信息对应的像素数组
#ifndef  _FONT_H_
#define _FONT_H_
const unsigned char oled_asc2_8x16[95][16]=
{{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
};      
const unsigned char hz_1616[][32]={
{0x02,0x00,0x02,0x00,0xE2,0xFF,0x22,0x42,0x22,0x42,0x32,0x42,0x2A,0x42,0x26,0x42,0x22,0x42,0x22,0x42,0x22,0x42,0x22,0x42,0xE2,0xFF,0x02,0x00,0x02,0x00,0x00,0x00},/*"百",0*/
{0x00,0x00,0xF8,0xFF,0x01,0x00,0x02,0x00,0x00,0x00,0xE2,0x1F,0x22,0x08,0x22,0x08,0x22,0x08,0xE2,0x1F,0x02,0x00,0x02,0x40,0x02,0x80,0xFE,0x7F,0x00,0x00,0x00,0x00},/*"问",1*/
{0x00,0x00,0xFE,0xFF,0x02,0x10,0x22,0x08,0x42,0x06,0x82,0x01,0x72,0x0E,0x02,0x10,0x22,0x08,0x42,0x06,0x82,0x01,0x72,0x4E,0x02,0x80,0xFE,0x7F,0x00,0x00,0x00,0x00},/*"网",2*/
};
#endif
//spi_oled.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>#include <linux/types.h>
#include <linux/spi/spidev.h>#include "font.h"#define OLED_IOC_INIT 			123
#define OLED_IOC_SET_POS 		124//为0 表示命令,为1表示数据
#define OLED_CMD 	0
#define OLED_DATA 	1static int fd_spidev;
static int dc_pin_num;void oled_write_cmd_data(unsigned char uc_data,unsigned char uc_cmd);
int oled_init(void);
int oled_fill_data(unsigned char fill_Data);
void OLED_DIsp_Clear(void);
void OLED_DIsp_All(void);
void OLED_DIsp_Set_Pos(int x, int y);
void OLED_DIsp_Char(int x, int y, unsigned char c);
void OLED_DIsp_String(int x, int y, char *str);
void OLED_DIsp_CHinese(unsigned char x,unsigned char y,unsigned char no);
void OLED_DIsp_Test();
void OLED_DIsp_Set_Pos(int x, int y);void oled_write_datas(const unsigned char *buf, int len)
{write(fd_spidev, buf, len);
}/*********************************************************************** 函数名称: OLED_DIsp_Clear* 功能描述: 整个屏幕显示数据清0* 输入参数:无* 输出参数: 无* 返 回 值: * 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/15		 V1.0	  芯晓		  创建***********************************************************************/
void OLED_DIsp_Clear(void)  
{unsigned char x, y;char buf[128];memset(buf, 0, 128);for (y = 0; y < 8; y++){OLED_DIsp_Set_Pos(0, y);oled_write_datas(buf, 128);}
}/*********************************************************************** 函数名称: OLED_DIsp_All* 功能描述: 整个屏幕显示全部点亮,可以用于检查坏点* 输入参数:无* 输出参数:无 * 返 回 值:* 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/15		 V1.0	  芯晓		  创建***********************************************************************/
void OLED_DIsp_All(void)  
{unsigned char x, y;char buf[128];memset(buf, 0xff, 128);for (y = 0; y < 8; y++){OLED_DIsp_Set_Pos(0, y);oled_write_datas(buf, 128);}}//坐标设置
/*********************************************************************** 函数名称: OLED_DIsp_Set_Pos* 功能描述:设置要显示的位置* 输入参数:@ x :要显示的column address@y :要显示的page address* 输出参数: 无* 返 回 值: * 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/15		 V1.0	  芯晓		  创建***********************************************************************/
void OLED_DIsp_Set_Pos(int x, int y)
{ 	ioctl(fd_spidev, OLED_IOC_SET_POS, x  | (y << 8));
}   	      	   			 
/*********************************************************************** 函数名称: OLED_DIsp_Char* 功能描述:在某个位置显示字符 1-9* 输入参数:@ x :要显示的column address@y :要显示的page address@c :要显示的字符的ascii码* 输出参数: 无* 返 回 值: * 修改日期		版本号	  修改人 	   修改内容* -----------------------------------------------* 2020/03/15		  V1.0	   芯晓		   创建
***********************************************************************/
void OLED_DIsp_Char(int x, int y, unsigned char c)
{int i = 0;/* 得到字模 */const unsigned char *dots = oled_asc2_8x16[c - ' '];/* 发给OLED */OLED_DIsp_Set_Pos(x, y);/* 发出8字节数据 *///for (i = 0; i < 8; i++)//	oled_write_cmd_data(dots[i], OLED_DATA);oled_write_datas(&dots[0], 8);OLED_DIsp_Set_Pos(x, y+1);/* 发出8字节数据 *///for (i = 0; i < 8; i++)//oled_write_cmd_data(dots[i+8], OLED_DATA);oled_write_datas(&dots[8], 8);
}/*********************************************************************** 函数名称: OLED_DIsp_String* 功能描述: 在指定位置显示字符串* 输入参数:@ x :要显示的column address@y :要显示的page address@str :要显示的字符串* 输出参数: 无* 返 回 值: 无* 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/15		 V1.0	  芯晓		  创建
***********************************************************************/
void OLED_DIsp_String(int x, int y, char *str)
{unsigned char j=0;while (str[j]){		OLED_DIsp_Char(x, y, str[j]);//显示单个字符x += 8;if(x > 127){x = 0;y += 2;}//移动显示位置j++;}
}
/*********************************************************************** 函数名称: OLED_DIsp_CHinese* 功能描述:在指定位置显示汉字* 输入参数:@ x :要显示的column address@y :要显示的page address@chr :要显示的汉字,三个汉字“百问网”中选择一个* 输出参数: 无* 返 回 值: 无* 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/15		 V1.0	  芯晓		  创建***********************************************************************/void OLED_DIsp_CHinese(unsigned char x,unsigned char y,unsigned char no)
{      			    unsigned char t,adder=0;OLED_DIsp_Set_Pos(x,y);	for(t=0;t<16;t++){//显示上半截字符	oled_write_datas(&hz_1616[no][t*2], 1);adder+=1;}	OLED_DIsp_Set_Pos(x,y+1);	for(t=0;t<16;t++){//显示下半截字符oled_write_datas(&hz_1616[no][t*2+1], 1);adder+=1;}					
}
/*********************************************************************** 函数名称: OLED_DIsp_Test* 功能描述: 整个屏幕显示测试* 输入参数:无* 输出参数: 无* 返 回 值: 无* 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/15		 V1.0	  芯晓		  创建***********************************************************************/
void OLED_DIsp_Test(void)
{ 	int i;OLED_DIsp_String(0, 0, "wiki.100ask.net");OLED_DIsp_String(0, 2, "book.100ask.net");OLED_DIsp_String(0, 4, "bbs.100ask.net");for(i = 0; i < 3; i++){   //显示汉字 百问网OLED_DIsp_CHinese(32+i*16, 6, i);}
} /* spi_oled /dev/100ask_oled */
int main(int argc, char **argv)
{	if (argc != 2){printf("Usage: %s /dev/100ask_oled\n", argv[0]);return -1;}fd_spidev = open(argv[1], O_RDWR);if (fd_spidev < 0) {printf("open %s err\n", argv[1]);return -1;}ioctl(fd_spidev, OLED_IOC_INIT);OLED_DIsp_Clear();	OLED_DIsp_Test();return 0;
}

5. 使用Framebuffer改造OLED驱动

5.1 思路

假设OLED的每个像素使用1位数据表示:

  • Linux Framebuffer中byte0对应OLED上第1行的8个像素
  • OLED显存中byte0对应OLED上第1列的8个像素
    在这里插入图片描述

  为了兼容基于Framebuffer的程序,驱动程序中分配一块Framebuffer,APP直接操作Framebuffer。驱动程序周期性地把Framebuffer中的数据搬移到OLED显存上。怎么搬移?

  发给OLED线程的byte0、1、2、3、4、5、6、7怎么构造出来?

  • 它们来自Framebuffer的byte0、16、32、48、64、80、96、112
  • OLED的byte0,由Framebuffer的这8个字节的bit0组合得到
  • OLED的byte1,由Framebuffer的这8个字节的bit1组合得到
  • OLED的byte2,由Framebuffer的这8个字节的bit2组合得到
  • OLED的byte3,由Framebuffer的这8个字节的bit3组合得到
  • ……

5.2 编程

5.2.1 Framebuffer编程

  分配、设置、注册fb_info结构体。

  • 分配fb_info
  • 设置fb_info
    • fb_var
    • fb_fix
  • 注册fb_info
  • 硬件操作
	/* A. 分配fb_info */myfb_info = framebuffer_alloc(0, NULL);/* B. 设置fb_info *//* B.1 var : LCD分辨率、颜色格式 */myfb_info->var.xres_virtual = myfb_info->var.xres = 128;myfb_info->var.yres_virtual = myfb_info->var.yres = 64;myfb_info->var.bits_per_pixel = 1; /* 一个像素由一个bit组成 *//* B.2 fix */strcpy(myfb_info->fix.id, "100ask_oled");myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * myfb_info->var.bits_per_pixel / 8;myfb_info->flags |= FBINFO_MODULE; /* 禁止显示LOGO *//* fb的虚拟地址 */myfb_info->screen_base = dma_alloc_wc(NULL, myfb_info->fix.smem_len, &phy_addr,GFP_KERNEL);myfb_info->fix.smem_start = phy_addr;  /* fb的物理地址 */myfb_info->fix.type = FB_TYPE_PACKED_PIXELS;myfb_info->fix.visual = FB_VISUAL_MONO10;myfb_info->fix.line_length = myfb_info->var.xres * myfb_info->var.bits_per_pixel / 8;	/* c. fbops */myfb_info->fbops = &myfb_ops;myfb_info->pseudo_palette = pseudo_palette;/* C. 注册fb_info */register_framebuffer(myfb_info);
5.2.2 数据搬移

  创建内核线程,周期性地把Framebuffer中的数据通过SPI发送给OLED。

  • 参考文件include\linux\kthread.h

  • 参考文章:https://blog.csdn.net/qq_37858386/article/details/115573565

  • kthread_create:创建内核线程,线程处于"停止状态",要运行它需要执行wake_up_process

  • kthread_run:创建内核线程,并马上让它处于"运行状态"

  • kernel_thread

oled_thread = kthread_run(oled_thread_func, NULL, "oled_kthead");
5.2.3 调试

  配置内核,把下列配置项去掉:
在这里插入图片描述

5.3 完整代码

5.3.1 驱动 oled_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>#include <linux/uaccess.h>
#include <linux/gpio/consumer.h>#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/kthread.h>#define OLED_IOC_INIT 			123
#define OLED_IOC_SET_POS 		124//为0 表示命令,为1表示数据
#define OLED_CMD 	0
#define OLED_DATA 	1static struct fb_info *myfb_info;static unsigned int pseudo_palette[16];static struct task_struct *oled_thread;static unsigned char *oled_buf; //[1024];/* from pxafb.c */
static inline unsigned int chan_to_field(unsigned int chan,struct fb_bitfield *bf)
{chan &= 0xffff;chan >>= 16 - bf->length;return chan << bf->offset;
}static int mylcd_setcolreg(unsigned regno,unsigned red, unsigned green, unsigned blue,unsigned transp, struct fb_info *info)
{unsigned int val;/* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n",regno, red, green, blue); */switch (info->fix.visual) {case FB_VISUAL_TRUECOLOR:/* true-colour, use pseudo-palette */if (regno < 16) {u32 *pal = info->pseudo_palette;val  = chan_to_field(red,   &info->var.red);val |= chan_to_field(green, &info->var.green);val |= chan_to_field(blue,  &info->var.blue);pal[regno] = val;}break;default:return 1;	/* unknown type */}return 0;
}static struct fb_ops myfb_ops = {.owner		= THIS_MODULE,.fb_setcolreg	= mylcd_setcolreg,.fb_fillrect	= cfb_fillrect,.fb_copyarea	= cfb_copyarea,.fb_imageblit	= cfb_imageblit,
};/*-------------------------------------------------------------------------*/static struct spi_device *oled;
static int major;
static struct gpio_desc *dc_gpio;static void dc_pin_init(void)
{gpiod_direction_output(dc_gpio, 1);
}static void oled_set_dc_pin(int val)
{gpiod_set_value(dc_gpio, val);
}static void spi_write_datas(const unsigned char *buf, int len)
{spi_write(oled, buf, len);
}/*********************************************************************** 函数名称: oled_write_cmd* 功能描述: oled向特定地址写入数据或者命令* 输入参数:@uc_data :要写入的数据@uc_cmd:为1则表示写入数据,为0表示写入命令* 输出参数:无* 返 回 值: 无* 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/04		 V1.0	  芯晓		  创建***********************************************************************/
static void oled_write_cmd_data(unsigned char uc_data,unsigned char uc_cmd)
{if(uc_cmd==0){oled_set_dc_pin(0);}else{oled_set_dc_pin(1);//拉高,表示写入数据}spi_write_datas(&uc_data, 1);//写入
}/*********************************************************************** 函数名称: oled_init* 功能描述: oled_init的初始化,包括SPI控制器得初始化* 输入参数:无* 输出参数: 初始化的结果* 返 回 值: 成功则返回0,否则返回-1* 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/15		 V1.0	  芯晓		  创建***********************************************************************/
static int oled_init(void)
{oled_write_cmd_data(0xae,OLED_CMD);//关闭显示oled_write_cmd_data(0x00,OLED_CMD);//设置 lower column addressoled_write_cmd_data(0x10,OLED_CMD);//设置 higher column addressoled_write_cmd_data(0x40,OLED_CMD);//设置 display start lineoled_write_cmd_data(0xB0,OLED_CMD);//设置page addressoled_write_cmd_data(0x81,OLED_CMD);// contract controloled_write_cmd_data(0x66,OLED_CMD);//128oled_write_cmd_data(0xa1,OLED_CMD);//设置 segment remapoled_write_cmd_data(0xa6,OLED_CMD);//normal /reverseoled_write_cmd_data(0xa8,OLED_CMD);//multiple ratiooled_write_cmd_data(0x3f,OLED_CMD);//duty = 1/64oled_write_cmd_data(0xc8,OLED_CMD);//com scan directionoled_write_cmd_data(0xd3,OLED_CMD);//set displat offsetoled_write_cmd_data(0x00,OLED_CMD);//oled_write_cmd_data(0xd5,OLED_CMD);//set osc divisionoled_write_cmd_data(0x80,OLED_CMD);//oled_write_cmd_data(0xd9,OLED_CMD);//ser pre-charge periodoled_write_cmd_data(0x1f,OLED_CMD);//oled_write_cmd_data(0xda,OLED_CMD);//set com pinsoled_write_cmd_data(0x12,OLED_CMD);//oled_write_cmd_data(0xdb,OLED_CMD);//set vcomholed_write_cmd_data(0x30,OLED_CMD);//oled_write_cmd_data(0x8d,OLED_CMD);//set charge pump disable oled_write_cmd_data(0x14,OLED_CMD);//oled_write_cmd_data(0xaf,OLED_CMD);//set dispkay onreturn 0;
}		  			 		  						  					  				 	   		  	  	 	  //坐标设置
/*********************************************************************** 函数名称: OLED_DIsp_Set_Pos* 功能描述:设置要显示的位置* 输入参数:@ x :要显示的column address@y :要显示的page address* 输出参数: 无* 返 回 值: * 修改日期 	   版本号	 修改人		  修改内容* -----------------------------------------------* 2020/03/15		 V1.0	  芯晓		  创建***********************************************************************/
static void OLED_DIsp_Set_Pos(int x, int y)
{ 	oled_write_cmd_data(0xb0+y,OLED_CMD);oled_write_cmd_data((x&0x0f),OLED_CMD); oled_write_cmd_data(((x&0xf0)>>4)|0x10,OLED_CMD);
}   	      	   			 static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{int x, y;/* 根据cmd操作硬件 */switch (cmd){case OLED_IOC_INIT: /* init */{dc_pin_init();oled_init();break;}case OLED_IOC_SET_POS: /* set pos */{x = arg & 0xff;y = (arg >> 8) & 0xff;OLED_DIsp_Set_Pos(x, y);break;}}return 0;
}static ssize_t
spidev_write(struct file *filp, const char __user *buf,size_t count, loff_t *f_pos)
{char *ker_buf;int err;ker_buf = kmalloc(count, GFP_KERNEL);err = copy_from_user(ker_buf, buf, count);oled_set_dc_pin(1);//拉高,表示写入数据spi_write_datas(ker_buf, count);kfree(ker_buf);return count;
}static const struct file_operations spidev_fops = {.owner =	THIS_MODULE,/* REVISIT switch to aio primitives, so that userspace* gets more complete API coverage.  It'll simplify things* too, except for the locking.*/.write =	spidev_write,.unlocked_ioctl = spidev_ioctl,
};/*-------------------------------------------------------------------------*//* The main reason to have this class is to make mdev/udev create the* /dev/spidevB.C character device nodes exposing our userspace API.* It also simplifies memory management.*/static struct class *spidev_class;static const struct of_device_id spidev_dt_ids[] = {{ .compatible = "100ask,oled" },{},
};static int oled_thread_func(void *param)
{unsigned char *p[8];unsigned char data[8];int i;int j;int line;int bit;unsigned char byte;unsigned char *fb  = myfb_info->screen_base;int k;while (!kthread_should_stop()) {/* 1. 从Framebuffer得到数据 *//* 2. 转换格式 */k = 0;for (i = 0; i < 8; i++){for (line = 0; line < 8; line++)p[line] = &fb[i*128 + line * 16];for (j = 0; j < 16; j++){for (line = 0; line < 8; line++){data[line] = *p[line];p[line] += 1;}for (bit = 0; bit < 8; bit++){byte =  (((data[0]>>bit) & 1) << 0) |(((data[1]>>bit) & 1) << 1) |(((data[2]>>bit) & 1) << 2) |(((data[3]>>bit) & 1) << 3) |(((data[4]>>bit) & 1) << 4) |(((data[5]>>bit) & 1) << 5) |(((data[6]>>bit) & 1) << 6) |(((data[7]>>bit) & 1) << 7);oled_buf[k++] = byte;}}}/* 3. 通过SPI发送给OLED */for (i = 0; i < 8; i++){OLED_DIsp_Set_Pos(0, i);oled_set_dc_pin(1);spi_write_datas(&oled_buf[i*128], 128);}/* 4. 休眠一会 */schedule_timeout_interruptible(HZ);}return 0;
}static int spidev_probe(struct spi_device *spi)
{dma_addr_t phy_addr;/* 1. 记录spi_device */oled = spi;/* 2. 注册字符设备 */major = register_chrdev(0, "100ask_oled", &spidev_fops);spidev_class = class_create(THIS_MODULE, "100ask_oled");device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_oled");	/* 3. 获得GPIO引脚 */dc_gpio = gpiod_get(&spi->dev, "dc", 0);/* A. 分配fb_info */myfb_info = framebuffer_alloc(0, NULL);/* B. 设置fb_info *//* B.1 var : LCD分辨率、颜色格式 */myfb_info->var.xres_virtual = myfb_info->var.xres = 128;myfb_info->var.yres_virtual = myfb_info->var.yres = 64;myfb_info->var.bits_per_pixel = 1;  /* rgb565 */	/* B.2 fix */strcpy(myfb_info->fix.id, "100ask_oled");myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * myfb_info->var.bits_per_pixel / 8;myfb_info->flags |= FBINFO_MODULE; /* 禁止显示LOGO *//* fb的虚拟地址 */myfb_info->screen_base = dma_alloc_wc(NULL, myfb_info->fix.smem_len, &phy_addr,GFP_KERNEL);myfb_info->fix.smem_start = phy_addr;  /* fb的物理地址 */myfb_info->fix.type = FB_TYPE_PACKED_PIXELS;myfb_info->fix.visual = FB_VISUAL_MONO10;myfb_info->fix.line_length = myfb_info->var.xres * myfb_info->var.bits_per_pixel / 8;	/* c. fbops */myfb_info->fbops = &myfb_ops;myfb_info->pseudo_palette = pseudo_palette;/* C. 注册fb_info */register_framebuffer(myfb_info);/* D. 创建内核线程 */oled_buf = kmalloc(1024, GFP_KERNEL);dc_pin_init();oled_init();oled_thread = kthread_run(oled_thread_func, NULL, "oled_kthead");return 0;
}static int spidev_remove(struct spi_device *spi)
{kthread_stop(oled_thread);kfree(oled_buf);/* A. 反注册fb_info */unregister_framebuffer(myfb_info);/* B. 释放内存 */dma_free_wc(NULL, myfb_info->fix.smem_len, myfb_info->screen_base,myfb_info->fix.smem_start);/* C. 释放fb_info */framebuffer_release(myfb_info);gpiod_put(dc_gpio);/* 反注册字符设 */device_destroy(spidev_class, MKDEV(major, 0));class_destroy(spidev_class);unregister_chrdev(major, "100ask_oled");return 0;
}static struct spi_driver spidev_spi_driver = {.driver = {.name =		"100ask_spi_oled_drv",.of_match_table = of_match_ptr(spidev_dt_ids),},.probe =	spidev_probe,.remove =	spidev_remove,/* NOTE:  suspend/resume methods are not necessary here.* We don't do anything except pass the requests to/from* the underlying controller.  The refrigerator handles* most issues; the controller driver handles the rest.*/
};/*-------------------------------------------------------------------------*/static int __init spidev_oled_init(void)
{int status;status = spi_register_driver(&spidev_spi_driver);if (status < 0) {}return status;
}
module_init(spidev_oled_init);static void __exit spidev_oled_exit(void)
{spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_oled_exit);
MODULE_LICENSE("GPL");
5.3.2 APP spi_oled.c


  本文章参考了韦东山老师驱动大全部分笔记,其余内容为自己整理总结而来。水平有限,欢迎各位在评论区指导交流!!!😁😁😁


http://www.mrgr.cn/news/23058.html

相关文章:

  • Git工作流程
  • C和指针:高级指针话题
  • 空间工作记忆策略在控制网络和默认模式网络中的激活差异
  • tushare库获取金融股票数据
  • 【计网】计算机网络基础
  • SQL基础语句
  • 【无线通信发展史⑨】1791年路易吉·伽伐尼-关于动物电的研究与1800年亚历山大·伏打伯爵-电池:伏打电池
  • P2343 宝石管理系统
  • Reflection 70B 遭质疑基模为 Llama 3;Replit Agent:编程 0 基础适用丨 RTE 开发者日报
  • 执行命令行程序测试自动化
  • 2024年1-7月份电子信息制造业运行情况
  • 【python篇】——python基础语法一篇就能明白,快速理解
  • 【服务器第一期】Xshell、Xftp下载及连接
  • 什么是跨站脚本攻击(XSS)和跨站请求伪造(CSRF)?
  • 哈希表及算法
  • MYSQL1
  • 树莓派通过串口驱动LD3320语音模块
  • MySQL JDBC URL各参数详解
  • 基于Spring Boot的旧物置换网站
  • 感知机(Perceptron)—有监督学习方法、非概率模型、判别模型、线性模型、参数化模型、批量学习、核方法