Linux SPI设备驱动:四线SPI OLED驱动实战

嵌入式技术

1337人已加入

描述

SPI 从设备芯片的种类非常广泛,包括用于模拟传感器和编解码器的数字/模拟转换器、内存芯片、USB 控制器或以太网适配器等外设,以及其他类型的芯片。

这样的驱动通常在linux看来是一个协议驱动,比如spi flash,负责和MTD系统打交道;比如触摸传感器,需要和input子系统打交道,再比如spi接口的OLED模块。

这样的设备使用的【接口】在驱动中使用struct spi_deivce表示

struct spi_device {
 struct device  dev;
 struct spi_controller *controller;
 struct spi_controller *master; /* compatibility layer */
 u32   max_speed_hz;
 u8   chip_select;
 u8   bits_per_word;
 bool   rt;
#define SPI_NO_TX BIT(31)  /* no transmit wire */
#define SPI_NO_RX BIT(30)  /* no receive wire */
 /*
  * All bits defined above should be covered by SPI_MODE_KERNEL_MASK.
  * The SPI_MODE_KERNEL_MASK has the SPI_MODE_USER_MASK counterpart,
  * which is defined in 'include/uapi/linux/spi/spi.h'.
  * The bits defined here are from bit 31 downwards, while in
  * SPI_MODE_USER_MASK are from 0 upwards.
  * These bits must not overlap. A static assert check should make sure of that.
  * If adding extra bits, make sure to decrease the bit index below as well.
  */
#define SPI_MODE_KERNEL_MASK (~(BIT(30) - 1))
 u32   mode;
 int   irq;
 void   *controller_state;
 void   *controller_data;
 char   modalias[SPI_NAME_SIZE];
 const char  *driver_override;
 int   cs_gpio; /* LEGACY: chip select gpio */
 struct gpio_desc *cs_gpiod; /* chip select gpio desc */
 struct spi_delay word_delay; /* inter-word delay */
 /* CS delays */
 struct spi_delay cs_setup;
 struct spi_delay cs_hold;
 struct spi_delay cs_inactive;

 /* the statistics */
 struct spi_statistics statistics;

 /*
  * likely need more hooks for more protocol options affecting how
  * the controller talks to each chip, like:
  *  - memory packing (12 bit samples into low bits, others zeroed)
  *  - priority
  *  - chipselect delays
  *  - ...
  */
};

linux内核文档中是这样描述的

A "struct spi_device" encapsulates the controller-side interface between those two types of drivers.

因此,应该表示一个接口而不是一个驱动,当然你说这个接口连接的不就是设备吗?这么理解好像也没错。

SPI 设备驱动使用struct spi_driver表示,提供probe驱动入口,老套路了,比如

static int ssd13306_probe(struct spi_device *spi)

以上是函数原型,留下一个疑问,struct spi_device是作为一个对象传进来的,它是什么时候被构造的呢?

一、编写设备树

&ecspi2{
 cs-gpios = < &gpio1 29 GPIO_ACTIVE_LOW >;//GPIO1_29
 num-cs = < 1 >;
 pinctrl-names = "default";
 pinctrl-0 = < &pinctrl_ecspi2 >;
 status = "okay";

 oled: ssd13306@0{
  compatible = "Justice,ssd13306";//自己写的oled驱动
  //compatible = "spidev,spidev";//使用spidev通用驱动
  //compatible = "solomon,ssd1306";//使用fbtftf的驱动,oled当成fb

  spi-cpol;
  spi-cpha;
  spi-rx-bus-width = < 0 >;
  spi-max-frequency = < 10000000 >;
  reset-gpios = < &gpio1 27 GPIO_ACTIVE_LOW >; 
  dc-gpios = < &gpio1 31 GPIO_ACTIVE_HIGH >;
  reg = < 0 >;
 };
};

ecspi2是soc的SPI控制器,我们使用这个SPI控制器和从设备oled通信,因此要在这个设备树节点下写一个子节点表示OLED设备。下面是必填项:

  1. cs-gpios 是SPI控制器的属性,描述了从设备使用了哪些cs片选引脚,如果有3个从设备就需要写3个从cs引脚,而且顺序要和从设备设备树节点的reg属性对应。如上面gpio1 29表示片选0的从设备,reg属性表示设备片选号。
  2. reg 表示此设备在这个SPI控制器中第几个片选,和cs-gpios顺序一致。
  3. compatible 用于匹配驱动。
  4. pinctrl-0 = <&pinctrl_ecspi2> 表示要引脚复用哪些信号
pinctrl_ecspi2:oled{//没有MISO因为ssd1306 oled不能读取
   fsl,pins = <
    MX6UL_PAD_UART4_RX_DATA__GPIO1_IO29 0x10b0  //cs引脚
    MX6UL_PAD_UART4_TX_DATA__ECSPI2_SCLK 0x10b1
    MX6UL_PAD_UART5_TX_DATA__ECSPI2_MOSI 0x10b1
    MX6UL_PAD_UART5_RX_DATA__GPIO1_IO31 0x10b1 //DC引脚,分辨数据还是命令
    MX6UL_PAD_UART3_RTS_B__GPIO1_IO27 0x10b1 //res复位引脚   
   >;
  };

其余都不是必要属性,但是如果驱动异常,需要进一步调试是不是缺了某些属性。

下面列举了从设备设备树节点可选属性:

属性 描述
spi-cpha spi->mode = SPI_CPHA CPHA=1
spi-cpol spi->mode = SPI_CPOL CPOL=1
spi-3wire spi->mode = SPI_3WIRE 使用三线SPI
spi-lsb-first spi->mode = SPI_LSB_FIRST 一般spi是MSB,指定后LSB
spi-cs-high spi->mode = SPI_CS_HIGH 一般片选CS是低有效,指定后高有效
spi-tx-bus-width spi->mode = SPI_NO_TX 发送方向为0,可能只是读
= SPI_TX_DUAL DUAL SPI 双线半双工
= SPI_TX_QUAD QUAD SPI 四线半双工
= SPI_TX_OCTAL OCTAL SPI 八线半双工
spi-rx-bus-width spi->mode = SPI_NO_RX 接受方向为0,可能只是发送
= SPI_RX_DUAL DUAL SPI 双线半双工
= SPI_RX_QUAD QUAD SPI 四线半双工
= SPI_RX_OCTAL OCTAL SPI 八线半双工
reg spi->chip_select 表示spi设备在第几个片选
spi-max-frequency spi->max_speed_hz 这个spi设备使用的spi传输速率,单位Hz

说说为什么这么设置:

SPI

查看ssd13306手册,SPI接口的OLED使用的时钟周期最大是100ns,也就是频率为10M的SPI时钟,因此设置spi-max-frequency = <10000000>;

时钟极性是空闲时为高电平,因此spi-cpol = 1,填上spi-cpol;

数据在第二个边沿锁定,因此spi-cpal = 1,填上spi-cpal;

二、编写设备驱动

编写成一个字符设备驱动,提供接口供上层调用。本驱动会不断完善,加入各种新知识运用进来。说说要点:

创建设备节点三件套

需要注册字符设备,创建类,创建设备节点

oled_dev- >major = register_chrdev(0, "ssd13306", &ssd13306_fops);
...

oled_dev- >oled_class = class_create(THIS_MODULE, "ssd13306");
 ...
device_create(oled_dev- >oled_class,NULL, MKDEV(oled_dev- >major, 0), NULL, "ssd13306");

获取使用到的gpiod

gpiod是较新的gpio描述符,OLED使用到cs、dc、reset三个gpio。

其中dc引脚和SPI协议无关,只和OLED这个模块相关,用来区分发送的是命令还是显示数据。

cs引脚不需要我们自己管理,实际上架构已经为我们获取了。

oled_dev- >dc_gpio = gpiod_get(&spi- >dev, "dc", GPIOD_OUT_LOW);
 //初始化dc引脚
dc_gpio_init(oled_dev- >dc_gpio);

oled_dev- >reset_gpio = gpiod_get(&spi- >dev, "reset", GPIOD_OUT_HIGH);

SPI发送命令函数

需要发送命令初始化oled,使用spi_write这个SPI架构提供的API可以以同步的方式发送SPI数据,经过源码研究,其实无所谓同步异步了,现架构都是使用异步的,都使用工作者线程来完成spi的传输管理。

static void ssd13306_write_cmd(struct ssd13306_oled *ssd13306,unsigned char cmd)
{
 
 int ret = 0;
 dc_gpio_set_value(g_oled- >dc_gpio,0);
 
 ret = spi_write(ssd13306- >ssd13306, &cmd, 1);
 if(ret)
  dev_err(&ssd13306- >ssd13306- >dev,"err spi write cmd(%d)",ret);
}

//spi 发送函数的原型
spi_write(struct spi_device *spi, const void *buf, size_t len)

dc_gpio_set_value(g_oled->dc_gpio,0)

dc gpio拉低,表示接下来发送的都是命令。

硬件初始化,刷屏

硬件初始化只针对ssd13306,其他OLED模块另外寻找初始化序列。

static void ssd13306_hw_init(struct ssd13306_oled *ssd13306)
{
 oled_reset(ssd13306);
 ssd13306_write_cmd(ssd13306,0xAE);//关闭oled显示
 ssd13306_write_cmd(ssd13306,0xd5);//设置时钟分频因子
 ssd13306_write_cmd(ssd13306,80);//[3:0]分频因子,[7:4]震荡频率
 ssd13306_write_cmd(ssd13306,0xa8);//设置驱动路数
 ssd13306_write_cmd(ssd13306,0x3f);
 ssd13306_write_cmd(ssd13306,0xd3);//设置显示偏移
 ssd13306_write_cmd(ssd13306,0x00);//设置显示偏移
 ssd13306_write_cmd(ssd13306,0x40);//设置显示开始行
 ssd13306_write_cmd(ssd13306,0x8d);//设置电荷泵
 ssd13306_write_cmd(ssd13306,0x14);//bit2开启或关闭
 ssd13306_write_cmd(ssd13306,0x20);//设置寻址模式
 ssd13306_write_cmd(ssd13306,0x2);//0x0列地址模式;0x1行地址模式;0x2页地址模式
 ssd13306_write_cmd(ssd13306,0xa1);//左右镜像
 ssd13306_write_cmd(ssd13306,0xc8);//上下镜像
 ssd13306_write_cmd(ssd13306,0xda);//设置com硬件引脚配置
 ssd13306_write_cmd(ssd13306,0x12);
 ssd13306_write_cmd(ssd13306,0x81);//亮度设置
 ssd13306_write_cmd(ssd13306,0xff);
 ssd13306_write_cmd(ssd13306,0xd9);//设置预充电周期
 ssd13306_write_cmd(ssd13306,0xf1);
 ssd13306_write_cmd(ssd13306,0xdb);//设置电压倍率
 ssd13306_write_cmd(ssd13306,0x30);//
 ssd13306_write_cmd(ssd13306,0xa4);//全局显示开启bit0 :0关闭,1开启
 ssd13306_write_cmd(ssd13306,0xa6);//设置显示方式,bit0 :0正常模式,1反相模式
 ssd13306_write_cmd(ssd13306,0xaf);//开启oled显示
}

static void oled_clear(unsigned char filldata)
{
 int page;
 int col;
 unsigned char * data;
 
 data = kmalloc(1, GFP_KERNEL);
 *data = filldata;

 for(page=0;page< 8;page++)
    {   
        ssd13306_write_cmd (g_oled,0xb0+page);    //设置页地址(0~7)
        ssd13306_write_cmd (g_oled,0x0);      //设置显示位置列低地址
        ssd13306_write_cmd (g_oled,0x10);      //设置显示位置列高地址
        for(col=0;col< 128;col++)ssd13306_write_datas(g_oled,data,1);
    }

 kfree(data);
}

其余剩下代码的完善,可以参考OLED裸机的代码添加。就仅仅学习linux SPI设备驱动来说,到这里应该就足够了。

三、完整代码

#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/spi/spi.h >
#include < linux/spi/spidev.h >
#include < linux/uaccess.h >
#include < linux/gpio/consumer.h >
#include < linux/gpio.h >
#include < linux/delay.h >

#define OLED_IOCTL_INIT 1
#define OLED_IOCTL_S_POS 2

struct ssd13306_oled {
 struct spi_device *ssd13306;
 struct class *oled_class;
 struct gpio_desc *dc_gpio;
 struct gpio_desc *reset_gpio;
 int major;
 int minor;
};

struct ssd13306_oled *g_oled;

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
};      

unsigned char nao1[]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xC0,0x40,0x40,0x40,
0xC0,0x40,0xC0,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0xE0,0x30,0x18,0x08,0x98,
0xF0,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x7E,
0xC1,0x86,0x86,0xCC,0x4C,0x20,0x1C,0x03,0x00,0x00,0x1C,0x2A,0x3E,0x2B,0x1D,0xE1,
0x53,0xF3,0x52,0xA4,0x46,0x0B,0x18,0x1C,0x23,0xC1,0xA1,0x31,0x0F,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x06,0x04,0xE8,0x38,0x18,0x80,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x00,0xC0,0x20,0x10,0x10,0x10,0x20,
0xC1,0x06,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xE0,0xC0,0x00,0x00,0x00,0x00,
0x00,0x3F,0x60,0xC0,0x83,0x04,0x08,0x18,0x10,0x10,0x10,0x18,0x08,0x04,0x00,0x00,
0x01,0x02,0x04,0x04,0x04,0x82,0xC1,0x38,0x0F,0x00,0x00,0x00,0x00,0xC0,0xE0,0x80,
0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,
0x01,0x01,0x03,0x03,0x06,0x84,0xFC,0x1C,0x06,0x01,0x01,0x03,0x02,0x06,0x04,0x04,
0x04,0x0C,0x0C,0x0C,0x0C,0x0C,0x04,0x04,0x06,0x02,0x03,0x03,0x0E,0x78,0xCC,0x06,
0x86,0x43,0x43,0x81,0x03,0x03,0x80,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x40,0xA0,0x20,0x20,0x20,0x20,0x40,0xC0,0x00,0x00,0xF8,0x1F,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x03,0xFE,0xE1,0x22,0x22,0x12,0x0B,0x0F,0xC0,0x60,0x10,0x10,
0x10,0x90,0x60,0x00,0x00,0x00,0x80,0x40,0x41,0x42,0x42,0x42,0x42,0x43,0xC0,0x06,
0x07,0x04,0x04,0x04,0xC4,0x44,0x44,0x74,0x14,0x1C,0x1C,0xF4,0x04,0x04,0x04,0x04,
0x84,0x44,0x44,0x74,0x14,0x1C,0x1C,0xF4,0x04,0x04,0x04,0x04,0x07,0x00,0x02,0x60,
0x60,0xA0,0x21,0x21,0x21,0x21,0x21,0x20,0xC0,0x00,0x00,0x07,0x08,0x08,0x08,0x08,
0x08,0x04,0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04,0x04,0x04,0x04,0x04,
0x04,0x07,0x00,0x00,0x00,0x00,0x03,0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x04,0x04,0x04,0x04,0x03,0x00,
};

static void dc_gpio_init(struct gpio_desc *dc_gpio)
{
 gpiod_direction_output(dc_gpio, 1);
}

static void oled_reset(struct ssd13306_oled *ssd13306)
{
 gpiod_direction_output(ssd13306- >reset_gpio, 0);
 msleep(300);
 gpiod_direction_output(ssd13306- >reset_gpio, 1);
 msleep(300);
 gpiod_direction_output(ssd13306- >reset_gpio, 0);
}

static void dc_gpio_set_value(struct gpio_desc *dc_gpio,int value)
{
 gpiod_set_value(dc_gpio,value);
}

static void ssd13306_write_cmd(struct ssd13306_oled *ssd13306,unsigned char cmd)
{
 
 int ret = 0;
 dc_gpio_set_value(g_oled- >dc_gpio,0);
 
 ret = spi_write(ssd13306- >ssd13306, &cmd, 1);
 if(ret)
  dev_err(&ssd13306- >ssd13306- >dev,"err spi write cmd(%d)",ret);
}

static void ssd13306_write_datas(struct ssd13306_oled *ssd13306,unsigned char *data,int len)
{
 int ret = 0;;
 dc_gpio_set_value(g_oled- >dc_gpio,1);

 
 ret = spi_write(g_oled- >ssd13306, data, len);
 if(ret)
  dev_err(&ssd13306- >ssd13306- >dev,"err spi write data(%d)",ret);
 

}

static void ssd13306_hw_init(struct ssd13306_oled *ssd13306)
{

 oled_reset(ssd13306);
 ssd13306_write_cmd(ssd13306,0xAE);//关闭oled显示
 ssd13306_write_cmd(ssd13306,0xd5);//设置时钟分频因子
 ssd13306_write_cmd(ssd13306,80);//[3:0]分频因子,[7:4]震荡频率
 ssd13306_write_cmd(ssd13306,0xa8);//设置驱动路数
 ssd13306_write_cmd(ssd13306,0x3f);
 ssd13306_write_cmd(ssd13306,0xd3);//设置显示偏移
 ssd13306_write_cmd(ssd13306,0x00);//设置显示偏移
 ssd13306_write_cmd(ssd13306,0x40);//设置显示开始行
 ssd13306_write_cmd(ssd13306,0x8d);//设置电荷泵
 ssd13306_write_cmd(ssd13306,0x14);//bit2开启或关闭
 ssd13306_write_cmd(ssd13306,0x20);//设置寻址模式
 ssd13306_write_cmd(ssd13306,0x2);//0x0列地址模式;0x1行地址模式;0x2页地址模式
 ssd13306_write_cmd(ssd13306,0xa1);//左右镜像
 ssd13306_write_cmd(ssd13306,0xc8);//上下镜像
 ssd13306_write_cmd(ssd13306,0xda);//设置com硬件引脚配置
 ssd13306_write_cmd(ssd13306,0x12);
 ssd13306_write_cmd(ssd13306,0x81);//亮度设置
 ssd13306_write_cmd(ssd13306,0xff);
 ssd13306_write_cmd(ssd13306,0xd9);//设置预充电周期
 ssd13306_write_cmd(ssd13306,0xf1);
 ssd13306_write_cmd(ssd13306,0xdb);//设置电压倍率
 ssd13306_write_cmd(ssd13306,0x30);//
 ssd13306_write_cmd(ssd13306,0xa4);//全局显示开启bit0 :0关闭,1开启
 ssd13306_write_cmd(ssd13306,0xa6);//设置显示方式,bit0 :0正常模式,1反相模式
 ssd13306_write_cmd(ssd13306,0xaf);//开启oled显示
 
 
}

static void ssd13306_set_pos(struct ssd13306_oled *ssd13306,int x, int y)
{
   ssd13306_write_cmd(ssd13306,0xb0+y);
   ssd13306_write_cmd(ssd13306,(x&0x0f));
   ssd13306_write_cmd(ssd13306,((x&0xf0) > >4)|0x10);
}

static long ssd13306_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
 int x,y =0;
 switch (cmd){

 case  OLED_IOCTL_INIT :{
  dc_gpio_init(g_oled- >dc_gpio);
  ssd13306_hw_init(g_oled);
  break;  
 }

 case  OLED_IOCTL_S_POS :{
  x = arg & 0xff;
  y = (arg > > 8)&0xff;
  ssd13306_set_pos(g_oled,x,y);
  break;  
 }

 default :
  break;
 }

 return 0;
}

static ssize_t ssd13306_write (struct file *filp, const char __user *ubuf, size_t count, loff_t * f_pos)
{
 int done;
 char kbuf[count];
 dc_gpio_set_value(g_oled- >dc_gpio, 1);
 copy_from_user(kbuf, ubuf, count);

 done = spi_write(g_oled- >ssd13306, kbuf, count);

 return done;
}

static void oled_clear(unsigned char filldata)
{
 int page;
 int col;

 unsigned char * data;
 
 data = kmalloc(1, GFP_KERNEL);
 *data = filldata;

 
 for(page=0;page< 8;page++)
    {  
        ssd13306_write_cmd (g_oled,0xb0+page);    //设置页地址(0~7)
        ssd13306_write_cmd (g_oled,0x0);      //设置显示位置列低地址
        ssd13306_write_cmd (g_oled,0x10);      //设置显示位置列高地址
        for(col=0;col< 128;col++)ssd13306_write_datas(g_oled,data,1);
    }

 kfree(data);
}

static void oled_Set_Pos(int x, int y)

{  ssd13306_write_cmd(g_oled,0xb0+y);
 ssd13306_write_cmd(g_oled,(x&0x0f)); 
 ssd13306_write_cmd(g_oled,((x&0xf0) > >4)|0x10);
}

void oled_draw_char(int x, int y, unsigned char c)
{
 int i = 0;
 /* 得到字模 */
 const unsigned char *dots = oled_asc2_8x16[c - ' '];

 /* 发给OLED */
 oled_Set_Pos(x, y);
 /* 发出8字节数据 */
 //for (i = 0; i < 8; i++)
 // oled_write_cmd_data(dots[i], OLED_DATA);
 ssd13306_write_datas(g_oled,&dots[0], 8);

 oled_Set_Pos(x, y+1);
 /* 发出8字节数据 */
 //for (i = 0; i < 8; i++)
  //oled_write_cmd_data(dots[i+8], OLED_DATA);
 ssd13306_write_datas(g_oled,&dots[8], 8);
}

static void oled_drawstring(u8 x,u8 y,const char *p)
{ 
    while((*p<='~')&&(*p >=' '))//判断是不是非法字符!
    {       
        if(x < 0 || x > 128 || y < 0 ||y > 128)
   break;
        oled_draw_char(x,y,*p);  
        x+=16/2;
        p++;
    }  
 
}

void oled_drawbmp(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
{  
 unsigned int j=0;
 unsigned char x,y;
  
  if(y1%8==0) y=y1/8;      
  else y=y1/8+1;
 for(y=y0;y< y1;y++)
 {
  oled_Set_Pos(x0,y);
    for(x=x0;x< x1;x++)
     {      
      ssd13306_write_datas(g_oled,&BMP[j++],1);      
     }
 }
} 

void oled_ver_scoll(int start_pg,int end_pg,int frame,int dir)
{
 ssd13306_write_cmd(g_oled,0x2e);
 ssd13306_write_cmd(g_oled,dir ? 0x27:0x26);
 ssd13306_write_cmd(g_oled,0x0);
 ssd13306_write_cmd(g_oled,start_pg );
 ssd13306_write_cmd(g_oled,frame );
 ssd13306_write_cmd(g_oled,end_pg );
 ssd13306_write_cmd(g_oled,0x0 );
 ssd13306_write_cmd(g_oled,0xff );
 

 ssd13306_write_cmd(g_oled, 0x2f);
}

static const struct file_operations ssd13306_fops = {
 .owner = THIS_MODULE,
 .write = ssd13306_write,
 //.read =  spidev_read,
 .unlocked_ioctl = ssd13306_ioctl,
 //.compat_ioctl = spidev_compat_ioctl,
 //.open =  spidev_open,
 //.release = spidev_release,
 //.llseek = no_llseek,
};

#ifdef CONFIG_OF
static const struct of_device_id spi_oled_dt_ids[] = {
 { .compatible = "Justice,ssd13306" },
 {},
};
MODULE_DEVICE_TABLE(of, spi_oled_dt_ids);
#endif
static int oledshow_thread(void *data)
{
 static unsigned char  i = 0xff;
 while(1){
  
  oled_clear(0);
  msleep(100);
  oled_drawbmp(0,0,54,8,nao1);
  oled_drawbmp(60,0,114,8,nao1);
  msleep(100);
 }
 return 0;
}

static int ssd13306_probe(struct spi_device *spi)
{
 struct ssd13306_oled *oled_dev; 
 
 oled_dev = kmalloc(sizeof(*oled_dev), GFP_KERNEL);
 g_oled = oled_dev;
 oled_dev- >ssd13306 = spi;
 oled_dev- >major = register_chrdev(0, "ssd13306", &ssd13306_fops);
 if (oled_dev- >major < 0)
  return -EINVAL;

 oled_dev- >oled_class = class_create(THIS_MODULE, "ssd13306");
 if (IS_ERR(oled_dev- >oled_class)) {
  unregister_chrdev(oled_dev- >major, "ssd13306");
  return PTR_ERR(oled_dev- >oled_class);
 }

 device_create(oled_dev- >oled_class, 
   NULL, MKDEV(oled_dev- >major, 0), NULL, "ssd13306");

 oled_dev- >dc_gpio = gpiod_get(&spi- >dev, "dc", GPIOD_OUT_LOW);
 //初始化dc引脚
 dc_gpio_init(oled_dev- >dc_gpio);

 oled_dev- >reset_gpio = gpiod_get(&spi- >dev, "reset", GPIOD_OUT_HIGH);

 ssd13306_hw_init(oled_dev);

 oled_clear(0x0);
 
 //oled_draw_Char(0, 30, 'L');
 //oled_drawbmp(0,0,54,8,nao1);//画一个小猪佩奇
 
 oled_drawstring(0,0,"hello justice");
 //oled_ver_scoll(0,7,256,0);
 //kthread_run(oledshow_thread,NULL,"oledthread");
 printk("ssd13306 probe done!\\n");
 spi_set_drvdata(spi,oled_dev);
 return 0;
}

static int ssd13306_remove(struct spi_device *spi)
{ 
 struct ssd13306_oled *oled_dev = spi_get_drvdata(spi);

 //kthread_del(oledshow_thread,NULL,"oledthread");
 gpiod_put(oled_dev- >dc_gpio);
 gpiod_put(oled_dev- >reset_gpio);
 device_destroy(oled_dev- >oled_class, MKDEV(oled_dev- >major, 0));
 
 class_destroy(oled_dev- >oled_class);

 unregister_chrdev(oled_dev- >major, "ssd13306");
 oled_clear(0x0);
 return 0;
}

static struct spi_driver oled_spi_driver = {
 .driver = {
  .name =  "ssd13306_drv",
  .of_match_table = of_match_ptr(spi_oled_dt_ids),
  
 },
 .probe = ssd13306_probe,
 .remove = ssd13306_remove,
};

static int __init spidev_init(void)
{
 int status;
 

 status = spi_register_driver(&oled_spi_driver);
 if (status < 0) {
  pr_err("spidev_init\\n");
  return status;
 }
 return status;
}
module_init(spidev_init);

static void __exit spidev_exit(void)
{
 spi_unregister_driver(&oled_spi_driver);
 
}
module_exit(spidev_exit);
MODULE_LICENSE("GPL");
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分