当前位置: 首页 > news >正文

用CMake 实现U8g2 的 SDL2 模拟环境

就是在电脑上运行U8G2 代码,模拟出OLED 屏幕上的显示效果。参考了别人写的教程:https://github.com/snqx-lqh/u8g2-windows-sdl-simulate。U8G2 本身带有SDL2 相关的支持,只是关于具体要怎么跑起来,找到的教程都比较土法炼钢,说要到处复制粘贴文件,然后写makefile。毕竟已经是21 世纪了,还是应该从那种恐龙时代的风格上前进一步,所以我决定试试用CMake。

准备项目文件

可以去用我配置好的项目:https://gitee.com/etberzin/u8g2_sdl2,注意里面有submodule,clone 的时候用:

git clone https://gitee.com/etberzin/u8g2_sdl2.git --recurse-submodule

也可以参照我的结构自己搞。项目结构:

u8g2_sdl2
├─lib
│  ├─SDL2
│  └─u8g2
├─src
│  ├─ Arduino.h
│  ├─ entry.cpp
│  └─ main.cpp
└─CMakeLists.txt

lib/SDL2 是SDL2 的库文件,版本2.23.6-mingw,自己下载的话去找下面这个压缩包:

在这里插入图片描述

lib/u8g2 是U8G2 的库文件,这里只放了个submodule,指向U8G2 的repo https://github.com/olikraus/u8g2。要自己添加的话,运行下面的指令添加submodule:

git submodule add https://github.com/olikraus/u8g2.git lib/u8g2

github 下载太慢的话,也可以去gitee 上找别人复制来的u8g2 repo。这两个库的文件都可以直接原样放进项目里,也可以自己裁剪掉不需要的部分。

src 里面是项目自己的源代码,详细的内容后面再说。

  • src/entry.cpp :里面是main 函数,也负责处理SDL 的事件队列;
  • src/man.cpp :里面是Arduino 风格的setuploop 函数,主要的代码都放在这里;
  • src/Arduino.h:提供了兼容Arduino 的接口,方便直接使用Arduino 代码,目前里面只有delay 函数;

CMakeLists.txt 是项目的编译脚本,内容如下。这里面写的什么意思,可以去问AI 逐行解释,反正都是很无聊的东西。

cmake_minimum_required(VERSION 3.21.0)# Get folder name and set as project name
get_filename_component(ProjectName ${CMAKE_CURRENT_SOURCE_DIR} NAME)
project(${ProjectName} VERSION 1.0 LANGUAGES C CXX)message("Project name: ${PROJECT_NAME}")# Set C++ standards
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD_REQUIRED ON)# Compile definitions
add_compile_definitions(U8G2_USE_LARGE_FONTS)# Source files
file(GLOB MainFiles CONFIGURE_DEPENDS "src/*.cpp")
file(GLOB U8g2SDL CONFIGURE_DEPENDS "lib/u8g2/sys/sdl/common/*.c")# Create executable
add_executable(${PROJECT_NAME} ${MainFiles}${U8g2SDL}
)target_include_directories(${PROJECT_NAME} PRIVATE src)# U8G2 Library
add_subdirectory(lib/u8g2)
target_link_libraries(${PROJECT_NAME} PRIVATE u8g2)# SDL2 Configuration
set(SDL2_DIR "lib/SDL2/cmake")
find_package(SDL2 REQUIRED)target_link_libraries(${PROJECT_NAME} PRIVATE ${SDL2_LIBRARIES})# copy DLL to build directory(Windows)
if(WIN32)add_custom_command(TARGET ${PROJECT_NAME} POST_BUILDCOMMAND ${CMAKE_COMMAND} -E copy${CMAKE_SOURCE_DIR}/lib/SDL2/x86_64-w64-mingw32/bin/SDL2.dll$<TARGET_FILE_DIR:${PROJECT_NAME}>)
endif()

编译环境

推荐用VScode 搭配CMake 插件,因为我是这么用的。直接命令行编译应该也行,我没试。

在这里插入图片描述

然后要装cmake 和mingw,用scoop 可以直接安装,并且自动配置好环境变量,如果不用scoop,那就自己想别的办法。

scoop install mingw-winlibs cmake

装好这些东西以后,用VScode 打开项目文件夹,cmake 扩展会自动启动,提示让选择编译工具,在这里选择mingw 提供的gcc。

在这里插入图片描述

然后会自动生成build 文件夹,输出一些类似下面这样的东西,就表示配置没有问题。

[main] Configuring project: u8g2_sdl2 
[proc] Executing command: C:\Users\shell\scoop\apps\mingw-winlibs\current\bin\cmake.EXE -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_C_COMPILER:FILEPATH=C:\Users\shell\scoop\apps\mingw-winlibs\current\bin\gcc.exe -DCMAKE_CXX_COMPILER:FILEPATH=C:\Users\shell\scoop\apps\mingw-winlibs\current\bin\g++.exe --no-warn-unused-cli -S C:/Users/shell/source/CXX/u8g2_sdl2 -B c:/Users/shell/source/CXX/u8g2_sdl2/build -G "MinGW Makefiles"
[cmake] Not searching for unused variables given on the command line.
[cmake] Project name: u8g2_sdl2
[cmake] -- Configuring done (2.3s)
[cmake] -- Generating done (0.4s)
[cmake] -- Build files have been written to: C:/Users/shell/source/CXX/u8g2_sdl2/build

编译和报错处理

在cmake 扩展的侧边栏里点击build 项目,开始编译。

在这里插入图片描述

目前应该是编译不过的,因为U8G2 里有一点小问题。如果报错u8x8_d_sdl_128x64.c 编译失败,提示

u8g2_sdl2\lib\u8g2\sys\sdl\common\u8x8_d_sdl_128x64.c:103:5: error: implicit declaration of function 'printf' [-Wimplicit-function-declaration]

这个错误是因为代码里调用了printf,但是并没有包含头文件stdio.h,也没有给出显式的声明。这种代码在C99 标准以前好像是可以编译的,因为在C99 以后就不允许这样写了。经典的老人写的老版本老代码的问题。解决方法是手动修改这个文件,加上头文件,文件路径在报错提示里有。当然也可以在编译时指定用C99 以前的标准,不推荐。

在这里插入图片描述

上面图里右边是修改后的结果。然后再点编译,可能会遇到另一个报错,提示找不到头文件U8g2lib.h。这是因为U8G2 把C 语言的头文件和C++ 的分开到两个文件夹里,但是在CMakeLists.txt 里没把C++ 的文件夹加入头文件路径。解决方法是去编辑u8g2 根目录下的CMakeLists.txt 文件。注意,是u8g2 的,不是这个项目下的文件。然后做如下修改:

在这里插入图片描述

这样改了以后应该就能编译成功了,之后点击运行。

在这里插入图片描述

默认仿真的对象是128x64 尺寸的OLED 屏幕,画面背景里每一个小方块代表8x8 像素。main.cpp 有U8G2 官方提供的图形测试示例代码,运行的效果大概就是下面这样,用过U8G2 的人肯定很熟悉了。

在这里插入图片描述

现在疑似还有个BUG,就是示例代码显示ASCII 字符表的时候,最后一行字母显示不全,超出范围了,不确定是哪里有问题。如果真的是个BUG,那就是u8x8_d_sdl_128x64.c 这个文件里有什么逻辑问题,需要先去研究一下SDL2 的用法,再看看要怎么修。

源文件

下面大致说一下src 里面文件的内容。

Arduino.h

#pragma once/*** @brief 提供兼容Arduino 的常用接口* */#include <stdint.h>#include <SDL_timer.h>void delay(uint32_t ms) {SDL_Delay(ms);
}

U8g2 的示例代码里用了delay 函数,所以有必要提供与之兼容的接口。不能直接把delay 删掉,否则界面绘图的代码跑的太快,显示的东西一闪而过,啥都看不见。

调用SDL_Delay 而不是别的Sleep 之类的延迟,因为这是个基于SDL2 的程序。但这样也有个问题,因为经典Arduino 程序里,delay 期间其实还在不停调用yield函数,所以可以把某些在delay 期间还要不停轮询的代码放在yield 里。用SDL_Delay 实现延迟的话,就不能去调用yield 了。以后再说吧,要是有必要模拟yield 逻辑,可以考虑开一个后台线程用来执行yield。只不过这样又会遇到多线程同步的问题,不方便用全局变量了。

entry.c

#include "SDL.h"extern void setup();extern void loop();extern "C" {int SDL_main(int argc, char **argv) {setup();SDL_Event event;while (1) {SDL_PollEvent(&event);if (event.type == SDL_QUIT) {return 0;}// TODO: keyloop();}
}
}

为了兼容Arduino 代码的结构,需要在main 函数里调用setuploop,Arduino 官方的代码也是这么写的。只不过因为用了SDL2,需要按照SDL2 的要求改写main 函数,把名字改成SDL_main,不然编译会报错找不到符号。大概是SDL2 内部需要做一些处理才能显示出图形界面。

SDL_main 函数内部,除了要调用setuploop,还要处理SDL2 的消息循环。写过win32 图形界面或者Qt 的人应该知道这是什么意思。我在每次loop 迭代的间隙调用SDL_PollEvent(&event) 处理消息,所以loop 里面的代码不能卡住太长时间,否则窗口会白屏,弹出个窗口无响应。

此外,需要处理SDL_QUIT 事件,在这个事件发生后退出SDL_main 函数,或者调用exit 终止程序,否则就没办法点击X 按钮关闭窗口。之后可以加上处理键盘事件的代码,比如把按键映射成单片机引脚,然后可以调用digitalRead 读取电平,要是按键A 按下了,digitalRead('A') 就返回低电平。这样就可以模拟一些界面交互逻辑。

main.cpp

#include <U8g2lib.h>#include "Arduino.h"class U8G2_SDL_128X64_F : public U8G2 {public:U8G2_SDL_128X64_F(const u8g2_cb_t *rotation) : U8G2() {// 全缓冲模式u8g2_SetupBuffer_SDL_128x64(&this->u8g2, rotation);}
};U8G2_SDL_128X64_F u8g2{U8G2_R0};void u8g2_prepare(void) {u8g2.setFont(u8g2_font_6x10_tf);u8g2.setFontRefHeightExtendedText();u8g2.setDrawColor(1);u8g2.setFontPosTop();u8g2.setFontDirection(0);
}void u8g2_box_frame(uint8_t a) {u8g2.drawStr(0, 0, "drawBox");u8g2.drawBox(5, 10, 20, 10);u8g2.drawBox(10 + a, 15, 30, 7);u8g2.drawStr(0, 30, "drawFrame");u8g2.drawFrame(5, 10 + 30, 20, 10);u8g2.drawFrame(10 + a, 15 + 30, 30, 7);
}void u8g2_disc_circle(uint8_t a) {u8g2.drawStr(0, 0, "drawDisc");u8g2.drawDisc(10, 18, 9);u8g2.drawDisc(24 + a, 16, 7);u8g2.drawStr(0, 30, "drawCircle");u8g2.drawCircle(10, 18 + 30, 9);u8g2.drawCircle(24 + a, 16 + 30, 7);
}void u8g2_r_frame(uint8_t a) {u8g2.drawStr(0, 0, "drawRFrame/Box");u8g2.drawRFrame(5, 10, 40, 30, a + 1);u8g2.drawRBox(50, 10, 25, 40, a + 1);
}void u8g2_string(uint8_t a) {u8g2.setFontDirection(0);u8g2.drawStr(30 + a, 31, " 0");u8g2.setFontDirection(1);u8g2.drawStr(30, 31 + a, " 90");u8g2.setFontDirection(2);u8g2.drawStr(30 - a, 31, " 180");u8g2.setFontDirection(3);u8g2.drawStr(30, 31 - a, " 270");
}void u8g2_line(uint8_t a) {u8g2.drawStr(0, 0, "drawLine");u8g2.drawLine(7 + a, 10, 40, 55);u8g2.drawLine(7 + a * 2, 10, 60, 55);u8g2.drawLine(7 + a * 3, 10, 80, 55);u8g2.drawLine(7 + a * 4, 10, 100, 55);
}void u8g2_triangle(uint8_t a) {uint16_t offset = a;u8g2.drawStr(0, 0, "drawTriangle");u8g2.drawTriangle(14, 7, 45, 30, 10, 40);u8g2.drawTriangle(14 + offset, 7 - offset, 45 + offset, 30 - offset, 57 + offset, 10 - offset);u8g2.drawTriangle(57 + offset * 2, 10, 45 + offset * 2, 30, 86 + offset * 2, 53);u8g2.drawTriangle(10 + offset, 40 + offset, 45 + offset, 30 + offset, 86 + offset, 53 + offset);
}void u8g2_ascii_1() {char s[2] = " ";uint8_t x, y;u8g2.drawStr(0, 0, "ASCII page 1");for (y = 0; y < 6; y++) {for (x = 0; x < 16; x++) {s[0] = y * 16 + x + 32;u8g2.drawStr(x * 7, y * 10 + 10, s);}}
}void u8g2_ascii_2() {char s[2] = " ";uint8_t x, y;u8g2.drawStr(0, 0, "ASCII page 2");for (y = 0; y < 6; y++) {for (x = 0; x < 16; x++) {s[0] = y * 16 + x + 160;u8g2.drawStr(x * 7, y * 10 + 10, s);}}
}void u8g2_extra_page(uint8_t a) {u8g2.drawStr(0, 0, "Unicode");u8g2.setFont(u8g2_font_unifont_t_symbols);u8g2.setFontPosTop();u8g2.drawUTF8(0, 24, "☀ ☁");switch (a) {case 0:case 1:case 2:case 3:u8g2.drawUTF8(a * 3, 36, "☂");break;case 4:case 5:case 6:case 7:u8g2.drawUTF8(a * 3, 36, "☔");break;}
}#define cross_width  24
#define cross_height 24
static const unsigned char cross_bits[] U8X8_PROGMEM = {0x00, 0x18, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00,0x00, 0x81, 0x00, 0x00, 0x81, 0x00, 0xC0, 0x00, 0x03, 0x38, 0x3C, 0x1C, 0x06, 0x42, 0x60, 0x01, 0x42, 0x80,0x01, 0x42, 0x80, 0x06, 0x42, 0x60, 0x38, 0x3C, 0x1C, 0xC0, 0x00, 0x03, 0x00, 0x81, 0x00, 0x00, 0x81, 0x00,0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x00, 0x42, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x18, 0x00,
};#define cross_fill_width  24
#define cross_fill_height 24
static const unsigned char cross_fill_bits[] U8X8_PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x64, 0x00, 0x26, 0x84, 0x00, 0x21, 0x08, 0x81, 0x10,0x08, 0x42, 0x10, 0x10, 0x3C, 0x08, 0x20, 0x00, 0x04, 0x40, 0x00, 0x02, 0x80, 0x00, 0x01, 0x80, 0x18, 0x01,0x80, 0x18, 0x01, 0x80, 0x00, 0x01, 0x40, 0x00, 0x02, 0x20, 0x00, 0x04, 0x10, 0x3C, 0x08, 0x08, 0x42, 0x10,0x08, 0x81, 0x10, 0x84, 0x00, 0x21, 0x64, 0x00, 0x26, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};#define cross_block_width  14
#define cross_block_height 14
static const unsigned char cross_block_bits[] U8X8_PROGMEM = {0xFF, 0x3F, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0xC1, 0x20,0xC1, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0xFF, 0x3F,
};void u8g2_bitmap_overlay(uint8_t a) {uint8_t frame_size = 28;u8g2.drawStr(0, 0, "Bitmap overlay");u8g2.drawStr(0, frame_size + 12, "Solid / transparent");u8g2.setBitmapMode(false /* solid */);u8g2.drawFrame(0, 10, frame_size, frame_size);u8g2.drawXBMP(2, 12, cross_width, cross_height, cross_bits);if (a & 4) u8g2.drawXBMP(7, 17, cross_block_width, cross_block_height, cross_block_bits);u8g2.setBitmapMode(true /* transparent*/);u8g2.drawFrame(frame_size + 5, 10, frame_size, frame_size);u8g2.drawXBMP(frame_size + 7, 12, cross_width, cross_height, cross_bits);if (a & 4) u8g2.drawXBMP(frame_size + 12, 17, cross_block_width, cross_block_height, cross_block_bits);
}void u8g2_bitmap_modes(uint8_t transparent) {const uint8_t frame_size = 24;u8g2.drawBox(0, frame_size * 0.5, frame_size * 5, frame_size);u8g2.drawStr(frame_size * 0.5, 50, "Black");u8g2.drawStr(frame_size * 2, 50, "White");u8g2.drawStr(frame_size * 3.5, 50, "XOR");if (!transparent) {u8g2.setBitmapMode(false /* solid */);u8g2.drawStr(0, 0, "Solid bitmap");}else {u8g2.setBitmapMode(true /* transparent*/);u8g2.drawStr(0, 0, "Transparent bitmap");}u8g2.setDrawColor(0);  // Blacku8g2.drawXBMP(frame_size * 0.5, 24, cross_width, cross_height, cross_bits);u8g2.setDrawColor(1);  // Whiteu8g2.drawXBMP(frame_size * 2, 24, cross_width, cross_height, cross_bits);u8g2.setDrawColor(2);  // XORu8g2.drawXBMP(frame_size * 3.5, 24, cross_width, cross_height, cross_bits);
}uint8_t draw_state = 0;void draw(void) {u8g2_prepare();switch (draw_state >> 3) {case 0:u8g2_box_frame(draw_state & 7);break;case 1:u8g2_disc_circle(draw_state & 7);break;case 2:u8g2_r_frame(draw_state & 7);break;case 3:u8g2_string(draw_state & 7);break;case 4:u8g2_line(draw_state & 7);break;case 5:u8g2_triangle(draw_state & 7);break;case 6:u8g2_ascii_1();break;case 7:u8g2_ascii_2();break;case 8:u8g2_extra_page(draw_state & 7);break;case 9:u8g2_bitmap_modes(0);break;case 10:u8g2_bitmap_modes(1);break;case 11:u8g2_bitmap_overlay(draw_state & 7);break;}
}void setup() { u8g2.begin(); }void loop() {// picture loopu8g2.clearBuffer();draw();u8g2.sendBuffer();// increase the statedraw_state++;if (draw_state >= 12 * 8) {draw_state = 0;}// delay between each pagedelay(100);
}

这里面主要就是U8g2 的GraphicsTest 示例代码,显示效果参考之前运行的截图。值得说的只有开头这部分:

class U8G2_SDL_128X64_F : public U8G2 {public:U8G2_SDL_128X64_F(const u8g2_cb_t *rotation) : U8G2() {// 全缓冲模式u8g2_SetupBuffer_SDL_128x64(&this->u8g2, rotation);}
};U8G2_SDL_128X64_F u8g2{U8G2_R0};

U8G2 没有内置SDL 显示环境相关的类,默认是只能使用C 语言API,但是我的代码都是用C++ API 的,用C 我咳嗽,所以需要自己添加这么个C++ 类。这个是全缓冲full buffer 模式的,所以加了_F 后缀,需要page buffer 模式的话可以自己实现。

总结

想用这个方法辅助调试代码的话,最好是把显示和绘图相关的代码都封装成独立的函数,完全不与程序中其他部分耦合,这样就能把这些函数拿出来单独调试。比如类似下面这样:

void clear_box(u8g2_int_t x, u8g2_int_t y, u8g2_int_t w, u8g2_int_t h) {u8g2.setDrawColor(0);  // 反色u8g2.drawBox(x, y, w, h);u8g2.setDrawColor(1);
}void show_logo() {u8g2.clearBuffer();// u8g2.drawBox(12, 16, 104, 40);// clear_box(17, 17, 98, 35);u8g2.setFont(CHS_FONT);u8g2.drawUTF8X2(35, 25, "刻");u8g2.drawUTF8X2(17, 55, "BITTER");u8g2.sendBuffer();
}void begin_msg() {u8g2.setCursor(20, 63);clear_box(20, 57, 45, 8);u8g2.setFont(SMALL_TEXT_FONT);  // 设置小字体
}void end_msg() {u8g2.setDrawColor(1);  // 恢复正常颜色
}

这样子调试U8G2 的图形界面大概稍微能比实机调试快一丢丢,改坐标,调大小什么的。还是要等编译,不用等烧录,像ESP8266 那种串口小水管烧录贼慢的,用这个正合适。优点理论上是完全支持U8G2 的所有显示功能,不会像模拟器一样可能遇到没实现的差异,而且后续比较容易扩展支持128x64 以外的屏幕尺寸。缺点是能实现的仅限U8G2 和一些硬件无关的东西,其他和单片机硬件相关的东西都模拟不了,所以可能还得为了模拟而修改代码。想更快、更方便的话,可能还是得找那些能模拟Arduino 硬件的模拟器,比如wowki,但是上模拟器还是可能要修改代码,好处大概只有不用等编译。

http://www.dtcms.com/a/495121.html

相关文章:

  • 企业网站排名提升软件智能优化wordpress 创业
  • 企业网站建设调查问卷网站开发周记30篇
  • 网站模板网站免费建商城网站
  • 安徽感智教育科技有限公司成功加入安徽省物流协会
  • Chart.js 雷达图
  • 百分点科技发布中国首个AI原生GEO产品Generforce,助力品牌决胜AI搜索新时代
  • 微算法科技(MLGO)突破性AI推理控制:一种基于集成学习优化算法的无线传感设备边缘协同推理控制技术
  • 智存跃迁,阿里云存储面向 AI 升级全栈数据存储能力
  • 临淄专业网站优化哪家好g3云推广官网
  • python离线包安装方法总结
  • Docker网络和存储卷
  • REFRAG技术详解:如何通过压缩让RAG处理速度提升30倍
  • C++ stack、queue栈和队列的使用——附加算法题
  • 论文解读--RCBEVDet++:Toward High-accuracy Radar-Camera Fusion 3D Perception Network
  • 网站建设公司 温州百度优化大师
  • Kubernetes:Ingress - Traefik
  • 自然的铁律与理想的迷梦:论阿伦特政治哲学的局限与谬误​​
  • 电商网站创办过程建站员工网站
  • Oracle数据库安全参数优化
  • 亚马逊云代理:利用亚马逊云进行大规模数据分析与处理的最佳实践
  • 生成链接的网站网站超链接用什么
  • 网站英文域名是什么django类似wordpress
  • 本地搭建EXAM-MASTER考试系统
  • 高级运维工程师面试题汇总-【DEVOPS】
  • 东莞浩智网站建设开发wordpress 中国地图
  • 【Go】C++ 转 Go 第(一)天:环境搭建 Windows + VSCode 远程连接 Linux
  • MYSQL学习笔记(个人)(第十五天)
  • 网站登录验证码不正确云端互联网站建设
  • Zotero安装+坚果云+iPad同步方法及问题整理
  • 做彩票网站的方案网站建设资金报告