目录

  • 1. 开发环境介绍
    • 1.1 野火的IMX.6ULL PRO开发板
    • 1.2 ST7735S TFT 液晶屏
  • 2. 连线说明
  • 3. 设备树节点
  • 3. 驱动程序编写步骤
    • 3.1 在驱动入口注册spi_driver, 在出口注销spi_driver
    • 3.2 在probe函数内部初始化液晶屏硬件设备,注册fire_oparation结构体
    • 3.3 编写基础的函数操作oled
    • 3.4 完善ioctl函数
  • 4. 将基础操作封装起来
    • 4.1 头文件
    • 4.2 主要的函数
  • 4. 效果

1. 开发环境介绍

1.1 野火的IMX.6ULL PRO开发板

移植了Linux5.4操作系统,所以本次的驱动是适用于该版本的,至于其他linux版本,有可能有些函数有差异,不过大部分都是一样的

1.2 ST7735S TFT 液晶屏

该液晶屏分辨率为128x128,使用SPI通信


2. 连线说明

从上一张图片中看出,我们需要使用该oled的VCC, GND, LED, CLK, SDI, RS, RST, CS总共8个引脚,每个引脚连接到linux开发板上。开发板有多个spi接口,我是用的spi1这个接口,而且在配置引脚复用的时候,配置的GPIO4.IO28(MISO), GPIO4.IO27(MOSI), GPIO4.IO25(CLK), GPIO4.IO26(SS)

3. 设备树节点

我的设备树默认并没有开启SPI,所以

&ecspi1 {fsl,spi-num-chipselects = <1>;cs-gpio = <&gpio4 26 GPIO_ACTIVE_LOW>;  /* 设置片选引脚,供子节点使用 */ pinctrl-names = "default";pinctrl-0 = <&pinctrl_ecspi1>;status = "okay";#address-cells = <1>;#size-cells = <0>; /* 上面这几行是配置SPI,如果你的SPI已经配置好了就不需要上面这几行 */oled: st7735s@0 {compatible = "fire,st7735s";reg = <0>;                        /* reg = <index>;  指定片选引脚,就是父节点的cs-gpio中第index个 */spi-max-frequency = <8000000>;     /* 指定设备的最高速度 *//* GPIO_ACTIVE_HIGH目的是指定有效电平, 使用gpiod_set_value()设置的是逻辑电平 */dc-gpio = <&gpio4 23 GPIO_ACTIVE_HIGH>;     /* 数据/命令配置引脚 */reset-gpio = <&gpio4 24 GPIO_ACTIVE_HIGH>;    /* 复位引脚 */};
};/* 配置引脚复用 */
&iomuxc {pinctrl_ecspi1:ecspi1grp {fsl,pins = <MX6UL_PAD_CSI_DATA05__ECSPI1_SS0            0x1a090MX6UL_PAD_CSI_DATA04__ECSPI1_SCLK           0x11090MX6UL_PAD_CSI_DATA06__ECSPI1_MOSI           0x11090MX6UL_PAD_CSI_DATA07__ECSPI1_MISO           0x11090>;};
};

3. 驱动程序编写步骤

3.1 在驱动入口注册spi_driver, 在出口注销spi_driver

static int __init oled_spi_init(void)
{int ret;ret = spi_register_driver(&oled_spi_driver);return 0;
}static void __exit oled_spi_exit(void)
{spi_unregister_driver(&oled_spi_driver);
}module_init(oled_spi_init);
module_exit(oled_spi_exit);
MODULE_LICENSE("GPL v2");

3.2 在probe函数内部初始化液晶屏硬件设备,注册fire_oparation结构体


static int oled_spi_probe(struct spi_device *spi)
{printk("===========%s %d=============\n", __FUNCTION__, __LINE__);oled_dev = spi;// 从设备树获取资源dc_pin = gpiod_get(&spi->dev, "dc", GPIOD_OUT_HIGH);reset_pin = gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH);if (dc_pin == NULL || dc_pin == NULL) {printk("=========引脚错误=======\n");return -1;}/* 注册字符设备 */major = register_chrdev(0, "oled", &oled_fops);  /* class_create */oled_class = class_create(THIS_MODULE, "oled_class");/* device_create */oled_device = device_create(oled_class, NULL, MKDEV(major, 0), NULL, "myoled");/* 初始化硬件 */Oled_Init();mdelay(100);//清屏Oled_Clear(WHITE);// 分配空间, 在iotcl函数内会使用到data_buf = vmalloc(128*128*2);if (data_buf == NULL) {printk("malloc error\n");return -1;}return 0;
}

3.3 编写基础的函数操作oled

包括写入8位指令,写入8位数据,oled复位等基础操作

//向液晶屏写一个8位指令
void Oled_WriteIndex(u8 cmd)
{int ret = 0;gpiod_set_value(dc_pin, 0);ret = spi_write(oled_dev, &cmd, 1);
}//向液晶屏写一个8位数据
void Oled_WriteData(u8 data)
{gpiod_set_value(dc_pin, 1);spi_write(oled_dev, &data, 1);
}// oled复位
void Oled_Reset(void)
{gpiod_set_value(reset_pin, 0);mdelay(100);gpiod_set_value(reset_pin, 1);mdelay(50);
}

3.4 完善ioctl函数

该函数的作用就是应用程序通过该函数来操作硬件。所以该函数应该根据应用程序的需要进行更改。

static long oled_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{void __user *from = (void __user *)arg;unsigned char param_buf[10];int size;int ret = 0;int i = 0, j = 0;int m = 0;switch(cmd & 0xff) {case OLED_GET_SCREEN_X_PIXEL:{// 获取x_maxreturn X_MAX_PIXEL;}case OLED_GET_SCREEN_Y_PIXEL:{ // 获取y_maxreturn Y_MAX_PIXEL;}case OLED_SET_REGION: // start_x, start_y, end_x, end_y{// 设置显示区域ret = copy_from_user(param_buf, from, 4);//printk("start: (%d, %d), end: (%d, %d)\n", param_buf[0], param_buf[1], param_buf[2], param_buf[3]);Oled_SetRegion(param_buf[0], param_buf[1], param_buf[2], param_buf[3]);//Oled_WriteIndex(0x2C);return 0;}case OLED_WRITE_COLOR:{// 写入Color数据size = cmd >> 8;//printk("size: %d\n", size);ret = copy_from_user(data_buf, from, size);for (m=0;m<size;m+=2) {Oled_WriteData_16Bit(data_buf[m] << 8 | data_buf[m+1]);}return 0;}case OLED_CEALR:        {   printk("clear screen use %lx\n", arg & 0xffff);// 清屏Oled_Clear(arg);return 0;}}return -ENXIO;
}

4. 将基础操作封装起来

如此封装的目的就是为了以后好操作,不需要去使用open, ioctl来操作,直接用封装好了的来操作oled

4.1 头文件

#ifndef __OLEDLIB_H
#define __OLEDLIB_H#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE  0x001F// 字体大小
#define ENGLISH_FONT_WIDTH      8
#define ENGLISH_FONT_HEIGHT     16
#define CHINESE_FONT_WIDTH      16
#define CHINESE_FONT_HEIGHT     16// 文字间隙
#define INTERVAL_X              2
#define INTERVAL_Y              1// ioctl 参数
#define OLED_SET_REGION             1
#define OLED_WRITE_COLOR            2
#define OLED_ALL_COLOR              3
#define OLED_CEALR                  4
#define OLED_DEBUG                  5
#define OLED_GET_SCREEN_X_PIXEL     6
#define OLED_GET_SCREEN_Y_PIXEL     7// 设备分辨率
#define X_MAX_PIXEL 128
#define Y_MAX_PIXEL 128// 提供的函数
int oled_open(char *devpath);                                                       // 打开设备
void oled_close(void);                                                              // 关闭设备
short rgb888_to_rgb565(int color);                                                  // rgb888---->rgb565
void oled_show_text(int start_x, int start_y, char *p);                             // 显示文字,可英文中文混合
void oled_set_font_color(unsigned short color);                                     // 设置字体显示
void oled_set_font_back(unsigned short color);                                      // 设置字体背景
int oled_clear_screen(unsigned short color);                                        // 指定颜色刷屏#define FONTDATAMAX 4096
// 英文字母(8x16)
static const unsigned char english_fontdata[FONTDATAMAX] = {/* 0 0x00 '^@' */0x00, /* 00000000 */0x00, /* 00000000 */0x00, /* 00000000 */0x00, /* 00000000 */0x00, /* 00000000 */0x00, /* 00000000 */0x00, /* 00000000 */............
}

4.2 主要的函数

oled_open函数主要用来打开文件,打开中文字库文件,并使用mmap映射到应用空间,方便其他函数的读取。

// 打开oled设备,使用的HZK16字库文件
int oled_open(char *devpath)
{struct stat st;oled_fd = open(devpath, O_RDWR);if (oled_fd < 0){printf("can't open /dev/myoled\n");return -1;}// 获取设备分辨率x_max = ioctl(oled_fd, OLED_GET_SCREEN_X_PIXEL);y_max = ioctl(oled_fd, OLED_GET_SCREEN_Y_PIXEL);printf("x_max: %d, y_max: %d\n", x_max, y_max);// 映射中文字库文件if ((hzk_fd = open("./HZK16", O_RDONLY)) == -1) {perror("open error");return -1;}fstat(hzk_fd, &st);hzkmem_size = st.st_size;hzkmem = (unsigned char *)mmap(NULL, hzkmem_size, PROT_READ, MAP_SHARED, hzk_fd, 0);if (hzkmem == (unsigned char *)-1){printf("can't mmap\n");return -1;}
}

oled_show_ascii() 函数用于显示单个ASCII字符。通过ASCII字符的码值在数组english_fontdata找到头地址,然后遍历该字符的字模,每个像素点转为一个16位的565的颜色保存在data数组内部,转换完成后通过ioctl设置显示区域,然后即将data数组通过ioctl传递给驱动程序,驱动程序就能够将数据显示出来。

/* 显示单个英文字符 */
static void oled_show_ascii(int x, int y, unsigned char c, unsigned short color)
{int ret = 0;int i = 0;int j = 0;int m = 0;int debug = 0;unsigned char params[10];unsigned char temp = 0;unsigned char *dots = (unsigned char *)&english_fontdata[c*ENGLISH_FONT_HEIGHT];params[0] = x & 0xff;          // x_startparams[1] = y & 0xff;            // y_startparams[2] = params[0] + ENGLISH_FONT_WIDTH - 1; // x_endparams[3] = params[1] + ENGLISH_FONT_HEIGHT -1;   // y_end// 必须提前把数据构造好,再一次性传给驱动程序for (i=0;i<ENGLISH_FONT_HEIGHT*ENGLISH_FONT_WIDTH/8;i++) {temp = *(dots+i);//printf("0x%x\n", temp);for (j=7;j>=0;j--) {//printf("compare: %d\n", temp & (1 << j));if (temp & (1 << j)) {//printf("1\n");data[m] = color >> 8;data[m+1] = color & 0xff;}else {//printf("0\n");data[m] = background_color >> 8;data[m+1] = background_color & 0xff;}m += 2;}}// 设置范围ret = ioctl(oled_fd, OLED_SET_REGION, params);if (ret != 0) {perror("error: ");printf("%s %d: ioctl error\n", __FUNCTION__, __LINE__);return;}//printf("debug111111111\n");ret = ioctl(oled_fd, (ENGLISH_FONT_HEIGHT*ENGLISH_FONT_WIDTH*2 << 8) | OLED_WRITE_COLOR, data);
}

中文字符同理,不同的是本程序使用的是HZK字库,每个字符占用两个字节,根据GB2312的编码规则,第一个字节表明了该字符所在码区,第二个字节表明了该字符在该码区的那一个位置(就相当于,我这里有一栋楼,第一个字节表明在哪一层,第二个字节表明那一个房间)。

/* 显示单个中文字符 */
void oled_show_chinese_char(int x, int y, unsigned char *chinese_char, unsigned short color)
{unsigned int area  = chinese_char[0] - 0xA1;unsigned int where = chinese_char[1] - 0xA1;unsigned char *pos = &hzkmem[(area*94+where)*32];unsigned char temp = 0;int i = 0;int j = 0;int m = 0;int ret = 0;unsigned char params[10];params[0] = x & 0xff;         // x_startparams[1] = y & 0xff;            // y_startparams[2] = params[0] + CHINESE_FONT_WIDTH - 1; // x_endparams[3] = params[1] + CHINESE_FONT_HEIGHT -1;   // y_end// 构造显示该字符所需的数据for(i=0;i<CHINESE_FONT_WIDTH*CHINESE_FONT_HEIGHT<<2;i++){temp = pos[i];for(j=7;j>=0;j--){if (temp & (1<<j)) {data[m] = color >> 8;data[m+1] = color & 0xff;} else {data[m] = background_color >> 8;data[m+1] = background_color & 0xff;}m += 2;}}// 设置范围ret = ioctl(oled_fd, OLED_SET_REGION, params);if (ret != 0) {perror("error: ");printf("%s %d: ioctl error\n", __FUNCTION__, __LINE__);return;}ret = ioctl(oled_fd, (CHINESE_FONT_WIDTH*CHINESE_FONT_HEIGHT*2 << 8) | OLED_WRITE_COLOR, data);}

英文字符串的显示只需要遍历字符串,调用oled_show_ascii即可。中文字符串的显示也是遍历字符串,每次循环去两个字节。而中英文混合就需要判断一下了。原理就是gb2312编码的两个字节都大于0xA1,设计之初就是为了兼容ASCII码的吧,只需要判断当前地址所指向的值是否大于0xA1, 如果大于,就是用oled_show_chinese_char处理接下来的两个字节,否则就用oled_show_ascii处理当前字节。

/* 显示字符串(可英文中文混合) * start_x: 起始位置(左上方的点基准点)的x* start_y: 起始位置(左上方的点基准点)的y* p: 要显示的字符串地址
*/
void oled_show_text(int start_x, int start_y, char *p)
{int i = 0;int current_x = start_x;int current_y = start_y;while (p[i] != '\0'){  // 当前字符是字母if (p[i] < 0xA1) {oled_show_ascii(current_x, current_y, *(p+i), font_color);current_x = current_x + ENGLISH_FONT_WIDTH + INTERVAL_X;//start_y = start_y + ENGLISH_FONT_HEIGHT + INTERVAL_Y;i += 1; // ascii字符占一个字节} else {oled_show_chinese_char(current_x, current_y, (unsigned char *)(p+i), font_color);current_x = current_x + CHINESE_FONT_WIDTH + INTERVAL_X;//start_y = start_y + CHINESE_FONT_HEIGHT + INTERVAL_Y;i += 2;}}
}

4. 效果

前面说的封装起来确实挺复杂的,不过使用起来也真的很香,看看下面的测试程序就知道了,注意: 在编译的时候需要指定输出文件的编码格式,因为我们使用的HZK字库文件是gb2312编码格式的,而默认会将其字符使用utf8来编码,每个字符在不同的编码规范里面的编码值是不一样的,所以在编译时需要使用-fexec-charset=GB2312指定编码格式,不然会出现莫名其妙的字符

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <string.h>#include "./oledlib.h"#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE  0x001Fint main(int argc, char **argv)
{if (argc != 2) {printf("%s /dev/xxx\n", argv[0]);return -1;}oled_open(argv[1]);//oled_clear_screen(GREEN);oled_set_font_color(BLACK);oled_set_font_back(WHITE);oled_show_text(2, 3, "你好,我是不会学习的小菜鸡,我喜欢编程,也喜欢Linux,希望以后可以走嵌入式Linux这个方向。Good bye!");oled_close();return 0;
}

运行效果


源码下载地址: ST7735S液晶屏Linux驱动

常用颜色汇总: csdn博客

1.44寸OLED的Linux驱动相关推荐

  1. 0.96寸OLED屏硬件驱动电路

    0.96寸OLED屏硬件驱动电路 该电路适合把OLED驱动电路集成到自己的板子上,最终的原理图和PCB已经上传CSDN,可直接点击链接下载: https://download.csdn.net/dow ...

  2. 基于STM32F103 0.96寸OLED液晶屏驱动(iic通讯)

    一.概述 OLED驱动方式有8080.6800.3线/4线SPI以及IIC,能够显示字符.汉字的图片,无字库需通过取模软件获取显示内容数组.本次实验使用的是IIC通信协议,SSD1306驱动芯片的OL ...

  3. 1.3寸OLED SH1106 IIC驱动显示错误解决方法

    网上买的1.3寸OLED 用的时候发现显示有问题,整体像素都往左两个,导致左边少两列像素,右边两列则为乱码. 解决方法 在设置光标函数中让x轴向右位移2个像素. void OLED_SetCursor ...

  4. esp32 cam 1.44寸TFT彩屏 ST7735S驱动 TFT_eSPI库驱动

    ESP32 CAM引脚与TFT1.44(ST7735S)引脚接线 ESP32 CAM TFT 1.44 5V VCC GND GND GND NC NC 5V BLC D14 SCL D15 SDA ...

  5. SWM181 驱动SH1106 1.3寸 OLED屏幕显示

    SWM181 驱动SH1106 1.3寸 OLED屏幕显示 ✨实测驱动的屏幕为sh1106,该工程源码不适合通过修改相关的偏移地址来适配ssd1306 I2C 0.96寸屏幕显示,实际修改后,经测试并 ...

  6. 0.96寸OLED屏显示(IIC通信)Ⅰ

    0.96寸OLED屏显示(IIC通信) 一.0.96寸OLED简介   0.96寸OLED屏内部驱动IC为SSD1306:兼容6800.8080两种并行接口方式,3线或 4线的串行SPI接口方式和 I ...

  7. linux移植1.3寸oled屏幕,芯片SH1106

    之前移植过MPU6050(I2C协议)和0.96寸oled(SPI协议),这次移植一个I2C协议的oled. I2C的介绍 可以参考上一篇I2C的文章: linux移植MPU6050的I2C驱动 -- ...

  8. STM8驱动0.96寸OLED(12864液晶屏)

    由于中景园电子给出的例程不够全面,因此特地花时间重新整理了一下代码,并加上了其他的功能.本文记录了使用模拟四线 SPI 协议驱动 OLED 的代码. 文章目录 1 oled.h 2 oled.c 2. ...

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

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

最新文章

  1. 【二分图最大匹配】【HDU2063】过山车
  2. Codeforces Round #491 (Div.2)
  3. 项目周期一般多久_深圳app开发公司的软件开发要多久?
  4. linux启动mysql_允许远程连接到MySQL数据库服务器的步骤
  5. 自己整理的90分以上最新物联网技术导论期末选择填空大题总考点
  6. Q3D之多视图(左视图,正视图等)
  7. Win11进桌面闪屏,亲测恢复正常
  8. 海思AI芯片3559A方案学习(一)
  9. 重装系统蓝屏,电脑开机蓝屏解决方法记录
  10. U盘量产后USB鼠标和键盘都无法使用,如何解决?
  11. Keil5中添加C51芯片
  12. 一周热图|杨紫韩国艺匠婚纱大片出炉;易烊千玺代言麦当劳;洛天依音乐综艺节目首秀...
  13. 网页上的广告条设计[zt]
  14. 安卓手机去水印哪个好用
  15. 电销CRM客户关系管理系统开发12大核心功能
  16. 我希望我们在Java中拥有十大锡兰语言功能
  17. OPENCV混合高斯模型原理
  18. 2020年“信创”火了!一文看懂什么是信创
  19. 房产行业怎么做数据分析?
  20. 致远项目管理SPM系统案例:道道全粮油股份有限公司人力资源管理

热门文章

  1. Parse 使用教程之四
  2. .NET Core MVC更换网页背景图片
  3. RobotFramework 基础语法
  4. 关于optimized out
  5. 用计算机听音乐教案,听音乐教案范文
  6. 行波iq调制器_IQ调制器的制作方法
  7. C4996 ‘strcat‘: This function or variable may be unsafe. Consider using strcat_s instead. To disable
  8. 【uniapp】uniapp小程序分享功能(App分享)
  9. 打造前端构建环境(1)
  10. 干货 | 基于 Python 的信用评分模型实战!