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 DriversSPI 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总线发送到OLEDstruct 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 GPIOresult = 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 GPIOresult = 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_messagespi_message_init(&message);//复位spi_transfermemset(&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];//复位OLEDoled_reset(oled);//发送初始化命令command[0 ] = 0xAE;				//--turn off oled panelcommand[1 ] = 0x00;				//---set low column addresscommand[2 ] = 0x10;				//---set high column addresscommand[3 ] = 0x40;				//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)command[4 ] = 0x81;				//--set contrast control registercommand[5 ] = 0xCF;				 // Set SEG Output Current Brightnesscommand[6 ] = 0xA1;				//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常command[7 ] = 0xC8;				//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常command[8 ] = 0xA6;				//--set normal displaycommand[9 ] = 0xA8;				//--set multiplex ratio(1 to 64)command[10] = 0x3F;				//--1/64 dutycommand[11] = 0xD3;				//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)command[12] = 0x00;				//-not offsetcommand[13] = 0xD5;				//--set display clock divide ratio/oscillator frequencycommand[14] = 0x80;				//--set divide ratio, Set Clock as 100 Frames/Seccommand[15] = 0xD9;				//--set pre-charge periodcommand[16] = 0xF1;				//Set Pre-Charge as 15 Clocks & Discharge as 1 Clockcommand[17] = 0xDA;				//--set com pins hardware configurationcommand[18] = 0x12;				command[19] = 0xDB;				//--set vcomhcommand[20] = 0x40;				//Set VCOM Deselect Levelcommand[21] = 0x20;				//-Set Addressing Mode (0x00/0x01/0x02)command[22] = 0x00;				//command[23] = 0x8D;				//--set Charge Pump enable/disablecommand[24] = 0x14;				//--set(0x10) disablecommand[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行1bytefor(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()){//缓冲帧内容改变才刷新OLEDif(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的GPIOresult = 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;//IDstrcpy(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;}//创建内核线程,更新OLEDoled->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.mzph.cn/news/621056.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】树和二叉树堆(基本概念介绍)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;《数据结构》https://blog.csdn.net/qinjh_/category_12536791.html?spm1001.2014.3001.5482 ​​ 目录 前言 树的概念 树的常见名词 树与…

linux TIME_WAIT时间变短

在Linux中&#xff0c;TIME_WAIT状态表示TCP连接已经关闭但还未完全清除的过程。默认情况下&#xff0c;TIME_WAIT状态会持续2分钟&#xff08;120秒&#xff09;以确保网络上没有重复的数据包被传输到错误的目标地址。 要将TIME_WAIT时间设置为更短的值&#xff0c;可以通过修…

2024.1.14每日一题

LeetCode 83.删除排序链表中的重复元素 83. 删除排序链表中的重复元素 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例 1&#xff1a; 输…

手把手教你学会接口自动化系列十-将用例写在json中,持久化管理起来上

我们之前写的把url,登录都封装了,但是用例的数据怎么用其他文件管理起来,和代码分离呢?由次,我就行了json进行用例的管理。 从接口的角度我们分析,我们都知道接口请求有以下几方面: url method headers data 由次我将我的用例管理格式设置成下面的 {"url"…

关闭免费版pycharm社区版双击shift时出现的搜索框

Pycharm 在双击 shift 的时候总是弹出搜索框&#xff0c;但作为中国玩家&#xff0c;经常需要双击 shift 循环切换中英文。这就很困恼。 下面就解决这个问题。单独关闭双击shift的功能。 步骤 1.左上角 File -> Settings 2. 如图&#xff0c;输入‘advan’ 找到高级设置&…

基于AidLux的智慧教育版面分析应用

基于AidLux的智慧教育版面分析应用 1. Aidlux平台介绍 融合架构操作系统AidLux,可以为单一ARM设备同时提供Android和Linux运行环境&#xff0c;双系统既能独立使用又能相互通信。 非虚拟机方式实现双系统融合并行 同时拥有两个系统的完整用户体验无需重启即可在两个系统之间…

RibbonGroup 添加QRadioButton

RibbonGroup添加QRadioButton&#xff1a; QRadioButton * pRadio new QRadioButton(tr("Radio")); pRadio->setToolTip(tr("Radio")); groupClipboard->addWidget(pRadio); connect(pRadio, SIGNAL(clicked(…

扩展卡尔曼滤波(Extended Kalman Filter, EKF):理论和应用

扩展卡尔曼滤波&#xff08;Extended Kalman Filter, EKF&#xff09;&#xff1a;理论、公式和应用 引言 卡尔曼滤波是一种广泛应用于估计动态系统状态的技术&#xff0c;但当系统的动态模型或测量模型是非线性的时候&#xff0c;传统的卡尔曼滤波方法就显得无能为力。扩展卡…

Ubuntu中用useradd创建用户后无法用su切换过去

原因&#xff1a; 没有设置密码&#xff0c;没有指定家目录和shell版本&#xff0c;就不能su切换到新用户 解决方法&#xff1a; su - root //切换到root权限 useradd -m -s /bin/bash node1 //-m自动创建home目录&#xff0c;-s指定shell版本 passwd node1 //设置密码 参考链…

springboot(ssm单位考勤系统 oa办公系统Java系统

springboot(ssm单位考勤系统 oa办公系统Java系统 开发语言&#xff1a;Java 框架&#xff1a;ssm/springboot vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.7&#xff08;或8.0&#xff09; 数据库工…

【保姆级教程|YOLOv8添加注意力机制】【1】添加SEAttention注意力机制步骤详解、训练及推理使用

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

SpringBoot+thymeleaf实战遇到的问题

目录 一、控制台&#xff1a; 二、数据库查询异常&#xff1a; 三、前后端错误校验 四、在serviceImp中需要添加一个eq条件&#xff0c;表示和数据库中的哪个字段进行比较&#xff0c;否则会查出所有数据&#xff0c;导致500 五、使用流转换数据更简洁 六、重复报错&…

动态规划篇-03:打家劫舍

198、打家劫舍 状态转移方程 base case 边界问题就是&#xff1a;走到最后一间房子门口也没抢&#xff0c;那么最终抢到的金额为0 明确状态 “原问题和子问题中会变化的变量” 抢到的金额数就是状态&#xff0c;因为随着在每一件房子门口做选择&#xff0c;抢到的金额数会随…

大模型训练营Day3 基于 InternLM 和 LangChain 搭建你的知识库 作业

本篇记录大模型训练营第三次的作业&#xff0c;属实是拖延症本症患者。 主要步骤前面的安装各种包和依赖如前面作业一样&#xff0c;按照文档操作即可&#xff1a; 再按照文档进行各种克隆&#xff0c;把知识库复制到本地&#xff1a; 复制粘贴操作文档中的构建向量数据库的文…

七:Day08_任务调度

第一章 定时任务概述 在项目中开发定时任务应该一种比较常见的需求&#xff0c;在 Java 中开发定时任务主要有三种解决方案&#xff1a;一是使用JDK 自带的 Timer&#xff0c;二是使用 Spring Task&#xff0c;三是使用第三方组件 Quartz。 建议&#xff1a; 单体项目架构使用…

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

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

UR5机械臂控制

1.ros环境安装 快速安装命令&#xff1a;wget http://fishros.com/install -O fishros && . fishros 2.ur驱动安装 虚拟机Ubuntu16.04ros-kinetic控制真实UR5机械臂总结记录ros kinetic控制UR3机械臂 3.ur命令行控制 使用了 URScript 语言来描述机器人的运动指令&…

数学建模.斯皮尔曼相关系数

一、两种定义 二、用matlab计算 三、两种相关系数计算结果的对比 四、取检验值&#xff08;临界值&#xff09;分为两种情况 &#xff08;1&#xff09;小样本查表 &#xff08;2&#xff09;大样本 P值是大于检验值的概率 本文是学习清风网课后的总结&#xff0c;希望对大家有…

1000以内的质数,用python获取放到list1中,1000以内的斐波那契数,用python获取放到list2中,然后两个list画出曲线图

# -*- coding: utf-8 -*- import matplotlib.pyplot as plt # 获取1000以内的质数 def get_primes(n): primes [] for possiblePrime in range(2, n 1): # 假设数是质数 isPrime True for num in range(2, int(possiblePrime ** 0.5) 1): if possiblePrime % num …

自动驾驶车辆运动规划方法综述 - 论文阅读

本文旨在对自己的研究方向做一些记录&#xff0c;方便日后自己回顾。论文里面有关其他方向的讲解读者自行阅读。 参考论文&#xff1a;自动驾驶车辆运动规划方法综述 1 摘要 规划决策模块中的运动规划环节负责生成车辆的局部运动轨迹 &#xff0c;决定车辆行驶质量的决定因素…