参考资料:

  • 内核头文件: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 address
	oled_write_cmd_data(0x10,OLED_CMD);//设置 higher column address

	oled_write_cmd_data(0x40,OLED_CMD);//设置 display start line

	oled_write_cmd_data(0xB0,OLED_CMD);//设置page address

	oled_write_cmd_data(0x81,OLED_CMD);// contract control
	oled_write_cmd_data(0x66,OLED_CMD);//128

	oled_write_cmd_data(0xa1,OLED_CMD);//设置 segment remap

	oled_write_cmd_data(0xa6,OLED_CMD);//normal /reverse

	oled_write_cmd_data(0xa8,OLED_CMD);//multiple ratio
	oled_write_cmd_data(0x3f,OLED_CMD);//duty = 1/64

	oled_write_cmd_data(0xc8,OLED_CMD);//com scan direction

	oled_write_cmd_data(0xd3,OLED_CMD);//set displat offset
	oled_write_cmd_data(0x00,OLED_CMD);//

	oled_write_cmd_data(0xd5,OLED_CMD);//set osc division
	oled_write_cmd_data(0x80,OLED_CMD);//

	oled_write_cmd_data(0xd9,OLED_CMD);//ser pre-charge period
	oled_write_cmd_data(0x1f,OLED_CMD);//

	oled_write_cmd_data(0xda,OLED_CMD);//set com pins
	oled_write_cmd_data(0x12,OLED_CMD);//

	oled_write_cmd_data(0xdb,OLED_CMD);//set vcomh
	oled_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 on

	return 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 	1

static 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 	1

static 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 address
	oled_write_cmd_data(0x10,OLED_CMD);//设置 higher column address

	oled_write_cmd_data(0x40,OLED_CMD);//设置 display start line

	oled_write_cmd_data(0xB0,OLED_CMD);//设置page address

	oled_write_cmd_data(0x81,OLED_CMD);// contract control
	oled_write_cmd_data(0x66,OLED_CMD);//128

	oled_write_cmd_data(0xa1,OLED_CMD);//设置 segment remap

	oled_write_cmd_data(0xa6,OLED_CMD);//normal /reverse

	oled_write_cmd_data(0xa8,OLED_CMD);//multiple ratio
	oled_write_cmd_data(0x3f,OLED_CMD);//duty = 1/64

	oled_write_cmd_data(0xc8,OLED_CMD);//com scan direction

	oled_write_cmd_data(0xd3,OLED_CMD);//set displat offset
	oled_write_cmd_data(0x00,OLED_CMD);//

	oled_write_cmd_data(0xd5,OLED_CMD);//set osc division
	oled_write_cmd_data(0x80,OLED_CMD);//

	oled_write_cmd_data(0xd9,OLED_CMD);//ser pre-charge period
	oled_write_cmd_data(0x1f,OLED_CMD);//

	oled_write_cmd_data(0xda,OLED_CMD);//set com pins
	oled_write_cmd_data(0x12,OLED_CMD);//

	oled_write_cmd_data(0xdb,OLED_CMD);//set vcomh
	oled_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 on

	return 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
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <wchar.h>
#include <sys/ioctl.h>

//开源的字体引擎库,用于处理字体和文本的渲染
//开源库网址:https://freetype.org/
#include <ft2build.h> 
#include FT_FREETYPE_H 
#include FT_GLYPH_H

int fd_fb;
struct fb_var_screeninfo var;	/* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;


/**********************************************************************
 * 函数名称: lcd_put_pixel
 * 功能描述: 在LCD指定位置上输出指定颜色(描点)
 * 输入参数: x坐标,y坐标,颜色
 * 输出参数: 无
 * 返 回 值: 会
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2020/05/12	     V1.0	  zh(angenao)	      创建
 ***********************************************************************/ 
void lcd_put_pixel(int x, int y, unsigned int color)
{
	unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
	unsigned short *pen_16;	
	unsigned int *pen_32;	

	unsigned int red, green, blue;	
	int bit;

	pen_16 = (unsigned short *)pen_8;
	pen_32 = (unsigned int *)pen_8;

	switch (var.bits_per_pixel)
	{
		case 1:
		{
			pen_8 = fbmem+y*line_width+x/8;
			bit = x & 7;
			if (color)
				*pen_8 = *pen_8 | (1<<bit);
			else
				*pen_8 = *pen_8 & ~(1<<bit);
			break;
		}
		case 8:
		{
			*pen_8 = color;
			break;
		}
		case 16:
		{
			/* 565 */
			red   = (color >> 16) & 0xff;
			green = (color >> 8) & 0xff;
			blue  = (color >> 0) & 0xff;
			color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
			*pen_16 = color;
			break;
		}
		case 32:
		{
			*pen_32 = color;
			break;
		}
		default:
		{
			printf("can't surport %dbpp\n", var.bits_per_pixel);
			break;
		}
	}
}

/**********************************************************************
 * 函数名称: draw_bitmap
 * 功能描述: 根据bitmap位图,在LCD指定位置显示汉字
 * 输入参数: x坐标,y坐标,位图指针
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2020/05/12	     V1.0	  zh(angenao)	      创建
 ***********************************************************************/ 
void
draw_bitmap( FT_Bitmap*  bitmap,
             FT_Int      x,
             FT_Int      y)
{
	FT_Int  i, j, p, q;
	FT_Int  x_max = x + bitmap->width;
	FT_Int  y_max = y + bitmap->rows;

	//printf("x = %d, y = %d\n", x, y);

	for ( j = y, q = 0; j < y_max; j++, q++ )
	{
		for ( i = x, p = 0; i < x_max; i++, p++ )
		{
			if ( i < 0      || j < 0       ||
				i >= var.xres || j >= var.yres )
			continue;

			//image[j][i] |= bitmap->buffer[q * bitmap->width + p];
			lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
		}
	}
}


int main(int argc, char **argv)
{
	wchar_t *chinese_str = L"繁";

	FT_Library	  library;
	FT_Face 	  face;
	int error;
    FT_Vector     pen;
	FT_GlyphSlot  slot;
	int font_size = 24;
	FT_Matrix	  matrix;				  /* transformation matrix */
	double		  angle;

	if (argc < 3)
	{
		printf("Usage : %s </dev/fbX> <font_file> <angle> [font_size]\n", argv[0]);
		return -1;
	}

	angle  = ( 1.0* strtoul(argv[3], NULL, 0) / 360 ) * 3.14159 * 2;	   /* use 25 degrees	 */

	if (argc == 5)
		font_size = strtoul(argv[4], NULL, 0);
		
	fd_fb = open(argv[1], O_RDWR);
	if (fd_fb < 0)
	{
		printf("can't open %s\n", argv[1]);
		return -1;
	}

	if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
	{
		printf("can't get var\n");
		return -1;
	}

	line_width  = var.xres * var.bits_per_pixel / 8;
	pixel_width = var.bits_per_pixel / 8;
	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
	fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if (fbmem == (unsigned char *)-1)
	{
		printf("can't mmap\n");
		return -1;
	}

	/* 清屏: 全部设为黑色 */
	memset(fbmem, 0, screen_size);

	/* 显示矢量字体 */
	error = FT_Init_FreeType( &library );			   /* initialize library */
	/* error handling omitted */
	
	error = FT_New_Face( library, argv[2], 0, &face ); /* create face object */
	/* error handling omitted */	
	slot = face->glyph;

	FT_Set_Pixel_Sizes(face, font_size, 0);

	/* 确定座标:
	 */
	pen.x = 0;
	pen.y = 0;

	/* set up matrix */
	matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L );
	matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L );
	matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L );
	matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L );

    /* set transformation */
    FT_Set_Transform( face, &matrix, &pen);

    /* load glyph image into the slot (erase previous one) */
    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );
	if (error)
	{
		printf("FT_Load_Char error\n");
		return -1;
	}
	
    draw_bitmap( &slot->bitmap,
                 var.xres/2,
                 var.yres/2);

	return 0;	
}

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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐