12.2内核空间基于SPI总线的OLED驱动

在内核空间编写SPI设备驱动的要点

  1. 在SPI总线控制器的设备树节点下增加SPI设备的设备树节点,节点中必须包含 reg 属性、 compatible 属性、 spi-max-frequency 属性, reg 属性用于描述片选索引, compatible属性用于设备和驱动的匹配, spi-max-frequency 用于描述设备可支持的最大 SPI 总线频率,在注册SPI总线控制器会解析其中的子节点,并注册成SPI设备。
  2. 创建并初始化struct spi_driver对象,其中重点关注of_match_table、probe、remove,of_match_table用于设备树和驱动匹配,probe在设备驱动匹配成功时执行,remove在设备或驱动卸载时执行。
  3. 在模块初始化函数中使用int spi_register_driver(struct spi_driver *sdrv)注册SPI设备驱动
  4. 使用void spi_message_init(struct spi_message *m)和void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)组织数据包,然后使用int spi_sync(struct spi_device *spi, struct spi_message *message)或int spi_async(struct spi_device *spi, struct spi_message *message)传输数据包
  5. 在模块卸载函数中使用void spi_unregister_driver(struct spi_driver *sdrv)注销SPI设备驱动

SPI OLED驱动编写

OLED模块原理图

模块一共由7个引脚,采用SPI模式时引脚定义如下:
GND:电源地
VCC:2.2V~5.5V
SCL(D0):CLK 时钟 (高电平 2.2V~5.5V)
SDA(D1):MOSI 数据(高电平 2.2V~5.5V)
RST:复位(高电平 2.2V~5.5V)
D/C:数据/命令(高电平 2.2V~5.5V)
CS:SPI片选
注意:没有MISO引脚,因为主控只能向OLED写数据,不能读取OLED的数据
在这里插入图片描述

与主控的连接示意图

在这里插入图片描述
要操作OLED,只需使用SPI接口发送数据,并不需要使用SPI接口读取数据。除此之外,还需要控制D/C引脚:

  • 当DC引脚是低电平时,是命令:比如复位、打开显示、设置地址
  • 当DC引脚是高电平时,是数据:写入要显示的数据

显存和像素

OLED上有128*64个像素(128列,64行),每个像素只有2种状态:亮、灭。
在这里插入图片描述
OLED内部有一块显存GDDRAM(Graphic Display Data RAM),显存中每位对应一个像素,入下图所示
在这里插入图片描述

  • byte0对应屏幕左上角竖向排列的8个像素,即COL0第0~第7行的8个像素
  • byte1对应COL1列第0~第7行的8个像素
  • ……
  • byte127对应COL127列第0~第7行的8个像素
  • byte128对应COL0那列第8~第15行的8个像素
  • ……

显存寻址模式

显存被分为8页、128列,要写某个字节时,需要先指定地址(哪页、哪列),然后写入1字节的数据。
OLED有三种寻址模式:

  • 页地址模式(Page addressing mode):每写入1个字节,行地址不变,列地址增1,列地址达到127后会从0开始
    在这里插入图片描述
  • 水平地址模式(Horizontal addressing mode):每写入1个字节,行地址不变,列地址增1,列地址达到127后从0开始,行地址指向下一页,列地址达到127、行地址达到7时,列地址和行地址都被复位为0,指向左上角(在此驱动中初始化时将地址设置为此模式)
    在这里插入图片描述
  • 垂直地址模式(Vertical addressing mode): 每写入1个字节,行地址增1,列地址不变,行地址达到7后从0开始,列地址指向下一列, 列地址达到127、行地址达到7时,列地址和行地址都被复位为0,指向左上角
    在这里插入图片描述

编写OLED设备树

  1. 在 stm32mp15-pinctrl.dtsi 的 &pinctrl_z 节点中修改 SPI 的引脚配置为如下内容:
	spi1_pins_a: spi1-0 {
		pins1 {
			pinmux = <STM32_PINMUX('Z', 0, AF5)>, /* SPI1_SCK */
				 <STM32_PINMUX('Z', 2, AF5)>; /* SPI1_MOSI */
			bias-disable;
			drive-push-pull;
			slew-rate = <3>;
		};

		pins2 {
			pinmux = <STM32_PINMUX('Z', 1, AF5)>; /* SPI1_MISO */
			bias-disable;
			drive-push-pull;
			slew-rate = <3>;
		};
	};

	spi1_sleep_pins_a: spi1-sleep-0 {
		pins {
			pinmux = <STM32_PINMUX('Z', 0, ANALOG)>, /* SPI1_SCK */
				 <STM32_PINMUX('Z', 1, ANALOG)>, /* SPI1_MISO */
				 <STM32_PINMUX('Z', 2, ANALOG)>; /* SPI1_MOSI */
		};
	};
  1. 在顶层设备树中引用spi1节点,并加入如下内容:
&spi1 {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&spi1_pins_a>;
	pinctrl-1 = <&spi1_sleep_pins_a>;
	cs-gpios = <&gpioz 3 GPIO_ACTIVE_LOW>, <&gpioa 14 GPIO_ACTIVE_LOW>;
	status = "okay";

	/* OLED屏幕 */
	oled@1 {
		compatible = "atk,oled";
		reg = <1>; /* CS #1 */
		spi-max-frequency = <1000000>;
		dc-gpios = <&gpioi 3 GPIO_ACTIVE_LOW>;
		rst-gpios = <&gpioi 11 GPIO_ACTIVE_LOW>;
	};
};
  1. 用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,并用新的.dtb文件启动系统

使能SPI控制器驱动

内核中使能 SPI 控制器驱动, ST 默认将SPI控制器驱动编译为模块,使能步骤如下:

  1. 执行命令make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- menuconfig打开内核配置菜单
  2. 进行如下配置
Device Drivers
	SPI support (SPI [=y])
		<*> STMicroelectronics STM32 SPI controller //编译进内核
  1. 使用命令make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- all LOADADDR=0XC2000040 -j16编译内核
  2. 使用命令make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- uImage dtbs LOADADDR=0XC2000040 -j16生成uImage

驱动代码编写

OLED驱动程序基于SPI总线驱动框架和缓冲帧驱动框架编写,有关缓冲帧的内容参考8.1缓冲帧(Framebuffer)驱动框架和8.2LCD-TFT显示控制器驱动 (LCD驱动)部分,驱动代码主要包括以下几个部分:

  1. 注册/注销SPI设备驱动
  2. 注册/注销缓冲帧驱动
  3. OLED初始化
  4. OLED显示更新
    驱动代码的完成内容如下所示:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>

#define OLED_DISPLAY_RAM_SIZE	(8*128)

struct oled_handle{
	struct spi_device *spi;				//oled所属spi设备
	int rst_gpio;						//复位引脚
	int dc_gpio;						//数据命令选择引脚
	struct task_struct *kthread;		//用于将显存内容更新到OLED的内核线程
	uint8_t (*oled_buffer)[128];		//oled buffer,将缓冲帧中的数据转换为OLED格式后在通过SPI总线发送到OLED
	struct fb_info *fb;					//缓冲帧句柄
	unsigned int pseudo_palette[16];	//调色板
	uint8_t (*fb_buffer)[16];			//缓冲帧
	dma_addr_t phy_addr;				//缓冲帧物理地址
	uint8_t (*old_fb_buffer)[16];		//缓冲帧上一次更新时的状态
};

//初始化OLED的复位引脚和数据命令选择引脚
static int devm_pin_init(struct oled_handle *oled)
{
	int result;

	//获取RST GPIO号
	oled->rst_gpio = of_get_named_gpio(oled->spi->dev.of_node, "rst-gpios", 0);
	if(oled->rst_gpio < 0)
	{
		printk("get rst_gpio failed\r\n");
		return oled->rst_gpio;
	}
	//申请RST GPIO
	result = devm_gpio_request(&oled->spi->dev, oled->rst_gpio, "oled,rst_gpio");
	if(result < 0)
	{
		printk("request rst_gpio failed\r\n");
		return result;
	}
	//设置复位引脚输出高电平
	gpio_direction_output(oled->rst_gpio, 1);

	//获取DC GPIO号
	oled->dc_gpio = of_get_named_gpio(oled->spi->dev.of_node, "dc-gpios", 0);
	if(oled->dc_gpio < 0)
	{
		printk("get dc_gpio failed\r\n");
		return oled->dc_gpio;
	}
	//申请DC GPIO
	result = devm_gpio_request(&oled->spi->dev, oled->dc_gpio, "oled,dc_gpio");
	if(result < 0)
	{
		printk("request dc_gpio failed\r\n");
		return result;
	}
	//设置数据/命令选择引脚输出高电平
	gpio_direction_output(oled->dc_gpio, 1);

	return 0;
}

//数据命令选择引脚拉高,表示发送数据
static void dc_high(struct oled_handle *oled)
{
	gpio_direction_output(oled->dc_gpio, 1);
}

//数据命令选择引脚拉低,表示发送命令
static void dc_low(struct oled_handle *oled)
{
	gpio_direction_output(oled->dc_gpio, 0);
}

//复位引脚拉高
static void rst_high(struct oled_handle *oled)
{
	gpio_direction_output(oled->rst_gpio, 1);
}

//复位引脚拉低
static void rst_low(struct oled_handle *oled)
{
	gpio_direction_output(oled->rst_gpio, 0);
}

//复位OLED屏幕
static void oled_reset(struct oled_handle *oled)
{
	//暂时拉高复位引脚
	rst_high(oled);
	msleep_interruptible(200);

	//拉低复位引脚,进行复位
	rst_low(oled);
	msleep_interruptible(200);
	//拉高复位引脚,复位结束
	rst_high(oled);
	msleep_interruptible(200);
}

//通过SPI总线向OLED设备发送数据
static int oled_write(struct oled_handle *oled, const uint8_t *data, uint32_t lenght)
{
	int result;
	uint8_t *buffer;
	struct spi_message message;
	struct spi_transfer transfer;

	//分配发送缓存
	buffer = kzalloc(lenght, GFP_KERNEL);
	if(!buffer)
		return -ENOMEM;
	//将数据拷贝到buffer中
	memcpy(buffer, data, lenght);

	//初始化spi_message
	spi_message_init(&message);

	//复位spi_transfer
	memset(&transfer, 0, sizeof(transfer));
	//发送缓存
	transfer.tx_buf = buffer;
	//接收缓存
	transfer.rx_buf = NULL;
	//传输的长度
	transfer.len = lenght;

	//将spi_transfer添加到spi_message队列
	spi_message_add_tail(&transfer, &message);

	//同步传输
	result = spi_sync(oled->spi, &message);

	//释放发送缓存
	kfree(buffer);

	return result;
}

//向OLED屏幕发送命令
static int oled_write_cmd(struct oled_handle *oled, uint8_t *command, uint32_t lenght)
{
	//拉低数据命令选择引脚,表示发送命令
	dc_low(oled);
	//通过SPI发送数据
	return oled_write(oled, command, lenght);
}

//向OLED屏幕发送数据
static int oled_write_data(struct oled_handle *oled, uint8_t *data, uint32_t lenght)
{
	//拉高数据命令选择引脚,表示发送数据
	dc_high(oled);
	//通过SPI发送数据
	return oled_write(oled, data, lenght);
}

//将oled_buffer中的数据显示在OLED屏幕上
static int oled_update(struct oled_handle *oled)
{
	int result;
	uint8_t command[3];

	//设置地址
	command[0] = 0xB0 + 0;					//设置页地址
	command[1] = 0x10 + 0;					//设置显示位置—列高地址高4位
	command[2] = 0x00 + 0;					//设置显示位置—列低地址低4位
	result = oled_write_cmd(oled, command, 3);
	if(result != 0)
		return result;

	//发送显示数据
	result = oled_write_data(oled, oled->oled_buffer[0], 128*8);
	if(result != 0)
		return result;

	return 0;
}

//初始化OLED屏幕
static int oled_init(struct oled_handle *oled)
{
	int result;
	uint8_t command[28];

	//复位OLED
	oled_reset(oled);
 
	//发送初始化命令
	command[0 ] = 0xAE;				//--turn off oled panel
	command[1 ] = 0x00;				//---set low column address
	command[2 ] = 0x10;				//---set high column address
	command[3 ] = 0x40;				//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
	command[4 ] = 0x81;				//--set contrast control register
	command[5 ] = 0xCF;				 // Set SEG Output Current Brightness
	command[6 ] = 0xA1;				//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
	command[7 ] = 0xC8;				//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
	command[8 ] = 0xA6;				//--set normal display
	command[9 ] = 0xA8;				//--set multiplex ratio(1 to 64)
	command[10] = 0x3F;				//--1/64 duty
	command[11] = 0xD3;				//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
	command[12] = 0x00;				//-not offset
	command[13] = 0xD5;				//--set display clock divide ratio/oscillator frequency
	command[14] = 0x80;				//--set divide ratio, Set Clock as 100 Frames/Sec
	command[15] = 0xD9;				//--set pre-charge period
	command[16] = 0xF1;				//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	command[17] = 0xDA;				//--set com pins hardware configuration
	command[18] = 0x12;				
	command[19] = 0xDB;				//--set vcomh
	command[20] = 0x40;				//Set VCOM Deselect Level
	command[21] = 0x20;				//-Set Addressing Mode (0x00/0x01/0x02)
	command[22] = 0x00;				//
	command[23] = 0x8D;				//--set Charge Pump enable/disable
	command[24] = 0x14;				//--set(0x10) disable
	command[25] = 0xA4;				// Disable Entire Display On (0xa4/0xa5)
	command[26] = 0xA6;				// Disable Inverse Display On (0xa6/a7) 
	command[27] = 0xAF;				//--turn on oled panel 
	result = oled_write_cmd(oled, command, 28);
	if(result != 0)
		return result;

	//更新OLED显示
	return oled_update(oled);
}

//将缓冲帧中的像素转换成OLED格式
static void convert_fb_to_oled(struct oled_handle *oled)
{
	int i, j, k;

	//一共8*8行,其中每8行1byte
	for(i=0; i<8; i++) {
		//一个128列
		for(j=0; j<16; j++) {
			for(k=0; k<8; k++) {
				oled->oled_buffer[i][j*8+k] = (((oled->fb_buffer[i*8+0][j] >> k) & 0x01) << 0) |
					(((oled->fb_buffer[i*8+1][j] >> k) & 0x01) << 1) |
					(((oled->fb_buffer[i*8+2][j] >> k) & 0x01) << 2) |
					(((oled->fb_buffer[i*8+3][j] >> k) & 0x01) << 3) |
					(((oled->fb_buffer[i*8+4][j] >> k) & 0x01) << 4) |
					(((oled->fb_buffer[i*8+5][j] >> k) & 0x01) << 5) |
					(((oled->fb_buffer[i*8+6][j] >> k) & 0x01) << 6) |
					(((oled->fb_buffer[i*8+7][j] >> k) & 0x01) << 7);
			}
		}
	}
}

//内核线程,用于周期性刷新OLED显示屏
static int oled_thread(void *arg)
{
	struct oled_handle *oled;

	oled = (struct oled_handle*)arg;
	while(!kthread_should_stop())
	{
		//缓冲帧内容改变才刷新OLED
		if(memcmp(oled->old_fb_buffer, oled->fb_buffer, OLED_DISPLAY_RAM_SIZE))
		{
			//应用层可能正在进行写操作,这里延时休眠600~700us等待应用层写完
			usleep_range(600, 700);

			//显存格式转换
			convert_fb_to_oled(oled);

			//记录缓冲帧状态
			memcpy(oled->old_fb_buffer, oled->fb_buffer, OLED_DISPLAY_RAM_SIZE);

			//更新显示
			oled_update(oled);
		}
		else
		{
			//休眠
			msleep_interruptible(2);
		}
	}

	return 0;
}

static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}

static int oled_setcolreg(unsigned regno, unsigned red, 
						unsigned green, unsigned blue, 
						unsigned transp, struct fb_info *info)
{
	unsigned int val;
	unsigned int *pseudo_palette;

	if (regno >= 16)
		return -EINVAL;

	val  = chan_to_field(red, &info->var.red);
	val |= chan_to_field(green, &info->var.green);
	val |= chan_to_field(blue, &info->var.blue);
	pseudo_palette = info->pseudo_palette;
	pseudo_palette[regno] = val;

	return 0;
}

//缓冲帧操作函数集合
static struct fb_ops oled_ops = {
	.owner = THIS_MODULE,
	.fb_setcolreg = oled_setcolreg,
	.fb_fillrect = cfb_fillrect,
	.fb_copyarea = cfb_copyarea,
	.fb_imageblit = cfb_imageblit,
};

static int oled_probe(struct spi_device *spi)
{
	int result;
	struct oled_handle *oled;

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

	//设置SPI设备的DMA寻址范围,不然dma_alloc会执行失败
	spi->dev.coherent_dma_mask = DMA_BIT_MASK(32);

	//分配OLED句柄
	oled = devm_kmalloc(&spi->dev, sizeof(struct oled_handle), GFP_KERNEL);
	if(!oled)
	{
		printk("alloc oled_buffer failed\r\n");
		return -ENOMEM;
	}
	memset(oled, 0x00, sizeof(struct oled_handle));

	//分配oled缓存,缓冲帧中的数据经过格式转换后拷贝到oled_buffer中,然后在显示到屏幕
	oled->oled_buffer = devm_kmalloc(&spi->dev, OLED_DISPLAY_RAM_SIZE, GFP_KERNEL);
	if(!oled->oled_buffer)
	{
		printk("alloc oled_buffer failed\r\n");
		return -ENOMEM;
	}
	memset(oled->oled_buffer, 0x00, OLED_DISPLAY_RAM_SIZE);

	//分配old_fb缓存,用于存储上一次更新显示器时缓冲帧中的状态
	oled->old_fb_buffer = devm_kmalloc(&spi->dev, OLED_DISPLAY_RAM_SIZE, GFP_KERNEL);
	if(!oled->old_fb_buffer)
	{
		printk("alloc old_fb_buffer failed\r\n");
		return -ENOMEM;
	}
	memset(oled->old_fb_buffer, 0x00, OLED_DISPLAY_RAM_SIZE);

	//分配缓冲帧
	oled->fb_buffer = dma_alloc_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, &oled->phy_addr, GFP_KERNEL);
	if(!oled->fb_buffer)
	{
		printk("alloc fb_buffer failed\r\n");
		return -ENOMEM;
	}
	memset(oled->fb_buffer, 0x00, OLED_DISPLAY_RAM_SIZE);

	//设置SPI设备的驱动私有数据
	spi->dev.driver_data = (void*)oled;

	//给oled句柄绑定SPI设备
	oled->spi = spi;

	//设置SPI模式,也可以在设备树中进行配置
	/*MODE3(CPOL=1,CPHA=1)*/
	oled->spi->mode = SPI_MODE_3;
	spi_setup(oled->spi);

	//初始化OLED的GPIO
	result = devm_pin_init(oled);
	if(result < 0)
	{
		dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
		printk("init gpio failed\r\n");
		return result;
	}

	//初始化OLED屏幕
	result = oled_init(oled);
	if(result < 0)
	{
		dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
		printk("oled_init failed\r\n");
		return result;
	}

	//分配缓冲帧句柄
	oled->fb = framebuffer_alloc(0, &spi->dev);
	if(!oled->fb)
	{
		dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
		printk("alloc fb failed\r\n");
		return -ENOMEM;
	}

	//设置fb
	//显存虚拟地址和大小
	oled->fb->screen_base = (char*)oled->fb_buffer;
	oled->fb->screen_size = OLED_DISPLAY_RAM_SIZE;
	//LCD分辨率、颜色格式
	oled->fb->var.xres = 128;
	oled->fb->var.yres = 64;
	oled->fb->var.xres_virtual = 128;
	oled->fb->var.yres_virtual = 64;
	oled->fb->var.bits_per_pixel = 1;
	//ID
	strcpy(oled->fb->fix.id, "atk,oled");
	//显存大小和物理地址
	oled->fb->fix.smem_len = OLED_DISPLAY_RAM_SIZE;
	oled->fb->fix.smem_start = oled->phy_addr;
	//一行的显存长度
	oled->fb->fix.line_length = 16;
	//显示器类型
	oled->fb->fix.type = FB_TYPE_PACKED_PIXELS;
	//像素格式
	oled->fb->fix.visual = FB_VISUAL_MONO10;
	//底层操作函数集合
	oled->fb->fbops = &oled_ops;
	//颜色表
	oled->fb->pseudo_palette = oled->pseudo_palette;

	//注册缓冲帧驱动
	result = register_framebuffer(oled->fb);
	if(result < 0)
	{
		framebuffer_release(oled->fb);
		dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
		printk("register fb failed\r\n");
		return result;
	}

	//创建内核线程,更新OLED
	oled->kthread = kthread_create(oled_thread, (void*)oled, "oled_thread%d,%d", oled->spi->controller->bus_num, oled->spi->chip_select);
	if(IS_ERR(oled->kthread))
	{
		unregister_framebuffer(oled->fb);
		framebuffer_release(oled->fb);
		dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);
		printk("create oled_thread failed\r\n");
		return PTR_ERR(oled->kthread);
	}
	wake_up_process(oled->kthread);

	return 0;
}

//设备或驱动卸载时执行
static int oled_remove(struct spi_device *spi)
{
	struct oled_handle *oled;

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

	oled = (struct oled_handle*)spi->dev.driver_data;
	if(!oled)
	{
		printk("verification failed\r\n");
		return -EINVAL;
	}

	//停止内核线程
	kthread_stop(oled->kthread);
	//注销缓冲帧驱动
	unregister_framebuffer(oled->fb);
	//释放缓冲帧句柄
	framebuffer_release(oled->fb);
	//释放缓冲帧
	dma_free_wc(&spi->dev, OLED_DISPLAY_RAM_SIZE, oled->fb_buffer, oled->phy_addr);

	return 0;
}

//匹配列表,用于设备树和平台驱动匹配
static const struct of_device_id oled_of_match[] = {
	{.compatible = "atk,oled"},
	{ /* Sentinel */}
};
//传统匹配方式ID列表
static const struct spi_device_id oled_id[] = {
	{}
};
//SPI驱动
static struct spi_driver oled_drv = {
	.driver = {
		.name = "oled",
		.owner = THIS_MODULE,
		.pm = NULL,
		.of_match_table = oled_of_match,
	},
	.id_table = oled_id,
	.probe = oled_probe,
	.remove = oled_remove,
};
static int __init oled_drv_init(void)
{
	int result = 0;

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

	//注册SPI设备驱动
	result = spi_register_driver(&oled_drv);
	if(result < 0)
	{
		printk("add cdev failed\r\n");
		return result;
	}

	return 0;
}

static void __exit oled_drv_exit(void)
{
	printk("%s\r\n", __FUNCTION__);

	//注销SPI驱动
	spi_unregister_driver(&oled_drv);
}

module_init(oled_drv_init);
module_exit(oled_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("CSDN");
MODULE_DESCRIPTION("oled_dev");

编写驱动测试程序

OLED应用程序开发步骤如下:

  1. 打开缓冲帧设备
  2. 获取屏幕参数,主要包括屏幕x、y像素个数,以及每个像素的bit数,然后计算出显存的大小
  3. 通过mmap函数映射显存地址到用户空间
  4. 通过向映射到用户空间的显存写入数据,以控制在OLED上的显示内容
  5. 使用完成后取消mmap的映射。关闭设备
    如下是OLED测试程序的主函数,其中oled_lib对OLED的常用功能进行了封装,比如初始化、反初始化、画线、画方块、读写像素点等,初始化完成的内容包括上面的1~3步,反初始化完成的上面的第5步,其他接口均是读写显存。
#include <unistd.h>
#include "oled_lib.h"

int main(int argc, char *argv[])
{
	if(argc < 2)
	{
		printf("Error Usage!\r\n");
		return -1;
	}

	oled_init(argv[1]);

	while(1)
	{
		oled_clear();
		usleep(100*1000);
		display_line(0, 0, 127, 63);
		usleep(100*1000);
		display_line(0, 63, 127, 31);
		usleep(100*1000);
		display_rect(55, 5, 50, 20);

		sleep(1);
	}

	return 0;
}

上机测试

  1. 修改设备树(设备树需要结合硬件进行修改),然后编译设备树,并用新的设备树启动
  2. 从这里下载代码,并进行编译,然后拷贝到目标板根文件系统的root目录
  3. 执行命令insmod oled.ko加载OLED驱动,加载完成后在/dev目录增加了一个以fb开通的缓冲帧设备文件
    在这里插入图片描述
  4. 执行命令./oled_app.out /dev/fb0运行测试命令(/dev/fb0是OLED的缓冲帧设备),可以看到屏幕上显示相应的测试图像,终端也会打印屏幕的参数。
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/319206.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pyinstaller,一个超酷的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个超酷的 Python 库 - pyinstaller。 Github地址&#xff1a;https://github.com/pyinstaller/pyinstaller PyInstaller是一个用于将Python应用程序打包成独立可执行文件的工具。这可以将Python…

Leading Dimension是什么

在LAPACK中频繁出现Leading Dimension&#xff08;中文翻译为“主维度”&#xff09;&#xff0c;那么它是什么呢&#xff1f; 首先了解行主序&#xff08;Row-Major&#xff09;和列主序&#xff08;Column-Major&#xff09;的概念&#xff1a; Given a matrix A of shape …

宝塔nginx部署前端页面刷新报404

问题&#xff1a; 当我们使用脚手架打包前端项目的时候&#xff0c;如果前端项目并没有静态化的配置&#xff0c;如以下 当我们刷新页面&#xff0c;或进行路由配置访问的时候就会报404的错误 原因&#xff1a; 这是因为通常我们做的vue项目属于单页面开发。所以只有index.html…

Cocos 使用VsCode调试-跨域问题

解决方案&#xff1a; 在添加完debug配置后 在项目文件夹中打开vscode然后找到launch.json 这个runtimeArgs参数在原本的配置中是没有的,给他添加上去 "runtimeArgs": ["--disable-web-security" ] 原理: 禁用浏览器跨域检查&#xff08;仅用于调试&…

格密码基础:SIS问题的定义与理解

目录 一. 介绍 二. SIS问题定义 2.1 直观理解 2.2 数学定义 2.3 基本性质 三. SIS与q-ary格 四. SIS问题的推广 五. Hermite标准型 六. 小结 一. 介绍 short interger solution problem短整数解问题&#xff0c;简称SIS问题。 1996年&#xff0c;Ajtai首次提出SIS问…

物联网介绍

阅读引言&#xff1a; 本文从多方面叙述物联网的定义以及在物联网当中的各种通信的介绍。 一、物联网的定义 1.1 通用的定义 物联网&#xff08;Internet of Things&#xff0c;IOT&#xff1b;也称为Web of Things&#xff09;是指通过各种信息传感设 备&#xff0c;如传感器、…

基于51单片机的智能热水器设计

需要全部文件请私信关注我&#xff01;&#xff01;&#xff01; 基于51单片机的智能热水器设计 摘要一、绪论1.1 选题背景及意义1.2 完成目标与功能设计 二、硬件系统设计2.1 硬件完成要求2.2 方案选择2.3 电源电路设计2.4 键盘电路2.5 蜂鸣器报警电路2.6 温度检测电路2.7 红…

Web前端-移动web开发——flex布局

移动web开发——flex布局 1.0传统布局和flex布局对比 1.1传统布局 兼容性好布局繁琐局限性&#xff0c;不能再移动端很好的布局 1.2 flex布局 操作方便&#xff0c;布局极其简单&#xff0c;移动端使用比较广泛pc端浏览器支持情况比较差IE11或更低版本不支持flex或仅支持部…

AWS-SAA-C03认证——之基础知识扫盲

文章目录 前言一、Amazon ECS二、 Amazon EKS三、Amazon EC2四、Elastic Beanstalk五、AWS Fargate六、 AWS Lambda &#xff08;serverless&#xff09;七、Amazon EBS7.1 EBS生命周期 八、Amazon Elastic File System (EFS) -共享文件系统九、什么是 Amazon S3&#xff1f;9.…

wechatpay-java 部署linux报错

ruoyimall部署linux环境报错 报错现象 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name wechatPayService: Unsatisfied dependency expressed through field service; nested exception is org.springframework.beans.fa…

数据结构排序——计数排序和排序总结(附上912. 排序数组讲解)

数据结构排序——计数排序和排序总结 现在常见算法排序都已讲解完成&#xff0c;今天就再讲个计数排序。再总结一下 文章目录 1.计数排序2.排序总结3.排序oj&#xff08;排序数组&#xff09;题目详情代码思路 1.计数排序 计数排序是一种非基于比较的排序算法&#xff0c;它通…

C#中的反射(Reflection)使用经典案例

文章目录 1. 动态加载和调用类的方法2. 记录用户修改行为3. 调用私有构造函数4. 泛型类型的动态创建和使用5. 动态类型转换与检查6. 获取和设置私有、受保护成员7. 枚举程序集、模块、类型等信息8. 处理泛型类型参数9. 动态生成代码或动态编译10. 配置驱动的应用程序扩展注意事…

CBA业务架构师认证考试含金量

CBA业务架构师认证考试的含金量主要体现在以下几个方面&#x1f447; 1️⃣权威性 &#x1f48e;CBA业务架构师是业务架构师协会提供了一项国际认证计划&#xff0c;该计划可以衡量业务架构师的能力&#xff0c; 并向证明公认的熟练程度的个人授予认证业务架构师(Certified Bus…

第四节课 XTuner 大模型单卡低成本微调实战 作业

文章目录 笔记作业 笔记 XTuner 大模型单卡低成本微调原理&#xff1a;https://blog.csdn.net/m0_49289284/article/details/135532140XTuner 大模型单卡低成本微调实战&#xff1a;https://blog.csdn.net/m0_49289284/article/details/135534817 作业 基础作业&#xff1a;…

限时福利,Adobe InCopy2024下载安装指南

Adobe InCopy 下载链接 https://pan.baidu.com/s/16j5MiXqfGw6puQbgyQnJSQ?pwd0531 #2024版本 1.鼠标右击【InCopy2024(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 InCopy2024(64bit)】。 2.打开解压后的文件夹&#xff0c;鼠…

codeforces(C++ Summation Game)

题目&#xff1a; 翻译&#xff1a; 思路&#xff1a; 1、将数据从大到小排序 2、用前缀和 3、每次用总和减去2倍的乘-1的数&#xff0c;求最大值 代码&#xff1a; #include <iostream> #include<algorithm> using namespace std;void solve() {int n, k, x;ci…

Baumer工业相机堡盟工业相机如何使用OpenCV实现相机图像的显示(C++)

Baumer工业相机堡盟工业相机如何使用OpenCV实现相机图像的显示&#xff08;C&#xff09; Baumer工业相机Baumer工业相机的图像转换为OpenCV的Mat图像的技术背景在NEOAPI SDK里使用OpenCV实现相机图像的显示联合OpenCV实现相机图像的显示测试演示图 工业相机通过使用OpenCV实现…

Docker介绍安装及使用

目录 引言一、什么是Docker?二、Docker的优势三、Docker的架构四、Docker的安装五、Docker的基本使用六、Docker与传统虚拟化的比较七、Docker的应用场景八、总结 引言 在现代的软件开发和部署中&#xff0c;容器化技术已经成为了一种趋势。Docker作为容器化技术的领先者&…

Centos7 系统使用Playbook批量部署多台LNMP环境

使用Playbook批量部署多台LNMP环境 配置absible源 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo wget -O /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo yum -y install ansible 配置主机清单 定义…
最新文章