• 1.44寸OLED的Linux驱动



    1. 开发环境介绍

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

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

    1.2 ST7735S TFT 液晶屏

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BkcC93Sh-1662545687567)(D:\Myblog\blogimages\image-20220906144633404.png)]
    在这里插入图片描述

    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 = ;  指定片选引脚,就是父节点的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            0x1a090
    			MX6UL_PAD_CSI_DATA04__ECSPI1_SCLK           0x11090
    			MX6UL_PAD_CSI_DATA06__ECSPI1_MOSI           0x11090
    			MX6UL_PAD_CSI_DATA07__ECSPI1_MISO           0x11090
    		>;
     	};
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    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");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    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_max
    			return X_MAX_PIXEL;
    		}
    		case OLED_GET_SCREEN_Y_PIXEL:
    		{	
    			// 获取y_max
    			return 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    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 */
        ......
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    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;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    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_start
    	params[1] = y & 0xff;			// y_start
    	params[2] = params[0] + ENGLISH_FONT_WIDTH - 1;	// x_end
    	params[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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    中文字符同理,不同的是本程序使用的是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_start
    	params[1] = y & 0xff;			// y_start
    	params[2] = params[0] + CHINESE_FONT_WIDTH - 1;	// x_end
    	params[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);
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    英文字符串的显示只需要遍历字符串,调用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;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    4. 效果

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

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include "./oledlib.h"
    
    #define BLACK 0x0000
    #define WHITE 0xFFFF
    #define RED 0xF800
    #define GREEN 0x07E0
    #define BLUE  0x001F
    
    
    int 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;	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    运行效果
    在这里插入图片描述


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

    常用颜色汇总: csdn博客

  • 相关阅读:
    基于BP神经网络进行手写体识别(Matlab代码实现)
    调优C / C ++编译器以在多核应用程序中获得最佳并行性能:第一部分
    Awesome GIS
    前端如何去除本地版本号缓存
    【Docker】 08-Dockerfile
    基于javaweb+jsp的客户关系管理系统CRM(JavaWeb MySQL JSP Bootstrap Servlet SSM SpringBoot)
    python使用mitmproxy和mitmdump抓包在电脑上抓包(二)
    L14.linux命令每日一练 -- 第二章 文件和目录操作命令 -- md5sum和chown命令
    计算机毕设(附源码)JAVA-SSM家乡旅游宣传书册
    编译原理:上下文无关文法 CFG
  • 原文地址:https://blog.csdn.net/weixin_47024013/article/details/126751321