Linux驱动 | OLED显示模块驱动(SPI)

SPI子系统

linux 驱动 | SPI子系统_★_仰望星空_★的博客-CSDN博客
https://blog.csdn.net/qq_36413982/article/details/123783511

OLED显示模块

  • 0.96寸
  • 分辨率128 x 64
  • 使用驱动芯片SSD1306
  • 支持I2C、4线SPI、3线SPI接口

在这里插入图片描述
OLED模块硬件接口具有6个引脚:

  • VCC
  • GND
  • SCL,无论是SPI还是I2C接口时,它都是始终引脚
  • SDA,当使用I2C接口,它是I2C的SDA,当使用SPI,它是MOSI
  • RST,复位引脚
  • DC,命令数据选择引脚
    • 当DC引脚拉低,表示写入的是命令
    • 当DC引脚拉高,表示写入的是数据

注意事项:

OLED 显示屏不同于 LCD,OLED 上电是没有反应的,需要程序驱动才会有显示!

具体如何编程查看SSD1306芯片手册


设备树


OLED驱动实现

1、OLED硬件操作

spi写操作:

static int oled_spi_write(char *buf, uint16_t len)
{
    int status;
#if 0
    struct spi_message msg;
	struct spi_transfer xfer = {
		.len = len,
		.tx_buf = buf,
	};

	spi_message_init(&msg);                       /* 初始化spi_message */
	spi_message_add_tail(&xfer, &msg);             /* 添加到传输队列 */
    status = spi_sync(oled_dev->spi, &msg);          /* 同步发送 */
#else
    status = spi_write(oled_dev->spi, buf, len);   
#endif
    return status;
}
  • spi_write,封装了spi_message_init、spi_message_add_tail和spi_sync步骤

OLED写操作:

enum {
    OLED_CMD = 0x00,
    OLED_DATA = 0x01,
};
typedef unsigned char oled_cmd_t;


static int oled_write_cmd_data(uint8_t data, oled_cmd_t cmd)
{
    int ret = 0;

    if (cmd == OLED_CMD)
        gpio_set_value(oled_dev->dc_gpio, OLED_CMD); /* 拉低,表示写入的是指令 */
    else
        gpio_set_value(oled_dev->dc_gpio, OLED_DATA); /* 拉高,表示写入数据 */
    ret = oled_spi_write(&data, sizeof(data));
    return ret;
}

static int oled_write_datas(uint8_t *datas, uint16_t len)
{
    int ret = 0;

    gpio_set_value(oled_dev->dc_gpio, OLED_DATA);
    ret = oled_spi_write(datas, len);
    return ret;
}

设置OLED显示坐标:

static int oled_set_pos(uint16_t x, uint16_t y)
{
    int ret = 0;

    ret = oled_write_cmd_data(0xb0 + y, OLED_CMD);
    ret = oled_write_cmd_data((x & 0x0f), OLED_CMD);
    ret = oled_write_cmd_data(((x & 0xf0) >> 4) | 0x10, OLED_CMD);
    return ret;
}

OLED复位 :

static void oled_set_rst(uint8_t on_off)
{
    gpio_set_value(oled_dev->rst_gpio, on_off);
}

static void oled_reset(void)
{
    oled_set_rst(0);
    mdelay(50);
    oled_set_rst(1);
}

OLED显示开关:

static void oled_disp_on_off(uint8_t on_off)
{
    if (on_off)
        oled_write_cmd_data(0xaf, OLED_CMD); /* set dispkay on */
    else
        oled_write_cmd_data(0xae, OLED_CMD); /* set dispkay off */
}

OLED清屏:

static void oled_disp_clear(void)
{
    uint8_t x, y;

    for (y = 0; y < 8; y++)
    {
        oled_set_pos(0, y);
        for (x = 0; x < 128; x++)
            oled_write_cmd_data(0, OLED_DATA); /* 清零 */
    }
}

OLED初始化:

static void oled_disp_test(void)
{
    uint8_t x, y;

    for (y = 0; y < 8; y++)
    {
        oled_set_pos(0, y);
        for (x = 0; x < 128; x++)
        {
            if (x % 2 == 0)
                oled_write_cmd_data(0, OLED_DATA);
            else
                oled_write_cmd_data(1, OLED_DATA);
        }
    }
}
static int oled_init(void)
{
    int ret = 0;

    oled_reset();

    ret = oled_write_cmd_data(0xae, OLED_CMD); //关闭显示

    ret = oled_write_cmd_data(0x00, OLED_CMD); //设置 lower column address
    ret = oled_write_cmd_data(0x10, OLED_CMD); //设置 higher column address

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

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

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

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

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

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

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

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

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

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

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

    ret = oled_write_cmd_data(0xdb, OLED_CMD); // set vcomh
    ret = oled_write_cmd_data(0x30, OLED_CMD); //

    ret = oled_write_cmd_data(0x8d, OLED_CMD); // set charge pump disable
    ret = oled_write_cmd_data(0x14, OLED_CMD); //

    ret = oled_write_cmd_data(0xaf, OLED_CMD); // set dispkay on

    oled_disp_clear();
    oled_set_pos(0, 0);

    return ret;
}
  • oled_reset,初始化必须调用复位,不然显示不了
  • oled_disp_clear,初始化还需要清屏,不然可能初始化完屏幕会显示乱码。

2、OLED Linux SPI驱动框架

数据结构、命令定义:oled_def.h

#ifndef _OLED_DEF_H_
#define _OLED_DEF_H_

typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;

#define OLED_CMD_SET_XY                   0x01       /* 显示开关*/
#define OLED_CMD_WRITE_DATAS              0x02
#define OLED_CMD_SET_XY_WRITE_DATAS       0x03
#define OLED_CMD_DISP_ON_OFF              0x04

#define CMD_COMBINE(cmd, datasize)        (cmd | (datasize << 8))         /* 命令和数据大小组合 */

struct oled_disp_buffer {
    uint8_t x;
    uint8_t y;
    uint16_t len;
    uint8_t *buffer;
};
typedef struct oled_disp_buffer  oled_disp_buf_t;

#endif /* _OLED_DEF_H_ */

核心的file_operations:


//static ssize_t _drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
//{
//    int ret = 0;

//    oled_disp_test();

//    printk("%s %s\r\n", __FUNCTION__, DEV_NAME);

//    ret = size;
//    return ret;
//}

static long _drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    uint8_t buf[3];
    uint16_t size;
    const void __user *userspace = (const void __user *)arg;

    switch (cmd & 0x0f)                /* 最低字节存放命令字段 */
    {
    case OLED_CMD_DISP_ON_OFF:
        ret = copy_from_user(&buf[0], userspace, 1);
        oled_disp_on_off(buf[0]);
        break;
    case OLED_CMD_SET_XY:
        ret = copy_from_user(&buf, userspace, 2);
        if (ret > 0) {
            ret = -EFAULT;
            goto exit;
        }
        // printk("x %d, y %d\r", buf[0], buf[1]);
        oled_set_pos(buf[0], buf[1]);
        break;
    case OLED_CMD_WRITE_DATAS:
        size = (uint16_t)(cmd & 0xffffff00);        /* 前三字节存放数据大小 */
        size >>= 8;
        // printk("size %d\r", size);
        ret = copy_from_user(oled_dev->databuf, userspace, size);
        if (ret > 0) {
            ret = -EFAULT;
            goto exit;
        }
        oled_write_datas(oled_dev->databuf, size);
    case OLED_CMD_SET_XY_WRITE_DATAS:
        ret = copy_from_user(buf, userspace, size);
        if (ret > 0) {
            ret = -EFAULT;
            goto exit;
        }
        break;
    }

exit:
    return ret;
}

static int _drv_release(struct inode *node, struct file *filp)
{
    struct oled_device *tmp_oled = filp->private_data;

    oled_disp_on_off(0);

    oled_reset();

    return 0;
}

static struct file_operations oled_drv_ops = {
    .owner = THIS_MODULE,
    .open = _drv_open,
//    .read = _drv_read,
    .unlocked_ioctl = _drv_ioctl,
    .release = _drv_release,
};

probe和remove实现:

static int _driver_probe(struct spi_device *spi)
{
    int err = 0;
    struct device *_dev;
    struct device_node *_dts_node;
    // struct device_node *oled_dev_node;

    oled_dev = (struct oled_device *)kzalloc(sizeof(struct oled_device), GFP_KERNEL);
    if (!oled_dev)
    {
        printk("can't kzalloc mpu6050 dev\n");
        return -ENOMEM;
    }

    _dts_node = spi->dev.of_node;
    if (!_dts_node)
    {
        printk("oled espi can not found!\r\n");
        err = -EINVAL;
        goto exit_free_dev;
    }

    oled_dev->dc_gpio = of_get_named_gpio(_dts_node, DC_GPIO_DTS_NAME, 0); /* 获取dc_gpio */
    if (!gpio_is_valid(oled_dev->dc_gpio))
    {
        printk("don't get oled %s!!!\n", DC_GPIO_DTS_NAME);
        err = -EINVAL;
        goto exit_free_dev;
    }
    printk("oled dc-gpio %d", oled_dev->dc_gpio);
    gpio_direction_output(oled_dev->dc_gpio, 1); /* 设置gpio为输入 */

    oled_dev->rst_gpio = of_get_named_gpio(_dts_node, RST_GPIO_DTS_NAME, 0); /* 获取rst_gpio */
    if (!gpio_is_valid(oled_dev->rst_gpio))
    {
        printk("don't get oled %s!!!\n", RST_GPIO_DTS_NAME);
        err = -EINVAL;
        goto exit_free_dev;
    }
    printk("oled dc-gpio %d", oled_dev->rst_gpio);
    gpio_direction_output(oled_dev->rst_gpio, 1); /* 设置gpio为输入 */

    /* 内核自动分配设备号 */
    err = alloc_chrdev_region(&oled_dev->dev_no, 0, 1, DEV_NAME);
    if (err < 0)
    {
        pr_err("Error: failed to register oled, err: %d\n", err);
        goto exit_free_dev;
    }

    cdev_init(&oled_dev->chrdev, &oled_drv_ops);
    err = cdev_add(&oled_dev->chrdev, oled_dev->dev_no, 1);
    if (err)
    {
        printk("cdev add failed\r\n");
        goto exit_unregister;
    }

    oled_dev->class = class_create(THIS_MODULE, DEV_NAME);
    if (IS_ERR(oled_dev->class))
    {
        err = PTR_ERR(oled_dev->class);
        goto exit_cdev_del;
    }

    /* 创建设备节点 */
    _dev = device_create(oled_dev->class, NULL, oled_dev->dev_no, NULL, DEV_NAME);
    if (IS_ERR(_dev))
    { /* 判断指针是否合法 */
        err = PTR_ERR(_dev);
        goto exit_class_del;
    }

    oled_dev->spi = spi;

    mutex_init(&oled_dev->m_lock); /* 初始化互斥锁  */

    printk("%s probe success\r\n", DEV_NAME);

    goto exit;

exit_class_del:
    class_destroy(oled_dev->class);
exit_cdev_del:
    cdev_del(&oled_dev->chrdev);
exit_unregister:
    unregister_chrdev_region(oled_dev->dev_no, 1); /* 注销设备 */
exit_free_dev:
    kfree(oled_dev);
    oled_dev = NULL;
exit:
    return err;
}

static int _driver_remove(struct spi_device *spi)
{
    int ret = 0;

    device_destroy(oled_dev->class, oled_dev->dev_no);
    class_destroy(oled_dev->class);
    cdev_del(&oled_dev->chrdev);
    unregister_chrdev_region(oled_dev->dev_no, 1); /* 注销设备 */
    kfree(oled_dev);

    printk(KERN_INFO "%s remove success\n", DEV_NAME);

    return ret;
}

出口入口函数实现:

/* 设备树的匹配列表 */
static struct of_device_id dts_match_table[] = {
    {.compatible = OLED_DTS_COMPATIBLE}, /* 通过设备树来匹配 */
    {},
};

/* 传统匹配方式 ID 列表 */
static const struct spi_device_id spi_dev_id[] = {
    {.name = OLED_DTS_COMPATIBLE, 0},
    {}};

/* SPI 驱动结构体 */
static struct spi_driver oled_driver = {
    .probe = _driver_probe,
    .remove = _driver_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = OLED_DTS_COMPATIBLE,
        .of_match_table = dts_match_table,
    },
    .id_table = spi_dev_id,
};

module_spi_driver(oled_driver);

MODULE_AUTHOR("Ares");
MODULE_LICENSE("GPL");

3、驱动编译

oled\Makefile :

KERN_DIR = /home/ares/work/ebf_linux_kernel-ebf_4.19.35_imx6ul

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o oled_test oled_test.c
clean:
	make -C $(KERN_DIR) M=`pwd` clean
	rm -rf modules.order  oled_test

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o


obj-m += oled_drv.o

执行make


测试程序

#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <stdint.h>
#include <stdlib.h>

#include "oled_def.h"
#include "font.h"

#define DEV_NAME   "/dev/oled"

int oled_fd;

void sleep_ms(unsigned int ms)
{
    struct timeval delay;
	delay.tv_sec = 0;
	delay.tv_usec = ms * 1000; 
	select(0, NULL, NULL, NULL, &delay);
}


void oled_disp_char(int x, int y, unsigned char c)
{
	int i = 0;
	/* 得到字模 */
	const unsigned char *dots = oled_asc2_8x16[c - ' '];
	char pos[2];
#if 0
	/* 发给OLED */
	OLED_DIsp_Set_Pos(x, y);
	/* 发出8字节数据 */
	for (i = 0; i < 8; i++)
		oled_write_cmd_data(dots[i], OLED_DATA);
#endif
	pos[0] = x;
	pos[1] = y;

	ioctl(oled_fd, CMD_COMBINE(OLED_CMD_SET_XY, 2), &pos);
	ioctl(oled_fd, CMD_COMBINE(OLED_CMD_WRITE_DATAS, 8), dots);

#if 0
	OLED_DIsp_Set_Pos(x, y+1);
	/* 发出8字节数据 */
	for (i = 0; i < 8; i++)
		oled_write_cmd_data(dots[i+8], OLED_DATA);
#endif	
	pos[0] = x;
	pos[1] = y+1;
	ioctl(oled_fd, CMD_COMBINE(OLED_CMD_SET_XY, 2), pos);
	ioctl(oled_fd, CMD_COMBINE(OLED_CMD_WRITE_DATAS, 8), &dots[8]);
}

void oled_disp_string(uint8_t x, uint8_t y, char *str)
{
	uint8_t j = 0;

	while (str[j])
	{		
		oled_disp_char(x, y, str[j]);  /* 显示单个字符 */
		x += 8;
		if(x > 127)
		{
			x = 0;
			y += 2;
		}  
		j++;    /* 移动显示位置 */
	}
}

static void oled_test(void)
{
	oled_disp_string(0, 0, "Sad!");
	oled_disp_string(0, 2, "Bad!");
	oled_disp_string(0, 4, "Moonlight");
}

int main(int argc, char **argv)
{

    int ret;

	/* 2. 打开文件 */
	oled_fd = open(DEV_NAME, O_RDWR | O_NONBLOCK);   // | O_NONBLOCK

	if (oled_fd < 0)
	{
		printf("can not open file %s, %d\n", DEV_NAME, oled_fd);
		return -1;
	}
    
	oled_test();

	sleep_ms(5000);
    
	close(oled_fd);
    
    return 0;
}

测试:

sudo insmod oled_drv.ko      /* 安装驱动 */
sudo ./oled_test             /* 执行app程序 */
sudo rmmod oled_drv.ko       /* 卸载驱动 */

在这里插入图片描述

  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欲盖弥彰1314

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值