基于esp-idf的arm2d移植

news2024/9/20 2:23:06

什么是ARM2D

Arm在Github上发布了一个专门针对“全体” Cortex-M处理器的2D图形加速库——Arm-2D
我们可以简单的把这个2D图形加速库理解为是一个专门针对Cortex-M处理器的标准“显卡驱动”。虽然这里的“显卡驱动”只是一个夸张的说法——似乎没有哪个Cortex-M处理器“配得上”所谓的显卡,但其实也并没有差多远——因为根据最新的趋势,随着单片机资源的逐步丰富(较高级的工艺节点正在逐步降价),处理器不仅跑得越来越快、存储器越来越大,而且大量的厂商已经或者正在考虑给Cortex-M处理器配备专属的2D图形加速引擎

以上摘自公众号裸机思维的文章

首先,arm2d是一个2d引擎库,他是纯软件的东西。很多人可能会被它的arm2d名字给误导。分不清arm2d和DMA2D。实际上DMA2D是硬件,arm2d则是一个软件。arm2d的优秀性能,让我瞠目结舌。在裸机思维的文章中,你不难看到诸如M0+内核、25M主频的主控的平台上跑出各种逆天的效果, 这也是它吸引我的原因。虽然arm开发的初衷是服务于自家的硬件,但是不意味着它不能够移植到别的平台。

以下是我摸索并熟悉arm2d的移植过程

移植前的准备

首先,我们是基于esp-idf 5.0的sdk做的移植。那么,第一需要的肯定是安装环境。这里参考官方手册
就不多赘述。

接下来就应该准备一份驱屏的基础代码了。我们准备了一块esp32s3的开发板,其中屏幕使用了st7789的240X240的spi屏幕。

屏幕驱动

我喜欢以esp-iot-solution中的bus和screen为基础写屏幕驱动。bus中提供了诸如:spi i2c 8080 rgb的通讯层封装,而screen则基于bus的封装提供了st7789 ili9341等lcd芯片封装。
这使得驱屏变得异常简单

cp -r esp-idf/example/get-started/sample_project ./
cd sample_project 

新建components文件夹,再复制刚才提到的bus和screen组件放到components文件夹下。接下来开始着手写屏幕驱动代码:

/**
 * @file arm_math.h
 * @author cangyu (sky.kirto@qq.com)
 * @brief 
 * @version 0.1
 * @date 2024-06-06
 * 
 * @copyright Copyright (c) 2024, CorAL. All rights reserved.
 * 
 */
 
#include <stdio.h>
#include <stdlib.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"

#include "screen_driver.h"
#include "esp_log.h"
/* ==================== [Defines] =========================================== */

#define BOARD_IO_SPI2_MISO          -1
#define BOARD_IO_SPI2_MOSI          11
#define BOARD_IO_SPI2_SCK           12
#define BOARD_LCD_SPI_CS_PIN        10
#define BOARD_LCD_SPI_DC_PIN        9
#define BOARD_LCD_SPI_RESET_PIN     -1
#define BOARD_LCD_SPI_BL_PIN        46
#define BOARD_LCD_SPI_CLOCK_FREQ    40000000

/* ==================== [Typedefs] ========================================== */

/* ==================== [Static Prototypes] ================================= */

static void screen_clear(scr_driver_t *lcd, int color);

/* ==================== [Static Variables] ================================== */

static const char *TAG = "screen example";
static scr_driver_t g_lcd;

/* ==================== [Macros] ============================================ */

/* ==================== [Global Functions] ================================== */

void app_main(void)
{
    spi_config_t bus_conf = {
        .miso_io_num = BOARD_IO_SPI2_MISO,
        .mosi_io_num = BOARD_IO_SPI2_MOSI,
        .sclk_io_num = BOARD_IO_SPI2_SCK,
        .max_transfer_sz = 1024*10
    };
    spi_bus_handle_t spi2_bus_handle = spi_bus_create(SPI2_HOST, &bus_conf);

    scr_interface_spi_config_t spi_lcd_cfg = {
        .spi_bus = spi2_bus_handle,
        .pin_num_cs = BOARD_LCD_SPI_CS_PIN,
        .pin_num_dc = BOARD_LCD_SPI_DC_PIN,
        .clk_freq = BOARD_LCD_SPI_CLOCK_FREQ,
        .swap_data = true,
    };

    scr_interface_driver_t *iface_drv;
    scr_interface_create(SCREEN_IFACE_SPI, &spi_lcd_cfg, &iface_drv);

    scr_find_driver(SCREEN_CONTROLLER_ST7789, &g_lcd);

    scr_controller_config_t lcd_cfg = {
        .interface_drv = iface_drv,
        .pin_num_rst = BOARD_LCD_SPI_RESET_PIN,
        .pin_num_bckl = BOARD_LCD_SPI_BL_PIN,
        .rst_active_level = 0,
        .bckl_active_level = 1,
        .offset_hor = 0,
        .offset_ver = 0,
        .width = 240,
        .height = 240,
        .rotate = SCR_DIR_LRTB,
    };
    g_lcd.init(&lcd_cfg);

    scr_info_t lcd_info;
    g_lcd.get_info(&lcd_info);
    ESP_LOGI(TAG, "Screen name:%s | width:%d | height:%d", lcd_info.name,
             lcd_info.width, lcd_info.height);


    screen_clear(&g_lcd, COLOR_GREEN);

}

/* ==================== [Static Functions] ================================== */

static void screen_clear(scr_driver_t *lcd, int color)
{
    scr_info_t lcd_info;
    lcd->get_info(&lcd_info);
    uint16_t *buffer = malloc(lcd_info.width * sizeof(uint16_t));
    for (size_t i = 0; i < lcd_info.width; i++) {
        buffer[i] = color;
    }

    for (int y = 0; y < lcd_info.height; y++) {
        lcd->draw_bitmap(0, y, lcd_info.width, 1, buffer);
    }

    free(buffer);
}

接下来就是编译烧录的事情了

idf.py set-target esp32s3 # 切换芯片
idf.py build 		# 编译代码
idf.py flash 		# 烧录
idf.py monitor      # 显示串口log

这是运行的效果

驱屏成果
如此,我们便得到了一个干净的驱屏的工程。

ARM2D的组件加入

我们在components文件夹下面创建一个arm2d的组件文件夹。再在arm2d文件夹里clone arm2d的仓库

cd  components 		# 进入组件文件夹
mkdir arm2d 		# 创建arm2d组件件夹
cd arm2d			# 进入arm2d组件文件夹
git clone https://github.com/ARM-software/Arm-2D.git # clone arm2d仓库

arm2d的仓库里面很多文件夹,很多文件。我们首要的就是要弄清楚哪些是我们需要的。我们需要的文件主要分布在Library中和Helper中。其中Library是核心部分,而Helper则是后续添加的有帮助的部分。我们在arm2d里面创建一个CMakeLists.txt用于添加编译

touch CMakeLists.txt

CMakeLists.txt内容如下:

idf_component_register(SRC_DIRS "Arm-2D/Library/Source" "Arm-2D/Helper/Source" 
                    INCLUDE_DIRS "Arm-2D/Library/Include" "Arm-2D/Helper/Include"
                    )

接下来就是退出到工程根目录开始启动上述的编译。
不出所料发生了报错, 找不到arm_2d_cfg.h。那我们在arm2d的文件夹下面添加它
通过搜索这个文件名,我们发现在components/arm2d/Arm-2D/Library/Include/template路径下是有一个同名的config文件。我们把内容复制粘贴过来。大致看一遍配置,值得注意的是,GLCD_CFG_SCEEN_WIDTHGLCD_CFG_SCEEN_HEIGHT 是屏幕的宽和高,别忘记改成我们的屏幕大小:240*240
修改后,别忘记CMakeLists.txt也要修改:

idf_component_register(SRC_DIRS "Arm-2D/Library/Source" "Arm-2D/Helper/Source" 
                    INCLUDE_DIRS "." "Arm-2D/Library/Include" "Arm-2D/Helper/Include"
                    )

再次进行编译,果然没那么简单。这里告诉我们缺少arm_2d_user_arch_port.h文件。哎,之前的tamplate文件夹里好像有。直接复制过来。再次进行编译。

然后发现缺少arm_math.h文件。这个文件比较棘手,是arm的dsp库。arm2d为了加速图形计算,使用了很多arm的dsp库来加速。我们的esp32s3不是arm架构的根本没法使用。这下只能自己写一个arm_math.h文件,将arm2d内部依赖arm-dsp库的内容提取出来, 并简单的替代。这个过程比较费时费力,需要从报错中找到源头,然后从arm2d中理解。使用math.h进行替换。这里我直接放出我最终的arm_math.h:

/**
 * @file arm_math.h
 * @author cangyu (sky.kirto@qq.com)
 * @brief 
 * @version 0.1
 * @date 2024-06-06
 * 
 * @copyright Copyright (c) 2024, CorAL. All rights reserved.
 * 
 */

#ifndef __ARM_MATH_H__
#define __ARM_MATH_H__

/* ==================== [Includes] ========================================== */
#include <math.h>

#ifdef __cplusplus
extern "C" {
#endif

/* ==================== [Defines] =========================================== */



/* ==================== [Typedefs] ========================================== */

typedef int16_t q15_t;
typedef int32_t q31_t;
typedef int64_t q63_t;

/* ==================== [Global Prototypes] ================================= */

__STATIC_FORCEINLINE q31_t clip_q63_to_q31(q63_t x)
{
    return ((q31_t) (x >> 32) != ((q31_t) x >> 31)) ?
           ((0x7FFFFFFF ^ ((q31_t) (x >> 63)))) : (q31_t) x;
}

__STATIC_FORCEINLINE float arm_sin_f32(float x)
{
    return sin(x);
}

__STATIC_FORCEINLINE float arm_cos_f32(float x)
{
    return cos(x);
}

__STATIC_FORCEINLINE q31_t arm_sin_q31(q31_t x)
{
    return (q31_t)sin((float)x);
}

__STATIC_FORCEINLINE q31_t arm_cos_q31(q31_t x)
{
    return (q31_t)cosl((float)x);
}

__STATIC_FORCEINLINE uint32_t usat(int32_t val, uint8_t sat) {
    uint32_t max = (1U << sat) - 1; // 最大值为 2^sat - 1
    if (val < 0) {
        return 0;
    } else if (val > max) {
        return max;
    } else {
        return (uint32_t)val;
    }
}

__STATIC_FORCEINLINE int32_t saturate_to_int32(int64_t value) {
    if (value > INT32_MAX) {
        return INT32_MAX;
    } else if (value < INT32_MIN) {
        return INT32_MIN;
    } else {
        return (int32_t)value;
    }
}

__STATIC_FORCEINLINE int32_t qadd_impl(int32_t x, int32_t y) {
    int64_t result = (int64_t)x + y; // 将x和y相加
    return saturate_to_int32(result); // 对结果进行饱和处理
}

/* ==================== [Macros] ============================================ */

// 计算一个32位整数从最高有效位
#define __CLZ(x) __builtin_clz(x)

// 确保一个数值在给定的位宽内   
#define __USAT(val, sat) usat(val, sat)

// 它将两个32位有符号整数相加,并在结果超出32位有符号整数范围时进行饱和处理
#define __QADD(x, y) qadd_impl(x, y)

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif // __ARM_MATH_H__

再度编译发现虽然编译过了,但是很多地方有warning,看着十分难受。这里去请教了arm2d的作者,傻孩子大佬。大致了解了原因后按照他的说法在CMakeLists.txt中加入了两行编译器命令

idf_component_register(SRC_DIRS "Arm-2D/Library/Source" "Arm-2D/Helper/Source" 
                    INCLUDE_DIRS "." "Arm-2D/Library/Include" "Arm-2D/Helper/Include"
                    )

target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-fallthrough -Wno-unused-variable)
target_compile_options(${COMPONENT_LIB} PRIVATE -fms-extensions)

这样的话编译就没有警告了,nice!

对接arm2d

arm2d的对接十分曲折,由于arm2d的源代码拥有若干个宏堆砌而成。很难读懂,我也是参考了components/arm2d/Arm-2D/examples/[template][pc][vscode]/platform路径下的arm_2d_disp_adapter_0.h和arm_2d_disp_adapter_0.c拉过来放到arm2d文件夹下。
接了这两个文件的代码后,由于引入了.c和一些esp32的代码,其中arm_2d_disp_adapter_0.c的代码还借用了components/arm2d/Arm-2D/examples/common里面的代码。那么CMakeLists.txt自然也要修改如下:

idf_component_register(SRC_DIRS "." "Arm-2D/Library/Source" "Arm-2D/Helper/Source" "Arm-2D/examples/common/controls" "Arm-2D/examples/common/asset"
                    INCLUDE_DIRS "." "Arm-2D/Library/Include" "Arm-2D/Helper/Include" "Arm-2D/examples/common/controls" "Arm-2D/examples/common/asset"
                    )

target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-fallthrough -Wno-unused-variable)
target_compile_options(${COMPONENT_LIB} PRIVATE -fms-extensions)

然而编译后,发现arm_2d_disp_adapter_0.c 和 arm_2d_disp_adapter_0.h里面的代码都是灰色,原来是缺少RTE_Acceleration_Arm_2D_Helper_Disp_Adapter0宏,搜索arm2d文件发现在 components/arm2d/Arm-2D/examples/[template][pc][vscode]/platform/RTE_Components.h 中有定义,那么拉取到arm2d的文件夹内后,还需要
修改CMakeLists.txt添加一个编译宏 _RTE_ 就可以了

idf_component_register(SRC_DIRS "." "Library/Source" "Helper/Source" "common/controls" "common/asset" 
                    INCLUDE_DIRS "." "Library/Include" "Helper/Include" "common/controls" "common/asset" 
        			)

target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-fallthrough -Wno-unused-variable)
target_compile_options(${COMPONENT_LIB} PRIVATE -fms-extensions)
target_compile_definitions(${COMPONENT_LIB} PRIVATE "_RTE_" )

这部分的代码真的很难理解,作者十分擅长用宏。在这样的基础之下,写下的代码如同自带一层混淆,让人难以读懂和移植。
然后我们编译后发现缺少 :

void Disp0_DrawBitmap(uint32_t x, uint32_t y, uint32_t width, uint32_t height, const uint8_t *bitmap)

int64_t arm_2d_helper_get_system_timestamp(void)

uint32_t arm_2d_helper_get_reference_clock_frequency(void)

这部分是对接esp32底层,我们留在main里面做

主函数调用arm2d

到了主函数了,首先我们要引入头文件

// arm2d的内容
#include "arm_2d.h"
#include "arm_2d_disp_adapter_0.h"

// 对接需要的内容
#include "esp_timer.h"

然后我们要对接Disp0_DrawBitmap函数,这部分是给arm2d底层刷新屏幕使用

void Disp0_DrawBitmap(uint32_t x, uint32_t y, uint32_t width, uint32_t height, const uint8_t *bitmap)
{
    g_lcd.draw_bitmap(x, y, width, height, (uint16_t*)bitmap);
}

对接arm_2d_helper_get_system_timestamp函数,这部分给arm2d提供时间戳:

int64_t arm_2d_helper_get_system_timestamp(void)
{
    return esp_timer_get_time();
}

对接arm_2d_helper_get_reference_clock_frequency函数,这部分是时间戳频率:

uint32_t arm_2d_helper_get_reference_clock_frequency(void)
{
    return 1000000;
}

然后,主函数下面加入代码:

 arm_irq_safe {
        arm_2d_init();
    }

    disp_adapter0_init(Disp0_DrawBitmap);

    while (1)
    {
        disp_adapter0_task();
        vTaskDelay(1);
    }

开始编译,结果最后的链接阶段报错:

A fatal error occurred: Segment loaded at 0x3c030390 lands in same 64KB flash mapping as segment loaded at 0x3c030020. Can't generate binary. Suggest changing linker script or ELF to merge sections.
ninja: build stopped: subcommand failed.

通过翻译软件我们知道,这里的段错误,好像是冲突了。
我们通过指令xtensa-esp32-elf-objdump -h build/lcd_tjpgd.elf查找了所有的段:

$ xtensa-esp32-elf-objdump -h build/lcd_tjpgd.elf 

build/lcd_tjpgd.elf:     file format elf32-xtensa-le

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .rtc.text     00000010  600fe000  600fe000  00054000  2**0
                  ALLOC
  1 .rtc.force_fast 00000000  600fe010  600fe010  0005374f  2**0
                  CONTENTS
  2 .rtc_noinit   00000000  50000000  50000000  0005374f  2**0
                  CONTENTS
  3 .rtc.force_slow 00000000  50000000  50000000  0005374f  2**0
                  CONTENTS
  4 .rtc_reserved 00000018  600fffe8  600fffe8  00053fe8  2**3
                  ALLOC
  5 .iram0.vectors 00000403  40374000  40374000  0001d000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  6 .iram0.text   0000edbb  40374404  40374404  0001d404  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  7 .dram0.dummy  0000b200  3fc88000  3fc88000  0000f000  2**0
                  ALLOC
  8 .dram0.data   0000255c  3fc93200  3fc93200  0001a200  2**4
                  CONTENTS, ALLOC, LOAD, DATA
  9 .noinit       00000000  3fc9575c  3fc9575c  0005374f  2**0
                  CONTENTS
 10 .dram0.bss    00004040  3fc95760  3fc95760  0001c75c  2**3
                  ALLOC
 11 .flash.text   0002672f  42000020  42000020  0002d020  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .flash_rodata_dummy 00030000  3c000020  3c000020  00001020  2**0
                  ALLOC
 13 .flash.appdesc 00000100  3c030020  3c030020  00001020  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 14 arm2d.tile.c_tileWhiteDotMask 00000010  3c030120  3c030120  00001120  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 15 arm2d.tile.c_tileWhiteDotRGB565 00000010  3c030130  3c030130  00001130  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 arm2d.asset.c_bmpWhiteDotRGB565 00000188  3c030140  3c030140  00001140  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 arm2d.asset.c_bmpWhiteDotAlpha 000000c4  3c0302c8  3c0302c8  000012c8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 18 .flash.rodata 0000d01c  3c030390  3c030390  00001390  2**4
                  CONTENTS, ALLOC, LOAD, DATA
 19 .flash.rodata_noload 00000000  3c03d3ac  3c03d3ac  0005374f  2**0
                  CONTENTS
 20 .ext_ram.dummy 0003ffe0  3c000020  3c000020  00001020  2**0
                  ALLOC
 21 .ext_ram.bss  00000000  3c040000  3c040000  0005374f  2**0
                  CONTENTS
 22 .iram0.text_end 00000041  403831bf  403831bf  0002c1bf  2**0
                  ALLOC
 23 .iram0.data   00000000  40383200  40383200  0005374f  2**0
                  CONTENTS
 24 .iram0.bss    00000000  40383200  40383200  0005374f  2**0
                  CONTENTS
 25 .dram0.heap_start 00000000  3fc997a0  3fc997a0  0005374f  2**0
                  CONTENTS
 26 .xt.prop      0002c2f8  00000000  00000000  0005374f  2**0
                  CONTENTS, READONLY
 27 .xt.lit       000013a0  00000000  00000000  0007fa47  2**0
                  CONTENTS, READONLY
 28 .xtensa.info  00000038  00000000  00000000  00080de7  2**0
                  CONTENTS, READONLY
 29 .comment      0000004b  00000000  00000000  00080e1f  2**0
                  CONTENTS, READONLY
 30 .debug_frame  00013cd8  00000000  00000000  00080e6c  2**2
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 31 .debug_info   001d8e2e  00000000  00000000  00094b44  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 32 .debug_abbrev 00028366  00000000  00000000  0026d972  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 33 .debug_loc    000d92c3  00000000  00000000  00295cd8  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 34 .debug_aranges 00007910  00000000  00000000  0036efa0  2**3
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 35 .debug_ranges 0000f150  00000000  00000000  003768b0  2**3
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 36 .debug_line   00176594  00000000  00000000  00385a00  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 37 .debug_str    000474ad  00000000  00000000  004fbf94  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 38 .debug_loclists 0000f07c  00000000  00000000  00543441  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 39 .debug_rnglists 00000418  00000000  00000000  005524bd  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 40 .debug_line_str 00001955  00000000  00000000  005528d5  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS

定位了问题出在arm2d里面,好像是arm2d的某个操作导致了内存段覆盖。搜索关键词arm2d.tile,发现很多地方使用了ARM_SECTION(“arm2d.tile.c_tileUTF8UserFontA1Mask”)。知道这里很简单,找到根源然后将它注释,这个宏就不会起作用了。
大约是在components/arm2d/Arm-2D/Library/Include/arm_2d_utils.h文件的620行我找到了这个宏,并在
arm_2d_cfg.h中加入宏定义来替换掉内部的宏:

// 屏蔽内部的段操作
#define ARM_SECTION(__X)

至此,我们编译终于成功。虽然这时候还有一些warning没有消除(arm2d被调用的时候产生的warning)。但是我也无力追求完美了。直接编译,烧录。
结果没有出现想要的动画效果,这里我们通过在arm_2d_cfg.h中打开log分析发现,是因为没有启动arm_2d_disp_adapter_0.h中的默认界面。我们通过arm_2d_disp_adapter_0.h的下面宏:

// <q>Disable the default scene
// <i> Remove the default scene for this display adapter. We highly recommend you to disable the default scene when creating real applications.
#ifndef __DISP0_CFG_DISABLE_DEFAULT_SCENE__
#   define __DISP0_CFG_DISABLE_DEFAULT_SCENE__                     0
#endif

这里默认__DISP0_CFG_DISABLE_DEFAULT_SCENE__ 是1,我们设置成0,打开它。再进行编译烧录(别忘记arm_2d_cfg.h中关闭log)。结果如下:

运行效果

总结

其实我的结果并不是很好。按照傻孩子大佬的话说,还是有很大优化空间。下一步优化应该就是在spi异步传输的方向上。乐鑫的spi分为queue传输和poll传输,其中bus库采用的是poll传输。然而这种方式相当于同步操作,应该用queue去异步等待,这样能在传输的同时计算像素。能够消除LCD-Latency的时间。我这次记录摸索过程相当于是抛砖引玉,希望大家能够优化出更好的版本

关于移植

移植的一个最大的准则就是”不要动别人的源码“。按照傻孩子大佬的说法就是【用扩展替代修改】。我遇到问题,虽然会深入源码,但是会根据源码的情况在配置文件或者是自己写的文件里面进行补充。如果别人的代码让你无法这样操作,那就是提issue的时候。

后记(碎碎念)

arm2d的理念是以mask为中心,所有的东西全都是贴图加上arm2d自带的蒙版(mask)完成的效果。这个理念实际上不是gui的理念,例如lvgl是以控件为中心。arm2d则是更加底层, 从使用者的角度实际上会比较麻烦,但是这种效果能够在性能有限的设备上发挥很大的效果。实际上arm2d的源码一度让我崩溃,以宏构建的内容, 很多情况下无从知晓如何使用。代码的抽象程度已经完全是另一种语言。这次的移植意义也不大,因为esp32性能足够,有更加有好的lvgl,没必要折腾arm2d。主要是想学习学习,也想挑战一下自己。arm2d的代码在我看来我不能评价他是不好的,毕竟恐怖的效率,惊人的效果还是深深折服。但是我个人还是不会学习他的做法,我希望能写出更加清晰易懂的代码。

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

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

相关文章

记一次对ouija渗透测试c语言逆向学习

概要 初始知识 web应用枚举 二进制逆向 文件枚举 堆栈溢出 学到知识 hash长度攻击 任意文件读取 二进制逆向分析 信息收集 端口扫描 nmap --min-rate 1000 -p- 10.129.30.104 发现22&#xff0c;80&#xff0c;3000端口 网站探测 目录枚举 feroxbuster -u http://10.1…

Qt 基于FFmpeg的视频播放器 - 播放、暂停以及拖动滑动条跳转

Qt 基于FFmpeg的视频转换器 - 播放、暂停以及拖动进度条跳转 引言一、设计思路二、核心源码以及相关参考链接 引言 本文基于FFmpeg&#xff0c;使用Qt制作了一个极简的视频播放器. 相比之前的版本&#xff0c;加入了播放、暂停、拖动滑动条跳转功能&#xff0c;如上所示 (左图)…

局域网聊天软件 matrix

窝有 3 只 Android 手机 (3 号手机, 6 号手机, 9 号手机), 2 台 ArchLinux PC (4 号 PC, 6 号 PC), 1 台 Fedora CoreOS 服务器 (5 号). (作为穷人, 窝使用的基本上是老旧的二手设备, 比如 5 年前的手机, 9 年前的笔记本, 10 年前的古老 e5v3 主机, 都比较便宜. ) 窝经常需要 …

format()函数

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法介绍 format()可以对数据进行格式化处理操作&#xff0c;语法如下&#xff1a; format(value, format_spec) format_spec为格式化解释。当参数…

高性能Web服务器-Nginx的常用模块

文章目录 Nginx安装Nginx平滑升级与回滚平滑升级流程第1步&#xff0c;下载新版本第2步&#xff0c;编译第3步&#xff0c;执行make第4步&#xff0c;对比新旧版本第5步&#xff0c;备份旧nginx二进制文件第6步&#xff0c;模拟用户正在访问nginx第7步&#xff0c;替换旧的ngin…

The First Descendant第一后裔联机失败、联机报错这样处理

第一后裔/The First Descendant是一款免费的多人合作射击游戏&#xff0c;玩家将进入一片混乱的英格里斯大陆&#xff0c;扮演继承者后裔&#xff0c;通过各种主支线任务和故事剧情触发&#xff0c;最终揭开自身的秘密&#xff0c;并带领大家一起抵抗邪恶势力的入侵。为了避免玩…

Flume学习

Flume(分布式数据采集系统)学习 1.Flume架构 什么是flume&#xff1f; flume是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统。 支持在日志系统中定制各类数据发送方&#xff0c;用于收集数据; 同时&#xff0c;Flume提供对数据进行简单处理&#xff0c;并写到…

华为昇腾310B1芯片DVPP模块VENC视频编码接口调用流程以及视频编码代码梳理

目录 1 接口调用流程 2 代码流程梳理 1 接口调用流程 在CANN 8.0.RC1 AscendCL应用软件开发指南 (C&C, 推理) 01.pdf 文档中有接口调用流程 2 代码流程梳理 代码在samples: CANN Samples - Gitee.com 然后我把这个代码完整的看了一遍&#xff0c;然后梳理了详细的代码…

web学习笔记(七十二)

目录 1.vue2通过$parent实现组件传值——父传子 2.vue2 通过$children实现组件传值——子传父 3. provide和inject传值&#xff08;依赖注入&#xff09; 4.vue2如何操作dom 5.vue2如何拿到最新的dom 6.filters过滤器 7.vue2的生命周期 8.vuex的用法 1.vue2通过$parent…

【SCI索引,Fellow主讲】2024年可持续发展与能源资源国际学术会议(SDER 2024,8月9-11)

2024年可持续发展与能源资源国际学术会议&#xff08;SDER 2024&#xff09;将在2024年8月9-11日于中国重庆召开。 大会旨在为从事可持续发展与能源资源方面的专家学者、工程技术人员、技术研发人员提供一个共享科研成果和前沿技术&#xff0c;了解学术发展趋势&#xff0c;拓…

2.4G特技翻斗车方案定制

遥控翻斗车不仅能够提供基本的前进、后退、左转和右转功能&#xff0c;还设计有多种特技动作和互动模式&#xff0c;以增加娱乐性和互动性。 1、无线遥控&#xff1a;玩具翻斗车一般通过2.4G无线遥控器进行控制&#xff0c;允许操作者在一定距离内远程操控车辆。 2、炫彩灯光…

Java程序员接单的十条“野路子”,分分钟收入20K!

Java程序员除了主业工作外&#xff0c;也要适当扩展兼职接单这条路。毕竟Java接单可以说是Java程序员进行技术变现的最佳方式之一。 因为Java程序员兼职接单的难度相对更低&#xff0c;单量也比较可观&#xff0c;最重要的是性价比也很顶&#xff0c;且听我一一道来&#xff1a…

Nature推荐的三种ChatGPT论文写作指令(含PDF下载)

1. 润色学术论文 ChatGPT学术润色指令&#xff1a; “I’m writing a paper on [topic]for a leading [discipline] academic journal. WhatItried to say in the following section is [specific point]. Please rephrase itfor clarity, coherence and conciseness, ensuri…

Charles抓包工具系列文章(五)-- DNS spoofing (DNS域名伪装)

一、背景 DNS域名是依赖DNS域名服务器&#xff0c;特别是内部域名&#xff0c;最后寻址到后端服务地址。 当我们无法修改客户端的域名&#xff0c;而想让其指向到我们期望地址时&#xff0c;可以采用charles的DNS spoofing。 何谓DNS 欺骗&#xff1a;将自己的主机名指定给远…

电商平台数据功能封装API需要注意些什么?如何调用封装后的API?

一、引言 随着电商行业的蓬勃发展&#xff0c;电商平台的数据功能愈发复杂多样&#xff0c;如何高效、安全地管理和使用这些数据成为了电商平台开发者面临的重要问题。API&#xff08;Application Programming Interface&#xff09;作为不同软件之间进行通信的桥梁&#xff0…

Win32消息机制原理及消息运转

一.消息机制原理 1.消息类型&#xff1a; WIndows定义的一系列WM_XXX开头的&#xff0c;用来表示键盘按键&#xff0c;鼠标点击&#xff0c;窗口变化&#xff0c;用户自定义等各种消息; 2.消息队列&#xff1a; Windows为每一个正在运行的程序维护一个消息队列应用程序的消…

Pycharm 文件标头设置

一、设置模板步骤&#xff1a; “文件File--设置Settings--编辑器Editor--File and Code Templates- Python Script” 里面设置模板 官方预设变量表 变量名 含义 ${DATE} 当前系统日期 ${DAY} 当前月的第几日 ${DAY_NAME_SHORT} 当前星期几的单词缩写&#xff08…

计算机网络之数据通信原理(下)

上一讲内容&#xff1a;数据传输方式、数据传输形式、传输差错处理、常用差错检测方法 数据通信过程中&#xff0c;一个很重要的问题就是如何控制数据的传输&#xff0c;就涉及到了传输控制规程&#xff08;协议&#xff09; 下面介绍两种&#xff1a; ①BSC&#xff1a;面向…

java基于ssm+jsp 弹幕视频网站

1前台首页功能模块 弹幕视频网站&#xff0c;在弹幕视频网站可以查看首页、视频信息、商品信息、论坛信息、我的、跳转到后台、购物车、客服等内容&#xff0c;如图1所示。 图1前台首页界面图 登录&#xff0c;通过登录填写账号、密码等信息进行登录操作&#xff0c;如图2所示…

高性能并行计算课程论文:并行网络爬虫的设计与实现

目录 1.绪论 1.1 研究背景 1.2 研究意义 ​​​​​​​1.3 文章结构 2. 网络爬虫相关理论 ​​​​​​​2.1 URL地址格式 ​​​​​​​2.2 网页爬取策略 2.2.1 深度优先策略 2.2.2 广度优先策略 2.2.3 最佳优先策略 ​​​​​​​2.3 网页分析算法 ​​​​​​​2.3.1 正…