Zephyr OS驱动0.96OLED

Zephyr OS驱动0.96OLED


0.前言

  本节在之前搭建的Zephyr环境下,实现合宙ESP32C3开发板驱动0.96寸OLED的功能,旨在熟悉zephyr的开发流程。

一、文件添加

  按照官方推荐的目录结构,在zephyr同级目录下,创建app目录,用于顶层的应用开发。并创建相关的源文件、cmake文件、工程配置文件和设备树插件:

二、添加板级支持及编译逻辑

  在CMakeLists.txt中,添加如下内容,将顶层源代码依赖于中间层的zyphyr os:

bash 复制代码
cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ssd1306_oled_demo)

target_sources(app PRIVATE src/main.c)

  修改 esp32c3_luatos_core.overlay 文件,对开发板上的管脚功能进行配置。

这里将 esp32c3 的 i2c0(GPIO4,GPIO5)进行使能,并在节点下添加 ssd1306 的子节点,compatible 属性用于和源码中的驱动进行匹配。

bash 复制代码
/ {
	chosen {
		zephyr,display = &ssd1306;
	};
};

&i2c0 {
	status = "okay";

	ssd1306: ssd1306@3c {
		compatible = "solomon,ssd1306fb";
		reg = <0x3c>;
		width = <128>;
		height = <64>;
		segment-offset = <0>;
		page-offset = <0>;
		display-offset = <0>;
		multiplex-ratio = <63>;
		segment-remap;
		com-invdir;
		prechargep = <0x22>;
	};
};

修改 prj.conf ,添加相关的依赖库支持包进行编译:

bash 复制代码
# 显示驱动配置
CONFIG_DISPLAY=y
CONFIG_SSD1306=y

# I2C 驱动配置
CONFIG_I2C=y

# 日志配置
CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_PRINTK=y
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y

# 显示驱动日志
CONFIG_DISPLAY_LOG_LEVEL_INF=y

也可以通过 west build -t menuconfig 命令到图形化界面中进行查找和添加。

三、业务代码

在main.c中,手动实现 ssd1306 的一些特定功能代码:

c 复制代码
/*
 * ESP32-C3 LuatOS Core SSD1306 OLED 示例程序
 *
 * 硬件连接:
 * - SSD1306 SDA -> GPIO4
 * - SSD1306 SCL -> GPIO5
 * - SSD1306 VCC -> 3.3V
 * - SSD1306 GND -> GND
 */

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/display.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(ssd1306_demo, LOG_LEVEL_INF);

/* 在屏幕上绘制一个填充矩形 */
static void draw_filled_rect(const struct device *dev, uint16_t x, uint16_t y,
			      uint16_t width, uint16_t height, bool fill)
{
	struct display_buffer_descriptor buf_desc;
	uint8_t *buf;
	size_t buf_size;

	/* SSD1306 使用 MONO01 像素格式,每个像素1位 */
	buf_size = (width * height + 7) / 8;
	buf = k_malloc(buf_size);
	if (!buf) {
		LOG_ERR("Failed to allocate buffer");
		return;
	}

	/* 填充缓冲区 */
	memset(buf, fill ? 0xFF : 0x00, buf_size);

	buf_desc.buf_size = buf_size;
	buf_desc.width = width;
	buf_desc.height = height;
	buf_desc.pitch = width;

	display_write(dev, x, y, &buf_desc, buf);
	k_free(buf);
}

/* 清空整个屏幕 */
static void clear_display(const struct device *dev,
			  struct display_capabilities *caps)
{
	draw_filled_rect(dev, 0, 0, caps->x_resolution, caps->y_resolution, false);
}

/* 绘制简单的图案 */
static void draw_pattern(const struct device *dev,
			 struct display_capabilities *caps)
{
	/* 绘制边框 */
	draw_filled_rect(dev, 0, 0, caps->x_resolution, 2, true);  /* 顶部 */
	draw_filled_rect(dev, 0, caps->y_resolution - 8,
			 caps->x_resolution, 2, true);  /* 底部 */
	draw_filled_rect(dev, 0, 0, 2, caps->y_resolution, true);  /* 左侧 */
	draw_filled_rect(dev, caps->x_resolution - 2, 0,
			 2, caps->y_resolution, true);  /* 右侧 */

	/* 在中心绘制一个小矩形 */
	uint16_t center_x = caps->x_resolution / 2 - 16;
	uint16_t center_y = caps->y_resolution / 2 - 8;
	draw_filled_rect(dev, center_x, center_y, 32, 16, true);

	/* 绘制四个角的小方块 */
	draw_filled_rect(dev, 8, 8, 8, 8, true);  /* 左上 */
	draw_filled_rect(dev, caps->x_resolution - 16, 8, 8, 8, true);  /* 右上 */
	draw_filled_rect(dev, 8, caps->y_resolution - 16, 8, 8, true);  /* 左下 */
	draw_filled_rect(dev, caps->x_resolution - 16,
			 caps->y_resolution - 16, 8, 8, true);  /* 右下 */
}

/* 绘制动画效果 - 移动的方块 */
static void draw_moving_box(const struct device *dev,
			    struct display_capabilities *caps,
			    uint16_t pos)
{
	const uint16_t box_size = 8;
	uint16_t x = pos % (caps->x_resolution - box_size);
	uint16_t y = 24;

	/* 清除上一帧的方块区域 */
	draw_filled_rect(dev, 0, y, caps->x_resolution, box_size, false);

	/* 绘制新位置的方块 */
	draw_filled_rect(dev, x, y, box_size, box_size, true);
}

int main(void)
{
	const struct device *display_dev;
	struct display_capabilities caps;
	uint16_t frame = 0;

	LOG_INF("ESP32-C3 LuatOS Core - SSD1306 OLED Demo");

	/* 获取显示设备 */
	display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
	if (!device_is_ready(display_dev)) {
		LOG_ERR("Display device not ready");
		return -1;
	}

	LOG_INF("Display device: %s", display_dev->name);

	/* 获取显示能力 */
	display_get_capabilities(display_dev, &caps);
	LOG_INF("Resolution: %dx%d", caps.x_resolution, caps.y_resolution);
	LOG_INF("Pixel format: %d", caps.current_pixel_format);

	/* 清空屏幕 */
	clear_display(display_dev, &caps);

	/* 关闭屏幕消隐(使能显示) */
	display_blanking_off(display_dev);

	/* 显示静态图案 */
	LOG_INF("Drawing pattern...");
	draw_pattern(display_dev, &caps);

	/* 等待3秒 */
	k_sleep(K_SECONDS(3));

	/* 清空屏幕准备动画 */
	clear_display(display_dev, &caps);

	/* 显示动画 - 移动的方块 */
	LOG_INF("Starting animation...");
	while (1) {
		draw_moving_box(display_dev, &caps, frame);
		frame += 2;

		/* 每50ms更新一次 */
		k_sleep(K_MSEC(50));

		/* 每隔一段时间重新绘制静态图案 */
		if (frame >= 500) {
			clear_display(display_dev, &caps);
			draw_pattern(display_dev, &caps);
			k_sleep(K_SECONDS(2));
			clear_display(display_dev, &caps);
			frame = 0;
		}
	}

	return 0;
}

这段代码中主要封装了一个填充矩形的代码,其他的代码以此为基础,进行矩形的平移、指定位置填充等操作。

显示节点的主要使用方法为:

在main函数中通过 display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));函数,查找在设备树中指定的 display 显示节点,然后通过 display_write 接口,将缓冲区数据写入到这个显示节点中。

相关推荐
ScilogyHunter14 天前
Zephyr串口驱动开发及构建完全指南
驱动开发·uart·zephyr
ScilogyHunter14 天前
Zephyr Hello World应用开发构建完全指南
zephyr·hello world
ScilogyHunter14 天前
Zephyr Twister测试框架完全指南
zephyr·twister
ScilogyHunter15 天前
west init 命令详解
init·zephyr·west
ScilogyHunter15 天前
使用Kconfig配置Zephyr工程完全指南
kconfig·zephyr
ScilogyHunter15 天前
Zephyr设备树完全指南
zephyr
ScilogyHunter16 天前
Zephyr项目按需配置完全指南
zephyr
ScilogyHunter16 天前
Zephyr最简工程配置指南
zephyr
ScilogyHunter16 天前
Zephyr主仓库目录结构完全指南
zephyr
ScilogyHunter16 天前
Zephyr工程配置完全指南
zephyr