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 0struct 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);          /* 同步发送 */
#elsestatus = spi_write(oled_dev->spi, buf, len);
#endifreturn 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); /* 拉低,表示写入的是指令 */elsegpio_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 */elseoled_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);elseoled_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 addressret = oled_write_cmd_data(0x10, OLED_CMD); //设置 higher column addressret = oled_write_cmd_data(0x40, OLED_CMD); //设置 display start lineret = oled_write_cmd_data(0xB0, OLED_CMD); //设置page addressret = oled_write_cmd_data(0x81, OLED_CMD); // contract controlret = oled_write_cmd_data(0x66, OLED_CMD); // 128ret = oled_write_cmd_data(0xa1, OLED_CMD); //设置 segment remapret = oled_write_cmd_data(0xa6, OLED_CMD); // normal /reverseret = oled_write_cmd_data(0xa8, OLED_CMD); // multiple ratioret = oled_write_cmd_data(0x3f, OLED_CMD); // duty = 1/64ret = oled_write_cmd_data(0xc8, OLED_CMD); // com scan directionret = oled_write_cmd_data(0xd3, OLED_CMD); // set displat offsetret = oled_write_cmd_data(0x00, OLED_CMD); //ret = oled_write_cmd_data(0xd5, OLED_CMD); // set osc divisionret = oled_write_cmd_data(0x80, OLED_CMD); //ret = oled_write_cmd_data(0xd9, OLED_CMD); // ser pre-charge periodret = oled_write_cmd_data(0x1f, OLED_CMD); //ret = oled_write_cmd_data(0xda, OLED_CMD); // set com pinsret = oled_write_cmd_data(0x12, OLED_CMD); //ret = oled_write_cmd_data(0xdb, OLED_CMD); // set vcomhret = oled_write_cmd_data(0x30, OLED_CMD); //ret = oled_write_cmd_data(0x8d, OLED_CMD); // set charge pump disableret = oled_write_cmd_data(0x14, OLED_CMD); //ret = oled_write_cmd_data(0xaf, OLED_CMD); // set dispkay onoled_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_imx6ulall:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o oled_test oled_test.c
clean:make -C $(KERN_DIR) M=`pwd` cleanrm -rf modules.order  oled_test# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.oobj-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);
#endifpos[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 0OLED_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_NONBLOCKif (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       /* 卸载驱动 */

Linux驱动 | OLED显示模块驱动(SPI)相关推荐

  1. OLED显示模块的电路连接和驱动(树莓派)

    一.电路连接 OLED显示模块焊接上I2C仅有四个引脚,分别为接电(+3V).接地(GND).SCL和SDA,分别利用母对母杜邦线将SCL和SDA接口接入树莓派GPIO3和GPIO2口,接地和接电分别 ...

  2. OLED显示模块介绍以及OLED显示模块使用注意事项

    1)什么是OLED显示模块: OLED显示模块为OLED显示屏+PCB+铁框构成. OLED显示屏是指有机电激发光二极管(OrganicLight-EmittingDiode,OLED)由于同时具备自 ...

  3. OLED 显示模块程序详解

    OLED 显示模块   最后代码已上传!!!!   代码文章链接    github 代码链接在该文章最后 如果还没有看过I2C 协议的,请移步先看下面这篇文章  I2C协议 好的,现在我们进入正题 ...

  4. STM32CubeMX驱动OLED屏模块_IIC

    文章目录 1. IIC-OLED模块简介 2. 移植源码到工程 3. 取字模软件的使用 3.1 汉字取模 3.2 图片取模 4. 驱动源码中函数介绍 4.1 初始化OLED屏 4.2 开启/关闭OLE ...

  5. void函数调用时显示不允许使用不完整的_4位数码管显示模块驱动

    TM1637四位数码管模块是一个带时钟点的4位共阳数码管(0.36英寸)的显示模块,驱动芯片为TM1637,驱动方式为IIC,因此只需2根信号线即可使单片机控制4位8段数码管(数码管8级亮度可调). ...

  6. Linux移植EC20 4G模块驱动简易教程

    关于4G模块常见的品牌与型号: 品牌 型号 市场价格 芯片组 状态 HUAWEI ME909s-821 210-260 海思芯片,只有五模 在售 HUAWEI ME909s-521 400左右 高通芯 ...

  7. linux下oled显示设计,OLED显示问题

    下面那个程序是正点原子的例程,例程里面有SPI和并口两种驱动方式,我现在只要并口的驱动方式,所以把关于SPI的都删了,但好像移植有错误,请各位大神帮我看看哪里出错了!还有一个问题是正点原子的oled例 ...

  8. ESP32设备驱动-TEA5767收音机模块驱动

    TEA5767收音机模块驱动 1.TEA5767介绍 TEA5767HN 是一款用于低压应用的单芯片电子调谐 FM 立体声收音机,具有完全集成的中频 (IF) 选择性和解调功能,频率范围从76-108 ...

  9. Arduino驱动oled显示动画

    1.模块介绍 I2C显示屏(驱动为ssd1306,分辨率为128*64) Arduino nano(Atmega328p) 2.模块连接 oled 开发板 GND GND VCC 3-5V SCL A ...

最新文章

  1. Python编译出现错误SyntaxError: Non-ASCII character '\xe7' 时解决方法
  2. 数字信道化中的兔耳效应
  3. IOS开发-GitHub使用详解
  4. python 录制web视频_Python django框架 web端视频加密的实例详解
  5. 配置Maven使用Nexus
  6. BZOJ 1101: [POI2007]Zap( 莫比乌斯反演 )
  7. Python+OpenCV:图像平滑
  8. [2005.04.24 20:47:26] 实践与思考之窥索[原创]
  9. mysql 不同的记录_Mysql通过一个限制条件,查出多条不同的记录
  10. 8.1.1使用BlockingQueue和ArrayBlockingQueue
  11. x264 编码数配置
  12. 经典排序算法(十九)--Flash Sort
  13. mysql索引之联合索引
  14. 如何写前端开发的改进建议书?
  15. 三星android p内测,两年前老机重生!三星开启Galaxy C9 Pro升级安卓8.0内测
  16. Linux-Shell脚本教学
  17. transforms及Compose
  18. php中如何过滤关键字,PHP - 过滤关键字
  19. Java中对两个对象进行比较大小
  20. kali WiFi密码破解分享

热门文章

  1. 筒仓计算表格_筒仓世界中的开源极客
  2. 自动驾驶在干线物流的商业化进展综述
  3. Android中自定义弧形的seekbar
  4. HbuilderX下载安装
  5. GGS ERROR 160 Bad column index
  6. C++-鼠标操作大全
  7. 坚持#第89天~胡晨晨会给予我力量!
  8. 上海计算机一级ps教程,计算机等级考试一级Photoshop图像合成教程
  9. 联想ThinkPad E14 如何修改BIOS开机画面LOGO
  10. JavaScript新的对象创建方式---Object.create()