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