Просмотр исходного кода

Merge branch 'master' of https://gitee.com/openLuat/LuatOS

陈取德 1 месяц назад
Родитель
Сommit
09017ca2e7
100 измененных файлов с 5773 добавлено и 1210 удалено
  1. 36 375
      components/fatfs/luat_lib_fatfs.c
  2. 34 4
      components/hzfont/src/luat_hzfont.c
  3. 58 56
      components/hzfont/src/ttf_parser.c
  4. 2 0
      components/pins/src/luat_pins.c
  5. 389 0
      components/rtmp/binding/luat_lib_rtmp.c
  6. 392 0
      components/rtmp/include/luat_rtmp_push.h
  7. 2516 0
      components/rtmp/src/luat_rtmp_push.c
  8. 6 0
      components/u8g2/u8g2_font.c
  9. 2 9
      lua/src/liolib.c
  10. 2 0
      luat/include/luat_libs.h
  11. 7 7
      luat/modules/luat_lib_crypto.c
  12. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_default_font_drv.lua
  13. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_gtfont_drv.lua
  14. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_hzfont_drv.lua
  15. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_default_font_drv.lua
  16. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_gtfont_drv.lua
  17. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_hzfont_drv.lua
  18. 12 28
      module/Air780EHM_Air780EHV_Air780EGH/demo/rtos/main.lua
  19. 59 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/rtos/readme.md
  20. 60 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/rtos/rtos_app.lua
  21. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/hw_font_drv.lua
  22. 5 5
      module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/main.lua
  23. 14 12
      module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/readme.md
  24. 15 14
      module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/win_horizontal_slide.lua
  25. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/win_vertical_slide.lua
  26. 0 230
      module/Air780EPM/demo/accessory_board/AirLCD_1000/AirLCD_1000.lua
  27. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_12.bin
  28. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_22.bin
  29. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_drv.lua
  30. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/images/logo.jpg
  31. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/lcd_drv/exlcd_drv.lua
  32. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/lcd_drv/lcd_drv.lua
  33. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/main.lua
  34. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/readme.md
  35. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/tp_key_drv/key_drv.lua
  36. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/customer_font_page.lua
  37. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/home_page.lua
  38. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/lcd_page.lua
  39. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/ui_main.lua
  40. BIN
      module/Air780EPM/demo/accessory_board/AirLCD_1000/logo.jpg
  41. 0 203
      module/Air780EPM/demo/accessory_board/AirLCD_1000/readme.md
  42. 0 187
      module/Air780EPM/demo/accessory_board/AirLCD_1000/ui_main.lua
  43. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_12.bin
  44. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_22.bin
  45. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_drv.lua
  46. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/images/logo.jpg
  47. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/lcd_drv/exlcd_drv.lua
  48. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/lcd_drv/lcd_drv.lua
  49. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/main.lua
  50. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/readme.md
  51. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/tp_key_drv/extp_drv.lua
  52. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/tp_key_drv/tp_drv.lua
  53. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/customer_font_page.lua
  54. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/home_page.lua
  55. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/lcd_page.lua
  56. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/ui_main.lua
  57. 66 0
      module/Air780EPM/demo/rtos/main.lua
  58. 54 0
      module/Air780EPM/demo/rtos/readme.md
  59. 60 0
      module/Air780EPM/demo/rtos/rtos_app.lua
  60. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_default_font_drv.lua
  61. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_gtfont_drv.lua
  62. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_hzfont_drv.lua
  63. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_default_font_drv.lua
  64. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_gtfont_drv.lua
  65. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_hzfont_drv.lua
  66. 17 3
      module/Air8000/demo/accessory_board/AirSHT30_1000/readme.md
  67. 12 1
      module/Air8000/demo/accessory_board/AirSHT30_1000/sht30_app.lua
  68. 66 0
      module/Air8000/demo/rtos/main.lua
  69. 50 0
      module/Air8000/demo/rtos/readme.md
  70. 60 0
      module/Air8000/demo/rtos/rtos_app.lua
  71. 1 1
      module/Air8000/demo/ui/easyui/single/hw_font_drv.lua
  72. 4 4
      module/Air8000/demo/ui/easyui/single/main.lua
  73. 2 2
      module/Air8000/demo/ui/easyui/single/readme.md
  74. 1 1
      module/Air8000/demo/ui/easyui/single/win_horizontal_slide.lua
  75. 1 1
      module/Air8000/demo/ui/easyui/single/win_vertical_slide.lua
  76. 2 1
      module/Air8101/demo/accessory_board/AirLCD_1020/exeasyui/ui/home_page.lua
  77. 2 4
      module/Air8101/demo/pwm/main.lua
  78. 34 27
      module/Air8101/demo/pwm/pwm_app.lua
  79. 25 20
      module/Air8101/demo/pwm/readme.md
  80. 66 0
      module/Air8101/demo/rtos/main.lua
  81. 53 0
      module/Air8101/demo/rtos/readme.md
  82. 60 0
      module/Air8101/demo/rtos/rtos_app.lua
  83. 9 0
      module/Air8101/demo/ui/easyui/combination/readme.md
  84. 88 0
      module/Air8101/demo/ui/easyui/single/hw_font_drv.lua
  85. BIN
      module/Air8101/demo/ui/easyui/single/images/1.jpg
  86. BIN
      module/Air8101/demo/ui/easyui/single/images/2.jpg
  87. BIN
      module/Air8101/demo/ui/easyui/single/images/3.jpg
  88. BIN
      module/Air8101/demo/ui/easyui/single/images/4.jpg
  89. BIN
      module/Air8101/demo/ui/easyui/single/images/5.jpg
  90. BIN
      module/Air8101/demo/ui/easyui/single/images/logo.jpg
  91. 133 0
      module/Air8101/demo/ui/easyui/single/main.lua
  92. 364 0
      module/Air8101/demo/ui/easyui/single/readme.md
  93. 346 0
      module/Air8101/demo/ui/easyui/single/win_all_component.lua
  94. 77 0
      module/Air8101/demo/ui/easyui/single/win_autoplay_picture.lua
  95. 68 0
      module/Air8101/demo/ui/easyui/single/win_button.lua
  96. 73 0
      module/Air8101/demo/ui/easyui/single/win_check_box.lua
  97. 79 0
      module/Air8101/demo/ui/easyui/single/win_combo_box.lua
  98. 89 0
      module/Air8101/demo/ui/easyui/single/win_dyn_progress_bar.lua
  99. 81 0
      module/Air8101/demo/ui/easyui/single/win_gtfont.lua
  100. 106 0
      module/Air8101/demo/ui/easyui/single/win_horizontal_slide.lua

+ 36 - 375
components/fatfs/luat_lib_fatfs.c

@@ -38,6 +38,8 @@ DRESULT diskio_open_sdio(BYTE pdrv, void* userdata);
 extern const struct luat_vfs_filesystem vfs_fs_fatfs;
 #endif
 
+static int s_fatfs_fmt = FM_FAT32;
+
 /*
 挂载fatfs
 @api fatfs.mount(mode,mount_point, spiid_or_spidevice, spi_cs, spi_speed, power_pin, power_on_delay, auto_format)
@@ -164,11 +166,14 @@ static int fatfs_mount(lua_State *L)
     if (re != FR_OK) {
 		if (lua_isboolean(L, 8) && lua_toboolean(L, 8) == 0) {
 			LLOGI("sd/tf mount failed %d but auto-format is disabled", re);
+			lua_pushboolean(L, 0);
+			lua_pushstring(L, "mount error");
+			return 2;
 		}
 		else {
-			LLOGD("mount failed, try auto format");
+			LLOGW("mount failed, try auto format");
 			MKFS_PARM parm = {
-				.fmt = FM_ANY, // 暂时应付一下ramdisk
+				.fmt = s_fatfs_fmt,
 				.au_size = 0,
 				.align = 0,
 				.n_fat = 0,
@@ -202,8 +207,7 @@ static int fatfs_mount(lua_State *L)
 	lua_pushboolean(L, re == FR_OK);
 	lua_pushinteger(L, re);
 	if (re == FR_OK) {
-		if (FATFS_DEBUG)
-			LLOGD("[FatFS]fatfs_init success");
+		LLOGI("mount success at %s", fs->fs_type == FS_EXFAT ? "exfat" : (fs->fs_type == FS_FAT32 ? "fat32" : "fat16"));
 		#ifdef LUAT_USE_FS_VFS
               luat_fs_conf_t conf2 = {
 		            .busname = (char*)fs,
@@ -215,8 +219,7 @@ static int fatfs_mount(lua_State *L)
 		#endif
 	}
 	else {
-		if (FATFS_DEBUG)
-			LLOGD("[FatFS]fatfs_init FAIL!! re=%d", re);
+		LLOGE("[FatFS]fatfs_init FAIL!! re=%d", re);
 	}
 
 	if (FATFS_DEBUG)
@@ -249,31 +252,6 @@ static int fatfs_unmount(lua_State *L) {
 	lua_pushinteger(L, re);
 	return 1;
 }
-/*
-static int fatfs_mkfs(lua_State *L) {
-	const char *mount_point = luaL_optstring(L, 1, "");
-	// BYTE sfd = luaL_optinteger(L, 2, 0);
-	// DWORD au = luaL_optinteger(L, 3, 0);
-	BYTE work[FF_MAX_SS] = {0};
-	if (FATFS_DEBUG)
-		LLOGI("mkfs GO %d");
-	MKFS_PARM parm = {
-		.fmt = FM_ANY, // 暂时应付一下ramdisk
-		.au_size = 0,
-		.align = 0,
-		.n_fat = 0,
-		.n_root = 0,
-	};
-	if (!strcmp("ramdisk", mount_point) || !strcmp("ram", mount_point)) {
-		parm.fmt = FM_ANY | FM_SFD;
-	}
-	FRESULT re = f_mkfs(mount_point, &parm, work, FF_MAX_SS);
-	lua_pushinteger(L, re);
-	if (FATFS_DEBUG)
-		LLOGI("mkfs ret %d", re);
-	return 1;
-}
-*/
 
 /**
 获取可用空间信息
@@ -344,334 +322,35 @@ static int fatfs_debug_mode(lua_State *L) {
 }
 
 /**
-设置fatfs一些特殊参数,大部分卡无需配置,部分不能正常读写的卡,经过配置后可能能读写成功
-@api fatfs.config(crc_check, write_to)
+设置fatfs一些特殊参数
+@api fatfs.config(crc_check, write_to, fmt)
 @int 读取时是否跳过CRC检查,1跳过不检查CRC,0不跳过检查CRC,默认不跳过,除非TF卡不支持CRC校验,否则不应该跳过!
 @int 单次写入超时时间,单位ms,默认100ms。
+@int 文件系统格式,默认FM_FAT32, 可选值 FM_FAT32, FM_EXFAT
 @return nil 无返回值
- */
+-- 前2个配置项不建议修改
+*/
 static int fatfs_config(lua_State *L) {
-	FATFS_NO_CRC_CHECK = luaL_optinteger(L, 1, 0);
-	FATFS_WRITE_TO = luaL_optinteger(L, 2, 100);
-	return 0;
-}
-
-#if 0
-
-// ------------------------------------------------
-// ------------------------------------------------
-
-static int fatfs_mkdir(lua_State *L) {
-	int luaType = lua_type( L, 1);
-	if(luaType != LUA_TSTRING) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "file path must string");
-		return 2;
-	}
-	FRESULT re = f_mkdir(lua_tostring(L, 1));
-	lua_pushinteger(L, re);
-	return 1;
-}
-
-static int fatfs_lsdir(lua_State *L)
-{
-	//FIL Fil;			/* File object needed for each open file */
-	DIR dir;
-	FILINFO fileinfo;
-	int luaType = lua_type( L, 1);
-	if(luaType != LUA_TSTRING) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "dir must string");
-		return 2;
-	}
-	//u8 *buf;
-	size_t len;
-	const char *buf = lua_tolstring( L, 1, &len );
-	char dirname[len+1];
-	memcpy(dirname, buf, len);
-	dirname[len] = 0x00;
-	FRESULT re = f_opendir(&dir, dirname);
-	if (re != FR_OK) {
-		lua_pushinteger(L, re);
-		return 1;
-	}
-
-	lua_pushinteger(L, 0);
-	lua_newtable(L);
-	while(f_readdir(&dir, &fileinfo) == FR_OK) {
-		if(!fileinfo.fname[0]) break;
-
-		lua_pushlstring(L, fileinfo.fname, strlen(fileinfo.fname));
-		lua_newtable(L);
-		
-		lua_pushstring(L, "size");
-		lua_pushinteger(L, fileinfo.fsize);
-		lua_settable(L, -3);
-		
-		lua_pushstring(L, "date");
-		lua_pushinteger(L, fileinfo.fdate);
-		lua_settable(L, -3);
-		
-		lua_pushstring(L, "time");
-		lua_pushinteger(L, fileinfo.ftime);
-		lua_settable(L, -3);
-		
-		lua_pushstring(L, "attrib");
-		lua_pushinteger(L, fileinfo.fattrib);
-		lua_settable(L, -3);
-
-		lua_pushstring(L, "isdir");
-		lua_pushinteger(L, fileinfo.fattrib & AM_DIR);
-		lua_settable(L, -3);
-
-		lua_settable(L, -3);
-	}
-	f_closedir(&dir);
-	//LLOGD("[FatFS] lua_gettop=%d", lua_gettop(L));
-    return 2;
-}
-
-//-------------------------------------------------------------
-
-static int fatfs_stat(lua_State *L) {
-	int luaType = lua_type(L, 1);
-	if(luaType != LUA_TSTRING) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "file path must string");
-		return 2;
-	}
-	FILINFO fileinfo;
-	const char *path = lua_tostring(L, 1);
-	FRESULT re = f_stat(path, &fileinfo);
-	lua_pushinteger(L, re);
-	if (re == FR_OK) {
-		lua_newtable(L);
-		
-		lua_pushstring(L, "size");
-		lua_pushinteger(L, fileinfo.fsize);
-		lua_rawset(L, -3);
-		
-		lua_pushstring(L, "date");
-		lua_pushinteger(L, fileinfo.fdate);
-		lua_rawset(L, -3);
-		
-		lua_pushstring(L, "time");
-		lua_pushinteger(L, fileinfo.ftime);
-		lua_rawset(L, -3);
-		
-		lua_pushstring(L, "attrib");
-		lua_pushinteger(L, fileinfo.fattrib);
-		lua_rawset(L, -3);
-
-		lua_pushstring(L, "isdir");
-		lua_pushinteger(L, fileinfo.fattrib & AM_DIR);
-		lua_rawset(L, -3);
+	if (lua_isinteger(L, 1)) {
+		FATFS_NO_CRC_CHECK = luaL_optinteger(L, 1, 0);
 	}
-	else {
-		lua_pushnil(L);
+	if (lua_isinteger(L, 2)) {
+		FATFS_WRITE_TO = luaL_optinteger(L, 2, 100);
 	}
-	return 2;
-}
-
-/**
- * fatfs.open("adc.txt") 
- * fatfs.open("adc.txt", 2) 
- */
-static int fatfs_open(lua_State *L) {
-	int luaType = lua_type( L, 1);
-	if(luaType != LUA_TSTRING) {
-		lua_pushnil(L);
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "file path must string");
-		return 3;
-	}
-	const char *path  = lua_tostring(L, 1);
-	int flag = luaL_optinteger(L, 2, 1); // 第二个参数
-	flag |= luaL_optinteger(L, 3, 0); // 第三个参数
-	flag |= luaL_optinteger(L, 4, 0); // 第四个参数
-
-	if (FATFS_DEBUG)
-		LLOGD("[FatFS]open %s %0X", path, flag);
-
-	FIL* fil = (FIL*)lua_newuserdata(L, sizeof(FIL));
-	FRESULT re = f_open(fil, path, (BYTE)flag);
-	if (re != FR_OK) {
-		lua_remove(L, -1);
-		lua_pushnil(L);
-		lua_pushinteger(L, re);
-		return 2;
-	}
-	return 1;
-}
-
-static int fatfs_close(lua_State *L) {
-	int luaType = lua_type(L, 1);
-	if(luaType != LUA_TUSERDATA) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "must be FIL*");
-		return 2;
-	}
-	FIL* fil = (FIL*)lua_touserdata(L, 1);
-	FRESULT re = f_close(fil);
-	//free(fil);
-	lua_pushinteger(L, re);
-	return 1;
-}
-
-static int fatfs_seek(lua_State *L) {
-	int luaType = lua_type( L, 1);
-	if(luaType != LUA_TUSERDATA) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "must be FIL*");
-		return 2;
-	}
-	UINT seek = luaL_optinteger(L, 2, 0);
-	FRESULT re = f_lseek((FIL*)lua_touserdata(L, 1), seek);
-	lua_pushinteger(L, re);
-	return 1;
-}
-
-static int fatfs_truncate(lua_State *L) {
-	int luaType = lua_type( L, 1);
-	if(luaType != LUA_TUSERDATA) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "must be FIL*");
-		return 2;
-	}
-	FRESULT re = f_truncate((FIL*)lua_touserdata(L, 1));
-	lua_pushinteger(L, re);
-	return 1;
-}
-
-static int fatfs_read(lua_State *L) {
-	int luaType = lua_type( L, 1);
-	if(luaType != LUA_TUSERDATA) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "must be FIL*");
-		return 2;
-	}
-	UINT limit = luaL_optinteger(L, 2, 512);
-	BYTE buf[limit];
-	UINT len;
-	if (FATFS_DEBUG)
-		LLOGD("[FatFS]readfile limit=%d", limit);
-	FRESULT re = f_read((FIL*)lua_touserdata(L, 1), buf, limit, &len);
-	lua_pushinteger(L, re);
-	if (re != FR_OK) {
-		return 1;
-	}
-	lua_pushlstring(L, (const char*)buf, len);
-	return 2;
-}
-
-static int fatfs_write(lua_State *L) {
-	int luaType = lua_type( L, 1);
-	if(luaType != LUA_TUSERDATA) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "must be FIL*");
-		return 2;
-	}
-	FIL* fil = (FIL*)lua_touserdata(L, 1);
-    luaType = lua_type( L, 2 );
-    size_t len;
-    char* buf;
-	FRESULT re = FR_OK;
-    
-    if(luaType == LUA_TSTRING )
-    {
-        buf = (char*)lua_tolstring( L, 2, &len );
-        
-        re = f_write(fil, buf, len, &len);
-    }
-    else if(luaType == LUA_TLIGHTUSERDATA)
-    {         
-         buf = lua_touserdata(L, 2);
-         len = lua_tointeger( L, 3);
-         
-         re = f_write(fil, buf, len, &len);
-    }
-    if (FATFS_DEBUG)
-		LLOGD("[FatFS]write re=%d len=%d", re, len);
-    lua_pushinteger(L, re);
-    lua_pushinteger(L, len);
-    return 2;
-}
-
-static int fatfs_remove(lua_State *L) {
-	int luaType = lua_type(L, 1);
-	if(luaType != LUA_TSTRING) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "file path must string");
-		return 2;
-	}
-	FRESULT re = f_unlink(lua_tostring(L, 1));
-	lua_pushinteger(L, re);
-	return 1;
-}
-
-static int fatfs_rename(lua_State *L) {
-	int luaType = lua_type(L, 1);
-	if(luaType != LUA_TSTRING) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "source file path must string");
-		return 2;
-	}
-	luaType = lua_type(L, 2);
-	if(luaType != LUA_TSTRING) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "dest file path must string");
-		return 2;
-	}
-	FRESULT re = f_rename(lua_tostring(L, 1), lua_tostring(L, 2));
-	lua_pushinteger(L, re);
-	return 1;
-}
-
-
-
-/**
- * fatfs.readfile("adc.txt") 
- * fatfs.readfile("adc.txt", 512, 0) 默认只读取512字节,从0字节开始读
- */
-static int fatfs_readfile(lua_State *L) {
-	int luaType = lua_type( L, 1);
-	if(luaType != LUA_TSTRING) {
-		lua_pushinteger(L, -1);
-		lua_pushstring(L, "file path must string");
-		return 2;
-	}
-	FIL fil;
-
-	FRESULT re = f_open(&fil, lua_tostring(L, 1), FA_READ);
-	if (re != FR_OK) {
-		lua_pushinteger(L, re);
-		return 1;
-	}
-
-	DWORD limit = luaL_optinteger(L, 2, 512);
-	DWORD seek = luaL_optinteger(L, 3, 0);
-	if (seek > 0) {
-		f_lseek(&fil, seek);
-	}
-
-	BYTE buf[limit];
-	size_t len;
-	if (FATFS_DEBUG)
-		LLOGD("[FatFS]readfile seek=%d limit=%d", seek, limit);
-	FRESULT fr = f_read(&fil, buf, limit, &len);
-	if (fr != FR_OK) {
-		lua_pushinteger(L, -3);
-		lua_pushinteger(L, fr);
-		return 2;
+	if (lua_isinteger(L, 3)) {
+		s_fatfs_fmt = luaL_optinteger(L, 3, FM_FAT32);
+		if (s_fatfs_fmt != FM_FAT32 && s_fatfs_fmt != FM_EXFAT) {
+			s_fatfs_fmt = FM_FAT32;
+		}
+		if (s_fatfs_fmt == FM_EXFAT) {
+			LLOGI("fatfs set to exfat , when format sd/tf");
+		}
+		else {
+			LLOGI("fatfs set to fat32 , when format sd/tf");
+		}
 	}
-	f_close(&fil);
-	lua_pushinteger(L, 0);
-	lua_pushlstring(L, (const char*)buf, len);
-	if (FATFS_DEBUG)
-		LLOGD("[FatFS]readfile seek=%d limit=%d len=%d", seek, limit, len);
-	return 2;
+	return 0;
 }
-#endif
 
 // Module function map
 #include "rotable2.h"
@@ -681,32 +360,14 @@ static const rotable_Reg_t reg_fatfs[] =
   { "mount",	ROREG_FUNC(fatfs_mount)}, //初始化,挂载
   { "getfree",	ROREG_FUNC(fatfs_getfree)}, // 获取文件系统大小,剩余空间
   { "debug",	ROREG_FUNC(fatfs_debug_mode)}, // 调试模式,打印更多日志
-  { "config",		ROREG_FUNC(fatfs_config)}, //初始化,挂载, 别名方法
+  { "config",	ROREG_FUNC(fatfs_config)}, //初始化,挂载, 别名方法
   { "unmount",	ROREG_FUNC(fatfs_unmount)}, // 取消挂载
-#if 0
-  { "mkfs",		ROREG_FUNC(fatfs_mkfs)}, // 格式化!!!
-  //{ "test",  fatfs_test)},
-
-  { "lsdir",	ROREG_FUNC(fatfs_lsdir)}, // 列举目录下的文件,名称,大小,日期,属性
-  { "mkdir",	ROREG_FUNC(fatfs_mkdir)}, // 列举目录下的文件,名称,大小,日期,属性
+  { "SPI",      ROREG_INT(DISK_SPI)},
+  { "SDIO",     ROREG_INT(DISK_SDIO)},
+  { "RAM",      ROREG_INT(DISK_RAM)},
 
-  { "stat",		ROREG_FUNC(fatfs_stat)}, // 查询文件信息
-  { "open",		ROREG_FUNC(fatfs_open)}, // 打开一个文件句柄
-  { "close",	ROREG_FUNC(fatfs_close)}, // 关闭一个文件句柄
-  { "seek",		ROREG_FUNC(fatfs_seek)}, // 移动句柄的当前位置
-  { "truncate",	ROREG_FUNC(fatfs_truncate)}, // 缩减文件尺寸到当前seek位置
-  { "read",		ROREG_FUNC(fatfs_read)}, // 读取数据
-  { "write",	ROREG_FUNC(fatfs_write)}, // 写入数据
-  { "remove",	ROREG_FUNC(fatfs_remove)}, // 删除文件,别名方法
-  { "unlink",	ROREG_FUNC(fatfs_remove)}, // 删除文件
-  { "rename",	ROREG_FUNC(fatfs_rename)}, // 文件改名
-
-  { "readfile",	ROREG_FUNC(fatfs_readfile)}, // 读取文件的简易方法
-#endif
-  { "SPI",         ROREG_INT(DISK_SPI)},
-  { "SDIO",        ROREG_INT(DISK_SDIO)},
-  { "RAM",         ROREG_INT(DISK_RAM)},
-//  { "USB",         ROREG_INT(DISK_USB)},
+  { "FM_FAT32",         ROREG_INT(FM_FAT32)},
+  { "FM_EXFAT",         ROREG_INT(FM_EXFAT)},
 
   { NULL,		ROREG_INT(0)}
 };

+ 34 - 4
components/hzfont/src/luat_hzfont.c

@@ -1,3 +1,4 @@
+// 负责:TTF 字体加载、缓存与渲染(HzFont)
 #include "luat_hzfont.h"
 
 #include "ttf_parser.h"
@@ -17,8 +18,10 @@
 #define HZFONT_ADVANCE_RATIO   0.4f
 #define HZFONT_ASCENT_RATIO    0.80f
 
-/* 默认缓存容量(当未显式设置或传入非法值时使用) */
+/* 默认/允许缓存容量集中定义,便于维护 */
 #define HZFONT_CACHE_DEFAULT 256u
+static const uint32_t HZFONT_CACHE_ALLOWED[] = {128u, 256u, 512u, 1024u, 2048u};
+static const size_t   HZFONT_CACHE_ALLOWED_LEN = sizeof(HZFONT_CACHE_ALLOWED) / sizeof(HZFONT_CACHE_ALLOWED[0]);
 
 #ifdef LUAT_CONF_USE_HZFONT_BUILTIN_TTF
 extern const unsigned char hzfont_builtin_ttf[];
@@ -85,6 +88,9 @@ typedef struct {
 
 static hzfont_cp_cache_entry_t *g_hzfont_cp_cache = NULL;
 static uint32_t g_hzfont_cp_cache_size = 0; /* 必须为 2 的幂,便于掩码寻址 */
+/* 单次告警开关,避免重复噪声 */
+static uint8_t g_warn_fontsize = 0;
+static uint8_t g_warn_cache_invalid = 0;
 
 extern luat_lcd_conf_t *lcd_dft_conf;
 extern luat_color_t BACK_COLOR;
@@ -156,8 +162,14 @@ static uint32_t hzfont_next_stamp(void) {
     return g_hzfont_cache_stamp;
 }
 
+/* 判断缓存容量是否在允许列表 */
 static int hzfont_is_allowed_capacity(uint32_t cap) {
-    return (cap == 128u || cap == 256u || cap == 512u || cap == 1024u || cap == 2048u);
+    for (size_t i = 0; i < HZFONT_CACHE_ALLOWED_LEN; i++) {
+        if (cap == HZFONT_CACHE_ALLOWED[i]) {
+            return 1;
+        }
+    }
+    return 0;
 }
 
 static void hzfont_cache_destroy(void) {
@@ -183,6 +195,10 @@ static void hzfont_cache_destroy(void) {
 
 static int hzfont_setup_caches(uint32_t capacity) {
     uint32_t cap = hzfont_is_allowed_capacity(capacity) ? capacity : HZFONT_CACHE_DEFAULT;
+    if (!hzfont_is_allowed_capacity(capacity) && !g_warn_cache_invalid) {
+        LLOGW("hzfont event=init cache_size_invalid=%lu use_default=%lu", (unsigned long)capacity, (unsigned long)cap);
+        g_warn_cache_invalid = 1;
+    }
 
     if (g_hzfont_cache_capacity == cap && g_hzfont_cache && g_hzfont_cp_cache && g_hzfont_cp_cache_size == cap) {
         memset(g_hzfont_cache, 0, sizeof(hzfont_cache_entry_t) * cap);
@@ -518,6 +534,12 @@ static luat_color_t hzfont_coverage_to_color(uint8_t coverage, const luat_lcd_co
     return hzfont_encode_color(conf, (uint8_t)rr, (uint8_t)gg, (uint8_t)bb);
 }
 
+/* 初始化字体库
+ * What: 加载 TTF(外部/内置),建立码点与位图缓存。
+ * Pre: 需要有效的 ttf_path 或启用内置字库宏;cache_size 仅允许 128/256/512/1024/2048。
+ * Post: 状态置为 READY,后续方可测宽/绘制;失败时状态为 ERROR。
+ * TODO: 增加 init 参数,支持选择将字库整包拷贝到 PSRAM,避免后续 IO(外部/内置均可)。
+ */
 int luat_hzfont_init(const char *ttf_path, uint32_t cache_size) {
     if (g_ft_ctx.state == LUAT_HZFONT_STATE_READY) {
         LLOGE("font already initialized");
@@ -671,6 +693,10 @@ static inline int hzfont_pick_antialias_auto(unsigned char font_size) {
 
 int luat_hzfont_draw_utf8(int x, int y, const char *utf8, unsigned char font_size, uint32_t color, int antialias) {
     if (!utf8 || font_size == 0) {
+        if (!g_warn_fontsize) {
+            LLOGE("hzfont event=draw invalid_font_size=%u range=1..255", (unsigned)font_size);
+            g_warn_fontsize = 1;
+        }
         return -1;
     }
     if (g_ft_ctx.state != LUAT_HZFONT_STATE_READY) {
@@ -687,7 +713,7 @@ int luat_hzfont_draw_utf8(int x, int y, const char *utf8, unsigned char font_siz
     }
 
     int timing_enabled = ttf_get_debug();
-    // 处理抗锯齿(antialias)方式的选择与设置
+    // 处理抗锯齿(antialias)方式的选择与设置(副作用:临时修改全局 supersample_rate,后面会恢复)
     // antialias < 0   :自动选择(根据字体大小决定抗锯齿等级,见hzfont_pick_antialias_auto)
     // antialias <= 1  :关闭抗锯齿(1x,即无抗锯齿)
     // antialias == 2  :2x2超采样抗锯齿
@@ -871,6 +897,7 @@ int luat_hzfont_draw_utf8(int x, int y, const char *utf8, unsigned char font_siz
             goto glyph_timing_update;
         }
 
+        /* 行缓冲按行申请/复用,绘制后立即释放,避免长生命周期占用 */
         luat_color_t *row_buf = (luat_color_t *)luat_heap_malloc(row_buf_capacity * sizeof(luat_color_t));
         if (!row_buf) {
             if (timing_enabled && draw_stamp) {
@@ -953,7 +980,10 @@ glyph_timing_update:
 
     /* 恢复超采样率,避免影响外部绘制 */
     if (new_rate != prev_rate) {
-        (void)ttf_set_supersample_rate(prev_rate);
+        int restore = ttf_set_supersample_rate(prev_rate);
+        if (restore != prev_rate) {
+            LLOGE("hzfont event=draw restore_supersample_fail prev=%d got=%d", prev_rate, restore);
+        }
     }
     if (timing_enabled) {
         uint32_t total_us = hzfont_elapsed_from(func_start_ts);

+ 58 - 56
components/hzfont/src/ttf_parser.c

@@ -1,3 +1,4 @@
+// 负责:TTF 解析、流式读取与栅格化(ttf_parser)
 #include "ttf_parser.h"
 
 #include <math.h>
@@ -8,12 +9,6 @@
 #include "luat_fs.h"
 #include "luat_mem.h"
 
-#define free luat_heap_free
-#define malloc luat_heap_malloc
-#define realloc luat_heap_realloc
-#define calloc luat_heap_calloc
-#define zalloc luat_heap_zalloc
-
 #define LUAT_LOG_TAG "ttf"
 #include "luat_log.h"
 
@@ -22,6 +17,13 @@ static int g_ttf_debug = 0;
 /* 运行时可调的超采样率,默认取编译期宏 */
 static int g_ttf_supersample_rate = 0;
 
+/* 防御性释放,避免 free(NULL) 提示 */
+static inline void ttf_safe_free(void *p) {
+    if (p) {
+        luat_heap_free(p);
+    }
+}
+
 #define TTF_TAG(a, b, c, d) (((uint32_t)(a) << 24) | ((uint32_t)(b) << 16) | ((uint32_t)(c) << 8) | (uint32_t)(d))
 
 /* 仅运行时控制超采样,不再依赖编译期宏 */
@@ -178,7 +180,7 @@ int ttf_load_from_file(const char *path, TtfFont *font) {
             fclose(fp);
             return TTF_ERR_IO;
         }
-        font->data = (uint8_t*)malloc((size_t)fileSize);
+        font->data = (uint8_t*)luat_heap_malloc((size_t)fileSize);
         if (!font->data) {
             fclose(fp);
             return TTF_ERR_OOM;
@@ -186,7 +188,7 @@ int ttf_load_from_file(const char *path, TtfFont *font) {
         size_t n = fread(font->data, 1, (size_t)fileSize, fp);
         fclose(fp);
         if (n != (size_t)fileSize) {
-            free(font->data);
+            ttf_safe_free(font->data);
             memset(font, 0, sizeof(*font));
             return TTF_ERR_IO;
         }
@@ -352,9 +354,9 @@ void ttf_unload(TtfFont *font) {
     if (!font) {
         return;
     }
-    if (font->data && font->ownsData) free(font->data);
+    if (font->data && font->ownsData) ttf_safe_free(font->data);
     if (font->file) luat_fs_fclose((FILE*)font->file);
-    if (font->cmapBuf) free(font->cmapBuf);
+    ttf_safe_free(font->cmapBuf);
     memset(font, 0, sizeof(*font));
 }
 
@@ -494,7 +496,7 @@ static void ttf_cache_cmap_subtable(TtfFont *font) {
     if (chosen.length == 0 || chosen.length > TTF_CMAP_CACHE_MAX) {
         return;
     }
-    uint8_t *buf = (uint8_t *)malloc(chosen.length);
+    uint8_t *buf = (uint8_t *)luat_heap_malloc(chosen.length);
     if (!buf) {
         if (g_ttf_debug) {
             LLOGW("cmap cache malloc fail len=%u", (unsigned)chosen.length);
@@ -502,7 +504,7 @@ static void ttf_cache_cmap_subtable(TtfFont *font) {
         return;
     }
     if (!ttf_read_range(font, chosen.offset, chosen.length, buf)) {
-        free(buf);
+        ttf_safe_free(buf);
         if (g_ttf_debug) {
             LLOGW("cmap cache read fail off=%u len=%u", (unsigned)chosen.offset, (unsigned)chosen.length);
         }
@@ -682,8 +684,8 @@ void ttf_free_glyph(TtfGlyph *glyph) {
     if (!glyph) {
         return;
     }
-    free(glyph->contourEnds);
-    free(glyph->points);
+    ttf_safe_free(glyph->contourEnds);
+    ttf_safe_free(glyph->points);
     memset(glyph, 0, sizeof(*glyph));
 }
 
@@ -705,13 +707,13 @@ static int append_component_glyph(TtfGlyph *dest, const TtfGlyph *src,
         return TTF_ERR_FORMAT;
     }
 
-    TtfPoint *points = (TtfPoint *)realloc(dest->points, newPointCount * sizeof(TtfPoint));
+    TtfPoint *points = (TtfPoint *)luat_heap_realloc(dest->points, newPointCount * sizeof(TtfPoint));
     if (!points) {
         return TTF_ERR_OOM;
     }
     dest->points = points;
 
-    uint16_t *contours = (uint16_t *)realloc(dest->contourEnds, newContourCount * sizeof(uint16_t));
+    uint16_t *contours = (uint16_t *)luat_heap_realloc(dest->contourEnds, newContourCount * sizeof(uint16_t));
     if (!contours) {
         return TTF_ERR_OOM;
     }
@@ -774,13 +776,13 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
     }
 
     /* 1.0: 按需把 glyph 数据读入临时缓冲 */
-    uint8_t *tmp = (uint8_t*)malloc(length);
+    uint8_t *tmp = (uint8_t*)luat_heap_malloc(length);
     if (!tmp) {
         return TTF_ERR_OOM;
     }
     if (!ttf_read_range(font, offset, length, tmp)) {
         if (g_ttf_debug) LLOGE("read glyf failed gid=%u off=%u len=%u", (unsigned)glyphIndex, (unsigned)offset, (unsigned)length);
-        free(tmp);
+        ttf_safe_free(tmp);
         return TTF_ERR_IO;
     }
     const uint8_t *ptr = tmp;
@@ -798,7 +800,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
             return TTF_ERR_FORMAT;
         }
 
-        glyph->contourEnds = (uint16_t *)calloc(glyph->contourCount, sizeof(uint16_t));
+        glyph->contourEnds = (uint16_t *)luat_heap_calloc(glyph->contourCount, sizeof(uint16_t));
         if (!glyph->contourEnds) {
             return TTF_ERR_OOM;
         }
@@ -811,14 +813,14 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
         const uint8_t *instructionPtr = contourPtr + glyph->contourCount * 2 + 2;
         if (instructionPtr + instructionLength > end) {
             ttf_free_glyph(glyph);
-            free(tmp);
+            ttf_safe_free(tmp);
             return TTF_ERR_FORMAT;
         }
         const uint8_t *flagsPtr = instructionPtr + instructionLength;
 
         if (glyph->contourEnds[glyph->contourCount - 1] >= 0xFFFFu) {
             ttf_free_glyph(glyph);
-            free(tmp);
+            ttf_safe_free(tmp);
             return TTF_ERR_FORMAT;
         }
         glyph->pointCount = glyph->contourEnds[glyph->contourCount - 1] + 1;
@@ -826,35 +828,35 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
             return TTF_OK;
         }
 
-        uint8_t *flags = (uint8_t *)malloc(glyph->pointCount);
+        uint8_t *flags = (uint8_t *)luat_heap_malloc(glyph->pointCount);
         if (!flags) {
             ttf_free_glyph(glyph);
-            free(tmp);
+            ttf_safe_free(tmp);
             return TTF_ERR_OOM;
         }
         const uint8_t *cursor = flagsPtr;
         uint16_t flagIndex = 0;
         while (flagIndex < glyph->pointCount) {
             if (cursor >= end) {
-                free(flags);
+                ttf_safe_free(flags);
                 ttf_free_glyph(glyph);
-                free(tmp);
+                ttf_safe_free(tmp);
                 return TTF_ERR_FORMAT;
             }
             uint8_t flag = *cursor++;
             flags[flagIndex++] = flag;
             if (flag & 0x08) {
                 if (cursor >= end) {
-                    free(flags);
+                    ttf_safe_free(flags);
                     ttf_free_glyph(glyph);
-                    free(tmp);
+                    ttf_safe_free(tmp);
                     return TTF_ERR_FORMAT;
                 }
                 uint8_t repeatCount = *cursor++;
                 if (flagIndex + repeatCount > glyph->pointCount) {
-                    free(flags);
+                    ttf_safe_free(flags);
                     ttf_free_glyph(glyph);
-                    free(tmp);
+                    ttf_safe_free(tmp);
                     return TTF_ERR_FORMAT;
                 }
                 for (uint8_t r = 0; r < repeatCount; ++r) {
@@ -863,9 +865,9 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
             }
         }
 
-        glyph->points = (TtfPoint *)calloc(glyph->pointCount, sizeof(TtfPoint));
+        glyph->points = (TtfPoint *)luat_heap_calloc(glyph->pointCount, sizeof(TtfPoint));
         if (!glyph->points) {
-            free(flags);
+            ttf_safe_free(flags);
             ttf_free_glyph(glyph);
             return TTF_ERR_OOM;
         }
@@ -876,7 +878,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
             uint8_t flag = flags[flagIndex++];
             if (flag & 0x02) {
                 if (cursor >= end) {
-                    free(flags);
+                    ttf_safe_free(flags);
                     ttf_free_glyph(glyph);
                     return TTF_ERR_FORMAT;
                 }
@@ -884,7 +886,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
                 x += (flag & 0x10) ? dx : -(int16_t)dx;
             } else if (!(flag & 0x10)) {
                 if (cursor + 1 >= end) {
-                    free(flags);
+                    ttf_safe_free(flags);
                     ttf_free_glyph(glyph);
                     return TTF_ERR_FORMAT;
                 }
@@ -901,7 +903,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
             uint8_t flag = flags[flagIndex];
             if (flag & 0x04) {
                 if (cursor >= end) {
-                    free(flags);
+                    ttf_safe_free(flags);
                     ttf_free_glyph(glyph);
                     return TTF_ERR_FORMAT;
                 }
@@ -909,7 +911,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
                 y += (flag & 0x20) ? dy : -(int16_t)dy;
             } else if (!(flag & 0x20)) {
                 if (cursor + 1 >= end) {
-                    free(flags);
+                    ttf_safe_free(flags);
                     ttf_free_glyph(glyph);
                     return TTF_ERR_FORMAT;
                 }
@@ -922,8 +924,8 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
             ++flagIndex;
         }
 
-        free(flags);
-            free(tmp);
+        ttf_safe_free(flags);
+            ttf_safe_free(tmp);
             return TTF_OK;
     }
 
@@ -933,7 +935,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
     do {
         if (cursor + 4 > end) {
             ttf_free_glyph(glyph);
-            free(tmp);
+            ttf_safe_free(tmp);
             return TTF_ERR_FORMAT;
         }
         flags = read_u16(cursor);
@@ -968,7 +970,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
             dy = (float)arg2;
         } else {
             ttf_free_glyph(glyph);
-            free(tmp);
+            ttf_safe_free(tmp);
             return TTF_ERR_UNSUPPORTED;
         }
 
@@ -979,7 +981,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
         if (flags & 0x0008) {
             if (cursor + 2 > end) {
                 ttf_free_glyph(glyph);
-                free(tmp);
+                ttf_safe_free(tmp);
                 return TTF_ERR_FORMAT;
             }
             float scale = read_f2dot14(cursor);
@@ -989,7 +991,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
         } else if (flags & 0x0040) {
             if (cursor + 4 > end) {
                 ttf_free_glyph(glyph);
-                free(tmp);
+                ttf_safe_free(tmp);
                 return TTF_ERR_FORMAT;
             }
             m00 = read_f2dot14(cursor);
@@ -1017,7 +1019,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
             ttf_free_glyph(&componentGlyph);
             ttf_free_glyph(glyph);
             if (g_ttf_debug) LLOGE("load component glyph rc=%d", rc);
-            free(tmp);
+            ttf_safe_free(tmp);
             return rc;
         }
         rc = append_component_glyph(glyph, &componentGlyph, m00, m01, m10, m11, dx, dy);
@@ -1025,7 +1027,7 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
         if (rc != TTF_OK) {
             ttf_free_glyph(glyph);
             if (g_ttf_debug) LLOGE("append component rc=%d", rc);
-            free(tmp);
+            ttf_safe_free(tmp);
             return rc;
         }
     } while (flags & 0x0020);
@@ -1033,20 +1035,20 @@ static int load_glyph_internal(const TtfFont *font, uint16_t glyphIndex, TtfGlyp
     if (flags & 0x0100) {
         if (cursor + 2 > end) {
             ttf_free_glyph(glyph);
-            free(tmp);
+            ttf_safe_free(tmp);
             return TTF_ERR_FORMAT;
         }
         uint16_t instructionLength = read_u16(cursor);
         cursor += 2;
         if (cursor + instructionLength > end) {
             ttf_free_glyph(glyph);
-            free(tmp);
+            ttf_safe_free(tmp);
             return TTF_ERR_FORMAT;
         }
         cursor += instructionLength;
     }
 
-    free(tmp);
+    ttf_safe_free(tmp);
     return TTF_OK;
 }
 
@@ -1077,7 +1079,7 @@ static int append_segment(SegmentList *segments, float x0, float y0, float x1, f
         while (segments->count * 4 + 4 > newCapacity) {
             newCapacity *= 2;
         }
-        float *newData = (float *)realloc(segments->data, newCapacity * sizeof(float));
+        float *newData = (float *)luat_heap_realloc(segments->data, newCapacity * sizeof(float));
         if (!newData) {
             return 0;
         }
@@ -1108,7 +1110,7 @@ static int convert_segments_to_fixed(const SegmentList *src, FixedSegmentList *d
         return 1;
     }
     size_t total = src->count * 4;
-    int32_t *buf = (int32_t *)malloc(total * sizeof(int32_t));
+    int32_t *buf = (int32_t *)luat_heap_malloc(total * sizeof(int32_t));
     if (!buf) {
         return 0;
     }
@@ -1127,7 +1129,7 @@ static void free_fixed_segments(FixedSegmentList *segments) {
     if (!segments) {
         return;
     }
-    free(segments->data);
+    ttf_safe_free(segments->data);
     segments->data = NULL;
     segments->count = 0;
 }
@@ -1292,7 +1294,7 @@ int ttf_rasterize_glyph(const TtfFont *font, const TtfGlyph *glyph, int ppem, Tt
     uint32_t width = (uint32_t)ceilf(widthF);
     uint32_t height = (uint32_t)ceilf(heightF);
 
-    uint8_t *pixels = (uint8_t *)calloc((size_t)width * height, sizeof(uint8_t));
+    uint8_t *pixels = (uint8_t *)luat_heap_calloc((size_t)width * height, sizeof(uint8_t));
     if (!pixels) {
         return TTF_ERR_OOM;
     }
@@ -1301,18 +1303,18 @@ int ttf_rasterize_glyph(const TtfFont *font, const TtfGlyph *glyph, int ppem, Tt
     float offsetX = 1.0f;
     float offsetY = 1.0f;
     if (!build_segments(glyph, scale, offsetX, offsetY, &segments)) {
-        free(pixels);
-        free(segments.data);
+        ttf_safe_free(pixels);
+        ttf_safe_free(segments.data);
         return TTF_ERR_FORMAT;
     }
 
     FixedSegmentList fixedSegments = {0};
     if (!convert_segments_to_fixed(&segments, &fixedSegments)) {
-        free(pixels);
-        free(segments.data);
+        ttf_safe_free(pixels);
+        ttf_safe_free(segments.data);
         return TTF_ERR_OOM;
     }
-    free(segments.data);
+    ttf_safe_free(segments.data);
 
     const int supersampleRate = ttf_get_supersample_rate();
     const int sampleCount = supersampleRate * supersampleRate;
@@ -1357,6 +1359,6 @@ void ttf_free_bitmap(TtfBitmap *bitmap) {
     if (!bitmap) {
         return;
     }
-    free(bitmap->pixels);
+    ttf_safe_free(bitmap->pixels);
     memset(bitmap, 0, sizeof(*bitmap));
 }

+ 2 - 0
components/pins/src/luat_pins.c

@@ -230,6 +230,8 @@ static luat_pin_peripheral_function_description_u luat_pin_function_analyze(char
 					if (string[5] >= '0' && string[5] <= '3')
 					{
 						function_id = string[5] - '0';
+						description.function_id = function_id;
+						goto LUAT_PIN_FUNCTION_ANALYZE_DONE;
 					}
 					break;
 				}

+ 389 - 0
components/rtmp/binding/luat_lib_rtmp.c

@@ -0,0 +1,389 @@
+/*
+@module  rtmp
+@summary RTMP 直播推流
+@version 1.0
+@date    2025.12.8
+@tag     LUAT_USE_RTMP
+@usage
+-- RTMP推流示例
+local rtmp = rtmp.create("rtmp://example.com:1935/live/stream")
+rtmp:setCallback(function(state, ...)
+    if state == rtmp.STATE_CONNECTED then
+        print("已连接到推流服务器")
+    elseif state == rtmp.STATE_PUBLISHING then
+        print("已开始推流")
+    elseif state == rtmp.STATE_ERROR then
+        print("出错:", ...)
+    end
+end)
+rtmp:connect()
+
+-- 开始处理
+rtmp:start()
+
+-- 30秒后停止
+sys.wait(30000)
+rtmp:stop()
+
+-- 断开连接
+rtmp:disconnect()
+rtmp:destroy()
+*/
+
+#include "luat_base.h"
+#include "luat_rtmp_push.h"
+#include "luat_msgbus.h"
+#include "luat_mem.h"
+#include "lauxlib.h"
+#include <stdlib.h>
+#include "lwip/timeouts.h"
+#include "lwip/tcpip.h"
+
+#define LUAT_LOG_TAG "rtmp"
+#include "luat_log.h"
+
+typedef struct {
+    rtmp_ctx_t *rtmp;
+    int callback_ref;
+} luat_rtmp_userdata_t;
+
+/**
+创建RTMP推流上下文
+@api rtmp.create(url)
+@string url RTMP服务器地址, 格式: rtmp://host:port/app/stream
+@return userdata RTMP上下文对象
+@usage
+local rtmp = rtmp.create("rtmp://example.com:1935/live/stream")
+*/
+static int l_rtmp_create(lua_State *L) {
+    const char *url = luaL_checkstring(L, 1);
+    
+    luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)lua_newuserdata(L, sizeof(luat_rtmp_userdata_t));
+    if (!ud) {
+        LLOGE("内存分配失败");
+        lua_pushnil(L);
+        return 1;
+    }
+    
+    ud->rtmp = rtmp_create();
+    if (!ud->rtmp) {
+        LLOGE("RTMP上下文创建失败");
+        lua_pushnil(L);
+        return 1;
+    }
+    ud->rtmp->user_data = (void *)ud;
+    
+    ud->callback_ref = LUA_NOREF;
+    
+    if (rtmp_set_url(ud->rtmp, url) != 0) {
+        LLOGE("RTMP URL设置失败");
+        rtmp_destroy(ud->rtmp);
+        lua_pushnil(L);
+        return 1;
+    }
+    
+    luaL_getmetatable(L, "rtmp_ctx");
+    lua_setmetatable(L, -2);
+    
+    LLOGD("RTMP上下文创建成功: %s", url);
+    return 1;
+}
+
+/**
+设置RTMP状态回调函数
+@api rtmp:setCallback(func)
+@function func 回调函数, 参数为 (state, ...) 
+@return nil 无返回值
+@usage
+rtmp:setCallback(function(state, ...)
+    if state == rtmp.STATE_IDLE then
+        print("空闲状态")
+    elseif state == rtmp.STATE_CONNECTING then
+        print("正在连接")
+    elseif state == rtmp.STATE_HANDSHAKING then
+        print("握手中")
+    elseif state == rtmp.STATE_CONNECTED then
+        print("已连接")
+    elseif state == rtmp.STATE_PUBLISHING then
+        print("推流中")
+    elseif state == rtmp.STATE_DISCONNECTING then
+        print("正在断开")
+    elseif state == rtmp.STATE_ERROR then
+        print("错误:", ...)
+    end
+end)
+*/
+static int l_rtmp_set_callback(lua_State *L) {
+    luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
+    if (!ud || !ud->rtmp) {
+        lua_pushboolean(L, 0);
+        return 1;
+    }
+    
+    if (lua_isfunction(L, 2)) {
+        if (ud->callback_ref != LUA_NOREF) {
+            luaL_unref(L, LUA_REGISTRYINDEX, ud->callback_ref);
+        }
+        lua_pushvalue(L, 2);
+        ud->callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+        LLOGD("RTMP回调函数已设置");
+    } else if (lua_isnil(L, 2)) {
+        if (ud->callback_ref != LUA_NOREF) {
+            luaL_unref(L, LUA_REGISTRYINDEX, ud->callback_ref);
+            ud->callback_ref = LUA_NOREF;
+        }
+        LLOGD("RTMP回调函数已清除");
+    } else {
+        LLOGE("参数错误,需要function或nil");
+        lua_pushboolean(L, 0);
+        return 1;
+    }
+    
+    lua_pushboolean(L, 1);
+    return 1;
+}
+
+static int l_rtmp_handler(lua_State *L, void *udata) {
+    rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
+    luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)msg->ptr;
+    if (!ud || ud->callback_ref == LUA_NOREF) {
+        return 0;
+    }
+    int state = msg->arg1;
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->callback_ref);
+    if (lua_isfunction(L, -1)) {
+        lua_pushinteger(L, state);
+        lua_call(L, 1, 0);
+    }
+    return 0;
+}
+
+/**
+状态回调函数(内部使用)
+*/
+static void l_state_callback(rtmp_ctx_t *ctx, rtmp_state_t oldstate, rtmp_state_t newstate, int error_code) {
+    rtos_msg_t msg = {0};
+    msg.handler = l_rtmp_handler;
+    msg.ptr = ctx->user_data;
+    msg.arg1 = (int)newstate;
+    msg.arg2 = (int)oldstate;
+    LLOGD("RTMP状态(%d)回调消息入队 %p %p", (int)newstate, &msg, ctx->user_data);
+    // luat_msgbus_put(&msg, 0);
+}
+
+/**
+连接到RTMP服务器
+@api rtmp:connect()
+@return boolean 成功返回true, 失败返回false
+@usage
+local ok = rtmp:connect()
+if ok then
+    print("连接请求已发送")
+else
+    print("连接失败")
+end
+*/
+static int l_rtmp_connect(lua_State *L) {
+    luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
+    if (!ud || !ud->rtmp) {
+        lua_pushboolean(L, 0);
+        return 1;
+    }
+    
+    rtmp_set_state_callback(ud->rtmp, l_state_callback);
+    
+    int ret = tcpip_callback_with_block(rtmp_connect, (void *)ud->rtmp, 0);
+    LLOGD("RTMP连接请求: %s", ret == 0 ? "成功" : "失败");
+    lua_pushboolean(L, ret == 0 ? 1 : 0);
+    return 1;
+}
+
+/**
+断开RTMP连接
+@api rtmp:disconnect()
+@return boolean 成功返回true, 失败返回false
+@usage
+rtmp:disconnect()
+*/
+static int l_rtmp_disconnect(lua_State *L) {
+    luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
+    if (!ud || !ud->rtmp) {
+        lua_pushboolean(L, 0);
+        return 1;
+    }
+    
+    int ret = rtmp_disconnect(ud->rtmp);
+    LLOGD("RTMP断开连接: %s", ret == 0 ? "成功" : "失败");
+    lua_pushboolean(L, ret == 0 ? 1 : 0);
+    return 1;
+}
+
+static void t_rtmp_poll(void *arg) {
+    rtmp_ctx_t *ctx = (rtmp_ctx_t *)arg;
+    rtmp_poll(ctx);
+    sys_timeout(20, t_rtmp_poll, ctx);
+}
+
+/**
+处理RTMP事件
+@api rtmp:start()
+@return nil 无返回值
+@usage
+rtmp:start()
+*/
+static int l_rtmp_start(lua_State *L) {
+    luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
+    if (!ud || !ud->rtmp) {
+        return 0;
+    }
+    sys_timeout(20, t_rtmp_poll, ud->rtmp);
+    return 0;
+}
+
+/**
+获取RTMP连接状态
+@api rtmp:getState()
+@return int 当前状态值
+@usage
+local state = rtmp:getState()
+if state == rtmp.STATE_CONNECTED then
+    print("已连接")
+elseif state == rtmp.STATE_PUBLISHING then
+    print("正在推流")
+end
+*/
+static int l_rtmp_get_state(lua_State *L) {
+    luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
+    if (!ud || !ud->rtmp) {
+        lua_pushinteger(L, -1);
+        return 1;
+    }
+    
+    rtmp_state_t state = rtmp_get_state(ud->rtmp);
+    lua_pushinteger(L, (lua_Integer)state);
+    return 1;
+}
+
+/**
+获取RTMP统计信息
+@api rtmp:getStats()
+@return table 统计信息表
+@usage
+local stats = rtmp:getStats()
+print("已发送字节数:", stats.bytes_sent)
+print("已发送视频帧数:", stats.video_frames_sent)
+print("已发送音频帧数:", stats.audio_frames_sent)
+*/
+static int l_rtmp_get_stats(lua_State *L) {
+    luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
+    if (!ud || !ud->rtmp) {
+        lua_pushnil(L);
+        return 1;
+    }
+    
+    rtmp_stats_t stats = {0};
+    rtmp_get_stats(ud->rtmp, &stats);
+    
+    lua_newtable(L);
+    lua_pushinteger(L, stats.bytes_sent);
+    lua_setfield(L, -2, "bytes_sent");
+    lua_pushinteger(L, stats.video_frames_sent);
+    lua_setfield(L, -2, "video_frames_sent");
+    lua_pushinteger(L, stats.audio_frames_sent);
+    lua_setfield(L, -2, "audio_frames_sent");
+    lua_pushinteger(L, stats.connection_time);
+    lua_setfield(L, -2, "connection_time");
+    
+    return 1;
+}
+
+/**
+销毁RTMP上下文,释放所有资源
+@api rtmp:destroy()
+@return nil 无返回值
+@usage
+rtmp:destroy()
+*/
+static int l_rtmp_destroy(lua_State *L) {
+    luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
+    if (!ud || !ud->rtmp) {
+        return 0;
+    }
+    
+    if (ud->callback_ref != LUA_NOREF) {
+        luaL_unref(L, LUA_REGISTRYINDEX, ud->callback_ref);
+        ud->callback_ref = LUA_NOREF;
+    }
+    
+    rtmp_destroy(ud->rtmp);
+    ud->rtmp = NULL;
+    
+    LLOGD("RTMP上下文已销毁");
+    return 0;
+}
+
+static int l_rtmp_gc(lua_State *L) {
+    luat_rtmp_userdata_t *ud = (luat_rtmp_userdata_t *)luaL_checkudata(L, 1, "rtmp_ctx");
+    if (ud && ud->rtmp) {
+        if (ud->callback_ref != LUA_NOREF) {
+            luaL_unref(L, LUA_REGISTRYINDEX, ud->callback_ref);
+        }
+        rtmp_destroy(ud->rtmp);
+        ud->rtmp = NULL;
+    }
+    return 0;
+}
+
+#include "rotable2.h"
+
+static const rotable_Reg_t reg_rtmp_ctx[] = {
+    {"setCallback",   ROREG_FUNC(l_rtmp_set_callback)},
+    {"connect",       ROREG_FUNC(l_rtmp_connect)},
+    {"disconnect",    ROREG_FUNC(l_rtmp_disconnect)},
+    {"start",         ROREG_FUNC(l_rtmp_start)},
+    {"getState",      ROREG_FUNC(l_rtmp_get_state)},
+    {"getStats",      ROREG_FUNC(l_rtmp_get_stats)},
+    {"destroy",       ROREG_FUNC(l_rtmp_destroy)},
+    {"__gc",          ROREG_FUNC(l_rtmp_gc)},
+    {NULL,            ROREG_INT(0)}
+};
+
+static const rotable_Reg_t reg_rtmp[] = {
+    {"create",            ROREG_FUNC(l_rtmp_create)},
+    
+    // RTMP状态常量
+    {"STATE_IDLE",        ROREG_INT(RTMP_STATE_IDLE)},
+    {"STATE_CONNECTING",  ROREG_INT(RTMP_STATE_CONNECTING)},
+    {"STATE_HANDSHAKING", ROREG_INT(RTMP_STATE_HANDSHAKING)},
+    {"STATE_CONNECTED",   ROREG_INT(RTMP_STATE_CONNECTED)},
+    {"STATE_PUBLISHING",  ROREG_INT(RTMP_STATE_PUBLISHING)},
+    {"STATE_DISCONNECTING",ROREG_INT(RTMP_STATE_DISCONNECTING)},
+    {"STATE_ERROR",       ROREG_INT(RTMP_STATE_ERROR)},
+    
+    {NULL,                ROREG_INT(0)}
+};
+
+static int _rtmp_struct_newindex(lua_State *L) {
+	const rotable_Reg_t* reg = reg_rtmp_ctx;
+    const char* key = luaL_checkstring(L, 2);
+	while (1) {
+		if (reg->name == NULL)
+			return 0;
+		if (!strcmp(reg->name, key)) {
+			lua_pushcfunction(L, reg->value.value.func);
+			return 1;
+		}
+		reg ++;
+	}
+}
+
+LUAMOD_API int luaopen_rtmp(lua_State *L) {
+    luat_newlib2(L, reg_rtmp);
+    
+    luaL_newmetatable(L, "rtmp_ctx");
+    lua_pushcfunction(L, _rtmp_struct_newindex);
+    lua_setfield(L, -2, "__index");
+    lua_pop(L, 1);
+    
+    return 1;
+}

+ 392 - 0
components/rtmp/include/luat_rtmp_push.h

@@ -0,0 +1,392 @@
+/**
+ * @file luat_rtmp_push.h
+ * @brief RTMP推流组件 - 基于lwip raw API实现
+ * @author LuatOS Team
+ * 
+ * 该组件实现了RTMP(Real Time Messaging Protocol)推流功能,
+ * 支持将H.264视频流推送到RTMP服务器。
+ * 
+ * 主要特性:
+ * - 基于lwip raw socket API,适应嵌入式环境
+ * - 支持自定义H.264帧来源,灵活的NALU帧注入
+ * - 完整的RTMP握手和连接管理
+ * - 支持FLV格式视频打包和发送
+ * - C99语法,内存使用优化
+ * 
+ * 调试说明:
+ * - 在 luat_rtmp_push.c 中修改 RTMP_DEBUG_VERBOSE 宏来控制详细日志输出
+ * - 设置为 1 开启详细调试信息,设置为 0 关闭(仅保留关键日志)
+ */
+
+#ifndef __LUAT_RTMP_PUSH_H__
+#define __LUAT_RTMP_PUSH_H__
+
+#include "luat_base.h"
+#include "lwip/tcp.h"
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ======================== RTMP常量定义 ======================== */
+
+/** RTMP默认块大小(字节) - RTMP规范规定的默认值为128 */
+#define RTMP_DEFAULT_CHUNK_SIZE 128
+
+/** RTMP缓冲区大小(字节) - 需要足够大以容纳I帧 */
+#define RTMP_BUFFER_SIZE (512 * 1024)
+
+/** 发送帧队列最大字节数上限,超出将丢弃未发送帧(优先丢弃非关键帧) */
+#define RTMP_MAX_QUEUE_BYTES (1024 * 1024)
+
+/** RTMP握手数据大小(字节) */
+#define RTMP_HANDSHAKE_SIZE 1536
+
+/** RTMP命令超时时间(毫秒) */
+#define RTMP_CMD_TIMEOUT 5000
+
+/* ======================== 返回值定义 ======================== */
+
+/** 操作成功 */
+#define RTMP_OK 0
+
+/** 通用错误 */
+#define RTMP_ERR_FAILED (-1)
+
+/** 参数无效 */
+#define RTMP_ERR_INVALID_PARAM (-2)
+
+/** 内存不足 */
+#define RTMP_ERR_NO_MEMORY (-3)
+
+/** 连接错误 */
+#define RTMP_ERR_CONNECT_FAILED (-4)
+
+/** 握手失败 */
+#define RTMP_ERR_HANDSHAKE_FAILED (-5)
+
+/** 网络错误 */
+#define RTMP_ERR_NETWORK (-6)
+
+/** 超时 */
+#define RTMP_ERR_TIMEOUT (-7)
+
+/** 缓冲区溢出 */
+#define RTMP_ERR_BUFFER_OVERFLOW (-8)
+
+/* ======================== 数据类型定义 ======================== */
+
+/**
+ * RTMP连接状态枚举
+ */
+typedef enum {
+    RTMP_STATE_IDLE = 0,           /**< 空闲状态 */
+    RTMP_STATE_CONNECTING = 1,     /**< 正在连接 */
+    RTMP_STATE_HANDSHAKING = 2,    /**< 握手中 */
+    RTMP_STATE_CONNECTED = 3,      /**< 已连接 */
+    RTMP_STATE_PUBLISHING = 4,     /**< 正在推流 */
+    RTMP_STATE_DISCONNECTING = 5,  /**< 正在断开连接 */
+    RTMP_STATE_ERROR = 6            /**< 错误状态 */
+} rtmp_state_t;
+
+/**
+ * RTMP消息类型枚举
+ */
+typedef enum {
+    RTMP_MSG_SET_CHUNK_SIZE = 1,    /**< 设置块大小 */
+    RTMP_MSG_ABORT = 2,             /**< 中止消息 */
+    RTMP_MSG_BYTES_READ = 3,        /**< 字节已读 */
+    RTMP_MSG_CONTROL = 4,           /**< 用户控制消息 */
+    RTMP_MSG_SERVER_BW = 5,         /**< 服务器带宽 */
+    RTMP_MSG_CLIENT_BW = 6,         /**< 客户端带宽 */
+    RTMP_MSG_AUDIO = 8,             /**< 音频数据 */
+    RTMP_MSG_VIDEO = 9,             /**< 视频数据 */
+    RTMP_MSG_AMFDATAFILE = 15,      /**< AMF数据 */
+    RTMP_MSG_COMMAND = 20,          /**< 命令(AMF0) */
+    RTMP_MSG_EXTENDED_COMMAND = 17  /**< 扩展命令 */
+} rtmp_msg_type_t;
+
+/**
+ * H.264 NALU类型枚举
+ */
+typedef enum {
+    NALU_TYPE_NON_IDR = 1,          /**< 非IDR帧 */
+    NALU_TYPE_IDR = 5,              /**< IDR帧(关键帧) */
+    NALU_TYPE_SEI = 6,              /**< SEI(补充增强信息) */
+    NALU_TYPE_SPS = 7,              /**< SPS(序列参数集) */
+    NALU_TYPE_PPS = 8,              /**< PPS(图像参数集) */
+    NALU_TYPE_AUD = 9               /**< AUD(访问单元分隔符) */
+} nalu_type_t;
+
+/**
+ * H.264视频帧标签结构体
+ * 用于描述FLV格式中的视频数据帧
+ */
+typedef struct {
+    uint8_t frame_type;             /**< 帧类型: 1=关键帧, 2=普通帧 */
+    uint8_t codec_id;               /**< 编码器ID: 7=H.264 */
+    uint32_t cts;                   /**< 时间戳偏移(ms) */
+    uint8_t *data;                  /**< 视频数据指针 */
+    uint32_t len;                   /**< 视频数据长度 */
+} video_tag_t;
+
+/**
+ * RTMP推流统计信息结构体
+ * 用于查询RTMP连接的实时统计数据
+ */
+typedef struct {
+    uint32_t bytes_sent;            /**< 已发送的字节数 */
+    uint32_t video_frames_sent;     /**< 已发送的视频帧数 */
+    uint32_t audio_frames_sent;     /**< 已发送的音频帧数 */
+    uint32_t connection_time;       /**< 连接持续时间(毫秒) */
+    uint32_t packets_sent;          /**< 已发送的包数 */
+    uint32_t last_video_timestamp;  /**< 最后视频时间戳(毫秒) */
+    uint32_t last_audio_timestamp;  /**< 最后音频时间戳(毫秒) */
+} rtmp_stats_t;
+
+/**
+ * RTMP推流上下文结构体
+ * 管理单个RTMP连接的所有状态和缓冲区
+ */
+typedef struct {
+    /** ============ 连接信息 ============ */
+    char *url;                      /**< RTMP服务器URL */
+    char *host;                     /**< RTMP服务器主机名/IP地址 */
+    char *app;                      /**< RTMP应用名 */
+    char *stream;                   /**< 推流名 */
+    char *auth;                     /**< 认证信息 */
+    uint16_t port;                  /**< 连接端口 */
+    
+    /** ============ TCP连接状态 ============ */
+    struct tcp_pcb *pcb;            /**< lwip TCP控制块 */
+    rtmp_state_t state;             /**< 当前连接状态 */
+    uint32_t last_activity_time;    /**< 最后活动时间戳 */
+    int handshake_state;            /**< 握手状态: 0=发送C0C1, 1=等待S0S1, 2=发送C2, 3=完成 */
+    
+    /** ============ RTMP协议状态 ============ */
+    uint32_t in_chunk_size;         /**< 输入块大小 */
+    uint32_t out_chunk_size;        /**< 输出块大小 */
+    uint32_t chunk_size;            /**< 当前chunk大小(用于分块发送)*/
+    uint32_t video_stream_id;       /**< 视频流ID */
+    uint32_t audio_stream_id;       /**< 音频流ID */
+    
+    /** ============ 缓冲区管理 ============ */
+    uint8_t *recv_buf;              /**< 接收缓冲区 */
+    uint32_t recv_buf_size;         /**< 接收缓冲区大小 */
+    uint32_t recv_pos;              /**< 接收缓冲区写位置 */
+    
+    uint8_t *send_buf;              /**< 发送缓冲区 */
+    uint32_t send_buf_size;         /**< 发送缓冲区大小 */
+    uint32_t send_pos;              /**< 发送缓冲区写位置 */
+
+    /** ============ 帧发送队列 ============ */
+    struct rtmp_frame_node *frame_head; /**< 待发送帧队列头 */
+    struct rtmp_frame_node *frame_tail; /**< 待发送帧队列尾 */
+    uint32_t frame_queue_bytes;          /**< 队列占用的总字节数 */
+    
+    /** ============ 时间戳管理 ============ */
+    uint32_t video_timestamp;       /**< 当前视频时间戳(ms) */
+    uint32_t audio_timestamp;       /**< 当前音频时间戳(ms) */
+    uint32_t base_timestamp;        /**< 基准时间戳 */
+    
+    /** ============ 统计信息 ============ */
+    uint32_t packets_sent;          /**< 已发送的包数 */
+    uint32_t bytes_sent;            /**< 已发送的字节数 */
+    uint32_t command_id;            /**< 当前命令ID */
+    
+    /** ============ 用户数据 ============ */
+    void *user_data;                /**< 用户自定义数据指针 */
+} rtmp_ctx_t;
+
+/* ======================== 核心接口函数 ======================== */
+
+/**
+ * 创建RTMP推流上下文
+ * 
+ * 分配并初始化RTMP上下文结构体,为后续的RTMP连接做准备。
+ * 
+ * @return 返回RTMP上下文指针,失败返回NULL
+ */
+rtmp_ctx_t* rtmp_create(void);
+
+/**
+ * 销毁RTMP推流上下文
+ * 
+ * 释放所有由RTMP上下文占用的资源,包括内存缓冲区和TCP连接。
+ * 
+ * @param ctx RTMP上下文指针
+ * @return 返回RTMP_OK表示成功
+ */
+int rtmp_destroy(rtmp_ctx_t *ctx);
+
+/**
+ * 设置RTMP服务器URL
+ * 
+ * 解析并设置RTMP服务器地址,支持的格式为:
+ * - rtmp://hostname:port/app/stream
+ * - rtmp://hostname/app/stream (使用默认端口1935)
+ * 
+ * 如果设置过URL,新的设置会覆盖旧的设置。
+ * 
+ * @param ctx RTMP上下文指针
+ * @param url RTMP服务器URL字符串
+ * @return RTMP_OK表示成功,其他值表示失败
+ */
+int rtmp_set_url(rtmp_ctx_t *ctx, const char *url);
+
+/**
+ * 连接到RTMP服务器
+ * 
+ * 建立与RTMP服务器的TCP连接,然后执行RTMP握手流程。
+ * 该函数是非阻塞的,实际的连接过程通过回调函数进行。
+ * 
+ * @param ctx RTMP上下文指针
+ * @return RTMP_OK表示连接已启动,其他值表示参数错误或资源不足
+ */
+int rtmp_connect(rtmp_ctx_t *ctx);
+
+/**
+ * 断开RTMP连接
+ * 
+ * 主动关闭与RTMP服务器的连接,释放TCP资源。
+ * 
+ * @param ctx RTMP上下文指针
+ * @return RTMP_OK表示断开已启动
+ */
+int rtmp_disconnect(rtmp_ctx_t *ctx);
+
+/**
+ * 发送H.264 NALU帧
+ * 
+ * 将一个H.264 NALU帧打包为FLV视频标签并发送。
+ * 支持自动检测关键帧(IDR)和普通帧。
+ * 
+ * 使用示例:
+ * @code
+ * uint8_t nalu_data[1024];
+ * uint32_t nalu_len = 1024;
+ * rtmp_send_nalu(ctx, nalu_data, nalu_len, 0); // 时间戳0ms
+ * @endcode
+ * 
+ * @param ctx RTMP上下文指针
+ * @param nalu_data NALU数据指针,包含完整的NALU单元
+ * @param nalu_len NALU数据长度
+ * @param timestamp 视频时间戳(毫秒),从0开始递增
+ * @return RTMP_OK表示发送成功,其他值表示错误
+ *         - RTMP_ERR_INVALID_PARAM: 参数无效
+ *         - RTMP_ERR_BUFFER_OVERFLOW: 缓冲区不足
+ *         - RTMP_ERR_FAILED: 发送失败
+ */
+int rtmp_send_nalu(rtmp_ctx_t *ctx, const uint8_t *nalu_data, 
+                   uint32_t nalu_len, uint32_t timestamp);
+
+/**
+ * 发送多个NALU帧(聚合发送)
+ * 
+ * 将多个NALU帧聚合打包为单个FLV视频数据包发送,
+ * 可以提高网络传输效率。
+ * 
+ * @param ctx RTMP上下文指针
+ * @param nalus NALU数据指针数组
+ * @param lengths 对应NALU的长度数组
+ * @param count NALU帧的个数
+ * @param timestamp 视频时间戳(毫秒)
+ * @return RTMP_OK表示发送成功
+ */
+int rtmp_send_nalu_multi(rtmp_ctx_t *ctx, const uint8_t **nalus,
+                         const uint32_t *lengths, uint32_t count, 
+                         uint32_t timestamp);
+
+/**
+ * 获取当前连接状态
+ * 
+ * @param ctx RTMP上下文指针
+ * @return 返回当前的rtmp_state_t状态值
+ */
+rtmp_state_t rtmp_get_state(rtmp_ctx_t *ctx);
+
+/**
+ * 处理RTMP事件(需要定期调用)
+ * 
+ * 处理TCP连接事件、接收数据、超时检测等。
+ * 该函数应该在主循环或定时器中定期调用,建议间隔为10-50毫秒。
+ * 
+ * @param ctx RTMP上下文指针
+ * @return RTMP_OK表示正常,其他值表示发生错误
+ */
+int rtmp_poll(rtmp_ctx_t *ctx);
+
+/**
+ * 设置用户自定义数据
+ * 
+ * 可用于关联用户的上下文信息,在回调函数中可以通过
+ * rtmp_get_user_data获取。
+ * 
+ * @param ctx RTMP上下文指针
+ * @param user_data 用户数据指针
+ * @return RTMP_OK表示成功
+ */
+int rtmp_set_user_data(rtmp_ctx_t *ctx, void *user_data);
+
+/**
+ * 获取用户自定义数据
+ * 
+ * @param ctx RTMP上下文指针
+ * @return 返回用户设置的数据指针,未设置则返回NULL
+ */
+void* rtmp_get_user_data(rtmp_ctx_t *ctx);
+
+/**
+ * 获取统计信息
+ * 
+ * 获取RTMP连接的实时统计数据,包括字节数、帧数、连接时长等。
+ * 该函数可以在任何时刻调用以查询当前的推流统计信息。
+ * 
+ * @param ctx RTMP上下文指针
+ * @param stats 指向rtmp_stats_t结构体的指针,用于返回统计信息
+ * @return RTMP_OK表示成功,其他值表示失败
+ *         - RTMP_ERR_INVALID_PARAM: ctx或stats参数为NULL
+ * 
+ * 使用示例:
+ * @code
+ * rtmp_stats_t stats;
+ * if (rtmp_get_stats(ctx, &stats) == RTMP_OK) {
+ *     printf("已发送: %u 字节, %u 视频帧\n", 
+ *            stats.bytes_sent, stats.video_frames_sent);
+ * }
+ * @endcode
+ */
+int rtmp_get_stats(rtmp_ctx_t *ctx, rtmp_stats_t *stats);
+
+/* ======================== 回调函数定义 ======================== */
+
+/**
+ * RTMP连接状态变化回调函数类型
+ * 
+ * @param ctx RTMP上下文指针
+ * @param old_state 旧状态
+ * @param new_state 新状态
+ * @param error_code 如果是ERROR状态,此参数表示错误码
+ */
+typedef void (*rtmp_state_callback)(rtmp_ctx_t *ctx, rtmp_state_t old_state, 
+                                    rtmp_state_t new_state, int error_code);
+
+/**
+ * 设置状态变化回调函数
+ * 
+ * 当RTMP连接状态发生变化时会调用此回调函数。
+ * 
+ * @param ctx RTMP上下文指针
+ * @param callback 回调函数指针,传NULL则禁用回调
+ * @return RTMP_OK表示成功
+ */
+int rtmp_set_state_callback(rtmp_ctx_t *ctx, rtmp_state_callback callback);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LUAT_RTMP_PUSH_H__ */

+ 2516 - 0
components/rtmp/src/luat_rtmp_push.c

@@ -0,0 +1,2516 @@
+/**
+ * @file luat_rtmp_push.c
+ * @brief RTMP推流组件实现 - 基于lwip raw API
+ * @author LuatOS Team
+ * 
+ * 实现了RTMP协议的核心功能,包括:
+ * - TCP连接管理
+ * - RTMP握手流程
+ * - AMF数据序列化
+ * - FLV视频数据打包
+ * - 网络数据收发
+ */
+
+#include "luat_rtmp_push.h"
+#include "luat_debug.h"
+#include "luat_mcu.h"
+#include "luat_mem.h"
+#include "luat_rtos.h"
+#include "lwip/tcp.h"
+#include "lwip/tcpip.h"
+#include "lwip/timeouts.h"
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#define LUAT_LOG_TAG "rtmp_push"
+#include "luat_log.h"
+
+/* ======================== 调试开关 ======================== */
+
+/** 启用详细调试日志 (0=关闭, 1=开启) */
+#define RTMP_DEBUG_VERBOSE 0
+
+#if RTMP_DEBUG_VERBOSE
+    #define RTMP_LOGV(...) LLOGD(__VA_ARGS__)
+#else
+    #define RTMP_LOGV(...)
+#endif
+
+/* ======================== 内部常量定义 ======================== */
+
+/** RTMP握手客户端数据大小 */
+#define RTMP_HANDSHAKE_CLIENT_SIZE 1536
+
+/** RTMP命令端口 */
+#define RTMP_DEFAULT_PORT 1935
+
+/** AMF数据类型 */
+#define AMF_TYPE_NUMBER 0x00
+#define AMF_TYPE_BOOLEAN 0x01
+#define AMF_TYPE_STRING 0x02
+#define AMF_TYPE_OBJECT 0x03
+#define AMF_TYPE_NULL 0x05
+#define AMF_TYPE_OBJECT_END 0x09
+
+/* ======================== 内部函数声明 ======================== */
+
+/**
+ * 解析URL并提取主机名、端口、应用名和流名
+ */
+static int rtmp_parse_url(rtmp_ctx_t *ctx, const char *url);
+
+/**
+ * TCP连接回调函数
+ */
+static err_t rtmp_tcp_connect_callback(void *arg, struct tcp_pcb *pcb, err_t err);
+
+/**
+ * TCP接收回调函数
+ */
+static err_t rtmp_tcp_recv_callback(void *arg, struct tcp_pcb *pcb, 
+                                   struct pbuf *p, err_t err);
+
+/**
+ * TCP错误回调函数
+ */
+static void rtmp_tcp_error_callback(void *arg, err_t err);
+
+/**
+ * TCP发送回调函数
+ */
+static err_t rtmp_tcp_sent_callback(void *arg, struct tcp_pcb *pcb, u16_t len);
+
+/**
+ * 执行RTMP握手
+ */
+static int rtmp_do_handshake(rtmp_ctx_t *ctx);
+
+/**
+ * 处理握手响应
+ */
+static int rtmp_process_handshake_response(rtmp_ctx_t *ctx);
+
+/**
+ * 发送RTMP命令
+ */
+static int rtmp_send_command(rtmp_ctx_t *ctx, const char *command, 
+                            uint32_t transaction_id, const char *args);
+
+/**
+ * 处理收到的RTMP数据
+ */
+static int rtmp_process_data(rtmp_ctx_t *ctx);
+
+/**
+ * 获取NALU类型
+ */
+static nalu_type_t rtmp_get_nalu_type(const uint8_t *nalu_data, uint32_t nalu_len);
+
+/**
+ * 检查是否为关键帧(IDR)
+ */
+static bool rtmp_is_key_frame(const uint8_t *nalu_data, uint32_t nalu_len);
+
+/**
+ * 打包FLV消息头
+ */
+static uint32_t rtmp_pack_flv_header(uint8_t *buffer, uint32_t buffer_len,
+                                     uint8_t msg_type, uint32_t msg_len,
+                                     uint32_t timestamp, uint32_t stream_id);
+
+/**
+ * 打包RTMP消息(包含FLV头和块打包)
+ */
+static int rtmp_pack_message(rtmp_ctx_t *ctx, uint8_t msg_type,
+                            const uint8_t *payload, uint32_t payload_len,
+                            uint32_t timestamp, uint32_t stream_id);
+
+/**
+ * 打包FLV视频标签
+ */
+static int rtmp_pack_video_tag(uint8_t *buffer, uint32_t buffer_len,
+                              const uint8_t *video_data, uint32_t video_len,
+                              bool is_key_frame);
+
+/**
+ * 帧发送队列节点
+ */
+typedef struct rtmp_frame_node {
+    uint8_t *data;              /* 完整RTMP消息(包含chunk头) */
+    uint32_t len;               /* 消息总长度 */
+    uint32_t sent;              /* 已发送字节数 */
+    bool is_key;                /* 是否关键帧 */
+    struct rtmp_frame_node *next;
+} rtmp_frame_node_t;
+
+/**
+ * 发送发出的缓冲数据
+ */
+static int rtmp_flush_send_buffer(rtmp_ctx_t *ctx);
+
+/**
+ * 更新状态
+ */
+static void rtmp_set_state(rtmp_ctx_t *ctx, rtmp_state_t new_state, int error_code);
+
+/**
+ * 发送单个NALU单元(内部函数)
+ * 支持大数据帧(最大300KB+)
+ */
+static int rtmp_send_single_nalu(rtmp_ctx_t *ctx, const uint8_t *nalu_data,
+                                uint32_t nalu_len, uint32_t timestamp);
+
+/**
+ * 发送AVC Sequence Header (SPS+PPS配置数据)
+ */
+static int rtmp_send_avc_sequence_header(rtmp_ctx_t *ctx, const uint8_t *seq_header,
+                                        uint32_t seq_len, uint32_t timestamp);
+static int rtmp_build_rtmp_message(rtmp_ctx_t *ctx, uint8_t msg_type,
+                                  const uint8_t *payload, uint32_t payload_len,
+                                  uint32_t timestamp, uint32_t stream_id,
+                                  uint8_t **out_buf, uint32_t *out_len);
+static void rtmp_free_frame_node(rtmp_frame_node_t *node);
+static int rtmp_queue_frame(rtmp_ctx_t *ctx, rtmp_frame_node_t *node);
+static void rtmp_try_send_queue(rtmp_ctx_t *ctx);
+
+/**
+ * 生成随机时间戳
+ */
+static uint32_t rtmp_gen_timestamp(void);
+
+/* ======================== 全局状态回调 ======================== */
+
+static rtmp_state_callback g_state_callback = NULL;
+
+/* ======================== 工具函数 ======================== */
+
+/**
+ * 大端字节序写入
+ */
+static inline void write_be32(uint8_t *buf, uint32_t val) {
+    buf[0] = (val >> 24) & 0xFF;
+    buf[1] = (val >> 16) & 0xFF;
+    buf[2] = (val >> 8) & 0xFF;
+    buf[3] = val & 0xFF;
+}
+
+/**
+ * 大端字节序写入16位
+ */
+static inline void write_be16(uint8_t *buf, uint16_t val) {
+    buf[0] = (val >> 8) & 0xFF;
+    buf[1] = val & 0xFF;
+}
+
+/**
+ * 大端字节序读取
+ */
+static inline uint32_t read_be32(const uint8_t *buf) {
+    return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | 
+           ((uint32_t)buf[2] << 8) | (uint32_t)buf[3];
+}
+
+/**
+ * 大端字节序读取16位
+ */
+static inline uint16_t read_be16(const uint8_t *buf) {
+    return ((uint16_t)buf[0] << 8) | (uint16_t)buf[1];
+}
+
+/**
+ * 写入AMF字符串
+ */
+static uint32_t rtmp_write_amf_string(uint8_t *buf, uint32_t buf_len, 
+                                     const char *str) {
+    uint32_t str_len = strlen(str);
+    if (buf_len < str_len + 2) {
+        return 0;
+    }
+    
+    write_be16(buf, (uint16_t)str_len);
+    if (str_len > 0) {
+        memcpy(buf + 2, str, str_len);
+    }
+    return str_len + 2;
+}
+
+/**
+ * 写入AMF数字
+ */
+static uint32_t rtmp_write_amf_number(uint8_t *buf, uint32_t buf_len, 
+                                     double num) {
+    if (buf_len < 9) {
+        return 0;
+    }
+    
+    buf[0] = AMF_TYPE_NUMBER;
+    uint64_t bits = *(uint64_t *)&num;
+    for (int i = 0; i < 8; i++) {
+        buf[8 - i] = (uint8_t)(bits >> (i * 8));
+    }
+    return 9;
+}
+
+/**
+ * 写入AMF对象
+ */
+static uint32_t rtmp_write_amf_object_end(uint8_t *buf, uint32_t buf_len) {
+    if (buf_len < 3) {
+        return 0;
+    }
+    
+    buf[0] = 0x00;
+    buf[1] = 0x00;
+    buf[2] = AMF_TYPE_OBJECT_END;
+    return 3;
+}
+
+/**
+ * 打包FLV消息头
+ * 
+ * FLV消息头格式(11字节):
+ * - 消息类型 (1字节): 8=音频, 9=视频, 18=数据
+ * - 消息长度 (3字节大端): 负载长度
+ * - 时间戳 (3字节大端): 毫秒
+ * - 时间戳扩展 (1字节): 时间戳的最高字节
+ * - 流ID (3字节大端): 通常为1
+ */
+static uint32_t rtmp_pack_flv_header(uint8_t *buffer, uint32_t buffer_len,
+                                     uint8_t msg_type, uint32_t msg_len,
+                                     uint32_t timestamp, uint32_t stream_id) {
+    if (buffer_len < 11) {
+        return 0;
+    }
+    
+    uint32_t offset = 0;
+    
+    /* 消息类型 */
+    buffer[offset++] = msg_type;
+    
+    /* 消息长度 (3字节大端) */
+    buffer[offset++] = (msg_len >> 16) & 0xFF;
+    buffer[offset++] = (msg_len >> 8) & 0xFF;
+    buffer[offset++] = msg_len & 0xFF;
+    
+    /* 时间戳 (3字节大端) */
+    buffer[offset++] = (timestamp >> 16) & 0xFF;
+    buffer[offset++] = (timestamp >> 8) & 0xFF;
+    buffer[offset++] = timestamp & 0xFF;
+    
+    /* 时间戳扩展 (1字节,用于时间戳超过24bit的情况) */
+    buffer[offset++] = (timestamp >> 24) & 0xFF;
+    
+    /* 流ID (3字节大端) */
+    buffer[offset++] = (stream_id >> 16) & 0xFF;
+    buffer[offset++] = (stream_id >> 8) & 0xFF;
+    buffer[offset++] = stream_id & 0xFF;
+    
+    return offset;
+}
+
+/**
+ * 打包RTMP块和消息头
+ * 
+ * RTMP块格式:
+ * - 块头 (1-3字节):
+ *   - 格式 (2bit): 0/1/2/3
+ *   - 块流ID (6bit) 或 (8bit) 或 (16bit)
+ * - 消息头 (0/3/7/11字节): 取决于块格式
+ * - 块数据: 最多out_chunk_size字节
+ */
+static int rtmp_pack_message(rtmp_ctx_t *ctx, uint8_t msg_type,
+                            const uint8_t *payload, uint32_t payload_len,
+                            uint32_t timestamp, uint32_t stream_id) {
+    if (!ctx || !payload || payload_len == 0) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    if (!ctx->pcb) {
+        return RTMP_ERR_FAILED;
+    }
+    
+    /* RTMP 块格式构建
+     * 
+     * RTMP 消息格式:
+     * [块头 (1-3字节)] + [消息头 (0/3/7/11字节)] + [消息体]
+     * 
+     * 块头格式:
+     * - 格式 (2bit): 使用0(完整消息头,包含所有信息)
+     * - 块流ID (6bit): 使用0-63之间的值,这里用3表示命令流,4表示视频流
+     * 
+     * 消息头格式(格式0,11字节):
+     * - 时间戳 (3字节大端)
+     * - 消息长度 (3字节大端)
+     * - 消息类型 (1字节)
+     * - 流ID (4字节小端)
+     */
+    
+    uint8_t chunk_header[12];  /* 1字节块头 + 11字节消息头 */
+    uint32_t chunk_header_len = 0;
+    
+    /* 块头:格式0 + 块流ID
+     * 格式0表示完整消息头(11字节)
+     * 块流ID:3用于命令,4用于视频流
+     */
+    uint8_t chunk_stream_id = (msg_type == 20 || msg_type == 17) ? 3 : 4;
+    uint8_t fmt = 0;  /* 格式0:完整消息头 */
+    
+    chunk_header[chunk_header_len++] = (fmt << 6) | (chunk_stream_id & 0x3F);
+    
+    /* 消息头(11字节,格式0)
+     * 注意:这里应该是完整的消息头,不是FLV头!
+     */
+    
+    /* 时间戳 (3字节大端) */
+    chunk_header[chunk_header_len++] = (timestamp >> 16) & 0xFF;
+    chunk_header[chunk_header_len++] = (timestamp >> 8) & 0xFF;
+    chunk_header[chunk_header_len++] = timestamp & 0xFF;
+    
+    /* 消息长度 (3字节大端) */
+    chunk_header[chunk_header_len++] = (payload_len >> 16) & 0xFF;
+    chunk_header[chunk_header_len++] = (payload_len >> 8) & 0xFF;
+    chunk_header[chunk_header_len++] = payload_len & 0xFF;
+    
+    /* 消息类型 (1字节) */
+    chunk_header[chunk_header_len++] = msg_type;
+    
+    /* 流ID (4字节小端) */
+    chunk_header[chunk_header_len++] = stream_id & 0xFF;
+    chunk_header[chunk_header_len++] = (stream_id >> 8) & 0xFF;
+    chunk_header[chunk_header_len++] = (stream_id >> 16) & 0xFF;
+    chunk_header[chunk_header_len++] = (stream_id >> 24) & 0xFF;
+    
+    if (chunk_header_len != 12) {
+        LLOGE("RTMP: Invalid chunk header length: %d", chunk_header_len);
+        return RTMP_ERR_FAILED;
+    }
+    
+    /* RTMP分块发送支持
+     * 对于大消息,需要按照chunk_size分块发送
+     * 每个块的格式:
+     * - 第一个块:完整的块头(1字节) + 消息头(11字节) + 数据
+     * - 后续块:简化块头(1字节,格式3) + 数据
+     */
+    
+    uint32_t chunk_size = ctx->chunk_size;
+    uint32_t bytes_sent = 0;
+    
+    /* 发送第一个块(带完整消息头)*/
+    uint32_t first_chunk_data = (payload_len < chunk_size) ? payload_len : chunk_size;
+    uint32_t first_chunk_total = chunk_header_len + first_chunk_data;
+    
+    /* 检查缓冲区空间 */
+    if (ctx->send_pos + first_chunk_total > ctx->send_buf_size) {
+        int ret = rtmp_flush_send_buffer(ctx);
+        if (ret != RTMP_OK) {
+            return ret;
+        }
+        
+        /* 如果单个块都放不下,使用临时缓冲区 */
+        if (first_chunk_total > ctx->send_buf_size) {
+            /* 将块头和数据临时放入缓冲区 */
+            memcpy(&ctx->send_buf[0], chunk_header, chunk_header_len);
+            uint32_t copy_size = (first_chunk_data < ctx->send_buf_size - chunk_header_len) ? 
+                                 first_chunk_data : (ctx->send_buf_size - chunk_header_len);
+            memcpy(&ctx->send_buf[chunk_header_len], payload, copy_size);
+            ctx->send_pos = chunk_header_len + copy_size;
+            
+            /* 发送缓冲区数据 */
+            int ret = rtmp_flush_send_buffer(ctx);
+            if (ret != RTMP_OK) {
+                return ret;
+            }
+            
+            bytes_sent = copy_size;
+        } else {
+            /* 放入缓冲区 */
+            memcpy(&ctx->send_buf[ctx->send_pos], chunk_header, chunk_header_len);
+            ctx->send_pos += chunk_header_len;
+            memcpy(&ctx->send_buf[ctx->send_pos], payload, first_chunk_data);
+            ctx->send_pos += first_chunk_data;
+            bytes_sent = first_chunk_data;
+        }
+    } else {
+        /* 正常情况:放入缓冲区 */
+        memcpy(&ctx->send_buf[ctx->send_pos], chunk_header, chunk_header_len);
+        ctx->send_pos += chunk_header_len;
+        memcpy(&ctx->send_buf[ctx->send_pos], payload, first_chunk_data);
+        ctx->send_pos += first_chunk_data;
+        bytes_sent = first_chunk_data;
+    }
+    
+    /* 发送后续块(如果有)*/
+    while (bytes_sent < payload_len) {
+        uint32_t remaining = payload_len - bytes_sent;
+        uint32_t chunk_data_size = (remaining < chunk_size) ? remaining : chunk_size;
+        
+        /* 格式3的块头:只有1字节,表示延续前一个消息 */
+        uint8_t continuation_header = (3 << 6) | (chunk_stream_id & 0x3F);
+        
+        uint32_t chunk_total = 1 + chunk_data_size;
+        
+        /* 检查缓冲区空间 */
+        if (ctx->send_pos + chunk_total > ctx->send_buf_size) {
+            int ret = rtmp_flush_send_buffer(ctx);
+            if (ret != RTMP_OK) {
+                return ret;
+            }
+            
+            /* 如果缓冲区还是放不下,使用临时缓冲区 */
+            if (chunk_total > ctx->send_buf_size) {
+                ctx->send_buf[0] = continuation_header;
+                uint32_t copy_size = (chunk_data_size < ctx->send_buf_size - 1) ? 
+                                     chunk_data_size : (ctx->send_buf_size - 1);
+                memcpy(&ctx->send_buf[1], &payload[bytes_sent], copy_size);
+                ctx->send_pos = 1 + copy_size;
+                
+                int ret = rtmp_flush_send_buffer(ctx);
+                if (ret != RTMP_OK) {
+                    return ret;
+                }
+            } else {
+                /* 放入缓冲区 */
+                ctx->send_buf[ctx->send_pos++] = continuation_header;
+                memcpy(&ctx->send_buf[ctx->send_pos], &payload[bytes_sent], chunk_data_size);
+                ctx->send_pos += chunk_data_size;
+            }
+        } else {
+            /* 正常情况:放入缓冲区 */
+            ctx->send_buf[ctx->send_pos++] = continuation_header;
+            memcpy(&ctx->send_buf[ctx->send_pos], &payload[bytes_sent], chunk_data_size);
+            ctx->send_pos += chunk_data_size;
+        }
+        
+        bytes_sent += chunk_data_size;
+    }
+    
+    return RTMP_OK;
+}
+
+/* ======================== 核心实现函数 ======================== */
+
+/**
+ * 创建RTMP推流上下文
+ */
+rtmp_ctx_t *g_rtmp_ctx;
+
+rtmp_ctx_t* rtmp_create(void) {
+    rtmp_ctx_t *ctx = (rtmp_ctx_t *)luat_heap_malloc(sizeof(rtmp_ctx_t));
+    if (!ctx) {
+        LLOGE("RTMP: Failed to allocate context");
+        return NULL;
+    }
+    
+    memset(ctx, 0, sizeof(rtmp_ctx_t));
+    
+    /* 初始化chunk大小 */
+    ctx->chunk_size = RTMP_DEFAULT_CHUNK_SIZE;
+    
+    /* 初始化缓冲区 */
+    ctx->recv_buf = (uint8_t *)luat_heap_malloc(RTMP_BUFFER_SIZE);
+    ctx->send_buf = (uint8_t *)luat_heap_malloc(RTMP_BUFFER_SIZE);
+    
+    if (!ctx->recv_buf || !ctx->send_buf) {
+        LLOGE("RTMP: Failed to allocate buffers");
+        if (ctx->recv_buf) luat_heap_free(ctx->recv_buf);
+        if (ctx->send_buf) luat_heap_free(ctx->send_buf);
+        luat_heap_free(ctx);
+        return NULL;
+    }
+    
+    ctx->recv_buf_size = RTMP_BUFFER_SIZE;
+    ctx->send_buf_size = RTMP_BUFFER_SIZE;
+    ctx->recv_pos = 0;
+    ctx->send_pos = 0;
+    
+    /* 初始化默认块大小 */
+    ctx->in_chunk_size = RTMP_DEFAULT_CHUNK_SIZE;
+    ctx->out_chunk_size = RTMP_DEFAULT_CHUNK_SIZE;
+    
+    /* 初始化流ID */
+    ctx->video_stream_id = 1;
+    ctx->audio_stream_id = 1;
+    
+    /* 初始化端口 */
+    ctx->port = RTMP_DEFAULT_PORT;
+    
+    /* 初始化状态 */
+    ctx->state = RTMP_STATE_IDLE;
+    
+    RTMP_LOGV("RTMP: Context created successfully");
+    g_rtmp_ctx = ctx;
+    return ctx;
+}
+
+/**
+ * 销毁RTMP推流上下文
+ */
+int rtmp_destroy(rtmp_ctx_t *ctx) {
+    if (!ctx) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    g_rtmp_ctx = NULL;
+    
+    /* 断开TCP连接 */
+    if (ctx->pcb) {
+        tcp_arg(ctx->pcb, NULL);
+        tcp_recv(ctx->pcb, NULL);
+        tcp_err(ctx->pcb, NULL);
+        tcp_sent(ctx->pcb, NULL);
+        tcp_close(ctx->pcb);
+        ctx->pcb = NULL;
+    }
+    
+    /* 释放内存 */
+    if (ctx->url) luat_heap_free(ctx->url);
+    if (ctx->host) luat_heap_free(ctx->host);
+    if (ctx->app) luat_heap_free(ctx->app);
+    if (ctx->stream) luat_heap_free(ctx->stream);
+    if (ctx->auth) luat_heap_free(ctx->auth);
+    if (ctx->recv_buf) luat_heap_free(ctx->recv_buf);
+    if (ctx->send_buf) luat_heap_free(ctx->send_buf);
+
+    /* 释放未发送的帧队列 */
+    rtmp_frame_node_t *cur = ctx->frame_head;
+    while (cur) {
+        rtmp_frame_node_t *next = cur->next;
+        rtmp_free_frame_node(cur);
+        cur = next;
+    }
+    
+    luat_heap_free(ctx);
+    
+    RTMP_LOGV("RTMP: Context destroyed");
+    return RTMP_OK;
+}
+
+/**
+ * 设置RTMP服务器URL
+ */
+int rtmp_set_url(rtmp_ctx_t *ctx, const char *url) {
+    if (!ctx || !url) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    if (ctx->state != RTMP_STATE_IDLE && ctx->state != RTMP_STATE_ERROR) {
+        LLOGE("RTMP: Cannot set URL while connected");
+        return RTMP_ERR_FAILED;
+    }
+    
+    return rtmp_parse_url(ctx, url);
+}
+
+/**
+ * 连接到RTMP服务器
+ */
+int rtmp_connect(rtmp_ctx_t *ctx) {
+    if (!ctx) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    if (!ctx->host || !ctx->app || !ctx->stream) {
+        LLOGE("RTMP: URL not set before connect");
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    /* 创建TCP控制块 */
+    ctx->pcb = tcp_new();
+    if (!ctx->pcb) {
+        LLOGE("RTMP: Failed to create TCP PCB");
+        return RTMP_ERR_NO_MEMORY;
+    }
+    
+    tcp_arg(ctx->pcb, (void *)ctx);
+    tcp_recv(ctx->pcb, rtmp_tcp_recv_callback);
+    tcp_err(ctx->pcb, rtmp_tcp_error_callback);
+    tcp_sent(ctx->pcb, rtmp_tcp_sent_callback);
+    
+    /* 将IP地址字符串转换为lwip ip_addr结构体 */
+    ip_addr_t remote_addr;
+    err_t parse_ret = ipaddr_aton(ctx->host, &remote_addr);
+    
+    if (!parse_ret) {
+        LLOGE("RTMP: Invalid IP address: %s", ctx->host);
+        tcp_close(ctx->pcb);
+        ctx->pcb = NULL;
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    rtmp_set_state(ctx, RTMP_STATE_CONNECTING, 0);
+    
+    /* 发起TCP连接 */
+    err_t err = tcp_connect(ctx->pcb, &remote_addr, ctx->port, rtmp_tcp_connect_callback);
+    
+    if (err != ERR_OK) {
+        LLOGE("RTMP: TCP connect failed: %d", err);
+        rtmp_set_state(ctx, RTMP_STATE_ERROR, RTMP_ERR_CONNECT_FAILED);
+        tcp_close(ctx->pcb);
+        ctx->pcb = NULL;
+        return RTMP_ERR_CONNECT_FAILED;
+    }
+    
+    ctx->last_activity_time = rtmp_gen_timestamp();
+    RTMP_LOGV("RTMP: Connecting to %s:%d (app:%s, stream:%s)", ctx->host, ctx->port, ctx->app, ctx->stream);
+    
+    return RTMP_OK;
+}
+
+/**
+ * 断开RTMP连接
+ */
+int rtmp_disconnect(rtmp_ctx_t *ctx) {
+    if (!ctx) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    if (ctx->pcb) {
+        rtmp_set_state(ctx, RTMP_STATE_DISCONNECTING, 0);
+        tcp_close(ctx->pcb);
+        ctx->pcb = NULL;
+    }
+    
+    rtmp_set_state(ctx, RTMP_STATE_IDLE, 0);
+    
+    return RTMP_OK;
+}
+
+/**
+ * 发送H.264 NALU帧
+ */
+int rtmp_send_nalu(rtmp_ctx_t *ctx, const uint8_t *nalu_data,
+                   uint32_t nalu_len, uint32_t timestamp) {
+    if (!ctx || !nalu_data || nalu_len == 0) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    if (ctx->state != RTMP_STATE_PUBLISHING) {
+        return RTMP_ERR_FAILED;
+    }
+    
+    /* 分析输入数据,提取所有NALU单元
+     * 格式: [起始码(0x00000001)] [NALU数据] [起始码] [NALU数据] ...
+     * 对于I帧: 通常包含 SPS + PPS + IDR
+     * 对于P帧: 只包含 P帧数据
+     */
+    
+    typedef struct {
+        const uint8_t *data;
+        uint32_t len;
+        uint8_t type;
+    } nalu_info_t;
+    
+    nalu_info_t nalus[16];  /* 支持最多16个NALU */
+    uint32_t nalu_count = 0;
+    
+    /* 扫描所有NALU单元 */
+    uint32_t pos = 0;
+    while (pos < nalu_len && nalu_count < 16) {
+        /* 查找起始码 0x00000001 */
+        if (pos + 4 <= nalu_len && 
+            nalu_data[pos] == 0x00 && nalu_data[pos+1] == 0x00 && 
+            nalu_data[pos+2] == 0x00 && nalu_data[pos+3] == 0x01) {
+            
+            uint32_t nalu_start = pos + 4;  /* 跳过起始码 */
+            
+            /* 查找下一个起始码或数据末尾 */
+            uint32_t nalu_end = nalu_len;
+            for (uint32_t i = nalu_start + 1; i + 3 < nalu_len; i++) {
+                if (nalu_data[i] == 0x00 && nalu_data[i+1] == 0x00 && 
+                    nalu_data[i+2] == 0x00 && nalu_data[i+3] == 0x01) {
+                    nalu_end = i;
+                    break;
+                }
+            }
+            
+            uint32_t current_nalu_len = nalu_end - nalu_start;
+            
+            if (current_nalu_len > 0 && nalu_start < nalu_len) {
+                nalus[nalu_count].data = &nalu_data[nalu_start];
+                nalus[nalu_count].len = current_nalu_len;
+                nalus[nalu_count].type = nalu_data[nalu_start] & 0x1F;
+                nalu_count++;
+            }
+            
+            pos = nalu_end;
+        } else {
+            pos++;
+        }
+    }
+    
+    if (nalu_count == 0) {
+        LLOGE("RTMP: No valid NALU found in input data");
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    /* 查找SPS、PPS和IDR */
+    const uint8_t *sps_data = NULL, *pps_data = NULL, *idr_data = NULL;
+    uint32_t sps_len = 0, pps_len = 0, idr_len = 0;
+    bool has_p_frame = false;
+    
+    for (uint32_t i = 0; i < nalu_count; i++) {
+        switch (nalus[i].type) {
+            case NALU_TYPE_SPS:
+                sps_data = nalus[i].data;
+                sps_len = nalus[i].len;
+                break;
+            case NALU_TYPE_PPS:
+                pps_data = nalus[i].data;
+                pps_len = nalus[i].len;
+                break;
+            case NALU_TYPE_IDR:
+                idr_data = nalus[i].data;
+                idr_len = nalus[i].len;
+                break;
+            case NALU_TYPE_NON_IDR:
+                has_p_frame = true;
+                break;
+        }
+    }
+    
+    /* 如果有SPS和PPS,发送AVC Sequence Header */
+    if (sps_data && pps_data) {
+        /* 构建AVC Sequence Header
+         * 格式: [configurationVersion(1)] [AVCProfileIndication(1)] [profile_compatibility(1)] 
+         *       [AVCLevelIndication(1)] [lengthSizeMinusOne(1)] [numOfSPS(1)] 
+         *       [spsLength(2)] [SPS data] [numOfPPS(1)] [ppsLength(2)] [PPS data]
+         */
+        uint32_t seq_header_size = 5 + 1 + 2 + sps_len + 1 + 2 + pps_len;
+        uint8_t *seq_header = (uint8_t *)luat_heap_malloc(seq_header_size);
+        if (!seq_header) {
+            LLOGE("RTMP: Failed to allocate AVC sequence header");
+            return RTMP_ERR_NO_MEMORY;
+        }
+        
+        uint32_t offset = 0;
+        seq_header[offset++] = 0x01;  /* configurationVersion */
+        seq_header[offset++] = sps_data[1];  /* AVCProfileIndication */
+        seq_header[offset++] = sps_data[2];  /* profile_compatibility */
+        seq_header[offset++] = sps_data[3];  /* AVCLevelIndication */
+        seq_header[offset++] = 0xFF;  /* lengthSizeMinusOne (4字节长度) */
+        
+        /* SPS */
+        seq_header[offset++] = 0xE1;  /* numOfSPS = 1 (高3位保留为111) */
+        write_be16(&seq_header[offset], sps_len);
+        offset += 2;
+        memcpy(&seq_header[offset], sps_data, sps_len);
+        offset += sps_len;
+        
+        /* PPS */
+        seq_header[offset++] = 0x01;  /* numOfPPS = 1 */
+        write_be16(&seq_header[offset], pps_len);
+        offset += 2;
+        memcpy(&seq_header[offset], pps_data, pps_len);
+        offset += pps_len;
+        
+        /* 发送AVC Sequence Header */
+        int ret = rtmp_send_avc_sequence_header(ctx, seq_header, seq_header_size, timestamp);
+        luat_heap_free(seq_header);
+        
+        if (ret != RTMP_OK) {
+            LLOGE("RTMP: Failed to send AVC sequence header");
+            return ret;
+        }
+    }
+    
+    /* 发送IDR帧(如果有)*/
+    if (idr_data) {
+        LLOGI("RTMP: Sending IDR frame, len=%u", idr_len);
+        int ret = rtmp_send_single_nalu(ctx, idr_data, idr_len, timestamp);
+        if (ret != RTMP_OK) {
+            return ret;
+        }
+    }
+    
+    /* 发送P帧(如果有)*/
+    if (has_p_frame) {
+        for (uint32_t i = 0; i < nalu_count; i++) {
+            if (nalus[i].type == NALU_TYPE_NON_IDR) {
+                int ret = rtmp_send_single_nalu(ctx, nalus[i].data, nalus[i].len, timestamp);
+                if (ret != RTMP_OK) {
+                    return ret;
+                }
+            }
+        }
+    }
+    
+    return RTMP_OK;
+}
+
+/**
+ * 发送AVC Sequence Header (SPS+PPS配置数据)
+ * 这是H.264流的配置信息,必须在发送视频数据前发送
+ */
+static int rtmp_send_avc_sequence_header(rtmp_ctx_t *ctx, const uint8_t *seq_header,
+                                        uint32_t seq_len, uint32_t timestamp) {
+    if (!ctx || !seq_header || seq_len == 0) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    /* 构建RTMP视频消息
+     * 格式: [FrameType+CodecID(1)] [AVCPacketType(1)] [CompositionTime(3)] [AVC Sequence Header]
+     */
+    uint32_t msg_len = 1 + 1 + 3 + seq_len;
+    uint8_t *msg_buf = (uint8_t *)luat_heap_malloc(msg_len);
+    if (!msg_buf) {
+        LLOGE("RTMP: Failed to allocate buffer for AVC sequence header");
+        return RTMP_ERR_NO_MEMORY;
+    }
+    
+    uint32_t offset = 0;
+    
+    /* FrameType(4bit) + CodecID(4bit)
+     * FrameType: 1=关键帧 (AVC Sequence Header作为关键帧发送)
+     * CodecID: 7=H.264
+     */
+    msg_buf[offset++] = (1 << 4) | 7;
+    
+    /* AVCPacketType
+     * 0 = AVC Sequence Header
+     * 1 = AVC NALU
+     * 2 = AVC End of Sequence
+     */
+    msg_buf[offset++] = 0;  /* AVC Sequence Header */
+    
+    /* CompositionTime (3字节,大端)
+     * 对于Sequence Header总是0
+     */
+    msg_buf[offset++] = 0;
+    msg_buf[offset++] = 0;
+    msg_buf[offset++] = 0;
+    
+    /* AVC Sequence Header数据 */
+    memcpy(&msg_buf[offset], seq_header, seq_len);
+    offset += seq_len;
+    
+    /* 发送消息 (消息类型9=视频, 流ID=1),放入帧队列 */
+    uint8_t *rtmp_buf = NULL;
+    uint32_t rtmp_len = 0;
+    int ret = rtmp_build_rtmp_message(ctx, 9, msg_buf, msg_len, timestamp, 1, &rtmp_buf, &rtmp_len);
+    luat_heap_free(msg_buf);
+    if (ret != RTMP_OK) {
+        LLOGE("RTMP: Failed to build RTMP message for AVC sequence header");
+        return ret;
+    }
+    
+    rtmp_frame_node_t *node = (rtmp_frame_node_t *)luat_heap_malloc(sizeof(rtmp_frame_node_t));
+    if (!node) {
+        luat_heap_free(rtmp_buf);
+        return RTMP_ERR_NO_MEMORY;
+    }
+    node->data = rtmp_buf;
+    node->len = rtmp_len;
+    node->sent = 0;
+    node->is_key = true; /* 配置按关键帧优先处理 */
+    node->next = NULL;
+    
+    ret = rtmp_queue_frame(ctx, node);
+    if (ret != RTMP_OK) {
+        rtmp_free_frame_node(node);
+        return ret;
+    }
+    
+    return RTMP_OK;
+}
+
+/**
+ * 发送单个NALU单元(内部函数)
+ * 支持大数据帧(最大300KB+)
+ */
+static int rtmp_send_single_nalu(rtmp_ctx_t *ctx, const uint8_t *nalu_data,
+                                uint32_t nalu_len, uint32_t timestamp) {
+    if (!ctx || !nalu_data || nalu_len == 0) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    /* 构建视频消息 - 支持大数据 */
+    uint32_t header_size = 11;  /* FLV标签头大小 */
+    uint8_t header_buf[11];
+    uint32_t header_len = 0;
+    
+    /* 获取NALU类型 */
+    uint8_t nal_type = nalu_data[0] & 0x1F;
+    
+    /* 检查是否为关键帧(IDR) */
+    bool is_key_frame = (nal_type == NALU_TYPE_IDR);
+    
+    /* 构建视频标签 */
+    /* 视频标签格式: 帧类型(4bit) + 编码ID(4bit) */
+    uint8_t tag_byte = 0;
+    tag_byte |= (is_key_frame ? 1 : 2) << 4;  /* 帧类型: 1=关键帧, 2=普通帧 */
+    tag_byte |= 7;                             /* 编码ID: 7=H.264 */
+    
+    header_buf[header_len++] = tag_byte;
+    
+    /* AVCPacketType: 1 = AVC NALU (视频数据) */
+    header_buf[header_len++] = 1;
+    
+    /* 添加CTS (Composition Time Stamp) - 3字节大端 */
+    header_buf[header_len++] = 0;
+    header_buf[header_len++] = 0;
+    header_buf[header_len++] = 0;
+    
+    /* 写入NALU长度(4字节大端) */
+    write_be32(&header_buf[header_len], nalu_len);
+    header_len += 4;
+    
+    /* 完整视频消息 = 头(11字节) + NALU数据 */
+    uint32_t total_msg_len = header_len + nalu_len;
+
+    uint8_t *msg_buf = (uint8_t *)luat_heap_malloc(total_msg_len);
+    if (!msg_buf) {
+        LLOGE("RTMP: Failed to allocate buffer for video message");
+        return RTMP_ERR_NO_MEMORY;
+    }
+
+    memcpy(msg_buf, header_buf, header_len);
+    memcpy(&msg_buf[header_len], nalu_data, nalu_len);
+
+    /* 构建完整RTMP消息并入队 */
+    uint8_t *rtmp_buf = NULL;
+    uint32_t rtmp_len = 0;
+    int ret = rtmp_build_rtmp_message(ctx, 9, msg_buf, total_msg_len, timestamp, 1, &rtmp_buf, &rtmp_len);
+    luat_heap_free(msg_buf);
+    if (ret != RTMP_OK) {
+        return ret;
+    }
+
+    rtmp_frame_node_t *node = (rtmp_frame_node_t *)luat_heap_malloc(sizeof(rtmp_frame_node_t));
+    if (!node) {
+        luat_heap_free(rtmp_buf);
+        return RTMP_ERR_NO_MEMORY;
+    }
+    node->data = rtmp_buf;
+    node->len = rtmp_len;
+    node->sent = 0;
+    node->is_key = is_key_frame;
+    node->next = NULL;
+
+    ret = rtmp_queue_frame(ctx, node);
+    if (ret != RTMP_OK) {
+        rtmp_free_frame_node(node);
+        return ret;
+    }
+
+    /* 更新统计 */
+    ctx->video_timestamp = timestamp;
+    ctx->packets_sent++;
+    ctx->bytes_sent += nalu_len;
+
+    return RTMP_OK;
+}
+
+/**
+ * 发送多个NALU帧
+ */
+int rtmp_send_nalu_multi(rtmp_ctx_t *ctx, const uint8_t **nalus,
+                         const uint32_t *lengths, uint32_t count,
+                         uint32_t timestamp) {
+    if (!ctx || !nalus || !lengths || count == 0) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    for (uint32_t i = 0; i < count; i++) {
+        int ret = rtmp_send_nalu(ctx, nalus[i], lengths[i], timestamp);
+        if (ret != RTMP_OK) {
+            return ret;
+        }
+    }
+    
+    return RTMP_OK;
+}
+
+/**
+ * 获取当前连接状态
+ */
+rtmp_state_t rtmp_get_state(rtmp_ctx_t *ctx) {
+    if (!ctx) {
+        return RTMP_STATE_ERROR;
+    }
+    return ctx->state;
+}
+
+/**
+ * 处理RTMP事件
+ */
+int rtmp_poll(rtmp_ctx_t *ctx) {
+    if (!ctx) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+
+    /* 优先尝试发送队列中的数据 */
+    rtmp_try_send_queue(ctx);
+    
+    /* 检查超时 */
+    uint32_t now = rtmp_gen_timestamp();
+    if (ctx->last_activity_time > 0 && 
+        (now - ctx->last_activity_time) > RTMP_CMD_TIMEOUT) {
+        
+        if (ctx->state == RTMP_STATE_CONNECTING || 
+            ctx->state == RTMP_STATE_HANDSHAKING) {
+            rtmp_set_state(ctx, RTMP_STATE_ERROR, RTMP_ERR_TIMEOUT);
+            return RTMP_ERR_TIMEOUT;
+        }
+    }
+    
+    /* 处理收到的数据 */
+    if (ctx->recv_pos > 0) {
+        int ret = rtmp_process_data(ctx);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+    
+    /* 发送缓冲的数据 */
+    /* 在CONNECTED和PUBLISHING状态下都可以发送数据 */
+    if (ctx->send_pos > 0 && (ctx->state == RTMP_STATE_PUBLISHING || 
+                              ctx->state == RTMP_STATE_CONNECTED)) {
+        int ret = rtmp_flush_send_buffer(ctx);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+    
+    return RTMP_OK;
+}
+
+/**
+ * 设置用户自定义数据
+ */
+int rtmp_set_user_data(rtmp_ctx_t *ctx, void *user_data) {
+    if (!ctx) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    ctx->user_data = user_data;
+    return RTMP_OK;
+}
+
+/**
+ * 获取用户自定义数据
+ */
+void* rtmp_get_user_data(rtmp_ctx_t *ctx) {
+    if (!ctx) {
+        return NULL;
+    }
+    return ctx->user_data;
+}
+
+/**
+ * 获取统计信息
+ */
+int rtmp_get_stats(rtmp_ctx_t *ctx, rtmp_stats_t *stats) {
+    if (!ctx || !stats) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    // 获取当前时间戳(毫秒),用于计算连接持续时间
+    uint32_t current_time = (uint32_t)(luat_mcu_tick64_ms());
+    
+    // 填充统计结构体
+    stats->bytes_sent = ctx->bytes_sent;
+    stats->packets_sent = ctx->packets_sent;
+    stats->video_frames_sent = (ctx->video_timestamp > 0) ? (ctx->video_timestamp / 33 + 1) : 0;  // 估计帧数(30fps约33ms)
+    stats->audio_frames_sent = 0;  // 当前仅支持视频
+    stats->last_video_timestamp = ctx->video_timestamp;
+    stats->last_audio_timestamp = ctx->audio_timestamp;
+    
+    // 计算连接持续时间
+    if (ctx->base_timestamp > 0 && current_time >= ctx->base_timestamp) {
+        stats->connection_time = current_time - ctx->base_timestamp;
+    } else {
+        stats->connection_time = 0;
+    }
+    
+    return RTMP_OK;
+}
+
+/**
+ * 设置状态变化回调函数
+ */
+int rtmp_set_state_callback(rtmp_ctx_t *ctx, rtmp_state_callback callback) {
+    if (!ctx) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    g_state_callback = callback;
+    return RTMP_OK;
+}
+
+/* ======================== 内部函数实现 ======================== */
+
+/**
+ * 解析URL并提取主机名、端口、应用名和流名
+ */
+static int rtmp_parse_url(rtmp_ctx_t *ctx, const char *url) {
+    if (!url || strncmp(url, "rtmp://", 7) != 0) {
+        LLOGE("RTMP: Invalid URL format %s", url ? url : "NULL");
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    const char *ptr = url + 7;
+    
+    /* 提取主机名和端口 */
+    char host[256] = {0};
+    uint32_t host_len = 0;
+    
+    while (*ptr && *ptr != '/' && *ptr != ':' && host_len < sizeof(host) - 1) {
+        host[host_len++] = *ptr++;
+    }
+    host[host_len] = '\0';
+    
+    /* 检查端口 */
+    ctx->port = RTMP_DEFAULT_PORT;
+    if (*ptr == ':') {
+        ptr++;
+        char port_str[10] = {0};
+        uint32_t port_len = 0;
+        
+        while (*ptr && isdigit((unsigned char)*ptr) && port_len < sizeof(port_str) - 1) {
+            port_str[port_len++] = *ptr++;
+        }
+        
+        if (port_len > 0) {
+            ctx->port = (uint16_t)atoi(port_str);
+        }
+    }
+    
+    /* 提取应用名和流名 */
+    if (*ptr != '/') {
+        LLOGE("RTMP: Invalid URL format, missing path");
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    ptr++;  /* 跳过 '/' */
+    
+    char app[256] = {0};
+    uint32_t app_len = 0;
+    
+    while (*ptr && *ptr != '/' && app_len < sizeof(app) - 1) {
+        app[app_len++] = *ptr++;
+    }
+    app[app_len] = '\0';
+    
+    if (app_len == 0) {
+        LLOGE("RTMP: Invalid URL format, missing app");
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    /* 提取流名 */
+    char stream[256] = {0};
+    uint32_t stream_len = 0;
+    
+    if (*ptr == '/') {
+        ptr++;
+        
+        while (*ptr && stream_len < sizeof(stream) - 1) {
+            stream[stream_len++] = *ptr++;
+        }
+    }
+    stream[stream_len] = '\0';
+    
+    if (stream_len == 0) {
+        LLOGE("RTMP: Invalid URL format, missing stream");
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    /* 保存URL组件 */
+    if (ctx->url) luat_heap_free(ctx->url);
+    ctx->url = (char *)luat_heap_malloc(strlen(url) + 1);
+    strcpy(ctx->url, url);
+    
+    if (ctx->host) luat_heap_free(ctx->host);
+    ctx->host = (char *)luat_heap_malloc(strlen(host) + 1);
+    strcpy(ctx->host, host);
+    
+    if (ctx->app) luat_heap_free(ctx->app);
+    ctx->app = (char *)luat_heap_malloc(strlen(app) + 1);
+    strcpy(ctx->app, app);
+    
+    if (ctx->stream) luat_heap_free(ctx->stream);
+    ctx->stream = (char *)luat_heap_malloc(strlen(stream) + 1);
+    strcpy(ctx->stream, stream);
+    
+    RTMP_LOGV("RTMP: URL parsed - host:%s, port:%d, app:%s, stream:%s",
+                     host, ctx->port, ctx->app, ctx->stream);
+    
+    return RTMP_OK;
+}
+
+/**
+ * TCP连接回调函数
+ */
+static err_t rtmp_tcp_connect_callback(void *arg, struct tcp_pcb *pcb, err_t err) {
+    rtmp_ctx_t *ctx = (rtmp_ctx_t *)arg;
+    
+    if (err != ERR_OK) {
+        LLOGE("RTMP: TCP connect failed: %d", err);
+        rtmp_set_state(ctx, RTMP_STATE_ERROR, RTMP_ERR_CONNECT_FAILED);
+        return err;
+    }
+    
+    RTMP_LOGV("RTMP: TCP connected %s:%d", ctx->host, ctx->port);
+    
+    /* 执行握手 */
+    int ret = rtmp_do_handshake(ctx);
+    if (ret != RTMP_OK) {
+        rtmp_set_state(ctx, RTMP_STATE_ERROR, ret);
+        return ERR_ABRT;
+    }
+    
+    rtmp_set_state(ctx, RTMP_STATE_HANDSHAKING, 0);
+    
+    return ERR_OK;
+}
+
+static void rtmp_send_connect(void *arg) {
+    rtmp_ctx_t *ctx = (rtmp_ctx_t *)arg;
+    LLOGI("RTMP: Received %u bytes after C2, handshake confirmed", ctx->recv_pos);
+    ctx->handshake_state = 3;
+    rtmp_set_state(ctx, RTMP_STATE_HANDSHAKING, 0);
+            
+    /* 发送RTMP connect命令 */
+    RTMP_LOGV("RTMP: Sending connect command...");
+    int ret = rtmp_send_command(ctx, "connect", 1, ctx->app);
+    if (ret == 0) {
+        rtmp_set_state(ctx, RTMP_STATE_CONNECTED, 0);
+        LLOGI("RTMP: Connect command sent successfully");
+        /* 握手状态机已完成,后续接收的数据是RTMP消息,需要处理 */
+        ctx->recv_pos = 0;
+    } else {
+        LLOGE("RTMP: Failed to send connect command");
+        rtmp_set_state(ctx, RTMP_STATE_ERROR, RTMP_ERR_FAILED);
+    }
+}
+
+/**
+ * TCP接收回调函数
+ */
+static err_t rtmp_tcp_recv_callback(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
+    rtmp_ctx_t *ctx = (rtmp_ctx_t *)arg;
+    if (arg == NULL) {
+        LLOGE("RTMP: TCP recv callback with NULL arg");
+        return ERR_ARG;
+    }
+    RTMP_LOGV("RTMP: TCP recv callback, err=%d, pbuf=%p len=%d", err, p, p ? p->tot_len : 0);
+    if (err != ERR_OK) {
+        LLOGE("RTMP: TCP recv error: %d", err);
+        rtmp_set_state(ctx, RTMP_STATE_ERROR, RTMP_ERR_NETWORK);
+        return ERR_OK;
+    }
+    
+    if (!p) {
+        LLOGW("RTMP: TCP connection closed");
+        rtmp_set_state(ctx, RTMP_STATE_IDLE, 0);
+        return ERR_OK;
+    }
+    
+    /* 将数据复制到接收缓冲区 */
+    uint32_t copy_len = (p->tot_len < (ctx->recv_buf_size - ctx->recv_pos)) ?
+                        p->tot_len : (ctx->recv_buf_size - ctx->recv_pos);
+    if (copy_len > 0) {
+        pbuf_copy_partial(p, &ctx->recv_buf[ctx->recv_pos], copy_len, 0);
+        
+        ctx->recv_pos += copy_len;
+    }
+    
+    RTMP_LOGV("RTMP: Received %d bytes, p->tot_len=%d", copy_len, p->tot_len);
+    tcp_recved(pcb, p->tot_len);
+    pbuf_free(p);
+    
+    ctx->last_activity_time = rtmp_gen_timestamp();
+    
+    /* 处理握手 */
+    if (ctx->handshake_state == 1) {
+        /* 等待接收完整的 S0+S1 (1+1536 = 1537 字节) */
+        uint32_t required_len = 1 + RTMP_HANDSHAKE_CLIENT_SIZE;
+        
+        if (ctx->recv_pos >= required_len) {
+            /* 验证S0版本号 */
+            if (ctx->recv_buf[0] != 0x03) {
+                LLOGE("RTMP: Invalid RTMP version from server: %d", ctx->recv_buf[0]);
+                rtmp_set_state(ctx, RTMP_STATE_ERROR, RTMP_ERR_HANDSHAKE_FAILED);
+                return ERR_ABRT;
+            }
+            
+            /* 已收到完整的S0+S1,现在发送C2 */
+            /* C2 是 S1 的完整 1536 字节回显 */
+            LLOGI("RTMP: Received complete S0+S1 (%u bytes), sending C2...", required_len);
+            
+            err_t err = tcp_write(ctx->pcb, &ctx->recv_buf[1], RTMP_HANDSHAKE_CLIENT_SIZE, TCP_WRITE_FLAG_COPY);
+            
+            if (err != ERR_OK) {
+                LLOGE("RTMP: Failed to send C2: %d", err);
+                rtmp_set_state(ctx, RTMP_STATE_ERROR, RTMP_ERR_NETWORK);
+                return ERR_ABRT;
+            }
+            
+            tcp_output(ctx->pcb);
+            
+            LLOGI("RTMP: C2 sent successfully (exactly %u bytes, S1 echo)", RTMP_HANDSHAKE_CLIENT_SIZE);
+            
+            /* 握手状态转为2,等待握手完全完成或后续RTMP数据 */
+            ctx->handshake_state = 2;
+            
+            /* 移除已处理的握手数据,保留剩余数据用于后续RTMP消息处理 */
+            if (ctx->recv_pos > required_len) {
+                /* 还有剩余数据(可能是服务器握手确认或RTMP消息),需要左移 */
+                LLOGI("RTMP: Extra data after S0+S1: %u bytes", ctx->recv_pos - required_len);
+                memmove(ctx->recv_buf, &ctx->recv_buf[required_len], ctx->recv_pos - required_len);
+                ctx->recv_pos -= required_len;
+                RTMP_LOGV("RTMP: Buffer adjusted, remaining: %u bytes", ctx->recv_pos);
+            } else {
+                /* 恰好接收到S0+S1,没有剩余数据 */
+                ctx->recv_pos = 0;
+                RTMP_LOGV("RTMP: No extra data after S0+S1, buffer cleared");
+            }
+        } else {
+            /* 数据不足,继续等待 */
+            RTMP_LOGV("RTMP: Waiting for complete S0+S1... received %u/%u bytes", ctx->recv_pos, required_len);
+        }
+    } 
+    else if (ctx->handshake_state == 2) {
+        /* 握手已发送C2
+           根据RTMP规范,握手完成后,可以直接开始发送RTMP消息
+           或等待服务器响应。这里我们检查是否有新数据到达
+           如果有新数据,说明服务器已确认握手,可以继续 */
+        
+        if (ctx->recv_pos >= RTMP_HANDSHAKE_SIZE) {
+            sys_timeout(100, rtmp_send_connect, (void *)ctx);
+        }
+    }
+    
+    return ERR_OK;
+}
+
+/**
+ * TCP错误回调函数
+ */
+static void rtmp_tcp_error_callback(void *arg, err_t err) {
+    rtmp_ctx_t *ctx = (rtmp_ctx_t *)arg;
+    
+    LLOGE("RTMP: TCP error: %d", err);
+    
+    if (ctx) {
+        rtmp_set_state(ctx, RTMP_STATE_ERROR, RTMP_ERR_NETWORK);
+        ctx->pcb = NULL;
+    }
+}
+
+/**
+ * TCP发送回调函数
+ */
+static err_t rtmp_tcp_sent_callback(void *arg, struct tcp_pcb *pcb, u16_t len) {
+    rtmp_ctx_t *ctx = (rtmp_ctx_t *)arg;
+    static uint64_t total_sent = 0;
+    total_sent += len;
+    //LLOGD("RTMP: TCP sent callback, len=%d, total_sent=%llu", len, total_sent);
+    if (ctx) {
+        ctx->bytes_sent += len;
+        /* 继续发送队列中的数据 */
+        rtmp_try_send_queue(ctx);
+    }
+    
+    return ERR_OK;
+}
+
+/**
+ * 执行RTMP握手
+ */
+static int rtmp_do_handshake(rtmp_ctx_t *ctx) {
+    /* 生成C0和C1数据 */
+    uint8_t handshake[1 + RTMP_HANDSHAKE_CLIENT_SIZE];
+    
+    /* C0: 版本号 */
+    handshake[0] = 0x03;  /* RTMP版本3 */
+    
+    /* C1: 握手数据 */
+    uint32_t timestamp = rtmp_gen_timestamp();
+    write_be32(&handshake[1], timestamp);
+    
+    /* Zero字段 */
+    memset(&handshake[5], 0, 4);
+    
+    /* 随机数据 */
+    for (uint32_t i = 9; i < 1 + RTMP_HANDSHAKE_CLIENT_SIZE; i++) {
+        handshake[i] = (uint8_t)(rand() & 0xFF);
+    }
+    
+    /* 发送握手数据 */
+    LLOGI("RTMP: Sending handshake (C0+C1)...");
+    err_t err = tcp_write(ctx->pcb, handshake, sizeof(handshake), TCP_WRITE_FLAG_COPY);
+    
+    if (err != ERR_OK) {
+        LLOGE("RTMP: Failed to send handshake: %d", err);
+        return RTMP_ERR_NETWORK;
+    }
+    
+    tcp_output(ctx->pcb);
+    
+    RTMP_LOGV("RTMP: C0+C1 sent (%d bytes), waiting for S0+S1...", sizeof(handshake));
+    
+    /* 设置握手状态为等待S0+S1 */
+    ctx->handshake_state = 1;
+    
+    return RTMP_OK;
+}
+
+/**
+ * 处理握手响应
+ */
+static int rtmp_process_handshake_response(rtmp_ctx_t *ctx) {
+    /* 检查收到的数据是否足够 */
+    if (ctx->recv_pos < 1 + 2 * RTMP_HANDSHAKE_CLIENT_SIZE) {
+        return RTMP_OK;  /* 数据不足,继续等待 */
+    }
+    
+    /* S0: 版本号 */
+    if (ctx->recv_buf[0] != 0x03) {
+        LLOGE("RTMP: Invalid RTMP version from server %d", ctx->recv_buf[0]);
+        return RTMP_ERR_HANDSHAKE_FAILED;
+    }
+    
+    /* 握手完成,发送connect命令 */
+    rtmp_set_state(ctx, RTMP_STATE_CONNECTED, 0);
+    
+    /* 发送connect命令 */
+    int ret = rtmp_send_command(ctx, "connect", 1, ctx->app);
+    if (ret != RTMP_OK) {
+        return ret;
+    }
+    
+    /* 清空接收缓冲区 */
+    ctx->recv_pos = 0;
+    
+    return RTMP_OK;
+}
+
+/**
+ * 发送 @setDataFrame 元数据
+ * 用于设置视频流的元信息
+ */
+static int rtmp_send_metadata(rtmp_ctx_t *ctx) {
+    if (!ctx) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    uint8_t amf_buf[512] = {0};
+    uint32_t offset = 0;
+    
+    /* 1. 写入命令名称 "@setDataFrame" */
+    const char *cmd = "@setDataFrame";
+    amf_buf[offset++] = AMF_TYPE_STRING;
+    write_be16(&amf_buf[offset], strlen(cmd));
+    offset += 2;
+    memcpy(&amf_buf[offset], cmd, strlen(cmd));
+    offset += strlen(cmd);
+    
+    /* 2. 写入元数据类型 "onMetaData" */
+    const char *meta_type = "onMetaData";
+    amf_buf[offset++] = AMF_TYPE_STRING;
+    write_be16(&amf_buf[offset], strlen(meta_type));
+    offset += 2;
+    memcpy(&amf_buf[offset], meta_type, strlen(meta_type));
+    offset += strlen(meta_type);
+    
+    /* 3. 写入元数据对象 */
+    amf_buf[offset++] = AMF_TYPE_OBJECT;
+    
+    /* duration */
+    {
+        const char *key = "duration";
+        write_be16(&amf_buf[offset], strlen(key));
+        offset += 2;
+        memcpy(&amf_buf[offset], key, strlen(key));
+        offset += strlen(key);
+        
+        amf_buf[offset++] = AMF_TYPE_NUMBER;
+        double val = 0.0;
+        uint64_t bits = *(uint64_t *)&val;
+        for (int i = 0; i < 8; i++) {
+            amf_buf[offset++] = (uint8_t)(bits >> (56 - i * 8));
+        }
+    }
+    
+    /* width */
+    {
+        const char *key = "width";
+        write_be16(&amf_buf[offset], strlen(key));
+        offset += 2;
+        memcpy(&amf_buf[offset], key, strlen(key));
+        offset += strlen(key);
+        
+        amf_buf[offset++] = AMF_TYPE_NUMBER;
+        double val = 1280.0;  /* 默认1280 */
+        uint64_t bits = *(uint64_t *)&val;
+        for (int i = 0; i < 8; i++) {
+            amf_buf[offset++] = (uint8_t)(bits >> (56 - i * 8));
+        }
+    }
+    
+    /* height */
+    {
+        const char *key = "height";
+        write_be16(&amf_buf[offset], strlen(key));
+        offset += 2;
+        memcpy(&amf_buf[offset], key, strlen(key));
+        offset += strlen(key);
+        
+        amf_buf[offset++] = AMF_TYPE_NUMBER;
+        double val = 720.0;  /* 默认720 */
+        uint64_t bits = *(uint64_t *)&val;
+        for (int i = 0; i < 8; i++) {
+            amf_buf[offset++] = (uint8_t)(bits >> (56 - i * 8));
+        }
+    }
+    
+    /* videodatarate */
+    {
+        const char *key = "videodatarate";
+        write_be16(&amf_buf[offset], strlen(key));
+        offset += 2;
+        memcpy(&amf_buf[offset], key, strlen(key));
+        offset += strlen(key);
+        
+        amf_buf[offset++] = AMF_TYPE_NUMBER;
+        double val = 2500.0;  /* 2.5 Mbps */
+        uint64_t bits = *(uint64_t *)&val;
+        for (int i = 0; i < 8; i++) {
+            amf_buf[offset++] = (uint8_t)(bits >> (56 - i * 8));
+        }
+    }
+    
+    /* framerate */
+    {
+        const char *key = "framerate";
+        write_be16(&amf_buf[offset], strlen(key));
+        offset += 2;
+        memcpy(&amf_buf[offset], key, strlen(key));
+        offset += strlen(key);
+        
+        amf_buf[offset++] = AMF_TYPE_NUMBER;
+        double val = 30.0;  /* 30 fps */
+        uint64_t bits = *(uint64_t *)&val;
+        for (int i = 0; i < 8; i++) {
+            amf_buf[offset++] = (uint8_t)(bits >> (56 - i * 8));
+        }
+    }
+    
+    /* videocodecid */
+    {
+        const char *key = "videocodecid";
+        write_be16(&amf_buf[offset], strlen(key));
+        offset += 2;
+        memcpy(&amf_buf[offset], key, strlen(key));
+        offset += strlen(key);
+        
+        amf_buf[offset++] = AMF_TYPE_NUMBER;
+        double val = 7.0;  /* 7 = H.264 */
+        uint64_t bits = *(uint64_t *)&val;
+        for (int i = 0; i < 8; i++) {
+            amf_buf[offset++] = (uint8_t)(bits >> (56 - i * 8));
+        }
+    }
+    
+    /* encoder */
+    {
+        const char *key = "encoder";
+        write_be16(&amf_buf[offset], strlen(key));
+        offset += 2;
+        memcpy(&amf_buf[offset], key, strlen(key));
+        offset += strlen(key);
+        
+        const char *val = "LuatOS RTMP";
+        amf_buf[offset++] = AMF_TYPE_STRING;
+        write_be16(&amf_buf[offset], strlen(val));
+        offset += 2;
+        memcpy(&amf_buf[offset], val, strlen(val));
+        offset += strlen(val);
+    }
+    
+    /* 对象结束 */
+    amf_buf[offset++] = 0x00;
+    amf_buf[offset++] = 0x00;
+    amf_buf[offset++] = AMF_TYPE_OBJECT_END;
+    
+    /* 检查缓冲区大小 */
+    if (offset > sizeof(amf_buf)) {
+        LLOGE("RTMP: Metadata buffer overflow");
+        return RTMP_ERR_BUFFER_OVERFLOW;
+    }
+    
+    LLOGI("RTMP: Metadata payload size: %u bytes", offset);
+    
+    /* 发送元数据作为数据消息 (类型18) */
+    int ret = rtmp_pack_message(ctx, 18, amf_buf, offset, 0, 1);
+    
+    if (ret != RTMP_OK) {
+        LLOGE("RTMP: Failed to pack metadata message: %d", ret);
+        return ret;
+    }
+    
+    /* 立即发送 */
+    ret = rtmp_flush_send_buffer(ctx);
+    
+    LLOGI("RTMP: Metadata @setDataFrame sent successfully");
+    
+    return ret;
+}
+
+/* ========== 帧队列与发送 ========== */
+
+static void rtmp_free_frame_node(rtmp_frame_node_t *node) {
+    if (!node) return;
+    if (node->data) {
+        luat_heap_free(node->data);
+    }
+    luat_heap_free(node);
+}
+
+/* 构建完整的RTMP消息(包含chunk分片),返回新分配的缓冲区 */
+static int rtmp_build_rtmp_message(rtmp_ctx_t *ctx, uint8_t msg_type,
+                                  const uint8_t *payload, uint32_t payload_len,
+                                  uint32_t timestamp, uint32_t stream_id,
+                                  uint8_t **out_buf, uint32_t *out_len) {
+    if (!ctx || !payload || payload_len == 0 || !out_buf || !out_len) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+
+    uint32_t chunk_size = ctx->chunk_size ? ctx->chunk_size : RTMP_DEFAULT_CHUNK_SIZE;
+    uint8_t chunk_stream_id = (msg_type == 20 || msg_type == 17) ? 3 : 4;
+
+    uint32_t num_chunks = (payload_len + chunk_size - 1) / chunk_size;
+    uint32_t total_len = 12 + payload_len;           /* 首块含完整头 */
+    if (num_chunks > 1) {
+        total_len += (num_chunks - 1);               /* 每个后续块1字节继续头 */
+    }
+
+    uint8_t *buf = (uint8_t *)luat_heap_malloc(total_len);
+    if (!buf) {
+        return RTMP_ERR_NO_MEMORY;
+    }
+
+    uint32_t offset = 0;
+
+    /* 首块头:fmt0 */
+    buf[offset++] = (0 << 6) | (chunk_stream_id & 0x3F);
+    buf[offset++] = (timestamp >> 16) & 0xFF;
+    buf[offset++] = (timestamp >> 8) & 0xFF;
+    buf[offset++] = timestamp & 0xFF;
+    buf[offset++] = (payload_len >> 16) & 0xFF;
+    buf[offset++] = (payload_len >> 8) & 0xFF;
+    buf[offset++] = payload_len & 0xFF;
+    buf[offset++] = msg_type;
+    buf[offset++] = stream_id & 0xFF;
+    buf[offset++] = (stream_id >> 8) & 0xFF;
+    buf[offset++] = (stream_id >> 16) & 0xFF;
+    buf[offset++] = (stream_id >> 24) & 0xFF;
+
+    /* 数据拷贝,带继续块头 */
+    uint32_t sent = 0;
+    uint32_t first_copy = (payload_len < chunk_size) ? payload_len : chunk_size;
+    memcpy(&buf[offset], payload, first_copy);
+    offset += first_copy;
+    sent += first_copy;
+
+    while (sent < payload_len) {
+        uint32_t remain = payload_len - sent;
+        uint32_t copy_len = (remain < chunk_size) ? remain : chunk_size;
+        buf[offset++] = (3 << 6) | (chunk_stream_id & 0x3F); /* continuation header */
+        memcpy(&buf[offset], &payload[sent], copy_len);
+        offset += copy_len;
+        sent += copy_len;
+    }
+
+    *out_buf = buf;
+    *out_len = offset;
+    return RTMP_OK;
+}
+
+/* 入队帧,必要时丢弃未开始发送的旧帧 */
+static int rtmp_queue_frame(rtmp_ctx_t *ctx, rtmp_frame_node_t *node) {
+    if (!ctx || !node) return RTMP_ERR_INVALID_PARAM;
+
+    /* 拥堵且来了关键帧,丢弃所有未开始发送的帧(sent==0) */
+    if (node->is_key && ctx->frame_head) {
+        rtmp_frame_node_t *cur = ctx->frame_head;
+        rtmp_frame_node_t *prev = NULL;
+        while (cur) {
+            if (cur->sent == 0) {
+                rtmp_frame_node_t *to_free = cur;
+                cur = cur->next;
+                if (prev) prev->next = cur; else ctx->frame_head = cur;
+                if (to_free == ctx->frame_tail) ctx->frame_tail = prev;
+                ctx->frame_queue_bytes -= to_free->len;
+                rtmp_free_frame_node(to_free);
+                continue;
+            }
+            prev = cur;
+            cur = cur->next;
+        }
+    }
+
+    /* 水位控制:超限则优先丢弃未发送的非关键帧,再丢未发送的旧帧 */
+    uint32_t need_bytes = node->len;
+    rtmp_frame_node_t *cur = ctx->frame_head;
+    rtmp_frame_node_t *prev = NULL;
+    while (ctx->frame_queue_bytes + need_bytes > RTMP_MAX_QUEUE_BYTES && cur) {
+        if (cur->sent == 0 && !cur->is_key) {
+            rtmp_frame_node_t *to_free = cur;
+            cur = cur->next;
+            if (prev) prev->next = cur; else ctx->frame_head = cur;
+            if (to_free == ctx->frame_tail) ctx->frame_tail = prev;
+            ctx->frame_queue_bytes -= to_free->len;
+            rtmp_free_frame_node(to_free);
+            continue;
+        }
+        prev = cur;
+        cur = cur->next;
+    }
+
+    cur = ctx->frame_head;
+    prev = NULL;
+    while (ctx->frame_queue_bytes + need_bytes > RTMP_MAX_QUEUE_BYTES && cur) {
+        if (cur->sent == 0) {
+            rtmp_frame_node_t *to_free = cur;
+            cur = cur->next;
+            if (prev) prev->next = cur; else ctx->frame_head = cur;
+            if (to_free == ctx->frame_tail) ctx->frame_tail = prev;
+            ctx->frame_queue_bytes -= to_free->len;
+            rtmp_free_frame_node(to_free);
+            continue;
+        }
+        prev = cur;
+        cur = cur->next;
+    }
+
+    /* 仍然超限,则放弃当前帧 */
+    if (ctx->frame_queue_bytes + need_bytes > RTMP_MAX_QUEUE_BYTES) {
+        LLOGE("RTMP: Drop frame, queue bytes %u exceed max %u", ctx->frame_queue_bytes + need_bytes, RTMP_MAX_QUEUE_BYTES);
+        return RTMP_ERR_BUFFER_OVERFLOW;
+    }
+
+    /* 追加到队尾 */
+    node->next = NULL;
+    if (ctx->frame_tail) {
+        ctx->frame_tail->next = node;
+    } else {
+        ctx->frame_head = node;
+    }
+    ctx->frame_tail = node;
+    ctx->frame_queue_bytes += node->len;
+
+    /* 尝试立即发送 */
+    rtmp_try_send_queue(ctx);
+
+    return RTMP_OK;
+}
+
+/* 发送队列中的数据,逐chunk写入lwip */
+static void rtmp_try_send_queue(rtmp_ctx_t *ctx) {
+    if (!ctx || !ctx->pcb) return;
+
+    while (ctx->frame_head) {
+        rtmp_frame_node_t *node = ctx->frame_head;
+        uint32_t remaining = node->len - node->sent;
+        if (remaining == 0) {
+            ctx->frame_head = node->next;
+            if (ctx->frame_head == NULL) ctx->frame_tail = NULL;
+            if (ctx->frame_queue_bytes >= node->len) ctx->frame_queue_bytes -= node->len; else ctx->frame_queue_bytes = 0;
+            rtmp_free_frame_node(node);
+            continue;
+        }
+
+        u16_t snd_avail = tcp_sndbuf(ctx->pcb);
+        if (snd_avail == 0) {
+            tcp_output(ctx->pcb);
+            break;
+        }
+
+        uint32_t to_send = remaining < snd_avail ? remaining : snd_avail;
+        /* 在空闲时多发,拥堵时受snd_avail限制;上限设为8KB */
+        if (to_send > 8192) to_send = 8192;
+
+        err_t err = tcp_write(ctx->pcb, node->data + node->sent, to_send, TCP_WRITE_FLAG_COPY);
+        if (err != ERR_OK) {
+            LLOGE("RTMP: tcp_write queue failed %d", err);
+            break;
+        }
+        node->sent += to_send;
+
+        tcp_output(ctx->pcb);
+
+        if (node->sent >= node->len) {
+            ctx->frame_head = node->next;
+            if (ctx->frame_head == NULL) ctx->frame_tail = NULL;
+            if (ctx->frame_queue_bytes >= node->len) ctx->frame_queue_bytes -= node->len; else ctx->frame_queue_bytes = 0;
+            rtmp_free_frame_node(node);
+        } else {
+            /* 发送缓冲区不足,等待sent回调继续 */
+            break;
+        }
+    }
+}
+
+/**
+ * 发送RTMP命令
+ */
+static int rtmp_send_command(rtmp_ctx_t *ctx, const char *command,
+                            uint32_t transaction_id, const char *args) {
+    if (!ctx || !command) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    uint8_t amf_buf[512] = {0};
+    uint32_t offset = 0;
+    
+    /* AMF消息格式:
+     * 1. 命令名称 (String) - "connect"
+     * 2. 事务ID (Number)
+     * 3. 命令对象 (Object) - connect参数
+     * 4. (可选) 附加参数
+     */
+    
+    /* 1. 写入命令名称 */
+    amf_buf[offset++] = AMF_TYPE_STRING;
+    uint32_t cmd_len = strlen(command);
+    write_be16(&amf_buf[offset], (uint16_t)cmd_len);
+    offset += 2;
+    memcpy(&amf_buf[offset], command, cmd_len);
+    offset += cmd_len;
+    
+    RTMP_LOGV("RTMP: Command name: %s (len=%u)", command, cmd_len);
+    
+    /* 2. 写入事务ID */
+    amf_buf[offset++] = AMF_TYPE_NUMBER;
+    /* 转换double */
+    double trans_id = (double)transaction_id;
+    uint64_t bits = *(uint64_t *)&trans_id;
+    for (int i = 0; i < 8; i++) {
+        amf_buf[offset++] = (uint8_t)(bits >> (56 - i * 8));
+    }
+    
+    RTMP_LOGV("RTMP: Transaction ID: %u", transaction_id);
+    
+    /* 3. 写入命令对象或参数 */
+    if (strcmp(command, "connect") == 0) {
+        /* connect 命令:带详细参数对象 */
+        amf_buf[offset++] = AMF_TYPE_OBJECT;
+        
+        /* 3.1 app 参数 */
+        if (ctx->app) {
+            const char *key = "app";
+            write_be16(&amf_buf[offset], strlen(key));
+            offset += 2;
+            memcpy(&amf_buf[offset], key, strlen(key));
+            offset += strlen(key);
+            
+            amf_buf[offset++] = AMF_TYPE_STRING;
+            write_be16(&amf_buf[offset], strlen(ctx->app));
+            offset += 2;
+            memcpy(&amf_buf[offset], ctx->app, strlen(ctx->app));
+            offset += strlen(ctx->app);
+            
+            RTMP_LOGV("RTMP: Parameter - app: %s", ctx->app);
+        }
+
+        // 添加 type参数
+        if (1) {
+            const char *key = "type";
+            const char *val = "nonprivate";
+            write_be16(&amf_buf[offset], strlen(key));
+            offset += 2;
+            memcpy(&amf_buf[offset], key, strlen(key));
+            offset += strlen(key);
+            
+            amf_buf[offset++] = AMF_TYPE_STRING;
+            write_be16(&amf_buf[offset], strlen(val));
+            offset += 2;
+            memcpy(&amf_buf[offset], val, strlen(val));
+            offset += strlen(val);
+            
+            RTMP_LOGV("RTMP: Parameter - type: %s", val);
+        }
+        
+        /* 3.2 flashVer 参数 */
+        {
+            const char *key = "flashVer";
+            const char *val = "LNX 9,0,124,2";
+            
+            write_be16(&amf_buf[offset], strlen(key));
+            offset += 2;
+            memcpy(&amf_buf[offset], key, strlen(key));
+            offset += strlen(key);
+            
+            amf_buf[offset++] = AMF_TYPE_STRING;
+            write_be16(&amf_buf[offset], strlen(val));
+            offset += 2;
+            memcpy(&amf_buf[offset], val, strlen(val));
+            offset += strlen(val);
+            
+            RTMP_LOGV("RTMP: Parameter - flashVer: %s", val);
+        }
+        
+        /* 3.3 tcUrl 参数 */
+        if (ctx->url) {
+            const char *key = "tcUrl";
+            write_be16(&amf_buf[offset], strlen(key));
+            offset += 2;
+            memcpy(&amf_buf[offset], key, strlen(key));
+            offset += strlen(key);
+            
+            amf_buf[offset++] = AMF_TYPE_STRING;
+            write_be16(&amf_buf[offset], strlen(ctx->url));
+            offset += 2;
+            memcpy(&amf_buf[offset], ctx->url, strlen(ctx->url));
+            offset += strlen(ctx->url);
+            
+            RTMP_LOGV("RTMP: Parameter - tcUrl: %s (len=%d)", tcurl, tcurl_len);
+        }
+        
+        /* 3.4 fpad 参数 */
+        if (0) {
+            const char *key = "fpad";
+            write_be16(&amf_buf[offset], strlen(key));
+            offset += 2;
+            memcpy(&amf_buf[offset], key, strlen(key));
+            offset += strlen(key);
+            
+            amf_buf[offset++] = AMF_TYPE_BOOLEAN;
+            amf_buf[offset++] = 0;  /* false */
+            
+            RTMP_LOGV("RTMP: Parameter - fpad: false");
+        }
+        
+        /* 3.5 audioCodecs 参数 */
+        if (0) {
+            const char *key = "audioCodecs";
+            write_be16(&amf_buf[offset], strlen(key));
+            offset += 2;
+            memcpy(&amf_buf[offset], key, strlen(key));
+            offset += strlen(key);
+            
+            amf_buf[offset++] = AMF_TYPE_NUMBER;
+            double codec_val = 3575.0;  /* 支持的音频编码 */
+            uint64_t codec_bits = *(uint64_t *)&codec_val;
+            for (int i = 0; i < 8; i++) {
+                amf_buf[offset++] = (uint8_t)(codec_bits >> (56 - i * 8));
+            }
+            
+            RTMP_LOGV("RTMP: Parameter - audioCodecs: 3575.0");
+        }
+        
+        /* 3.6 videoCodecs 参数 */
+        if (0) {
+            const char *key = "videoCodecs";
+            write_be16(&amf_buf[offset], strlen(key));
+            offset += 2;
+            memcpy(&amf_buf[offset], key, strlen(key));
+            offset += strlen(key);
+            
+            amf_buf[offset++] = AMF_TYPE_NUMBER;
+            double codec_val = 252.0;  /* 支持的视频编码 */
+            uint64_t codec_bits = *(uint64_t *)&codec_val;
+            for (int i = 0; i < 8; i++) {
+                amf_buf[offset++] = (uint8_t)(codec_bits >> (56 - i * 8));
+            }
+            
+            RTMP_LOGV("RTMP: Parameter - videoCodecs: 252.0");
+        }
+        
+        /* 3.7 objectEncoding 参数 */
+        if (0) {
+            const char *key = "objectEncoding";
+            write_be16(&amf_buf[offset], strlen(key));
+            offset += 2;
+            memcpy(&amf_buf[offset], key, strlen(key));
+            offset += strlen(key);
+            
+            amf_buf[offset++] = AMF_TYPE_NUMBER;
+            double enc_val = 0.0;
+            uint64_t enc_bits = *(uint64_t *)&enc_val;
+            for (int i = 0; i < 8; i++) {
+                amf_buf[offset++] = (uint8_t)(enc_bits >> (56 - i * 8));
+            }
+            
+            RTMP_LOGV("RTMP: Parameter - objectEncoding: 0.0");
+        }
+        
+        /* 对象结束 */
+        amf_buf[offset++] = 0x00;
+        amf_buf[offset++] = 0x00;
+        amf_buf[offset++] = AMF_TYPE_OBJECT_END;
+        
+    } else if (strcmp(command, "releaseStream") == 0 || 
+               strcmp(command, "FCPublish") == 0 ||
+               strcmp(command, "FCUnpublish") == 0) {
+        /* releaseStream / FCPublish / FCUnpublish 命令:NULL对象 + 流名称 */
+        amf_buf[offset++] = AMF_TYPE_NULL;  /* 命令对象为null */
+        
+        /* 流名称参数 */
+        if (ctx->stream) {
+            amf_buf[offset++] = AMF_TYPE_STRING;
+            write_be16(&amf_buf[offset], strlen(ctx->stream));
+            offset += 2;
+            memcpy(&amf_buf[offset], ctx->stream, strlen(ctx->stream));
+            offset += strlen(ctx->stream);
+            
+            RTMP_LOGV("RTMP: Parameter - stream: %s", ctx->stream);
+        }
+        
+    } else if (strcmp(command, "createStream") == 0) {
+        /* createStream 命令:NULL对象 */
+        amf_buf[offset++] = AMF_TYPE_NULL;
+        RTMP_LOGV("RTMP: createStream with NULL object");
+        
+    } else if (strcmp(command, "publish") == 0) {
+        /* publish 命令:NULL对象 + 流名称 + 发布类型 */
+        amf_buf[offset++] = AMF_TYPE_NULL;
+        
+        /* 流名称 */
+        if (ctx->stream) {
+            amf_buf[offset++] = AMF_TYPE_STRING;
+            write_be16(&amf_buf[offset], strlen(ctx->stream));
+            offset += 2;
+            memcpy(&amf_buf[offset], ctx->stream, strlen(ctx->stream));
+            offset += strlen(ctx->stream);
+        }
+        
+        /* 发布类型:"live" */
+        const char *pub_type = "live";
+        amf_buf[offset++] = AMF_TYPE_STRING;
+        write_be16(&amf_buf[offset], strlen(pub_type));
+        offset += 2;
+        memcpy(&amf_buf[offset], pub_type, strlen(pub_type));
+        offset += strlen(pub_type);
+        
+        RTMP_LOGV("RTMP: publish - stream: %s, type: %s", ctx->stream ? ctx->stream : "NULL", pub_type);
+    }
+    
+    /* 检查缓冲区大小 */
+    if (offset > sizeof(amf_buf)) {
+        LLOGE("RTMP: AMF buffer overflow");
+        return RTMP_ERR_BUFFER_OVERFLOW;
+    }
+    
+    RTMP_LOGV("RTMP: AMF payload size: %u bytes", offset);
+    
+    /* 打印前64字节的hex数据用于调试 */
+    if (0) {
+        uint32_t print_len = (offset > 64) ? 64 : offset;
+        LLOGI("RTMP: AMF payload (first %u bytes):", print_len);
+        
+        char hex_buf[256] = {0};
+        uint32_t hex_pos = 0;
+        
+        for (uint32_t i = 0; i < print_len; i++) {
+            hex_pos += snprintf(&hex_buf[hex_pos], sizeof(hex_buf) - hex_pos, "%02X ", amf_buf[i]);
+            if ((i + 1) % 16 == 0 && i + 1 < print_len) {
+                RTMP_LOGV("RTMP:   %s", hex_buf);
+                hex_pos = 0;
+                memset(hex_buf, 0, sizeof(hex_buf));
+            }
+        }
+        
+        if (hex_pos > 0) {
+            LLOGD("RTMP:   %s", hex_buf);
+        }
+    }
+    
+    /* 发送connect命令作为RTMP消息 */
+    /* 消息类型 20 = 命令消息 (AMF0) */
+    int ret = rtmp_pack_message(ctx, 20, amf_buf, offset, 0, 0);
+    
+    if (ret != RTMP_OK) {
+        LLOGE("RTMP: Failed to pack connect message: %d", ret);
+        return ret;
+    }
+    
+    /* 立即发送connect命令 */
+    ret = rtmp_flush_send_buffer(ctx);
+    
+    LLOGI("RTMP: command sent successfully: %s (tx_id=%u, payload_size=%u bytes)", 
+          command, transaction_id, offset);
+    
+    return ret;
+}
+
+/**
+ * 处理收到的RTMP数据
+ */
+static int rtmp_process_data(rtmp_ctx_t *ctx) {
+    if (!ctx || ctx->recv_pos == 0) {
+        return RTMP_OK;
+    }
+
+    const char *success_str = "NetConnection.Connect.Success";
+    const char *on_status_str = "onStatus";
+    const char *failed_str = "NetConnection.Connect.Failed";
+    const char *result_str = "_result";
+    const char *publish_start_str = "NetStream.Publish.Start";
+    const char *on_bw_done_str = "onBWDone";
+
+    bool found_success = false;
+    bool found_failed = false;
+    bool found_on_status = false;
+    bool found_result = false;
+    bool found_publish_start = false;
+    bool found_on_bw_done = false;
+
+    uint32_t pos = 0;
+    while (pos + 12 <= ctx->recv_pos) {
+        uint8_t chunk_header = ctx->recv_buf[pos];
+        uint8_t fmt = (chunk_header >> 6) & 0x03;
+        /* 仅处理格式0头,其他格式简单跳过到下一个字节 */
+        if (fmt != 0) {
+            pos++;
+            continue;
+        }
+
+        /* 检查剩余长度是否包含完整头 */
+        if (pos + 12 > ctx->recv_pos) {
+            break; /* 不完整,等待更多数据 */
+        }
+
+        uint32_t msg_len = ((uint32_t)ctx->recv_buf[pos + 4] << 16) |
+                           ((uint32_t)ctx->recv_buf[pos + 5] << 8) |
+                           (uint32_t)ctx->recv_buf[pos + 6];
+        uint8_t msg_type = ctx->recv_buf[pos + 7];
+
+        /* 检查消息是否完整 */
+        if (pos + 12 + msg_len > ctx->recv_pos) {
+            break; /* 不完整,等待更多数据 */
+        }
+
+        const uint8_t *payload = &ctx->recv_buf[pos + 12];
+
+        if (msg_type == RTMP_MSG_SET_CHUNK_SIZE && msg_len >= 4) {
+            uint32_t new_chunk_size = read_be32(payload) & 0x7FFFFFFF;
+            if (new_chunk_size > 0 && new_chunk_size <= 0x7FFFFFFF) {
+                ctx->in_chunk_size = new_chunk_size;
+                ctx->chunk_size = new_chunk_size;
+                LLOGI("RTMP: Received Set Chunk Size from server: %u, updated local chunk_size", new_chunk_size);
+            }
+        } else if (msg_type == RTMP_MSG_COMMAND || msg_type == RTMP_MSG_EXTENDED_COMMAND || msg_type == RTMP_MSG_AMFDATAFILE) {
+            /* 在命令/数据消息体内查找关键字符串 */
+            if (!found_success && msg_len >= strlen(success_str)) {
+                for (uint32_t i = 0; i + strlen(success_str) <= msg_len; i++) {
+                    if (memcmp(&payload[i], success_str, strlen(success_str)) == 0) {
+                        found_success = true;
+                        break;
+                    }
+                }
+            }
+            if (!found_failed && msg_len >= strlen(failed_str)) {
+                for (uint32_t i = 0; i + strlen(failed_str) <= msg_len; i++) {
+                    if (memcmp(&payload[i], failed_str, strlen(failed_str)) == 0) {
+                        found_failed = true;
+                        break;
+                    }
+                }
+            }
+            if (!found_result && msg_len >= strlen(result_str)) {
+                for (uint32_t i = 0; i + strlen(result_str) <= msg_len; i++) {
+                    if (memcmp(&payload[i], result_str, strlen(result_str)) == 0) {
+                        found_result = true;
+                        break;
+                    }
+                }
+            }
+            if (!found_publish_start && msg_len >= strlen(publish_start_str)) {
+                for (uint32_t i = 0; i + strlen(publish_start_str) <= msg_len; i++) {
+                    if (memcmp(&payload[i], publish_start_str, strlen(publish_start_str)) == 0) {
+                        found_publish_start = true;
+                        break;
+                    }
+                }
+            }
+            if (!found_on_status && msg_len >= strlen(on_status_str)) {
+                for (uint32_t i = 0; i + strlen(on_status_str) <= msg_len; i++) {
+                    if (memcmp(&payload[i], on_status_str, strlen(on_status_str)) == 0) {
+                        found_on_status = true;
+                        break;
+                    }
+                }
+            }
+            if (!found_on_bw_done && msg_len >= strlen(on_bw_done_str)) {
+                for (uint32_t i = 0; i + strlen(on_bw_done_str) <= msg_len; i++) {
+                    if (memcmp(&payload[i], on_bw_done_str, strlen(on_bw_done_str)) == 0) {
+                        found_on_bw_done = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        /* 前进到下一条消息 */
+        pos += 12 + msg_len;
+    }
+
+    /* 如果有未处理完的部分,将剩余数据前移 */
+    if (pos < ctx->recv_pos) {
+        uint32_t remain = ctx->recv_pos - pos;
+        memmove(ctx->recv_buf, &ctx->recv_buf[pos], remain);
+        ctx->recv_pos = remain;
+    } else {
+        ctx->recv_pos = 0;
+    }
+
+    /* 根据查找结果更新状态 */
+    if (found_success) {
+        /* 连接成功,开始发送发布流的控制命令 */
+        //RTMP_LOGV("RTMP: Connection successful, sending publish commands...");
+        
+        /* 1. 发送 setChunkSize */
+        uint8_t chunk_size_msg[4];
+        uint32_t negotiated_chunk = ctx->in_chunk_size ? ctx->in_chunk_size : (ctx->chunk_size ? ctx->chunk_size : RTMP_DEFAULT_CHUNK_SIZE);
+        negotiated_chunk &= 0x7FFFFFFF; /* 规范要求最高位为0 */
+        if (negotiated_chunk == 0) {
+            negotiated_chunk = RTMP_DEFAULT_CHUNK_SIZE;
+        }
+        write_be32(chunk_size_msg, negotiated_chunk);
+        
+        int ret = rtmp_pack_message(ctx, RTMP_MSG_SET_CHUNK_SIZE, chunk_size_msg, sizeof(chunk_size_msg), 0, 0);
+        if (ret == RTMP_OK) {
+            ctx->out_chunk_size = negotiated_chunk;
+            ctx->chunk_size = negotiated_chunk;  /* 更新实际使用的chunk大小 */
+            LLOGI("RTMP: Sent setChunkSize: %u", negotiated_chunk);
+        }
+        
+        /* 2. 发送 releaseStream */
+        ret = rtmp_send_command(ctx, "releaseStream", 2, NULL);
+        if (ret == RTMP_OK) {
+            RTMP_LOGV("RTMP: Sent releaseStream");
+        }
+        
+        /* 3. 发送 FCPublish */
+        ret = rtmp_send_command(ctx, "FCPublish", 3, NULL);
+        if (ret == RTMP_OK) {
+            RTMP_LOGV("RTMP: Sent FCPublish");
+        }
+        
+        /* 4. 发送 createStream */
+        ret = rtmp_send_command(ctx, "createStream", 4, NULL);
+        if (ret == RTMP_OK) {
+            RTMP_LOGV("RTMP: Sent createStream");
+        }
+        
+        /* 立即发送缓冲数据 */
+        rtmp_flush_send_buffer(ctx);
+        
+        RTMP_LOGV("RTMP: Sent publish control commands, waiting for createStream response");
+        
+    } else if (found_result && ctx->state == RTMP_STATE_CONNECTED) {
+        /* 收到 _result 响应(createStream的响应)
+         * 现在可以发送 publish 命令了 */
+        RTMP_LOGV("RTMP: Received createStream _result, sending publish command...");
+        
+        /* 发送 publish 命令 */
+        int ret = rtmp_send_command(ctx, "publish", 5, NULL);
+        if (ret == RTMP_OK) {
+            RTMP_LOGV("RTMP: Sent publish command");
+            rtmp_flush_send_buffer(ctx);
+        } else {
+            LLOGE("RTMP: Failed to send publish command");
+        }
+        
+    } else if (found_publish_start) {
+        /* 收到 NetStream.Publish.Start 响应
+         * 表示推流已成功开始,发送元数据后即可发送视频数据 */
+        RTMP_LOGV("RTMP: Publish started successfully, sending metadata");
+        
+        /* 发送 @setDataFrame 元数据 */
+        if (rtmp_send_metadata(ctx) == RTMP_OK) {
+            LLOGI("RTMP: Metadata sent, ready to send video data");
+            rtmp_set_state(ctx, RTMP_STATE_PUBLISHING, 0);
+            // 通知摄像头开始采集
+            extern int luat_camera_capture(int id, uint8_t quality, const char *path);
+            luat_camera_capture(0, 80, "rtmp");
+        } else {
+            LLOGE("RTMP: Failed to send metadata");
+            rtmp_set_state(ctx, RTMP_STATE_ERROR, RTMP_ERR_FAILED);
+        }
+        
+    } else if (found_failed) {
+        /* 连接失败 */
+        rtmp_set_state(ctx, RTMP_STATE_ERROR, RTMP_ERR_CONNECT_FAILED);
+        LLOGE("RTMP: Connection failed");
+        
+    } else if (found_on_bw_done) {
+        /* 收到 onBWDone 带宽检测完成信号
+         * 服务器已完成带宽检测,可以继续发送流命令 */
+        RTMP_LOGV("RTMP: Received onBWDone (bandwidth detection complete)");
+        /* onBWDone 是通知,不需要额外响应,继续现有流程 */
+    } else if (ctx->state == RTMP_STATE_CONNECTED && found_on_status) {
+        /* 收到onStatus,继续等待 */
+        RTMP_LOGV("RTMP: Received onStatus response");
+    }
+    
+    return RTMP_OK;
+}
+
+/**
+ * 获取NALU类型
+ */
+static nalu_type_t rtmp_get_nalu_type(const uint8_t *nalu_data, 
+                                     uint32_t nalu_len) {
+    if (nalu_len < 4) {
+        return NALU_TYPE_NON_IDR;
+    }
+    
+    /* 检查起始码 */
+    uint32_t start_code = read_be32(nalu_data);
+    if (start_code == 0x00000001) {
+        if (nalu_len < 5) {
+            return NALU_TYPE_NON_IDR;
+        }
+        uint8_t nalu_header = nalu_data[4];
+        return (nalu_type_t)(nalu_header & 0x1F);
+    }
+    
+    return NALU_TYPE_NON_IDR;
+}
+
+/**
+ * 检查是否为关键帧
+ */
+static bool rtmp_is_key_frame(const uint8_t *nalu_data, uint32_t nalu_len) {
+    nalu_type_t type = rtmp_get_nalu_type(nalu_data, nalu_len);
+    return (type == NALU_TYPE_IDR);
+}
+
+/**
+ * 打包FLV视频标签
+ */
+static int rtmp_pack_video_tag(uint8_t *buffer, uint32_t buffer_len,
+                              const uint8_t *video_data, uint32_t video_len,
+                              bool is_key_frame) {
+    if (!buffer || buffer_len < 5 || !video_data || video_len == 0) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    /* 视频标签格式:
+     * byte 0: 帧类型(4bit) + 编码ID(4bit)
+     * byte 1-3: 包类型和时间戳偏移
+     * byte 4+: NAL单元数据
+     */
+    
+    uint32_t offset = 0;
+    
+    /* 帧类型和编码ID */
+    uint8_t tag = 0;
+    tag |= (is_key_frame ? 1 : 2) << 4;
+    tag |= 7;  /* H.264 */
+    
+    buffer[offset++] = tag;
+    
+    if (offset + video_len > buffer_len) {
+        return RTMP_ERR_BUFFER_OVERFLOW;
+    }
+    
+    memcpy(&buffer[offset], video_data, video_len);
+    offset += video_len;
+    
+    return (int)offset;
+}
+
+/**
+ * 发送缓冲的数据
+ */
+static int rtmp_flush_send_buffer(rtmp_ctx_t *ctx) {
+    if (!ctx || ctx->send_pos == 0) {
+        return RTMP_OK;
+    }
+    
+    if (!ctx->pcb) {
+        return RTMP_ERR_FAILED;
+    }
+    
+    //LLOGI("RTMP: Flushing %u bytes from send buffer", ctx->send_pos);
+    
+    uint32_t bytes_sent = 0;
+    uint32_t total_bytes = ctx->send_pos;
+    
+    /* 分批发送数据,避免lwip缓冲区溢出 */
+    while (bytes_sent < total_bytes) {
+        /* 检查TCP发送缓冲区可用空间 */
+        u16_t available = tcp_sndbuf(ctx->pcb);
+        if (available == 0) {
+            /* 缓冲区已满,先输出已有数据,等待sent回调继续 */
+            tcp_output(ctx->pcb);
+            return RTMP_ERR_NETWORK;
+        }
+        
+        /* 计算本次可以发送的字节数 */
+        uint32_t remaining = total_bytes - bytes_sent;
+        uint32_t to_send = (remaining < available) ? remaining : available;
+        
+        /* 限制单次发送大小,避免过大 */
+        if (to_send > 4096) {
+            to_send = 4096;
+        }
+        
+        /* 发送数据 */
+        err_t err = tcp_write(ctx->pcb, &ctx->send_buf[bytes_sent], to_send, TCP_WRITE_FLAG_COPY);
+        if (err != ERR_OK) {
+            LLOGE("RTMP: tcp_write failed: %d, sent %u/%u bytes", err, bytes_sent, total_bytes);
+            return RTMP_ERR_NETWORK;
+        }
+        
+        bytes_sent += to_send;
+        
+        /* 每发送一批数据后触发输出 */
+        if (bytes_sent % 8192 == 0 || bytes_sent >= total_bytes) {
+            tcp_output(ctx->pcb);
+        }
+    }
+    
+    //LLOGI("RTMP: Successfully sent %u bytes", bytes_sent);
+    ctx->send_pos = 0;
+    
+    return RTMP_OK;
+}
+
+/**
+ * 更新状态
+ */
+static void rtmp_set_state(rtmp_ctx_t *ctx, rtmp_state_t new_state, int error_code) {
+    rtmp_state_t old_state = ctx->state;
+    
+    if (old_state == new_state) {
+        return;
+    }
+    
+    ctx->state = new_state;
+    
+    // 在连接成功时初始化 base_timestamp
+    if (new_state == RTMP_STATE_CONNECTED && old_state != RTMP_STATE_CONNECTED) {
+        if (ctx->base_timestamp == 0) {
+            ctx->base_timestamp = (uint32_t)(luat_mcu_tick64_ms());
+        }
+    }
+    
+    // 在断开连接时重置 base_timestamp
+    if (new_state == RTMP_STATE_IDLE) {
+        ctx->base_timestamp = 0;
+    }
+    
+    LLOGI("RTMP: State changed from %d to %d", old_state, new_state);
+    
+    if (g_state_callback) {
+        g_state_callback(ctx, old_state, new_state, error_code);
+    }
+}
+
+/**
+ * 生成随机时间戳
+ */
+static uint32_t rtmp_gen_timestamp(void) {
+    static uint32_t start_time = 0;
+    
+    if (start_time == 0) {
+        /* 首次调用,初始化基准时间 */
+        start_time = (uint32_t)(uint32_t)(luat_mcu_tick64_ms());
+    }
+    
+    return (uint32_t)(uint32_t)(luat_mcu_tick64_ms()) - start_time;
+}

+ 6 - 0
components/u8g2/u8g2_font.c

@@ -1402,6 +1402,12 @@ void u8g2_SetFont(u8g2_t *u8g2, const uint8_t  *font)
     }else
 #endif
     {
+#if (defined __LUATOS__) || defined (__USER_CODE__)
+        if (u8g2->font_file) {
+            luat_fs_fclose(u8g2->font_file);
+            u8g2->font_file = NULL;
+        }
+#endif
         u8g2_read_font_info(&(u8g2->font_info), font);
     }
     u8g2_UpdateRefHeight(u8g2);

+ 2 - 9
lua/src/liolib.c

@@ -921,15 +921,8 @@ end
  */
 static int io_fileSize (lua_State *L) {
   const char *filename = luaL_checkstring(L, 1);
-  FILE* f = fopen(filename, "rb");
-  if(f == NULL) {
-    lua_pushinteger(L, 0);
-  }
-  else {
-    fseek(f, 0, SEEK_END);
-    lua_pushinteger(L,ftell(f));
-    fclose(f);
-  }
+  size_t len = luat_fs_fsize(filename);
+  lua_pushinteger(L, len);
   return 1;
 }
 

+ 2 - 0
luat/include/luat_libs.h

@@ -231,4 +231,6 @@ LUAMOD_API int luaopen_modbus( lua_State *L );
 LUAMOD_API int luaopen_airtalk( lua_State *L );
 /** misc库*/
 LUAMOD_API int luaopen_misc(lua_State *L);
+/** rtmp推流库*/
+LUAMOD_API int luaopen_rtmp(lua_State *L);
 #endif

+ 7 - 7
luat/modules/luat_lib_crypto.c

@@ -294,15 +294,15 @@ typedef struct{
 
 static const crc16method crc16method_table[] = 
 {
-    {(const char*)"IBM", 0x8005, 0x0000, 0x0000, 1, 1},
-    {(const char*)"MAXIM", 0x8005, 0x0000, 0xffff, 1, 1}, 
-    {(const char*)"USB", 0x8005, 0xffff, 0xffff, 1, 1}, 
-    {(const char*)"MODBUS", 0x8005, 0xffff, 0x0000, 1, 1}, 
-    {(const char*)"CCITT", 0x1021, 0x0000, 0x0000, 1, 1}, 
+    {(const char*)"IBM", 0x8005, 0x0000, 0x0000, 1, 0},
+    {(const char*)"MAXIM", 0x8005, 0x0000, 0xffff, 1, 0}, 
+    {(const char*)"USB", 0x8005, 0xffff, 0xffff, 1, 0}, 
+    {(const char*)"MODBUS", 0x8005, 0xffff, 0x0000, 1, 0}, 
+    {(const char*)"CCITT", 0x1021, 0x0000, 0x0000, 1, 0}, 
     {(const char*)"CCITT-FALSE", 0x1021, 0xffff, 0x0000, 0, 0}, 
-    {(const char*)"X25", 0x1021, 0xffff, 0xffff, 1, 1}, 
+    {(const char*)"X25", 0x1021, 0xffff, 0xffff, 1, 0}, 
     {(const char*)"XMODEM", 0x1021, 0x0000,0x0000, 0, 0}, 
-    {(const char*)"DNP", 0x3D65, 0x0000, 0xffff, 1, 1},
+    {(const char*)"DNP", 0x3D65, 0x0000, 0xffff, 1, 0},
     {(const char*)"USER-DEFINED", 0x0000, 0x0000,0x0000, 0, 0},
 };
 

+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_default_font_drv.lua

@@ -32,7 +32,7 @@ ui.hw_init({
         -- pin_vcc = 24,           -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = nil,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_gtfont_drv.lua

@@ -35,7 +35,7 @@ ui.hw_init({
         -- pin_vcc = 24,                  -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_hzfont_drv.lua

@@ -35,7 +35,7 @@ ui.hw_init({
         -- pin_vcc = 24,           -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_default_font_drv.lua

@@ -32,7 +32,7 @@ ui.hw_init({
         -- pin_vcc = 24,                  -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_gtfont_drv.lua

@@ -35,7 +35,7 @@ ui.hw_init({
         -- pin_vcc = 24,                  -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_hzfont_drv.lua

@@ -35,7 +35,7 @@ ui.hw_init({
         -- pin_vcc = 24,           -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 12 - 28
module/Air780EPM/demo/accessory_board/AirLCD_1000/main.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/rtos/main.lua

@@ -1,21 +1,13 @@
 --[[
 @module  main
 @summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
-@version 1.0
-@date    2025.09.04
-@author  江访
+@version 001.000.000
+@date    2025.12.2
+@author  王城钧
 @usage
-
-本demo演示的核心功能为:
-1、点亮AirLCD_1000屏幕
-2、通过屏幕显示图片、字符、色块等内容
-3、演示背光亮度5%-100%变化
-4、演示使屏幕进入休眠和唤醒屏幕
-
-更多说明参考本目录下的readme.md文件
+1. rtos_app:对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
 ]]
 
-
 --[[
 必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
 PROJECT:项目名,ascii string类型
@@ -26,19 +18,13 @@ VERSION:项目版本号,ascii string类型
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
 ]]
+PROJECT = "rtos_demo"
+VERSION = "001.000.000"
 
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
 
 
--- 定义项目名称和版本号
-PROJECT = "AirLCD_1000_demo"     -- 项目名称
-VERSION = "1.0.0"                -- 版本号
-
--- 打印项目名称和版本号咋看    
-log.info("AirLCD_demo", PROJECT, VERSION)
-
--- 设置日志输出风格为样式2(建议调试时开启)
--- log.style(2)
-
 -- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
 -- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
 if wdt then
@@ -48,7 +34,6 @@ if wdt then
     sys.timerLoopStart(wdt.feed, 3000)
 end
 
-
 -- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
 -- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
 -- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
@@ -73,10 +58,9 @@ end
 -- end, 3000)
 
 
--- 加载用户界面系统主模块
-require "ui_main"
+--加载rtos时钟驱动模块
+require "rtos_app"
+
 
-------------------------------------------用户代码已结束------------------------------------------
--- 启动系统事件循环,这是程序的最终执行点,确保系统持续运行
+-- 启动系统调度(必须放在最后)
 sys.run()
--- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 59 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/rtos/readme.md

@@ -0,0 +1,59 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、rtos_app:对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试;
+
+## 演示功能概述
+
+1、对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+
+## 演示硬件环境
+
+1、Air780EHM/Air780EHV/Air780EGH核心板一块;
+
+![](https://docs.openluat.com/accessory/AirSPINORFLASH_1000/image/780EHV.jpg)
+
+2、TYPE-C USB数据线一根
+
+* Air780EHM/Air780EHV/Air780EGH核心板通过 TYPE-C USB 口供电;
+* TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EHM V2018版本固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+[Air780EHV V2018版本固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+[Air780EGH V2018版本固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、可以看到代码运行结果如下:
+
+日志中如果出现以下类似以下打印则说明rtos功能正常
+
+```lua
+[2025-12-08 11:30:58.055][000000000.264] I/user.main rtos_demo 001.000.000
+[2025-12-08 11:30:58.074][000000000.272] I/user.固件信息 版本: V2018 1
+[2025-12-08 11:30:58.120][000000000.272] I/user.编译信息 日期: Nov  7 2025 BSP: Air780EHM
+[2025-12-08 11:30:58.144][000000000.273] I/user.完整描述 LuatOS-SoC_V2018_Air780EHM
+[2025-12-08 11:30:58.181][000000000.273] I/user.内存信息 Lua - 总: 4194296 已用: 36384 峰值: 36384 系统 - 总: 3211632 已用: 104576 峰值: 105336
+[2025-12-08 11:30:58.203][000000000.273] D/rtos mem collect param 100,80,90 -> 200,75,85
+[2025-12-08 11:30:58.222][000000000.274] I/user.RTOS测试 所有测试已启动
+[2025-12-08 11:30:59.423][000000002.307] D/mobile cid1, state0
+[2025-12-08 11:30:59.432][000000002.308] D/mobile bearer act 0, result 0
+[2025-12-08 11:30:59.437][000000002.308] D/mobile NETIF_LINK_ON -> IP_READY
+[2025-12-08 11:30:59.444][000000002.337] D/mobile TIME_SYNC 0
+[2025-12-08 11:31:07.338][000000010.289] I/user.性能测试 1000次nop耗时: 16 毫秒
+
+
+```

+ 60 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/rtos/rtos_app.lua

@@ -0,0 +1,60 @@
+--[[
+@module  rtos_app
+@summary rtos应用模块
+@version 1.0
+@date    2025.12.2
+@author  王城钧
+@usage
+本文件为rtos应用模块,核心业务逻辑为:
+1、对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+
+本文件没有对外接口,直接在其他功能模块中require "rtos_app"就可以加载运行;
+]]
+
+-- 1. 系统信息查询测试
+-- 读取版本号及数字版本号, 2025.11.1之后的固件支持
+-- 如果不是数字固件,luatos_version_num 会是0
+-- 如果是不支持的固件, luatos_version_num 会是nil
+local luatos_version, luatos_version_num = rtos.version(true)
+log.info("固件信息", "版本:", luatos_version, luatos_version_num )
+
+log.info("编译信息", "日期:", rtos.buildDate(), "BSP:", rtos.bsp())
+log.info("完整描述", rtos.firmware())
+
+-- 2. 内存信息测试
+local total_lua, used_lua, max_used_lua = rtos.meminfo("lua")
+local total_sys, used_sys, max_used_sys = rtos.meminfo("sys")
+log.info("内存信息", 
+    "Lua - 总:", total_lua, "已用:", used_lua, "峰值:", max_used_lua,
+    "系统 - 总:", total_sys, "已用:", used_sys, "峰值:", max_used_sys)
+
+-- 3. 定时器测试
+-- rtos.timer_start()和rtos.timer_stop()两个接口,仅仅给sys核心库使用
+-- 如果要使用定时器,直接使用sys核心库提供的定时器接口即可
+-- 用户脚本中不要直接使用rtos.timer_start()和rtos.timer_stop()两个接口
+-- 否则和sys核心库中的定时器功能出现冲突而导致系统异常的问题
+
+-- 4. 内存自动回收配置
+rtos.autoCollectMem(200, 75, 85)  -- 配置较宽松的自动回收
+
+-- 5. 空函数测试(性能测试用)
+local function test_nop()
+    local start = mcu.ticks()
+    for i = 1, 1000 do
+        rtos.nop()
+    end
+    local duration = mcu.ticks() - start
+    log.info("性能测试", "1000次nop耗时:", duration, "毫秒")
+end
+
+-- 10秒后执行空函数测试
+sys.timerStart(test_nop, 10000)
+
+-- 6. 重启测试(注释掉防止意外重启)
+-- local function reboot()
+--     log.info("系统", "准备重启...")
+--     rtos.reboot()
+-- end
+-- sys.timerStart(reboot, 30000)
+
+log.info("RTOS测试", "所有测试已启动")

+ 2 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/hw_font_drv.lua

@@ -36,9 +36,9 @@ local hw_config = {
         -- pin_vcc = 24,           -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
-        -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,默认:nil
+        -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致
         w = 320,                   -- lcd 水平分辨率
         h = 480,                   -- lcd 竖直分辨率

+ 5 - 5
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/main.lua

@@ -36,8 +36,8 @@
 - win_horizontal_slide: 横向滑动页面演示
 - win_vertical_slide: 纵向滑动页面演示
 - win_switch_page: 页面切换演示
-- win_hzfont: 矢量字体演示
-- win_gtfont: 点阵字体演示
+- win_hzfont: 内置软件矢量字体演示
+- win_gtfont: 外置硬件矢量字体演示
 
 更多说明参考本目录下的readme.md文件
 ]]
@@ -57,7 +57,7 @@ VERSION:项目版本号,ascii string类型
 
 -- 项目名称和版本定义
 PROJECT = "exEasyUI_demo" -- 项目名称,用于标识当前工程
-VERSION = "1.0.0"         -- 项目版本号
+VERSION = "001.000.000"         -- 项目版本号
 
 -- 在日志中打印项目名和项目版本号
 log.info("ui_demo", PROJECT, VERSION)
@@ -124,8 +124,8 @@ require("win_all_component")  --所有组件综合演示
 -- require("win_horizontal_slide")  --横向滑动页面演示
 -- require("win_vertical_slide")  --纵向滑动页面演示
 -- require("win_switch_page")  --页面切换演示
--- require("win_hzfont")  --矢量字体演示
--- require("win_gtfont")  --点阵字体演示
+-- require("win_hzfont")  --内置软件矢量字体演示
+-- require("win_hzfont")  --外置硬件矢量字体演示
 
 -- 用户代码已结束
 -- 结尾总是这一句

+ 14 - 12
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/readme.md

@@ -101,7 +101,7 @@
 ### 4.3 下载底层固件和上层运行脚本
 
 1. 下载运行所需固件,点击资源管理--选择 Air780EHM 的 LuatOS 固件--下载最新版本的 1 号固件和 14 号固件
-2. 下载本演示 demo 内所有.lua 脚本文件、images 文件夹
+2. 下载本演示 demo 内所有.lua 脚本文件、images 文件夹内的图片
 
 ![](https://docs.openLuat.com/cdn/image/PC模拟器下载固件.png)
 
@@ -143,13 +143,13 @@
 
 - Air780EHM/Air780EHV/Air780EGH 核心板 × 1
 - AirLCD_1010 触摸配件板 × 1
-- GTFont 矢量字库,使用的是 AirFONT_1000 配件板 × 1
+- GTFont 矢量字库,使用的是 AirFONTS_1000 配件板 × 1
 - 母对母杜邦线 × 17,杜邦线太长的话,会出现 spi 通信不稳定的现象;
 - TYPE-C 数据线 × 1
-- Air780EHM/Air780EHV/Air780EGH 核心板和 AirLCD_1010 配件板以及 AirFONT_1000 配件板的硬件接线方式为
+- Air780EHM/Air780EHV/Air780EGH 核心板和 AirLCD_1010 配件板以及 AirFONTS_1000 配件板的硬件接线方式为
 
-  - Air780EHM/Air780EHV/Air780EGH 核心板通过 TYPE-C USB 口供电(核心板背面的功耗测试开关拨到 OFF 一端),此种供电方式下,VDD_EXT 引脚为 3.3V,可以直接给 AirLCD_1010 配件板和 AirFONT_1000 配件板供电;
-  - 为了演示方便,所以 Air780EHM/Air780EHV/Air780EGH 核心板上电后直接通过 VDD_EXT 引脚给 AirLCD_1010 配件板供电,通过3V3引脚给 AirFONT_1000 配件板供电;
+  - Air780EHM/Air780EHV/Air780EGH 核心板通过 TYPE-C USB 口供电(核心板正面开关拨到 ON 一端),此种供电方式下,VDD_EXT 引脚为 3.3V,可以直接给 AirLCD_1010 配件板和 AirFONTS_1000 配件板供电;
+  - 为了演示方便,所以 Air780EHM/Air780EHV/Air780EGH 核心板上电后直接通过 VDD_EXT 引脚给 AirLCD_1010 配件板供电,通过3V3引脚给 AirFONTS_1000 配件板供电;
   - 客户在设计实际项目时,一般来说,需要通过一个 GPIO 来控制 LDO 给配件板供电,这样可以灵活地控制配件板的供电,可以使项目的整体功耗降到最低;
 
 ### 5.2 接线配置
@@ -185,7 +185,7 @@
 
 <table>
 <tr>
-<td>Air780EHM/Air780EHV/Air780EGH 核心板<br/></td><td>AirFONT_1000配件板<br/></td></tr>
+<td>Air780EHM/Air780EHV/Air780EGH 核心板<br/></td><td>AirFONTS_1000配件板<br/></td></tr>
 <tr>
 <td>83/SPI0_CS<br/></td><td>CS<br/></td></tr>
 <tr>
@@ -205,13 +205,15 @@
 
 ### 6.1 开发工具
 
-- [Luatools 下载调试工具](https://gitee.com/link?target=https%3A%2F%2Fdocs.openluat.com%2Fair780egh%2Fluatos%2Fcommon%2Fdownload%2F) - 固件烧录和代码调试
+- [Luatools下载调试工具](https://docs.openluat.com/air780egh/luatos/common/download/) - 固件烧录和代码调试
 
 ### 6.2 内核固件
 
-- [点击下载 Air780EHM 系列最新版本内核固件](https://gitee.com/link?target=https%3A%2F%2Fdocs.openluat.com%2Fair780epm%2Fluatos%2Ffirmware%2Fversion%2F)
-- [点击下载 Air780EHV 系列最新版本内核固件](https://gitee.com/link?target=https%3A%2F%2Fdocs.openluat.com%2Fair780ehv%2Fluatos%2Ffirmware%2Fversion%2F)
-- [点击下载 Air780EGH 系列最新版本内核固件](https://gitee.com/link?target=https%3A%2F%2Fdocs.openluat.com%2Fair780egh%2Fluatos%2Ffirmware%2Fversion%2F)
+- [点击下载Air780EHM系列最新版本内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/),demo所使用的是LuatOS-SoC_V2018_Air780EHM 1号固件
+  
+- [点击下载Air780EHV系列最新版本内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/),demo所使用的是LuatOS-SoC_V2018_Air780EHV 1号固件
+  
+- [点击下载Air780EGH系列最新版本内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/),demo所使用的是LuatOS-SoC_V2018_Air780EGH 1号固件
 
 使用 HZfont 需要使用 V2020 版本以上的 14 号固件或114号固件,且 14 号固件或114号固件仅支持 HZfont
 
@@ -256,8 +258,8 @@ require("win_all_component")  --所有组件综合演示
 -- require("win_horizontal_slide")  --横向滑动页面演示
 -- require("win_vertical_slide")  --纵向滑动页面演示
 -- require("win_switch_page")  --页面切换演示
--- require("win_hzfont")  --矢量字体演示
--- require("win_gtfont")  --点阵字体演示
+-- require("win_hzfont")  --内置软件矢量字体演示
+-- require("win_hzfont")  --外置硬件矢量字体演示
 ```
 
 ### 7.3 软件烧录步骤

+ 15 - 14
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/win_horizontal_slide.lua

@@ -16,7 +16,6 @@
 ]]
 
 local function ui_main()
-
     -- 显示触摸初始化
     hw_font_drv.init()
 
@@ -27,15 +26,15 @@ local function ui_main()
     local win = ui.window({ background_color = ui.COLOR_WHITE })
 
     -- 启用横向滚动,将两页内容并排布置
-    local page_w, page_h = 320, 480
+    local page_w, page_h = lcd.getSize()
     local totalW = page_w * 2
 
     -- 创建横向滑动窗口
-    win:enable_scroll({ 
-        direction = "horizontal", 
-        content_width = totalW, 
-        threshold = 8, 
-        page_width = page_w 
+    win:enable_scroll({
+        direction = "horizontal",
+        content_width = totalW,
+        threshold = 8,
+        page_width = page_w
     })
 
     -- 创建网格按钮函数
@@ -49,10 +48,12 @@ local function ui_main()
             for c = 0, cols - 1 do
                 local x = mx + c * (bw + gapx)
                 local y = my + r * (bh + gapy)
-                local btn = ui.button({ 
-                    x = x, y = y, 
-                    w = bw, h = bh, 
-                    text = string.format("%s-%d", label_prefix, n) 
+                local btn = ui.button({
+                    x = x,
+                    y = y,
+                    w = bw,
+                    h = bh,
+                    text = string.format("%s-%d", label_prefix, n)
                 })
                 win:add(btn)
                 n = n + 1
@@ -61,8 +62,8 @@ local function ui_main()
     end
 
     -- 创建左页和右页内容
-    makeGrid(0, "P1")  -- 第一页
-    makeGrid(page_w, "P2")  -- 第二页
+    makeGrid(0, "P1")      -- 第一页
+    makeGrid(page_w, "P2") -- 第二页
 
     -- 注册窗口到UI系统
     ui.add(win)
@@ -74,4 +75,4 @@ local function ui_main()
     end
 end
 
-sys.taskInit(ui_main)
+sys.taskInit(ui_main)

+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/win_vertical_slide.lua

@@ -27,7 +27,7 @@ local function ui_main()
     local win = ui.window({ background_color = ui.COLOR_WHITE })
 
     -- 启用纵向分页滚动,将两页内容上下排布
-    local page_w, page_h = 320, 480
+    local page_w, page_h = lcd.getSize()
     local total_h = page_h * 2
 
     -- 创建纵向滑动窗口

+ 0 - 230
module/Air780EPM/demo/accessory_board/AirLCD_1000/AirLCD_1000.lua

@@ -1,230 +0,0 @@
---[[
-@module  AirLCD_1000
-@summary AirLCD_1000显示驱动模块
-@version 1.0
-@date    2025.09.4
-@author  江访
-@usage
-
-核心业务逻辑为:
-1、初始化AirLCD_1000显示屏
-2、管理屏幕背光亮度及开关状态
-3、管理屏幕休眠和唤醒状态
-4、提供屏幕状态管理功能
-
-本文件的对外接口有5个:
-1、AirLCD_1000.lcd_init()           --LCD初始化函数,
-2、AirLCD_1000.set_backlight(level) -- 设置背光亮度接口,level 亮度级别(0-100)
-3、AirLCD_1000.lcd_on()             -- 开启LCD背光
-4、AirLCD_1000.lcd_off()            -- 关闭LCD背光
-5、AirLCD_1000.set_sleep(sleep)     -- 设置休眠状态接口,sleep:true进入休眠, false唤醒
-]]
-
--- AirLCD_1000.lua - LCD显示驱动模块
--- 此文件负责初始化LCD显示屏,管理背光及休眠状态
-local AirLCD_1000 = {}
-
--- 屏幕状态管理表
-local screen_state = {
-    lcd_pin_pwr = 1,        -- 屏幕背光引脚GPIO号
-    lcd_pwm_id = 0,         -- 屏幕背光引脚PWM端口号
-    is_sleeping = false,    -- 是否休眠中标识
-    last_brightness = 50,   -- 默认亮度50%
-    backlight_on = true     -- 背光默认开启
-}
-
--- LCD初始化函数
--- @param LCD_MODEL   显示屏型号型号
--- @param lcd_vcc     屏幕供电引脚GPIO号
--- @param lcd_pin_rst 复位引脚GPIO号
--- @param lcd_pin_pwr 背光引脚GPIO号
--- @param lcd_pwm_id  背光引脚PWM端口号
-
-function AirLCD_1000.lcd_init(LCD_MODEL, lcd_vcc, lcd_pin_rst, lcd_pin_pwr,lcd_pwm_id)
-    -- 根据接线设置AirLCD_1000初始化参数
-    local LCD_MODEL, lcd_vcc, lcd_pin_rst, lcd_pin_pwr,lcd_pwm_id = "AirLCD_1000" or LCD_MODEL, lcd_vcc or 29, lcd_pin_rst or 36, lcd_pin_pwr or 1, lcd_pwm_id or 0
-
-    -- 设置屏幕尺寸
-    local width, height = 320, 480
-
-    -- LCD参数配置表
-    local lcd_param = {
-        port = lcd.HWID_0,       -- 使用SPI设备模式
-        pin_dc = 0xff,           -- DC引脚,无需设置
-        pin_rst = lcd_pin_rst,   -- 复位引脚,对应GPIO号
-        pin_pwr = lcd_pin_pwr,   -- 背光引脚(可选)
-        direction = 0,           -- 屏幕默认0°方向
-                                 -- 0:0°, 1:90°, 2:180°, 3:270°
-        w = width,               -- 屏幕宽度(像素)
-        h = height,              -- 屏幕高度(像素)
-        xoffset = 0,             -- X方向偏移量
-        yoffset = 0,             -- Y方向偏移量
-        sleepcmd = 0x10,         -- 睡眠命令
-        wakecmd = 0x11           -- 唤醒命令
-    }
-
-    -- 初始化SPI设备
-    spi.deviceSetup(
-        lcd.HWID_0,  -- LCD端口号
-        nil,         -- CS片选脚,可选
-        0,           -- CPHA=0
-        0,           -- CPOL=0
-        8,           -- 8位数据宽度
-        20000000,    -- 20MHz波特率
-        spi.MSB,     -- 高位先传
-        1,           -- 主机模式
-        1            -- 全双工模式
-    )
-
-    -- 设置VCC/RST/背光引脚为输出模式,并启用上拉电阻
-    gpio.setup(lcd_vcc, 1, gpio.PULLUP)
-    gpio.setup(lcd_pin_rst, 0, gpio.PULLUP)
-    gpio.setup(lcd_pin_pwr, 1, gpio.PULLUP)
-    
-    -- 开启屏幕VCC供电
-    gpio.set(lcd_vcc, 1)
-
-    -- 拉低屏幕复位引脚
-    gpio.set(lcd_pin_rst, 0)  -- 拉低复位引脚
-    sys.wait(20)                    -- 等待20ms(ST7796复位拉低大于10ms生效)
-    gpio.set(lcd_pin_rst, 1)  -- 拉高复位引脚
-    sys.wait(150)                   -- 等待150ms(ST7796复位拉高大于120ms重置)
-    gpio.set(lcd_pin_pwr, 1)
-
-    -- 初始化ST7796显示芯片
-    lcd.init("st7796", lcd_param)
-
-    -- 通用显示设置
-    lcd.setupBuff(nil,false)     -- 设置帧缓冲区
-    lcd.autoFlush(false)         -- 禁止自动刷新
-
-    screen_state.lcd_pin_pwr = lcd_pin_pwr
-    screen_state.lcd_pwm_id = lcd_pwm_id
-
-    log.info("AirLCD_1000", "LCD初始化成功,尺寸:", width, "x", height)
-    -- return true
-end
-
--- 设置背光亮度接口
--- 使用背光PWM模式控制亮度,休眠和关闭背光使用背光引脚GPIO模式
--- @param level 亮度级别(0-100)
--- @return 设置成功状态
-function AirLCD_1000.set_backlight(level)
-
-    -- 检查屏幕状态和PWM配置
-    if screen_state.is_sleeping then
-        log.warn("AirLCD_1000", "屏幕处于休眠状态,无法调节背光")
-        return false
-    end
-    if not screen_state.lcd_pin_pwr then
-        log.error("AirLCD_1000", "PWM配置不存在,无法调节背光")
-        return false
-    end
-
-    -- 确保GPIO已关闭 
-    gpio.close(screen_state.lcd_pin_pwr)
-
-    -- 设置并开启PWM
-    pwm.stop(screen_state.lcd_pwm_id)
-    pwm.close(screen_state.lcd_pwm_id)
-    pwm.setup(screen_state.lcd_pwm_id, 1000, 100)
-    pwm.open(screen_state.lcd_pwm_id, 1000, level)
-
-    -- 修改默认背光亮度为当前设置
-    screen_state.last_brightness = level
-    screen_state.backlight_on = (level > 0)
-    log.info("AirLCD_1000", "背光设置为", level, "%")
-    return true
-end
-
--- 开启LCD显示屏背光
--- @return 操作成功状态
-function AirLCD_1000.lcd_on()
-    if screen_state.is_sleeping then
-        log.warn("AirLCD_1000", "屏幕处于休眠状态,无法开启背光")
-        return false
-    end
-
-    pwm.stop(screen_state.lcd_pwm_id)
-    pwm.close(screen_state.lcd_pwm_id)
-
-    -- 设置GPIO控制电源(如果存在)
-    if screen_state.lcd_pin_pwr then
-        gpio.setup(screen_state.lcd_pin_pwr, 1, gpio.PULLUP)
-    end
-
-    lcd.on()
-    log.info("AirLCD_1000", "LCD背光已开启")
-end
-
--- 关闭LCD显示屏背光
--- @return 操作成功状态
-function AirLCD_1000.lcd_off()
-    if screen_state.is_sleeping then
-        log.warn("AirLCD_1000", "屏幕处于休眠状态,无法关闭背光")
-        return false
-    end
-    pwm.stop(screen_state.lcd_pwm_id)
-    pwm.close(screen_state.lcd_pwm_id)
-        -- 设置GPIO控制电源(如果存在)
-    if screen_state.lcd_pin_pwr then
-        gpio.setup(screen_state.lcd_pin_pwr, 1, gpio.PULLUP)
-    end
-    lcd.off()
-    log.info("AirLCD_1000", "LCD背光已关闭")
-end
-
--- 设置休眠状态接口
--- @param sleep true进入休眠, false唤醒
--- @return 操作成功状态
-function AirLCD_1000.set_sleep(sleep)
-    if sleep then
-        -- 进入休眠模式
-        if not screen_state.is_sleeping then
-            -- 关闭PWM(如果存在)
-            if screen_state.lcd_pwm_id then
-                pwm.stop(screen_state.lcd_pwm_id)
-                pwm.close(screen_state.lcd_pwm_id)
-            end
-
-            -- 设置GPIO控制电源(如果存在)
-            if screen_state.lcd_pin_pwr then
-                gpio.setup(screen_state.lcd_pin_pwr, 1, gpio.PULLUP)
-                gpio.set(screen_state.lcd_pin_pwr, 0)
-            end
-
-            -- 执行LCD睡眠
-            lcd.sleep()
-            screen_state.is_sleeping = true
-            log.info("AirLCD_1000", "LCD进入休眠状态")
-        end
-    else
-        -- 退出休眠模式
-        if screen_state.is_sleeping then
-            -- 唤醒LCD
-            lcd.wakeup()
-
-            -- 关闭GPIO控制(如果存在)
-            if screen_state.lcd_pin_pwr then
-                gpio.close(screen_state.lcd_pin_pwr)
-            end
-
-            -- 恢复之前的背光设置(如果PWM存在)
-            if screen_state.lcd_pin_pwr then
-                if screen_state.backlight_on then
-                    AirLCD_1000.set_backlight(screen_state.last_brightness)
-                else
-                    -- 如果背光原本是关闭状态,保持关闭
-                    pwm.stop(screen_state.lcd_pwm_id)
-                    pwm.close(screen_state.lcd_pwm_id)
-                end
-            end
-
-            screen_state.is_sleeping = false
-            log.info("AirLCD_1000", "LCD唤醒")
-        end
-    end
-    return true
-end
-
-return AirLCD_1000

+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_12.bin → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_12.bin


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_22.bin → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_22.bin


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_drv.lua → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_drv.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/images/logo.jpg → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/images/logo.jpg


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/lcd_drv/exlcd_drv.lua → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/lcd_drv/exlcd_drv.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/lcd_drv/lcd_drv.lua → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/lcd_drv/lcd_drv.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/main.lua → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/main.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/readme.md → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/readme.md


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/tp_key_drv/key_drv.lua → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/tp_key_drv/key_drv.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/ui/customer_font_page.lua → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/customer_font_page.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/ui/home_page.lua → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/home_page.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/ui/lcd_page.lua → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/lcd_page.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1000/lcd/ui/ui_main.lua → module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/ui_main.lua


BIN
module/Air780EPM/demo/accessory_board/AirLCD_1000/logo.jpg


+ 0 - 203
module/Air780EPM/demo/accessory_board/AirLCD_1000/readme.md

@@ -1,203 +0,0 @@
-# AirLCD_1000_Air780EPM 演示 demo
-
-
-## 一、功能模块介绍
-
-1. main.lua:主程序入口;
-
-2. ui_main.lua:UI应用模块,负责UI启动初始和UI主循环
-
-3. AirLCD_1000.lua:显示驱动模块,负责执行AirLCD_1000初始化,背光亮度调节和屏幕休眠和唤醒
-
-## 二、演示功能概述
-
-1. 屏幕接线引脚配置在AirLCD_1000.lua内进行设置
-
-2. 设置好后,启动主循环屏幕初始化程序,点亮屏幕
-
-3. 进入UI主task,循环显示显示图片、字符、色块等内容,并通过接口设置背光亮度和对屏幕进行休眠和唤醒。
-
-## 三、演示硬件环境
-
-### 3.1 硬件准备
-
-1. AirLCD_1000 显示屏*1
-
-2. Air780EPM 开发板或核心板*1
-
-3. 母对母杜邦线*8
-
-4. TYPE-C 数据线*1
-
-5. 按照硬件接线图正确连接 LCD 显示屏
-
-6. 将核心板正面开关打到 ON 位置
-
-7. 本demo使用的背光引脚,既是GPIO_1也是PWM_0引脚
-
-8. 调节屏幕亮度需要确保背光控制引脚(如 demo 中 GPIO1)支持 PWM 功能
-
-9. 背光引脚通过切换到PWM模式,通过调节占空比实现背光亮度调节
-
-10. 背光引脚通过切换到GPIO模式,再调用休眠/唤醒接口使屏幕进入休眠/唤醒
-
-### 3.2接线说明
-
-<table>
-<tr>
-<td>引脚说明<br/></td><td>核心开发板<br/></td><td>屏幕<br/></td><td>引脚说明<br/></td></tr>
-<tr>
-<td>屏幕供电<br/></td><td>VDD-EXT<br/><br/></td><td>VCC<br/><br/></td><td>显示屏电源供电脚<br/><br/></td></tr>
-<tr>
-<td>电源地<br/></td><td>GND<br/></td><td>GND<br/></td><td>电源地<br/></td></tr>
-<tr>
-<td>LCD SPI 串口的时钟信号<br/></td><td>LCD_CLK<br/></td><td>CLK<br/></td><td>SPI 串口的时钟信号<br/></td></tr>
-<tr>
-<td>LCD SPI 串口的数据脚<br/></td><td>LCD_SDA<br/></td><td>MOS<br/></td><td>SPI 串口的数据输入脚<br/></td></tr>
-<tr>
-<td>LCD 复位脚<br/></td><td>LCD_RST<br/></td><td>RES<br/></td><td>显示屏复位脚<br/></td></tr>
-<tr>
-<td>LCD 数据/寄存器控制脚<br/></td><td>LCD_RS<br/><br/></td><td>DC<br/></td><td>4 线 SPI 串口的显示数据/寄存器指令<br/></td></tr>
-<tr>
-<td>LCD SPI片选,同一个SPI接口上有多个设备才使用<br/></td><td>LCD_CS<br/></td><td>CS<br/></td><td>LCD驱动芯片片选脚<br/></td></tr>
-<tr>
-<td>LCD 背光控制引脚<br/></td><td>GPIO_1/PWM0<br/></td><td>BKL<br/></td><td>背光使能引脚<br/></td></tr>
-</table>
-
-![](https://docs.openLuat.com/cdn/image/Air780EPM_AirLCD_1000.jpg)
-
-注意:模组GPIO供电能力弱,所以使用VDD-EXT供电。
-
-
-## 四、演示软件环境
-
-### 4.1 底层固件准备
-
-固件版本,推荐使用 V2015 及以后最新固件版本:
-
-  [Air780EPM固件下载链接(Air780EHM固件下载下方)](https://docs.openluat.com/air780epm/luatos/firmware/version/)
-
-
-
-### 4.2Luatools下载调试工具
-[demo下载调试工具:Luatools](https://docs.openluat.com/air780egh/luatos/common/download/?h=luatools#33-luatools)
-
-### 4.2演示emo下载
-下载本 demo中除readme外的所有的代码文件以及图片文件
-
-## 五、演示核心步骤
-
-### 5.1 烧录程序
-
-1. 下载 luatools 烧录工具:[luatools下载链接及使用说明](https://docs.openluat.com/air780epm/common/Luatools/)
-
-2. 按照操作说明,将本 demo 内除 readme.md 外的全部文件通过 luatools 下载到模块内**(按住 BOOT 键开机,选择下载底层和脚本)**
-
-3. 如需显示图片,如 demo 中显示的 logo,确保 `logo.jpg` 图片文件存在,下载时会放在模组 `/luadb/` 目录下
-   ![](https://docs.openluat.com/air8000/luatos/app/AirLCD_1000%E6%BC%94%E7%A4%BAdemo/imges/UdWZbZ0tXoYPK5xG2Kbcx2y7nSc.png)
-
-
-### 5.2、演示 demo 效果
-
-1. 通过 demo 了解屏幕连接、配置、初始化过程,为显示做好准备
-
-2. 通过 demo 将图片、12 号中文、英文、点、线、圆形、矩形、颜色填充、二维码显示在屏幕上
-
-3. 通过 demo 了解如何使用接口对屏幕进行背光亮度调节、屏幕休眠、屏幕唤醒
-
-4. 实现效果图
-
-![](https://docs.openLuat.com/cdn/image/Air780EPM_AirLCD_1000_2.jpg)
-
-![](https://docs.openluat.com/air8000/luatos/app/AirLCD_1000%E6%BC%94%E7%A4%BAdemo/imges/BaH5b9WnKomL4SxNJxvc5rZKnIg.png)
-
-
-
-
-## 六、程序对外接口
-
-### 6.1 AirLCD_1000 模块接口
-
-#### `AirLCD_1000.lcd_init()`
-
-- 功能:初始化 AirLCD_1000LCD 显示屏
-- 参数:
-
-  - LCD_MODEL: LCD 型号字符串(如"AirLCD_1000")
-  - lcd_pin_dc: 数据/命令引脚号
-  - lcd_pin_rst: 复位引脚号
-  - lcd_pin_pwr: 背光控制引脚号
-- 返回值:初始化成功状态(true/false), 屏幕宽度, 屏幕高度
-
-#### `AirLCD_1000.set_backlight(level)`
-
-- 功能:设置背光亮度
-- 参数:level - 亮度级别(0-100)
-- 返回值:设置成功状态(true/false)
-
-#### `AirLCD_1000.set_sleep(sleep)`
-
-- 功能:设置屏幕休眠或唤醒
-- 参数:sleep - true 进入休眠, false 唤醒
-- 返回值:设置成功状态(true/false)
-- **注意事项:目前在屏幕休眠模式下,仅 AIR780EPM 支持模组设置 pm.power(pm.WORK_MODE, 1),其他型号暂时不支持,唤醒模组会导致模组死机**
-
-### 6.2 screen_data_table 配置参数
-
-- `lcd_models`: 支持的 LCD 型号及其参数
-- `default`: 默认配置(滑动阈值、点击时间阈值等)预留后续扩展功能使用
-
-### 6.3 更多显示接口
-
-- 可以参考合宙 docs 上 LCD 库进行扩展使用:[https://docs.openluat.com/osapi/core/lcd/](https://docs.openluat.com/osapi/core/lcd/)
-
-## 七、扩展说明
-
-1. 目前支持 ST7796、ST7789、CO5300 显示芯片,其他 st7735 、st7735v、st7735s、gc9a01、gc9106l、gc9306x、ili9486 也同样支持,
-
-2. 模组字体支持有限,需要使用其他字体可以搭配合宙 AirFONTS_1000 矢量字库使用
-
-3. Air780EPM/EHM/EGH/EHV、Air8000 系列、Air8101,按正确的接口接线并修改 screen_data_table.lua 都可以使用此 demo
-
-4. 其他屏幕可以参考 custom 的方式自定义屏幕初始化配置
-
-```lua
--- 配置接口参数
-local lcd_param = {
-        port = lcd.HWID_0,        -- 使用的spi id 号
-        pin_dc = 0xff,            -- 命令选择引脚
-        pin_rst = 36,             -- 复位引脚
-        direction = 0,            -- 屏幕方向
-        w = width,                -- 屏幕宽度
-        h = height,               -- 屏幕高度
-        xoffset = 0,              -- X轴偏移像素
-        yoffset = 0,              -- Y轴偏移像素
-        sleepcmd = 0x10,          -- LCD睡眠命令
-        wakecmd = 0x11,           -- LCD唤醒命令
-    }
-    
-  -- 初始化SPI设备
-        spi.deviceSetup(
-            lcd.HWID_0,  -- LCD端口号
-            nil,         -- CS片选脚,可选
-            0,           -- CPHA=0
-            0,           -- CPOL=0
-            8,           -- 8位数据宽度
-            20000000,    -- 20MHz波特率
-            spi.MSB,     -- 高位先传
-            1,           -- 主机模式
-            1            -- 全双工模式
-        )
-        
-    -- QSPI如有特殊配置,需要在lcd.init前配置好
-    --lcd.qspi(0x02, 0x32, 0x12)
-        
-    -- 初始化屏幕 
-    lcd.init("custom", lcd_param)
-    
-    -- 如有其他参数需要配置,可使用lcd.cmd命令
-    --lcd.cmd(0x53, 0x20)
-        
-    -- 自定义初始化后必须运行该程序
-    lcd.user_done()
-```

+ 0 - 187
module/Air780EPM/demo/accessory_board/AirLCD_1000/ui_main.lua

@@ -1,187 +0,0 @@
---[[
-@module  ui_main
-@summary UI子模块的主程序
-@version 1.0
-@date    2025.09.04
-@author  江访
-@usage
-
-本demo演示的核心功能为:
-1、依据显示屏配置参数初始化显示屏,点亮AirLCD_1000屏幕
-2、循环显示显示图片、字符、色块等内容
-3、通过接口设置背光亮度和对屏幕进行休眠和唤醒。
-4、本demo使用的背光引脚,既是GPIO_1也是PWM_0引脚
-5、背光引脚通过切换到PWM模式,通过调节占空比实现背光亮度调节
-6、背光引脚通过切换到GPIO模式,再调用休眠/唤醒接口使屏幕进入休眠/唤醒
-]]
-
-
--- 加载AirLCD_1000驱动模块
-local AirLCD_1000 = require "AirLCD_1000"           -- 显示初始化执行程序
-
--- 配置AirLCD_1000接线引脚
-local LCD_MODEL = "AirLCD_1000" -- 显示屏型号
-local lcd_vcc = 29              -- 屏幕供电引脚GPIO号,VDD-EXT供电可填255
-local lcd_pin_rst = 36          -- 复位引脚GPIO号
-local lcd_pin_pwr = 1           -- 背光引脚GPIO号
-local lcd_pwm_id = 0            -- 背光引脚PWM端口号
-
--- UI主task,所有UI相关的代码都会通过该task进行调度
-local function ui_main()
-
-    AirLCD_1000.lcd_init(LCD_MODEL, lcd_vcc, lcd_pin_rst, lcd_pin_pwr,lcd_pwm_id)
-
-    -- 设置字体为模组自带的opposansm12中文字体
-    lcd.setFont(lcd.font_opposansm12_chinese)
-
-    -- 清除屏幕显示
-    -- lcd.clear()
-
-    -- 主循环
-    while true do
-        -- 获取并打印屏幕尺寸信息,实际是初始化传入的信息
-        -- log.info("屏幕尺寸", lcd.getSize())
-        
-        ------------------------------------------以下为图片/位图/英文显示设置------------------------------------------
-        --设置前景色和背景色(RGB565格式)
-        --需要放在循环内部,因为是一次性设置
-        lcd.setColor(0xFFFF, 0x0000)  -- 白底黑字,背景色:白色(0xFFFF), 前景色:黑色(0x0000),
-
-        -- 在屏幕左上角(0,0)显示一张图片
-        -- 图片路径为/luadb/logo.jpg
-        lcd.showImage(0, 0, "/luadb/logo.jpg")
-
-        -- 在位置(0,82)绘制一个16x16的位图,内容依次为“上”,“海”,“合”,“宙”
-        -- 位图数据使用字符串格式表示
-        lcd.drawXbm(0, 82, 16, 16, string.char(
-        0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x3F,0x80,0x00,
-        0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xFE,0x7F,0x00,0x00))
-
-        lcd.drawXbm(18, 82, 16, 16, string.char(
-        0x00,0x00,0x80,0x00,0xC4,0x7F,0x28,0x00,0x10,0x00,0xD0,0x3F,0x42,0x20,0x44,0x22,
-        0x40,0x24,0xF0,0xFF,0x24,0x20,0x24,0x22,0x24,0x20,0xE2,0x7F,0x02,0x20,0x02,0x1E))
-
-        lcd.drawXbm(36, 82, 16, 16, string.char(
-        0x00,0x00,0x00,0x01,0x80,0x01,0x40,0x02,0x20,0x04,0x18,0x18,0xF4,0x6F,0x02,0x00,
-        0x00,0x00,0xF8,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0xF8,0x1F,0x08,0x10))
-
-        lcd.drawXbm(54, 82, 16, 16, string.char(
-        0x00,0x00,0x80,0x00,0x00,0x01,0xFE,0x7F,0x02,0x40,0x02,0x40,0x00,0x01,0xFC,0x3F,
-        0x04,0x21,0x04,0x21,0xFC,0x3F,0x04,0x21,0x04,0x21,0x04,0x21,0xFC,0x3F,0x04,0x20))
-
-        -- 在位置(120,40)绘制一个蓝色点
-        lcd.drawPoint(120, 40, 0x001F)
-
-        -- 以(120,40)为圆心,40为半径绘制一个蓝色圆
-        lcd.drawCircle(120, 40, 40, 0x001F)
-
-        -- 从(170,40)到(280,40)绘制一条蓝色水平线
-        lcd.drawLine(170, 40, 280, 40, 0x001F)
-
-        -- 从(170,50)到(280,80)绘制一个蓝色矩形框
-        lcd.drawRectangle(170, 50, 280, 80, 0x001F)
-
-
-        -- 在位置(200,170)绘制一个100x100的二维码,内容为指定URL
-        lcd.drawQrcode(200, 170, "https://docs.openluat.com/air8000/", 100)
-
-        lcd.setFont(lcd.font_opposansm12)
-        lcd.drawStr(20,172,"hello hezhou") --显示字符
-        lcd.setFont(lcd.font_opposansm16)
-        lcd.drawStr(20,189,"hello hezhou") --显示字符
-        lcd.setFont(lcd.font_opposansm18)
-        lcd.drawStr(20,210,"hello hezhou") --显示字符
-        lcd.setFont(lcd.font_opposansm20)
-        lcd.drawStr(20,233,"hello hezhou") --显示字符
-        lcd.setFont(lcd.font_opposansm22)
-        lcd.drawStr(20,258,"hello hezhou") --显示字符
-        lcd.setFont(lcd.font_opposansm24)
-        lcd.drawStr(20,285,"hello hezhou") --显示字符
-        lcd.setFont(lcd.font_opposansm32)
-        lcd.drawStr(20,316,"hello hezhou") --显示字符
-
-        lcd.fill(10, 380, 150, 460, 0xF800)  -- 绘制红色矩形区域(0xF800是红色)
-        lcd.fill(170, 380, 310, 460, 0x07E0)  -- 绘制绿色矩形区域(0x07E0是绿色)
-
-        -- 主动刷新数据到屏幕
-        lcd.flush()
-        sys.wait(5000)
-
-        --------------------------------------------以下为显示中文设置--------------------------------------------
-        -- Air780EPM不支持中文显示
-        -- Air780EHM/EGH/EHV/Air8000支持12号中文字体
-        -- 中文以左下角为坐标显示与位图左上角方式不同
-
-        -- 设置字体为opposansm12中文字体,从英文显示切换到中文前一定要设置
-        lcd.setFont(lcd.font_opposansm12_chinese)
-        lcd.setColor(0xFFFF, 0x0000)  -- 白底黑字,背景色:白色(0xFFFF), 前景色:黑色(0x0000),
-
-        -- 显示重拍按钮(左侧)
-        lcd.drawStr(70, 420, "重拍", 0xFFFF)  -- 在按钮上绘制白色文字"重拍"
-
-        -- 显示返回按钮(右侧)
-        lcd.drawStr(230, 420, "返回", 0xFFFF)  -- 在按钮上绘制白色文字"返回"
-
-        -- 在位置(160,155)绘制文本
-        -- 不设置字体颜色,默认会使用lcd.setColor所设置的字体颜色
-        lcd.drawStr(160, 155, "扫码进入Air8000资料站",0x0000)
-
-        -- 主动刷新数据到屏幕
-        lcd.flush()
-        sys.wait(5000)
-        --------------------------------------------以下为背光亮度设置--------------------------------------------
-        -- 背光引脚使用PWM模式控制,pwm_id正确配置后可以实现背光控制
-        -- 参数 level: 亮度级别(0-100)
-        AirLCD_1000.set_backlight(5)  -- 设置背光为5%
-        sys.wait(5000)
-        AirLCD_1000.set_backlight(10)  -- 设置背光为10%
-        sys.wait(5000)
-        AirLCD_1000.set_backlight(20)  -- 设置背光为20%
-        sys.wait(5000)
-        AirLCD_1000.set_backlight(30)  -- 设置背光为30%
-        sys.wait(5000)
-        AirLCD_1000.set_backlight(40)  -- 设置背光为40%
-        sys.wait(5000)
-        AirLCD_1000.set_backlight(50)  -- 设置背光为50%
-        sys.wait(5000)
-        AirLCD_1000.set_backlight(60)  -- 设置背光为60%
-        sys.wait(5000)
-        AirLCD_1000.set_backlight(70)  -- 设置背光为70%
-        sys.wait(5000)
-        AirLCD_1000.set_backlight(80)  -- 设置背光为80%
-        sys.wait(5000)
-        AirLCD_1000.set_backlight(90)  -- 设置背光为90%
-        sys.wait(5000)
-        AirLCD_1000.set_backlight(100)  -- 设置背光为100%
-
-        --------------------------------------------以下为屏幕休眠设置--------------------------------------------
-        -- 背光引脚使用GPIO模式控制,pin_pwr正确配置可以实现背光控制
-
-        -- 进入休眠,功耗13ma
-        AirLCD_1000.set_sleep(true)    -- 进入休眠状态,此时屏幕供电需要稳定,若不稳定,唤醒后需要重新初始化屏幕
-        pm.power(pm.WORK_MODE, 1)
-        sys.wait(10000)
-        pm.power(pm.WORK_MODE, 0)
-        AirLCD_1000.set_sleep(false)   -- 唤醒屏幕,自动恢复之前的背光设置
-
-        -- 主动刷新数据到屏幕
-        lcd.flush()
-        sys.wait(5000)
-        ----------------------------------------以下为屏幕关闭/打开背光设置----------------------------------------
-        -- --关闭LCD背光,功耗20ma
-        -- AirLCD_1000.lcd_off()
-        -- pm.power(pm.WORK_MODE, 1)
-        -- sys.wait(15000)
-        -- -- 唤醒
-        -- pm.power(pm.WORK_MODE, 0)
-        -- sys.wait(5000)
-        -- AirLCD_1000.lcd_on()
-
-        -- 主动刷新数据到屏幕
-        -- lcd.flush()
-        -- sys.wait(50)
-    end
-end
-
--- 创建UI主循环ui_main的task
-sys.taskInit(ui_main)

+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_12.bin → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_12.bin


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_22.bin → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_22.bin


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_drv.lua → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_drv.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/images/logo.jpg → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/images/logo.jpg


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/lcd_drv/exlcd_drv.lua → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/lcd_drv/exlcd_drv.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/lcd_drv/lcd_drv.lua → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/lcd_drv/lcd_drv.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/main.lua → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/main.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/readme.md → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/readme.md


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/tp_key_drv/extp_drv.lua → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/tp_key_drv/extp_drv.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/tp_key_drv/tp_drv.lua → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/tp_key_drv/tp_drv.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/ui/customer_font_page.lua → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/customer_font_page.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/ui/home_page.lua → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/home_page.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/ui/lcd_page.lua → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/lcd_page.lua


+ 0 - 0
module/Air780EPM/accessory_board/AirLCD_1010/lcd/ui/ui_main.lua → module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/ui_main.lua


+ 66 - 0
module/Air780EPM/demo/rtos/main.lua

@@ -0,0 +1,66 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 001.000.000
+@date    2025.12.2
+@author  王城钧
+@usage
+1. rtos_app:对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+]]
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "rtos_demo"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+--加载rtos时钟驱动模块
+require "rtos_app"
+
+
+-- 启动系统调度(必须放在最后)
+sys.run()

+ 54 - 0
module/Air780EPM/demo/rtos/readme.md

@@ -0,0 +1,54 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、rtos_app:对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试;
+
+## 演示功能概述
+
+1、对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+
+## 演示硬件环境
+
+1、Air780EPM开发板一块;
+
+![netdrv_multi](https://docs.openLuat.com/cdn/image/Air780EPM开发板.jpg)
+
+2、TYPE-C USB数据线一根
+
+* Air780EPM开发板通过 TYPE-C USB 口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+* TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EPM V2018版本]([固件和应用脚本Demo - luatos@air780epm - 合宙模组资料中心](https://docs.openluat.com/air780epm/luatos/firmware/))(理论上最新版本固件也可以,如果使用最新版本的固件不可以,可以烧录V2018-1固件对比验证)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、可以看到代码运行结果如下:
+
+日志中如果出现以下类似以下打印则说明rtos功能正常
+
+```lua
+[2025-12-08 10:37:40.798][000000000.217] I/user.main rtos_demo 001.000.000
+[2025-12-08 10:37:40.802][000000000.224] I/user.固件信息 版本: V2018 1
+[2025-12-08 10:37:40.806][000000000.224] I/user.编译信息 日期: Nov  7 2025 BSP: Air780EPM
+[2025-12-08 10:37:40.808][000000000.225] I/user.完整描述 LuatOS-SoC_V2018_Air780EPM
+[2025-12-08 10:37:40.813][000000000.225] I/user.内存信息 Lua - 总: 1048568 已用: 35168 峰值: 35168 系统 - 总: 2375368 已用: 53796 峰值: 59060
+[2025-12-08 10:37:40.816][000000000.225] D/rtos mem collect param 100,80,90 -> 200,75,85
+[2025-12-08 10:37:40.821][000000000.226] I/user.RTOS测试 所有测试已启动
+[2025-12-08 10:37:42.437][000000002.322] D/mobile cid1, state0
+[2025-12-08 10:37:42.442][000000002.323] D/mobile bearer act 0, result 0
+[2025-12-08 10:37:42.449][000000002.324] D/mobile NETIF_LINK_ON -> IP_READY
+[2025-12-08 10:37:42.456][000000002.364] D/mobile TIME_SYNC 0
+[2025-12-08 10:37:50.317][000000010.242] I/user.性能测试 1000次nop耗时: 16 毫秒
+
+```

+ 60 - 0
module/Air780EPM/demo/rtos/rtos_app.lua

@@ -0,0 +1,60 @@
+--[[
+@module  rtos_app
+@summary rtos应用模块
+@version 1.0
+@date    2025.12.2
+@author  王城钧
+@usage
+本文件为rtos应用模块,核心业务逻辑为:
+1、对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+
+本文件没有对外接口,直接在其他功能模块中require "rtos_app"就可以加载运行;
+]]
+
+-- 1. 系统信息查询测试
+-- 读取版本号及数字版本号, 2025.11.1之后的固件支持
+-- 如果不是数字固件,luatos_version_num 会是0
+-- 如果是不支持的固件, luatos_version_num 会是nil
+local luatos_version, luatos_version_num = rtos.version(true)
+log.info("固件信息", "版本:", luatos_version, luatos_version_num )
+
+log.info("编译信息", "日期:", rtos.buildDate(), "BSP:", rtos.bsp())
+log.info("完整描述", rtos.firmware())
+
+-- 2. 内存信息测试
+local total_lua, used_lua, max_used_lua = rtos.meminfo("lua")
+local total_sys, used_sys, max_used_sys = rtos.meminfo("sys")
+log.info("内存信息", 
+    "Lua - 总:", total_lua, "已用:", used_lua, "峰值:", max_used_lua,
+    "系统 - 总:", total_sys, "已用:", used_sys, "峰值:", max_used_sys)
+
+-- 3. 定时器测试
+-- rtos.timer_start()和rtos.timer_stop()两个接口,仅仅给sys核心库使用
+-- 如果要使用定时器,直接使用sys核心库提供的定时器接口即可
+-- 用户脚本中不要直接使用rtos.timer_start()和rtos.timer_stop()两个接口
+-- 否则和sys核心库中的定时器功能出现冲突而导致系统异常的问题
+
+-- 4. 内存自动回收配置
+rtos.autoCollectMem(200, 75, 85)  -- 配置较宽松的自动回收
+
+-- 5. 空函数测试(性能测试用)
+local function test_nop()
+    local start = mcu.ticks()
+    for i = 1, 1000 do
+        rtos.nop()
+    end
+    local duration = mcu.ticks() - start
+    log.info("性能测试", "1000次nop耗时:", duration, "毫秒")
+end
+
+-- 10秒后执行空函数测试
+sys.timerStart(test_nop, 10000)
+
+-- 6. 重启测试(注释掉防止意外重启)
+-- local function reboot()
+--     log.info("系统", "准备重启...")
+--     rtos.reboot()
+-- end
+-- sys.timerStart(reboot, 30000)
+
+log.info("RTOS测试", "所有测试已启动")

+ 1 - 1
module/Air8000/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_default_font_drv.lua

@@ -32,7 +32,7 @@ ui.hw_init({
         -- pin_vcc = 24,           -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = nil,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 1 - 1
module/Air8000/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_gtfont_drv.lua

@@ -35,7 +35,7 @@ ui.hw_init({
         -- pin_vcc = 24,                  -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 1 - 1
module/Air8000/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_hzfont_drv.lua

@@ -35,7 +35,7 @@ ui.hw_init({
         -- pin_vcc = 24,           -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 1 - 1
module/Air8000/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_default_font_drv.lua

@@ -32,7 +32,7 @@ ui.hw_init({
         -- pin_vcc = 24,                  -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 1 - 1
module/Air8000/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_gtfont_drv.lua

@@ -35,7 +35,7 @@ ui.hw_init({
         -- pin_vcc = 24,                  -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 1 - 1
module/Air8000/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_hzfont_drv.lua

@@ -35,7 +35,7 @@ ui.hw_init({
         -- pin_vcc = 24,           -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 17 - 3
module/Air8000/demo/accessory_board/AirSHT30_1000/readme.md

@@ -17,7 +17,7 @@ Air8000核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
 
 ## 核心板+配件板资料
 
-[Air8000核心板](https://docs.openluat.com/air8000/product/shouce/#air8000_1)
+[Air8000核心板/Air8000开发板](https://docs.openluat.com/air8000/product/shouce/#air8000_1)
 
 [AirSHT30_1000配件板相关资料](https://docs.openluat.com/accessory/AirSHT30_1000/)
 
@@ -26,14 +26,18 @@ Air8000核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
 
 ![](https://docs.openluat.com/accessory/AirSHT30_1000/image/connect_8000.jpg)
 
+![](https://docs.openluat.com/accessory/AirSHT30_1000/image/connect_8000_board.jpg)
+
 ![](https://docs.openluat.com/accessory/AirSHT30_1000/image/8000.png)
 
-1、Air8000核心板
+1、Air8000核心板 或 Air8000开发板
 
 2、AirSHT30_1000配件板
 
 3、母对母的杜邦线4根
 
+Air8000核心板与AirSHT30_1000配件板连接方式如下:
+
 | Air8000核心板 | AirSHT30_1000配件板|
 | ------------ | ------------------ |
 |     VDD_EXT     |         3V3        |
@@ -41,6 +45,14 @@ Air8000核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
 | I2C1_SDA  |         SDA        |
 | I2C1_SCL |         SCL        |
 
+Air8000开发板与AirSHT30_1000配件板连接方式如下:
+
+| Air8000开发板 | AirSHT30_1000配件板 |  稳压电源  |
+|  -----------  | ------------------ | ----------- |
+|      不接     |         3V3        |     3V3     |
+|      不接     |         GND        |     GND     |
+|   I2C0_SDA    |         SDA        |     不接     |
+|   I2C0_SCL    |         SCL        |     不接     |
 
 ## 演示软件环境
 
@@ -53,7 +65,9 @@ Air8000核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
 
 1、搭建好演示硬件环境
 
-2、不需要修改demo脚本代码
+2、使用Air8000核心板不需要修改demo脚本代码
+
+3、使用Air8000开发板,需要在`sht30_app.lua`中将`air_sht30.open(0)`和`gpio.setup(164, 1, gpio.PULLUP)`打开,同时屏蔽掉`air_sht30.open(1)`
 
 3、Luatools烧录内核固件和demo脚本代码
 

+ 12 - 1
module/Air8000/demo/accessory_board/AirSHT30_1000/sht30_app.lua

@@ -8,6 +8,8 @@
 本文件为sht30_app应用功能模块,核心业务逻辑为:
 1、每隔1秒读取一次温湿度数据;
 
+本demo默认使用Air8000核心板进行演示,如果需要使用开发板,需要打开音频的codec;
+
 本文件没有对外接口,直接在main.lua中require "sht30_app"就可以加载运行;
 ]]
 
@@ -19,7 +21,16 @@ local air_sht30 = require "AirSHT30_1000"
 --每隔1秒读取一次温湿度数据
 local function read_sht30_task_func()
     
-    --打开sht30硬件
+    --使用Air8000开发板打开下面注释
+    --使用i2c0打开sht30硬件
+    -- air_sht30.open(0)
+
+    --注:因为整机开发板的音频i2c 共用,不打开会导致I2C 对地
+    --所以如果使用整机开发板,需要打开音频的codec
+    -- gpio.setup(164, 1, gpio.PULLUP) --打开音频的codec
+
+    --使用Air8000核心板打开下面注释(默认)
+    --使用i2c1打开sht30硬件
     air_sht30.open(1)
 
     while true do

+ 66 - 0
module/Air8000/demo/rtos/main.lua

@@ -0,0 +1,66 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 001.000.000
+@date    2025.12.2
+@author  王城钧
+@usage
+1. rtos_app:对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+]]
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "rtos_demo"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+--加载rtos时钟驱动模块
+require "rtos_app"
+
+
+-- 启动系统调度(必须放在最后)
+sys.run()

+ 50 - 0
module/Air8000/demo/rtos/readme.md

@@ -0,0 +1,50 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、rtos_app:对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试;
+
+## 演示功能概述
+
+1、对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+
+## 演示硬件环境
+
+1、Air8000开发板一块;
+
+![netdrv_multi](https://docs.openLuat.com/cdn/image/8000开发板.jpg)
+
+2、TYPE-C USB数据线一根
+
+* Air8000开发板通过 TYPE-C USB 口供电;(外部供电/USB供电 拨动开关 拨到 USB供电一端)
+* TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air8000 V2018版本]([固件和应用脚本Demo - luatos@air8000 - 合宙模组资料中心](https://docs.openluat.com/air8000/luatos/firmware/))(理论上最新版本固件也可以,如果使用最新版本的固件不可以,可以烧录V2018-1固件对比验证)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、可以看到代码运行结果如下:
+
+日志中如果出现以下类似以下打印则说明rtos功能正常
+
+```lua
+[2025-12-08 10:18:05.044][000000000.668] I/user.main rtos_demo 001.000.000
+[2025-12-08 10:18:05.047][000000000.679] I/user.固件信息 版本: V2018 1
+[2025-12-08 10:18:05.051][000000000.679] I/user.编译信息 日期: Nov  7 2025 BSP: Air8000
+[2025-12-08 10:18:05.055][000000000.679] I/user.完整描述 LuatOS-SoC_V2018_Air8000
+[2025-12-08 10:18:05.057][000000000.680] I/user.内存信息 Lua - 总: 4194296 已用: 36704 峰值: 36704 系统 - 总: 3207056 已用: 347628 峰值: 349884
+[2025-12-08 10:18:05.060][000000000.680] D/rtos mem collect param 100,80,90 -> 200,75,85
+[2025-12-08 10:18:05.063][000000000.681] I/user.RTOS测试 所有测试已启动
+[2025-12-08 10:18:14.967][000000010.696] I/user.性能测试 1000次nop耗时: 15 毫秒
+
+```

+ 60 - 0
module/Air8000/demo/rtos/rtos_app.lua

@@ -0,0 +1,60 @@
+--[[
+@module  rtos_app
+@summary rtos应用模块
+@version 1.0
+@date    2025.12.2
+@author  王城钧
+@usage
+本文件为rtos应用模块,核心业务逻辑为:
+1、对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+
+本文件没有对外接口,直接在其他功能模块中require "rtos_app"就可以加载运行;
+]]
+
+-- 1. 系统信息查询测试
+-- 读取版本号及数字版本号, 2025.11.1之后的固件支持
+-- 如果不是数字固件,luatos_version_num 会是0
+-- 如果是不支持的固件, luatos_version_num 会是nil
+local luatos_version, luatos_version_num = rtos.version(true)
+log.info("固件信息", "版本:", luatos_version, luatos_version_num )
+
+log.info("编译信息", "日期:", rtos.buildDate(), "BSP:", rtos.bsp())
+log.info("完整描述", rtos.firmware())
+
+-- 2. 内存信息测试
+local total_lua, used_lua, max_used_lua = rtos.meminfo("lua")
+local total_sys, used_sys, max_used_sys = rtos.meminfo("sys")
+log.info("内存信息", 
+    "Lua - 总:", total_lua, "已用:", used_lua, "峰值:", max_used_lua,
+    "系统 - 总:", total_sys, "已用:", used_sys, "峰值:", max_used_sys)
+
+-- 3. 定时器测试
+-- rtos.timer_start()和rtos.timer_stop()两个接口,仅仅给sys核心库使用
+-- 如果要使用定时器,直接使用sys核心库提供的定时器接口即可
+-- 用户脚本中不要直接使用rtos.timer_start()和rtos.timer_stop()两个接口
+-- 否则和sys核心库中的定时器功能出现冲突而导致系统异常的问题
+
+-- 4. 内存自动回收配置
+rtos.autoCollectMem(200, 75, 85)  -- 配置较宽松的自动回收
+
+-- 5. 空函数测试(性能测试用)
+local function test_nop()
+    local start = mcu.ticks()
+    for i = 1, 1000 do
+        rtos.nop()
+    end
+    local duration = mcu.ticks() - start
+    log.info("性能测试", "1000次nop耗时:", duration, "毫秒")
+end
+
+-- 10秒后执行空函数测试
+sys.timerStart(test_nop, 10000)
+
+-- 6. 重启测试(注释掉防止意外重启)
+-- local function reboot()
+--     log.info("系统", "准备重启...")
+--     rtos.reboot()
+-- end
+-- sys.timerStart(reboot, 30000)
+
+log.info("RTOS测试", "所有测试已启动")

+ 1 - 1
module/Air8000/demo/ui/easyui/single/hw_font_drv.lua

@@ -36,7 +36,7 @@ local hw_config = {
         -- pin_vcc = 24,                  -- 供电引脚,使用GPIO控制屏幕供电可配置
         pin_rst = 36,              -- 复位引脚
         pin_pwr = 1,               -- 背光控制引脚GPIO ID号
-        pin_pwm = 0,               -- 背光控制引脚PWM D号
+        pin_pwm = 0,               -- 背光控制引脚PWM ID号
         port = lcd.HWID_0,         -- 驱动端口
         -- pin_dc = 0xFF,          -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
         direction = 0,             -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致

+ 4 - 4
module/Air8000/demo/ui/easyui/single/main.lua

@@ -36,8 +36,8 @@
 - win_horizontal_slide: 横向滑动页面演示
 - win_vertical_slide: 纵向滑动页面演示
 - win_switch_page: 页面切换演示
-- win_hzfont: 矢量字体演示
-- win_gtfont: 点阵字体演示
+- win_hzfont: 内置软件矢量字体演示
+- win_gtfont: 外置硬件矢量字体演示
 
 更多说明参考本目录下的readme.md文件
 ]]
@@ -124,8 +124,8 @@ require("win_all_component")  --所有组件综合演示
 -- require("win_horizontal_slide")  --横向滑动页面演示
 -- require("win_vertical_slide")  --纵向滑动页面演示
 -- require("win_switch_page")  --页面切换演示
--- require("win_hzfont")  --矢量字体演示
--- require("win_gtfont")  --点阵字体演示
+-- require("win_hzfont")  --内置软件矢量字体演示
+-- require("win_hzfont")  --外置硬件矢量字体演示
 
 -- 用户代码已结束
 -- 结尾总是这一句

+ 2 - 2
module/Air8000/demo/ui/easyui/single/readme.md

@@ -243,8 +243,8 @@ require("win_all_component")  --所有组件综合演示
 -- require("win_horizontal_slide")  --横向滑动页面演示
 -- require("win_vertical_slide")  --纵向滑动页面演示
 -- require("win_switch_page")  --页面切换演示
--- require("win_hzfont")  --矢量字体演示
--- require("win_gtfont")  --点阵字体演示
+-- require("win_hzfont")  --内置软件矢量字体演示
+-- require("win_hzfont")  --外置硬件矢量字体演示
 ```
 
 ### 7.3 软件烧录步骤

+ 1 - 1
module/Air8000/demo/ui/easyui/single/win_horizontal_slide.lua

@@ -27,7 +27,7 @@ local function ui_main()
     local win = ui.window({ background_color = ui.COLOR_WHITE })
 
     -- 启用横向滚动,将两页内容并排布置
-    local page_w, page_h = 320, 480
+    local page_w, page_h = lcd.getSize()
     local totalW = page_w * 2
 
     -- 创建横向滑动窗口

+ 1 - 1
module/Air8000/demo/ui/easyui/single/win_vertical_slide.lua

@@ -27,7 +27,7 @@ local function ui_main()
     local win = ui.window({ background_color = ui.COLOR_WHITE })
 
     -- 启用纵向分页滚动,将两页内容上下排布
-    local page_w, page_h = 320, 480
+    local page_w, page_h = lcd.getSize()
     local total_h = page_h * 2
 
     -- 创建纵向滑动窗口

+ 2 - 1
module/Air8101/demo/accessory_board/AirLCD_1020/exeasyui/ui/home_page.lua

@@ -31,9 +31,10 @@ home_page.create()
 
 function home_page.create()
     -- 创建主页 - 使用全屏
+    local page_w, page_h = lcd.getSize()
     local home = ui.window({ 
         background_color = ui.COLOR_WHITE,
-        x = 0, y = 0, w = 800, h = 480  -- 全屏窗口
+        x = 0, y = 0, w = page_w, h = page_h  -- 全屏窗口
     })
     home.visible = true
 

+ 2 - 4
module/Air8101/demo/pwm/main.lua

@@ -2,7 +2,7 @@
 @module  main
 @summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
 @version 1.0
-@date    2025.11.05
+@date    2025.12.10
 @author  马梦阳
 @usage
 
@@ -20,11 +20,9 @@
     新风格 PWM 接口支持在运行中动态调整占空比和信号频率
 
 注意事项:
-1. 本 demo 演示所使用的是 Air8101 模组的 PWM4 通道(GPIO24,PIN33);
+1. 本 demo 演示所使用的是 Air8101 模组的 PWM2 通道(GPIO24,PIN33);
 2. PWM 功能需要使用 V2xxx 版本固件,固件下载链接:https://docs.openluat.com/air8101/luatos/firmware/;
 
-注意!!!!pwm.setFreq() 函数目前出现一些 BUG,调用成功后无法正常输出波形,正在紧急修复,时间 2025.10.29
-
 更多说明参考本目录下的 readme.md 文件;
 ]]
 

+ 34 - 27
module/Air8101/demo/pwm/pwm_app.lua

@@ -2,7 +2,7 @@
 @module  pwm_app
 @summary PWM 输出功能模块
 @version 1.0
-@date    2025.11.05
+@date    2025.12.10
 @author  马梦阳
 @usage
 本功能模块演示的内容为:
@@ -20,12 +20,19 @@
 3. 综合任务调度:顺序运行上述两种风格示例,并在关键节点进行日志输出
 
 注意事项:
-1. 本 demo 演示所使用的是 Air8101 模组的 PWM4 通道(GPIO24,PIN33);
+1. 本 demo 演示所使用的是 Air8101 模组的 PWM2 通道(GPIO24,PIN33);
 2. PWM 功能需要使用 V2xxx 版本固件,固件下载链接:https://docs.openluat.com/air8101/luatos/firmware/;
 
 本文件没有对外接口,直接在 main.lua 中 require "pwm_app" 就可以加载运行;
 ]]
 
+local result = pins.setup(33, "PWM2")
+if result then
+    log.info("PWM", "PWM2 通道配置成功(GPIO24,PIN33)")
+else
+    log.error("PWM", "PWM2 通道配置失败(GPIO24,PIN33)")
+end
+
 --[[
 旧风格 PWM 演示函数
 使用 pwm.open() 一次性完成配置和启动
@@ -34,22 +41,22 @@
 local function task1_old_pwm()
     log.info("PWM", "旧风格 PWM 示例开始")
 
-    -- 选择 PWM 通道 4
-    -- 注意:本 demo 演示所使用的是 Air8101 模组的 PWM4 通道(GPIO24,PIN33);
-    local pwm_channel = 4
+    -- 选择 PWM 通道 2
+    -- 注意:本 demo 演示所使用的是 Air8101 模组的 PWM2 通道(GPIO24,PIN33);
+    local pwm_channel = 2
 
     -- 第一次输出:1 kHz,45% 占空比,分频精度 100
     local pwm_success = pwm.open(pwm_channel, 1000, 45, 0, 100)
     if pwm_success then
-        log.info("PWM", "PWM4 通道开启成功: 信号频率 1000 Hz, 分频精度 100, 占空比 45%")
+        log.info("PWM", "PWM2 通道开启成功: 信号频率 1000 Hz, 分频精度 100, 占空比 45%")
     else
-        log.info("PWM", "PWM4 通道开启失败")
+        log.info("PWM", "PWM2 通道开启失败")
     end
 
     -- 持续 1 s 后关闭
     sys.wait(1000)
     pwm.close(pwm_channel)
-    log.info("PWM", "PWM4 通道已关闭")
+    log.info("PWM", "PWM2 通道已关闭")
 
     -- 增加 1 秒的间隔时间
     sys.wait(1000)
@@ -57,15 +64,15 @@ local function task1_old_pwm()
     -- 第二次输出:500 Hz,60% 占空比,分频精度 100
     local pwm_success = pwm.open(pwm_channel, 500, 60, 0, 100)
     if pwm_success then
-        log.info("PWM", "PWM4 通道开启成功: 信号频率 500 Hz, 分频精度 100, 占空比 60%")
+        log.info("PWM", "PWM2 通道开启成功: 信号频率 500 Hz, 分频精度 100, 占空比 60%")
     else
-        log.info("PWM", "PWM4 通道开启失败")
+        log.info("PWM", "PWM2 通道开启失败")
     end
 
     -- 持续 2 s 后关闭
     sys.wait(2000)
     pwm.close(pwm_channel)
-    log.info("PWM", "PWM4 通道已关闭")
+    log.info("PWM", "PWM2 通道已关闭")
 
     -- 增加 1 秒的间隔时间
     sys.wait(1000)
@@ -73,15 +80,15 @@ local function task1_old_pwm()
     -- 第三次输出:300 Hz,80% 占空比,分频精度 100
     local pwm_success = pwm.open(pwm_channel, 300, 80, 0, 100)
     if pwm_success then
-        log.info("PWM", "PWM4 通道开启成功: 信号频率 300 Hz, 分频精度 100, 占空比 80%")
+        log.info("PWM", "PWM2 通道开启成功: 信号频率 300 Hz, 分频精度 100, 占空比 80%")
     else
-        log.info("PWM", "PWM4 通道开启失败")
+        log.info("PWM", "PWM2 通道开启失败")
     end
 
     -- 持续 3 s 后关闭
     sys.wait(3000)
     pwm.close(pwm_channel)
-    log.info("PWM", "PWM4 通道已关闭")
+    log.info("PWM", "PWM2 通道已关闭")
 
     log.info("PWM", "旧风格 PWM 示例结束")
 end
@@ -95,24 +102,24 @@ end
 local function task2_new_pwm()
     log.info("PWM", "新风格 PWM 示例开始")
 
-    -- 选择 PWM 通道 4
-    -- 注意:本 demo 演示所使用的是 Air8101 模组的 PWM4 通道(GPIO24,PIN33);
-    local pwm_channel = 4
+    -- 选择 PWM 通道 2
+    -- 注意:本 demo 演示所使用的是 Air8101 模组的 PWM2 通道(GPIO24,PIN33);
+    local pwm_channel = 2
 
     -- 配置 PWM 参数:频率 1000 Hz、占空比 50%、分频精度 100
     local setup_success = pwm.setup(pwm_channel, 1000, 50, 0, 100)
     if setup_success then
-        log.info("PWM", "PWM4 配置成功: 信号频率 1000 Hz, 分频精度 100, 占空比 50%")
+        log.info("PWM", "PWM2 配置成功: 信号频率 1000 Hz, 分频精度 100, 占空比 50%")
     else
-        log.info("PWM", "PWM4 配置失败")
+        log.info("PWM", "PWM2 配置失败")
     end
 
     -- 启动 PWM 输出
     local pwm_success = pwm.start(pwm_channel)
     if pwm_success then
-        log.info("PWM", "PWM4 启动成功")
+        log.info("PWM", "PWM2 启动成功")
     else
-        log.info("PWM", "PWM4 启动失败")
+        log.info("PWM", "PWM2 启动失败")
     end
 
     -- 持续输出 2 秒
@@ -121,9 +128,9 @@ local function task2_new_pwm()
     -- 动态调整占空比至 25%
     local setduty_success = pwm.setDuty(pwm_channel, 25)
     if setduty_success then
-        log.info("PWM", "PWM4 占空比更新为 25%")
+        log.info("PWM", "PWM2 占空比更新为 25%")
     else
-        log.info("PWM", "PWM4 占空比设置失败")
+        log.info("PWM", "PWM2 占空比设置失败")
     end
 
     -- 持续输出 2 秒
@@ -132,9 +139,9 @@ local function task2_new_pwm()
     -- 动态调整信号频率为 2000 Hz
     local setfreq_success = pwm.setFreq(pwm_channel, 2000)
     if setfreq_success then
-        log.info("PWM", "PWM4 频率更新为 2000 Hz")
+        log.info("PWM", "PWM2 频率更新为 2000 Hz")
     else
-        log.error("PWM", "PWM4 频率设置失败")
+        log.error("PWM", "PWM2 频率设置失败")
     end
 
     -- 持续输出 2 秒
@@ -143,9 +150,9 @@ local function task2_new_pwm()
     -- 停止 PWM 输出
     local pwm_success = pwm.stop(pwm_channel)
     if pwm_success then
-        log.info("PWM", "PWM4 停止成功")
+        log.info("PWM", "PWM2 停止成功")
     else
-        log.info("PWM", "PWM4 停止失败")
+        log.info("PWM", "PWM2 停止失败")
     end
 
     log.info("PWM", "新风格 PWM 示例结束")

+ 25 - 20
module/Air8101/demo/pwm/readme.md

@@ -8,7 +8,7 @@
 
 注意事项:
 
-1、本 demo 演示所使用的是 Air8101 模组的 PWM4 通道(GPIO24,PIN33);
+1、本 demo 演示所使用的是 Air8101 模组的 PWM2 通道(GPIO24,PIN33);
 
 2、PWM 功能需要使用 V2xxx 版本固件,固件下载链接:https://docs.openluat.com/air8101/luatos/firmware/;
 
@@ -42,7 +42,7 @@ PWM 库目前有两套 API 风格:
 
 4、逻辑分析仪或者示波器,用于观察 PWM 输出的波形
 
-5、代码中选用的 PWM 通道是 Air8101 模组的 PWM4 通道(GPIO24,PIN33)
+5、代码中选用的 PWM 通道是 Air8101 模组的 PWM2 通道(GPIO24,PIN33)
 
 ## 演示软件环境
 
@@ -61,24 +61,29 @@ PWM 库目前有两套 API 风格:
 4、正常运行情况时的日志如下:
 
 ```
-[000000000.427] I/user.main PWM 001.000.000
-[000000000.434] I/user.PWM PWM 综合演示任务开始
-[000000000.434] I/user.PWM 旧风格 PWM 示例开始
-[000000000.435] I/user.PWM PWM4 通道开启成功: 信号频率 1000 Hz, 分频精度 100, 占空比 45%
-[000000001.435] I/user.PWM PWM4 通道已关闭
-[000000002.435] I/user.PWM PWM4 通道开启成功: 信号频率 500 Hz, 分频精度 100, 占空比 60%
-[000000004.436] I/user.PWM PWM4 通道已关闭
-[000000005.436] I/user.PWM PWM4 通道开启成功: 信号频率 300 Hz, 分频精度 100, 占空比 80%
-[000000008.437] I/user.PWM PWM4 通道已关闭
-[000000008.437] I/user.PWM 旧风格 PWM 示例结束
-[000000011.437] I/user.PWM 新风格 PWM 示例开始
-[000000011.438] I/user.PWM PWM4 配置成功: 信号频率 1000 Hz, 分频精度 100, 占空比 50%
-[000000011.438] I/user.PWM PWM4 启动成功
-[000000013.438] I/user.PWM PWM4 占空比更新为 25%
-[000000015.439] I/user.PWM PWM4 频率更新为 2000 Hz
-[000000017.439] I/user.PWM PWM4 停止成功
-[000000017.440] I/user.PWM 新风格 PWM 示例结束
-[000000017.440] I/user.PWM PWM 综合演示任务结束
+[2025-12-10 09:38:25.280] luat:U(2057):I/user.main PWM 001.000.000
+[2025-12-10 09:38:25.287] luat:U(2082):I/user.PWM PWM2 通道配置成功(GPIO24,PIN33)
+[2025-12-10 09:38:25.287] luat:U(2083):I/user.PWM PWM 综合演示任务开始
+[2025-12-10 09:38:25.287] luat:U(2083):I/user.PWM 旧风格 PWM 示例开始
+[2025-12-10 09:38:25.287] luat:D(2084):pwm:pwm(2) gpio init : pwm_pin 24
+[2025-12-10 09:38:25.287] luat:U(2085):I/user.PWM PWM2 通道开启成功: 信号频率 1000 Hz, 分频精度 100, 占空比 45%
+[2025-12-10 09:38:26.288] luat:U(3088):I/user.PWM PWM2 通道已关闭
+[2025-12-10 09:38:27.314] luat:D(4089):pwm:pwm(2) gpio init : pwm_pin 24
+[2025-12-10 09:38:27.314] luat:U(4089):I/user.PWM PWM2 通道开启成功: 信号频率 500 Hz, 分频精度 100, 占空比 60%
+[2025-12-10 09:38:29.321] luat:U(6091):I/user.PWM PWM2 通道已关闭
+[2025-12-10 09:38:30.321] luat:D(7093):pwm:pwm(2) gpio init : pwm_pin 24
+[2025-12-10 09:38:30.321] luat:U(7094):I/user.PWM PWM2 通道开启成功: 信号频率 300 Hz, 分频精度 100, 占空比 80%
+[2025-12-10 09:38:33.323] luat:U(10095):I/user.PWM PWM2 通道已关闭
+[2025-12-10 09:38:33.323] luat:U(10096):I/user.PWM 旧风格 PWM 示例结束
+[2025-12-10 09:38:36.320] luat:U(13096):I/user.PWM 新风格 PWM 示例开始
+[2025-12-10 09:38:36.320] luat:U(13097):I/user.PWM PWM2 配置成功: 信号频率 1000 Hz, 分频精度 100, 占空比 50%
+[2025-12-10 09:38:36.320] luat:D(13098):pwm:pwm(2) gpio init : pwm_pin 24
+[2025-12-10 09:38:36.320] luat:U(13098):I/user.PWM PWM2 启动成功
+[2025-12-10 09:38:38.302] luat:U(15099):I/user.PWM PWM2 占空比更新为 25%
+[2025-12-10 09:38:40.328] luat:U(17100):I/user.PWM PWM2 频率更新为 2000 Hz
+[2025-12-10 09:38:42.326] luat:U(19102):I/user.PWM PWM2 停止成功
+[2025-12-10 09:38:42.326] luat:U(19103):I/user.PWM 新风格 PWM 示例结束
+[2025-12-10 09:38:42.326] luat:U(19103):I/user.PWM PWM 综合演示任务结束
 ```
 
 5、使用逻辑分析仪或者示波器观察 PWM 输出波形是否与配置的参数一致

+ 66 - 0
module/Air8101/demo/rtos/main.lua

@@ -0,0 +1,66 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 001.000.000
+@date    2025.12.2
+@author  王城钧
+@usage
+1. rtos_app:对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+]]
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "rtos_demo"
+VERSION = "001.000.000"
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+--加载rtos时钟驱动模块
+require "rtos_app"
+
+
+-- 启动系统调度(必须放在最后)
+sys.run()

+ 53 - 0
module/Air8101/demo/rtos/readme.md

@@ -0,0 +1,53 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、rtos_app:对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试;
+
+## 演示功能概述
+
+1、对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+
+## 演示硬件环境
+
+1、Air8101核心板一块;
+
+![netdrv_multi](https://docs.openluat.com/accessory/AirSPINORFLASH_1000/image/8101.jpg)
+
+2、TYPE-C USB数据线一根
+
+* Air8101核心板通过 TYPE-C USB 口供电;
+* TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air8101 V1006版本]([固件和应用脚本Demo - luatos@air8000 - 合宙模组资料中心](https://docs.openluat.com/air8101/luatos/firmware/))(理论上最新版本固件也可以,如果使用最新版本的固件不可以,可以烧录V1006固件对比验证)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、可以看到代码运行结果如下:
+
+日志中如果出现以下类似以下打印则说明rtos功能正常
+
+```lua
+[2025-12-08 11:55:21.736] luat:U(162):I/user.main rtos_demo 001.000.000
+[2025-12-08 11:55:21.736] luat:U(165):I/user.固件信息 版本: V1006 nil
+[2025-12-08 11:55:21.736] luat:U(165):I/user.编译信息 日期: Aug 31 2025 BSP: Air8101
+[2025-12-08 11:55:21.736] luat:U(166):I/user.完整描述 LuatOS-SoC_V1006_Air8101
+[2025-12-08 11:55:21.736] luat:U(166):I/user.内存信息 Lua - 总: 1572856 已用: 38248 峰值: 38248 系统 - 总: 181304 已用: 74296 峰值: 86240
+[2025-12-08 11:55:21.736] luat:D(166):rtos:mem collect param 100,80,90 -> 200,75,85
+[2025-12-08 11:55:21.736] luat:U(167):I/user.RTOS测试 所有测试已启动
+[2025-12-08 11:55:31.728] luat:U(10170):I/user.性能测试 1000次nop耗时: 3 毫秒
+
+
+```
+
+

+ 60 - 0
module/Air8101/demo/rtos/rtos_app.lua

@@ -0,0 +1,60 @@
+--[[
+@module  rtos_app
+@summary rtos应用模块
+@version 1.0
+@date    2025.12.2
+@author  王城钧
+@usage
+本文件为rtos应用模块,核心业务逻辑为:
+1、对rtos核心库的各项功能进行测试,包括系统信息查询、内存信息获取、内存自动回收配置以及性能测试
+
+本文件没有对外接口,直接在其他功能模块中require "rtos_app"就可以加载运行;
+]]
+
+-- 1. 系统信息查询测试
+-- 读取版本号及数字版本号, 2025.11.1之后的固件支持
+-- 如果不是数字固件,luatos_version_num 会是0
+-- 如果是不支持的固件, luatos_version_num 会是nil
+local luatos_version, luatos_version_num = rtos.version(true)
+log.info("固件信息", "版本:", luatos_version, luatos_version_num )
+
+log.info("编译信息", "日期:", rtos.buildDate(), "BSP:", rtos.bsp())
+log.info("完整描述", rtos.firmware())
+
+-- 2. 内存信息测试
+local total_lua, used_lua, max_used_lua = rtos.meminfo("lua")
+local total_sys, used_sys, max_used_sys = rtos.meminfo("sys")
+log.info("内存信息", 
+    "Lua - 总:", total_lua, "已用:", used_lua, "峰值:", max_used_lua,
+    "系统 - 总:", total_sys, "已用:", used_sys, "峰值:", max_used_sys)
+
+-- 3. 定时器测试
+-- rtos.timer_start()和rtos.timer_stop()两个接口,仅仅给sys核心库使用
+-- 如果要使用定时器,直接使用sys核心库提供的定时器接口即可
+-- 用户脚本中不要直接使用rtos.timer_start()和rtos.timer_stop()两个接口
+-- 否则和sys核心库中的定时器功能出现冲突而导致系统异常的问题
+
+-- 4. 内存自动回收配置
+rtos.autoCollectMem(200, 75, 85)  -- 配置较宽松的自动回收
+
+-- 5. 空函数测试(性能测试用)
+local function test_nop()
+    local start = mcu.ticks()
+    for i = 1, 1000 do
+        rtos.nop()
+    end
+    local duration = mcu.ticks() - start
+    log.info("性能测试", "1000次nop耗时:", duration, "毫秒")
+end
+
+-- 10秒后执行空函数测试
+sys.timerStart(test_nop, 10000)
+
+-- 6. 重启测试(注释掉防止意外重启)
+-- local function reboot()
+--     log.info("系统", "准备重启...")
+--     rtos.reboot()
+-- end
+-- sys.timerStart(reboot, 30000)
+
+log.info("RTOS测试", "所有测试已启动")

+ 9 - 0
module/Air8101/demo/ui/easyui/combination/readme.md

@@ -0,0 +1,9 @@
+
+# exEasyUI 组件演示
+
+## 一、项目概述
+
+本项目是基于 exEasyUI 图形用户界面库的完整组件演示程序,展示了 20 种不同的 UI 组件和功能模块。每个演示模块独立运行,通过主程序统一调度管理。
+
+
+exeasyui项目demo可以参考 [AirLCD_1020 exeasyui 项目演示](https://gitee.com/openLuat/LuatOS/tree/master/module/Air8101/demo/accessory_board/AirLCD_1020/exeasyui)

+ 88 - 0
module/Air8101/demo/ui/easyui/single/hw_font_drv.lua

@@ -0,0 +1,88 @@
+--[[
+@module  hw_font_drv
+@summary 字体驱动配置模块
+@version 1.0
+@date    2025.12.9
+@author  江访
+@usage
+本文件为字体驱动配置模块,核心业务逻辑为:
+1、提供统一的硬件初始化接口;
+2、支持默认字体、HzFont矢量字体和GTFont矢量字体配置;
+3、配置LCD显示和触摸屏参数;
+
+本文件的对外接口有1个:
+1、hw_font_drv.init(font_config) - 初始化硬件系统;
+
+@api hw_font_drv.init(font_config)
+@summary 初始化exEasyUI硬件系统
+@table font_config 字体配置参数(可选)
+@field type string 字体类型,支持 "hzfont"、"gtfont",不传则使用默认字体
+@field size number 字体大小
+@field antialias number 抗锯齿设置(仅HzFont有效)
+@field spi table SPI配置(仅GTFont有效)
+@return nil
+
+@usage
+-- 使用默认字体初始化硬件
+hw_font_drv.init()
+
+-- 使用HzFont矢量字体初始化硬件
+hw_font_drv.init({
+    type = "hzfont",
+    size = 24,
+    antialias = -1
+})
+
+-- 使用GTFont矢量字体初始化硬件
+hw_font_drv.init({
+    type = "gtfont",
+    spi = { id = 0, cs = 8 },
+    size = 32
+})
+]]
+
+local hw_font_drv = {}
+
+-- 硬件配置参数
+local hw_config = {
+    -- lcd_config参数填写可以参考合宙exlcd显示扩展库exlcd.init(param)接口说明:https://docs.openluat.com/osapi/ext/exlcd/#31-exlcdinitparam
+    lcd_config = {
+        lcd_model = "AirLCD_1020", -- LCD型号
+    },
+
+    -- tp_config参数填写可以参考合宙extp触摸扩展库以下三个接口说明:https://docs.openluat.com/osapi/ext/extp/#41-extpinitparam
+    -- 按extp.init(param)接口说明填写tp_model、i2c_id、pin_rst、pin_int参数
+    -- 按extp.set_publish_enabled(msg_type, enabled)接口说明和实际需求填写message_enabled{}列表内参数
+    -- 按extp.set_swipe_threshold(threshold)接口说明填写swipe_threshold和long_press_threshold参数
+    tp_config = {
+        tp_model = "AirLCD_1020", -- 触摸芯片/设备型号
+        -- @param message_enabled 消息类型 ("ALL", "RAW_DATA", "TOUCH_DOWN", "MOVE_X", "MOVE_Y", "SWIPE_LEFT", "SWIPE_RIGHT", "SWIPE_UP", "SWIPE_DOWN", "SINGLE_TAP", "LONG_PRESS")
+        message_enabled = {
+            TOUCH_DOWN = true,      -- 启用按下检测
+            MOVE_X = true,          -- 启用横向滑动
+            MOVE_Y = true,          -- 启用纵向滑动
+            SWIPE_LEFT = true,      -- 启用左滑手势
+            SWIPE_RIGHT = true,     -- 启用右滑手势
+            SWIPE_UP = true,        -- 启用上滑手势
+            SWIPE_DOWN = true,      -- 启用下滑手势
+            SINGLE_TAP = true,      -- 启用点击手势
+            LONG_PRESS = false      -- 禁用长按手势
+        },
+        swipe_threshold = 10,       -- 设置滑动阈值
+        long_press_threshold = 2000 -- 设置长按阈值(毫秒)
+    }
+}
+
+function hw_font_drv.init(font_config)
+    -- 如果有字体配置,则添加到硬件配置中
+    if font_config then
+        hw_config.font_config = font_config
+    end
+
+    -- 初始化硬件
+    ui.hw_init(hw_config)
+
+    log.info("hw_font_drv", "硬件初始化完成", font_config and "使用字体配置" or "使用默认字体")
+end
+
+return hw_font_drv

BIN
module/Air8101/demo/ui/easyui/single/images/1.jpg


BIN
module/Air8101/demo/ui/easyui/single/images/2.jpg


BIN
module/Air8101/demo/ui/easyui/single/images/3.jpg


BIN
module/Air8101/demo/ui/easyui/single/images/4.jpg


BIN
module/Air8101/demo/ui/easyui/single/images/5.jpg


BIN
module/Air8101/demo/ui/easyui/single/images/logo.jpg


+ 133 - 0
module/Air8101/demo/ui/easyui/single/main.lua

@@ -0,0 +1,133 @@
+--[[
+@module  main
+@summary exEasyUI组件演示主程序入口,总体调度各个UI组件演示模块
+@version 1.0.0
+@date    2025.12.9
+@author  江访
+@usage
+本demo演示的核心功能为:
+1、提供统一的exEasyUI组件演示程序入口;
+2、通过选择不同的演示模块,展示exEasyUI库的各种UI组件功能;
+3、每个演示模块独立运行,展示不同的UI组件和交互逻辑;
+4、支持标签、按钮、进度条、输入框、下拉框、图片轮播、消息框、复选框等20种UI组件演示;
+5、支持横向/纵向滑动页面、页面切换等高级功能演示;
+6、支持不同字体后端(HzFont矢量字体、GTFont矢量字体)的演示;
+
+硬件初始化说明:
+- 所有演示模块都通过hw_font_drv模块进行硬件初始化
+- 基础组件演示:使用默认字体,不传递字体参数
+- 字体演示模块:传递对应的字体配置参数
+
+演示模块列表:
+- win_label: 动态更新标签演示
+- win_button: 基础按钮组件演示
+- win_toggle_button: 切换按钮演示
+- win_progress_bar: 静态进度条演示
+- win_dyn_progress_bar: 动态进度条演示
+- win_message_box: 消息框组件演示
+- win_check_box: 复选框组件演示
+- win_picture: 静态图片显示演示
+- win_autoplay_picture: 自动轮播图片演示
+- win_combo_box: 下拉框组件演示
+- win_input: 文本输入框演示
+- win_password_input: 密码输入框演示
+- win_number_input: 数字输入框演示
+- win_all_component: 所有组件综合演示
+- win_horizontal_slide: 横向滑动页面演示
+- win_vertical_slide: 纵向滑动页面演示
+- win_switch_page: 页面切换演示
+- win_hzfont: 内置软件矢量字体演示
+- win_gtfont: 外置硬件矢量字体演示
+
+更多说明参考本目录下的readme.md文件
+]]
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+
+-- main.lua - 程序入口文件
+
+-- 项目名称和版本定义
+PROJECT = "exEasyUI_demo" -- 项目名称,用于标识当前工程
+VERSION = "1.0.0"         -- 项目版本号
+
+-- 在日志中打印项目名和项目版本号
+log.info("ui_demo", PROJECT, VERSION)
+
+-- 设置日志输出风格为样式2(建议调试时开启)
+-- log.style(2)
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+-- 必须加载才能启用exeasyui的功能
+ui = require("exeasyui")
+
+-- 加载显示、触摸和字体驱动模块
+hw_font_drv = require("hw_font_drv")
+
+-- 引入演示模块
+-- 使用哪个加载哪个,每次选择加载一个;
+-- require("win_label")--动态更新标签演示
+-- require("win_button")  --基础按钮组件演示
+-- require("win_toggle_button")  --切换按钮演示
+-- require("win_progress_bar")  --静态进度条演示
+-- require("win_dyn_progress_bar")  --动态进度条演示
+-- require("win_message_box")  --消息框组件演示
+-- require("win_check_box")  --复选框组件演示
+-- require("win_picture")  --静态图片显示演示
+-- require("win_autoplay_picture")  --自动轮播图片演示
+-- require("win_combo_box")  --下拉框组件演示
+-- require("win_input")  --文本输入框演示
+-- require("win_password_input")  --密码输入框演示
+-- require("win_number_input")  --数字输入框演示
+require("win_all_component")  --所有组件综合演示
+-- require("win_horizontal_slide")  --横向滑动页面演示
+-- require("win_vertical_slide")  --纵向滑动页面演示
+-- require("win_switch_page")  --页面切换演示
+-- require("win_hzfont")  --内置软件矢量字体演示(目前Air8101支持HZFont的固件正在开发中)
+-- require("win_hzfont")  --外置硬件矢量字体演示
+
+-- 用户代码已结束
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 364 - 0
module/Air8101/demo/ui/easyui/single/readme.md

@@ -0,0 +1,364 @@
+# exEasyUI 组件演示
+
+## 一、项目概述
+
+本项目是基于 exEasyUI 图形用户界面库的完整组件演示程序,展示了 20 种不同的 UI 组件和功能模块。每个演示模块独立运行,通过主程序统一调度管理。
+
+## 二、演示模块列表
+
+### 2.1 系统核心模块
+
+1. **main.lua** - 主程序入口模块
+   - 项目初始化和版本定义
+   - 系统任务调度和管理
+   - 看门狗配置和系统监控
+   - 演示模块的选择和加载
+   - 提供统一的程序启动入口
+
+2. **ui_main.lua** - 用户界面主控模块
+   - 多页面管理和切换逻辑
+   - 触摸事件分发和处理
+   - 界面渲染调度
+   - 主题管理和配置
+   - 提供完整的用户交互体验
+
+3. **hw_font_drv.lua** - 硬件字体驱动模块
+   - 统一的硬件初始化接口
+   - 支持多种字体后端配置
+   - LCD显示参数管理
+   - 触摸屏配置管理
+   - 字体渲染引擎初始化
+
+### 2.2 基础组件演示
+
+1. **win_label.lua** - 动态更新标签演示(实时时间显示,使用默认字体)
+2. **win_button.lua** - 基础按钮组件演示(使用默认字体)
+3. **win_toggle_button.lua** - 切换按钮演示(图标切换,使用默认字体)
+4. **win_progress_bar.lua** - 静态进度条演示(使用默认字体)
+5. **win_dyn_progress_bar.lua** - 动态进度条演示(自动往复动画,使用默认字体)
+
+### 2.3 交互组件演示
+
+1. **win_message_box.lua** - 消息框组件演示(使用默认字体)
+2. **win_check_box.lua** - 复选框组件演示(使用默认字体)
+3. **win_combo_box.lua** - 下拉框组件演示(使用默认字体)
+4. **win_input.lua** - 文本输入框演示(使用默认字体)
+5. **win_password_input.lua** - 密码输入框演示(带显示/隐藏切换,使用默认字体)
+6. **win_number_input.lua** - 数字输入框演示(带增减按钮,使用默认字体)
+
+### 2.4 多媒体组件演示
+
+1. **win_picture.lua** - 静态图片显示演示(使用默认字体)
+2. **win_autoplay_picture.lua** - 自动轮播图片演示(使用默认字体)
+
+### 2.5 高级功能演示
+
+1. **win_all_component.lua** - 所有组件综合演示(带滚动功能,使用默认字体)
+2. **win_horizontal_slide.lua** - 横向滑动页面演示(使用默认字体)
+3. **win_vertical_slide.lua** - 纵向滑动页面演示(使用默认字体)
+4. **win_switch_page.lua** - 页面切换演示(多页面导航,使用默认字体)
+
+### 2.6 字体渲染演示
+
+1. **win_hzfont.lua** - 内置HzFont矢量字体演示(目前Air8101支持HZFont的固件正在开发中)
+2. **win_gtfont.lua** - 外置矢量字体演示(GTFont,需要 AirFONTS_1000 配件板)
+
+## 三、演示效果
+
+<table>
+<tr>
+<td>组件样式<br/></td><td>输入法<br/></td></tr>
+<tr>
+<td><img src="https://docs.openLuat.com/cdn/image/exeasyui_AirLCD_1020_component.png" width="150" /><br/></td><td><img src="https://docs.openLuat.com/cdn/image/exeasyui_input.png" width="150" /><br/></td></tr>
+</table>
+
+<table>
+<tr>
+<td>下拉框<br/></td><td>消息框<br/></td></tr>
+<tr>
+<td><img src="https://docs.openLuat.com/cdn/image/exeasyui_combo_box.png" width="150" /><br/></td><td><img src="https://docs.openLuat.com/cdn/image/exeasyui_message_box.png" width="150" /><br/></td></tr>
+</table>
+
+## 四 使用合宙 LuatOS-PC 模拟器仿真 exeasyui
+
+### 4.1 PC 模拟器说明
+
+- 合宙 LuatOS-PC 模拟器是一个能在 win10/win11 上模拟运行 lua 脚本的仿真软件,内置 LuatOS 内核固件,运行.lua 脚本效果与实际设备类似;
+- 目前 PC 模拟器可以通过 LuaTools 工具的资源管理器进行下载,所以我们需要先下载安装 LuaTools 工具,然后再通过 LuaTools 工具来下载 LuatOS-PC 模拟器,最后通过 LuatOS-PC 模拟器运行 exeasyui 演示 demo;
+
+### 4.2 LuatOS-PC 模拟器安装步骤
+
+1. 点击下载:[Luatools v3 下载调试工具](https://docs.openluat.com/air8101/luatos/common/download/)
+2. 通过 LuaTools 工具下载 LuatOS-PC 模拟器
+
+   - LuaTools 工具安装完毕后,点击首页面左上角的--账户--打开资源下载
+   - 选择-公共资源--LuatOS 的 PC 模拟器--选择最新版本 LuatOS-PC 模拟器--点击开始下载(非刷机)
+
+![](https://docs.openLuat.com/cdn/image/PC模拟器下载_1.png)
+
+![](https://docs.openLuat.com/cdn/image/PC模拟器下载_2.png)
+
+### 4.3 下载底层固件和上层运行脚本
+
+1. 下载运行所需固件,点击资源管理--选择 Air8101 的 LuatOS 固件--下载最新版本的 1 号固件
+2. 下载本演示 demo 内所有.lua 脚本文件、images 文件夹内的图片
+
+![](https://docs.openLuat.com/cdn/image/Air8101使用PC模拟器下载固件.png)
+
+### 4.4 使用 LuatOS-PC 模拟器仿真 运行 exeasyui 演示 demo
+
+1. 返回 Luatloos 工具首页,点击--项目管理测试
+
+![](https://docs.openLuat.com/cdn/image/进入luatools项目管理测试.png)
+
+2. 创建一个项目并命名
+
+![](https://docs.openLuat.com/cdn/image/创建项目.png)
+
+3. 选择固件刚才下载的固件--点击打开,路径在 Luatools 目录下 resource\LuatOS_Air8101\LuatOS-SoC_VXXXX_Air8101
+
+![](https://docs.openLuat.com/cdn/image/Air8101选择固件.png)
+
+4. 将下载的 demo 图片资源和.lua文件拖入到项目管理内的脚本和资源列表区域--勾选添加默认 lib--点击模拟器运行--出来的界面就是 demo 在实际设备上运行界面的仿真,可以用鼠标进行交互
+
+![](https://docs.openLuat.com/cdn/image/Air8101PC模拟器运行.png)
+
+![](https://docs.openLuat.com/cdn/image/模拟器仿真界面.png)
+
+5. 如需切换 demo 内的演示内容,可打开下载脚本文件中的 mian.lua 文件,将需要演示 demo 的 require 前面--去掉,将不需要演示 demo 的 require 前面加上--。注释掉 require 的其他 demo 文件后,再点击模拟器运行,就会出现所 require 的 demo 对应的界面仿真。
+   - 比如:需要演示下拉框组件,将-- require("win_combo_box") 改为 require("win_combo_box") ,并把其他加载的组件改为注释状态。
+     ![](https://docs.openLuat.com/cdn/image/PC模拟器使用_2.png)
+
+
+## 五、真实设备演示硬件环境
+
+### 5.1 实际设备演示说明
+
+- 演示所使用的是 Air8101 核心板与 AirFONTS_1000 配件板
+- 使用 HZfont目前还没有支持的固件
+- 其他组件演示,demo 所使用的固件是 LuatOS-SoC_V1006_Air8101_1.soc
+- 使用其他型号模块可以参考 [docs 文档](https://docs.openluat.com/)中对应型号的固件支持功能进行固件选择,按管脚说明进行接线和配置 hw_font_drv.lua 中的参数,然后进行烧录使用
+
+### 5.2 硬件清单
+
+- Air8101 核心板 × 1
+- AirLCD_1020 触摸配件板 × 1
+- GTFont 矢量字库,使用的是 AirFONTS_1000 配件板 × 1
+- 双排40PIN的双头线 x 1
+- 母对母杜邦线 × 6,杜邦线太长的话,会出现 spi 通信不稳定的现象;
+- TYPE-C 数据线 × 1
+- Air8101 核心板和 AirLCD_1020配件板以及AirFONTS_1000 配件板的硬件接线方式为
+
+  - Air8101 核心板通过 TYPE-C USB 口供电(核心板背面的功耗测试开关拨到 OFF 一端,正面开关打到 3.3V 一端),此种供电方式下,vbat 引脚为 3.3V,可以直接给 AirLCD_1020配件板和AirFONTS_1000 配件板供电;
+  - 为了演示方便,所以 Air8101 核心板上电后直接通过 vbat 引脚给 AirLCD_1020配件板和AirFONTS_1000 配件板提供了 3.3V 的供电;
+  - 客户在设计实际项目时,一般来说,需要通过一个GPIO来控制LDO给LCD和TP供电,这样可以灵活地控制供电,可以使项目的整体功耗降到最低;
+  - 核心板和配件板之间配备了双排40PIN的双头线,可以参考下表很方便地连接双方各自的40个管脚,插入或者拔出双头线时,要慢慢的操作,防止将排针折弯;
+
+### 5.2 接线配置
+
+#### 5.2.1 显示屏接线
+
+<table>
+<tr>
+<td>Air8101核心板<br/></td><td>AirLCD_1020配件板<br/></td></tr>
+<tr>
+<td>gnd<br/></td><td>GND<br/></td></tr>
+<tr>
+<td>vbat<br/></td><td>VCC<br/></td></tr>
+<tr>
+<td>42/R0<br/></td><td>RGB_R0<br/></td></tr>
+<tr>
+<td>40/R1<br/></td><td>RGB_R1<br/></td></tr>
+<tr>
+<td>43/R2<br/></td><td>RGB_R2<br/></td></tr>
+<tr>
+<td>39/R3<br/></td><td>RGB_R3<br/></td></tr>
+<tr>
+<td>44/R4<br/></td><td>RGB_R4<br/></td></tr>
+<tr>
+<td>38/R5<br/></td><td>RGB_R5<br/></td></tr>
+<tr>
+<td>45/R6<br/></td><td>RGB_R6<br/></td></tr>
+<tr>
+<td>37/R7<br/></td><td>RGB_R7<br/></td></tr>
+<tr>
+<td>46/G0<br/></td><td>RGB_G0<br/></td></tr>
+<tr>
+<td>36/G1<br/></td><td>RGB_G1<br/></td></tr>
+<tr>
+<td>47/G2<br/></td><td>RGB_G2<br/></td></tr>
+<tr>
+<td>35/G3<br/></td><td>RGB_G3<br/></td></tr>
+<tr>
+<td>48/G4<br/></td><td>RGB_G4<br/></td></tr>
+<tr>
+<td>34/G5<br/></td><td>RGB_G5<br/></td></tr>
+<tr>
+<td>49/G6<br/></td><td>RGB_G6<br/></td></tr>
+<tr>
+<td>33/G7<br/></td><td>RGB_G7<br/></td></tr>
+<tr>
+<td>50/B0<br/></td><td>RGB_B0<br/></td></tr>
+<tr>
+<td>32/B1<br/></td><td>RGB_B1<br/></td></tr>
+<tr>
+<td>51/B2<br/></td><td>RGB_B2<br/></td></tr>
+<tr>
+<td>31/B3<br/></td><td>RGB_B3<br/></td></tr>
+<tr>
+<td>52/B4<br/></td><td>RGB_B4<br/></td></tr>
+<tr>
+<td>30/B5<br/></td><td>RGB_B5<br/></td></tr>
+<tr>
+<td>53/B6<br/></td><td>RGB_B6<br/></td></tr>
+<tr>
+<td>29/B7<br/></td><td>RGB_B7<br/></td></tr>
+<tr>
+<td>28/DCLK<br/></td><td>RGB_DCLK<br/></td></tr>
+<tr>
+<td>54/DISP<br/></td><td>RGB_DISP<br/></td></tr>
+<tr>
+<td>55/HSYN<br/></td><td>RGB_HSYNC<br/></td></tr>
+<tr>
+<td>56/VSYN<br/></td><td>RGB_VSYNC<br/></td></tr>
+<tr>
+<td>57/DE<br/></td><td>RGB_DE<br/></td></tr>
+<tr>
+<td>14/GPIO8<br/></td><td>LCD_BL<br/></td></tr>
+<tr>
+<td>13/GPIO9<br/></td><td>LCD_RST<br/></td></tr>
+<tr>
+<td>8/GPIO5<br/></td><td>LCD_SDI<br/></td></tr>
+<tr>
+<td>9/GPIO6<br/></td><td>LCD_SCL<br/></td></tr>
+<tr>
+<td>68/GPIO12<br/></td><td>LCD_CS<br/></td></tr>
+<tr>
+<td>75/GPIO28<br/></td><td>TP_RST<br/></td></tr>
+<tr>
+<td>10/GPIO7<br/></td><td>TP_INT<br/></td></tr>
+<tr>
+<td>12/U1TX<br/></td><td>TP_SCL<br/></td></tr>
+<tr>
+<td>11/U1RX<br/></td><td>TP_SDA<br/></td></tr>
+</table>
+
+#### 5.2.2 GTFont 字库接线
+
+<table> 
+<tr> <td>Air8101 核心板</td><td>AirFONTS_1000配件板</td></tr>
+ <tr> <td>66/GPIO3</td><td>CS</td></tr> 
+ <tr> <td>67/GPIO4</td><td>MISO</td></tr> 
+ <tr> <td>8/GPIO5</td><td>MOSI</td></tr> 
+ <tr> <td>65/GPIO2</td><td>CLK</td></tr> 
+ <tr> <td>vbat</td><td>VCC</td></tr> 
+</table>
+
+#### 5.2.3 接线图
+![](https://docs.openLuat.com/cdn/image/Air8101_AirLCD_1020_AirFONTS_1000接线图.jpg)
+
+## 六、演示软件环境
+
+### 6.1 开发工具
+
+- [Luatools下载调试工具](https://docs.openluat.com/air8101/luatos/common/download/) - 固件烧录和代码调试
+
+### 6.2 内核固件
+
+- [点击下载Air8101最新版本内核固件](https://docs.openluat.com/air8101/luatos/firmware/),demo所使用的是LuatOS-SoC_V1006_Air8101 1号固件
+
+
+## 七、快速开始
+
+### 7.1 硬件准备
+
+1. 按照硬件接线表连接所有设备
+2. 如使用 GTFont 演示,需要连接 AirFONTS_1000  配件板
+3. 通过 TYPE-C USB 口供电
+4. 检查所有接线无误
+
+### 7.2 软件配置
+
+在 `main.lua` 中选择要运行的演示模块:
+
+```lua
+-- 必须加载才能启用exeasyui的功能
+ui = require("exeasyui")
+
+-- 加载显示、触摸和字体驱动模块
+hw_font_drv = require("hw_font_drv")
+
+-- 引入演示模块
+-- 使用哪个加载哪个,每次选择加载一个;
+-- require("win_label") --动态更新标签演示
+-- require("win_button")  --基础按钮组件演示
+-- require("win_toggle_button")  --切换按钮演示
+-- require("win_progress_bar")  --静态进度条演示
+-- require("win_dyn_progress_bar")  --动态进度条演示
+-- require("win_message_box")  --消息框组件演示
+-- require("win_check_box")  --复选框组件演示
+-- require("win_picture")  --静态图片显示演示
+-- require("win_autoplay_picture")  --自动轮播图片演示
+-- require("win_combo_box")  --下拉框组件演示
+-- require("win_input")  --文本输入框演示
+-- require("win_password_input")  --密码输入框演示
+-- require("win_number_input")  --数字输入框演示
+require("win_all_component")  --所有组件综合演示
+-- require("win_horizontal_slide")  --横向滑动页面演示
+-- require("win_vertical_slide")  --纵向滑动页面演示
+-- require("win_switch_page")  --页面切换演示
+-- require("win_hzfont")  --内置软件矢量字体演示
+-- require("win_hzfont")  --外置硬件矢量字体演示
+```
+
+### 7.3 软件烧录步骤
+
+1. 使用 Luatools 烧录对应型号的最新内核固件
+2. 下载本项目所有脚本文件
+3. 将演示图片文件(如 `1.jpg`、`2.jpg` 等)同.lua脚本文件一起烧录到脚本分区
+4. 设备自动重启后开始运行选定的演示模块
+
+## 八、演示效果说明
+
+### 8.1 组件功能特点
+
+- **响应式设计**:所有组件支持触摸交互
+- **主题支持**:支持浅色/深色主题切换
+- **滚动功能**:支持横向和纵向滚动页面
+- **动态更新**:支持实时数据更新显示
+- **事件处理**:完整的触摸和点击事件处理
+
+## 九、故障排除
+
+### 9.1 常见问题及解决方案
+
+1. **显示异常**
+
+   - 检查 LCD 接线是否存在异常
+   - 确认 hw_font_drv.lua 内屏幕型号及其参数配置是否正确
+2. **触摸无响应**
+
+   - 检查 I2C 接线是否存在异常
+   - 确认确认 hw_font_drv.lua 内触摸芯片型号配置正确
+3. **字体显示异常**
+
+   - 确认选择的字体驱动与硬件匹配
+   - 检查固件版本是否支持所选字体
+4. **图片无法显示**
+
+   - 确认图片文件已正确烧录
+   - 检查文件路径和名称是否正确
+5. **系统运行缓慢**
+
+   - 检查是否有过多的组件同时渲染
+   - 适当调整刷新间隔时间
+
+### 9.2 调试技巧
+
+- 使用 `log.info()` 输出调试信息
+- 检查系统内存使用情况
+- 逐步启用组件排查问题
+
+## 十、扩展开发
+
+本演示 demo 所有接口都在 [exeasyUI UI 扩展库](https://docs.openluat.com/osapi/ext/exeasyui/)内有详细说明,如需实现更丰富的自定义功能可按接口说明实现。

+ 346 - 0
module/Air8101/demo/ui/easyui/single/win_all_component.lua

@@ -0,0 +1,346 @@
+--[[
+@module  win_all_component
+@summary 所有UI组件综合演示模块
+@version 1.0.0
+@date    2025.12.9
+@author  江访
+@usage
+本文件为所有UI组件综合演示模块,核心业务逻辑为:
+1、创建带滚动功能的窗口容器;
+2、集成展示所有exEasyUI组件;
+3、实现组件间的交互逻辑;
+4、展示进度条、消息框、按钮、复选框、输入框等完整功能;
+5、启动UI渲染循环持续刷新显示;
+
+本文件没有对外接口;
+]]
+
+local function ui_main()
+
+    -- 显示触摸初始化
+    hw_font_drv.init()
+
+    -- 设置主题
+    ui.sw_init({ theme = "light" })
+
+    -- 创建窗口容器并启用滚动
+    local page1 = ui.window({ background_color = ui.COLOR_WHITE })
+    page1:enable_scroll({
+        direction = "vertical",
+        content_height = 600,
+        threshold = 8
+    })
+
+    -- ==================== 标题区域 ====================
+    local title = ui.label({
+        x = 250, y = 25,
+        text = "所有UI组件综合演示",
+        color = ui.COLOR_BLACK,
+        size = 24
+    })
+    page1:add(title)
+
+    -- 左列和右列布局
+    local left_column_x = 50
+    local right_column_x = 450
+    local current_y = 80
+
+    -- ==================== 左列:基础组件 ====================
+
+    -- 进度条组件
+    local progress_label = ui.label({
+        x = left_column_x, y = current_y,
+        text = "1. 进度条组件",
+        color = ui.COLOR_BLACK,
+        size = 18
+    })
+    page1:add(progress_label)
+
+    local progress = 50
+    local pb = ui.progress_bar({
+        x = left_column_x, y = current_y + 30,
+        w = 300, h = 26,
+        progress = progress
+    })
+    page1:add(pb)
+
+    local btn_progress = ui.button({
+        x = left_column_x + 310, y = current_y + 30,
+        w = 80, h = 26,
+        text = "+10%",
+        size = 14,
+        on_click = function(self)
+            progress = progress + 10
+            if progress > 100 then progress = 0 end
+            pb:set_progress(progress)
+        end
+    })
+    page1:add(btn_progress)
+
+    current_y = current_y + 80
+
+    -- 消息框按钮
+    local msgbox_label = ui.label({
+        x = left_column_x, y = current_y,
+        text = "2. 消息框组件",
+        color = ui.COLOR_BLACK,
+        size = 18
+    })
+    page1:add(msgbox_label)
+
+    local btn_msgbox = ui.button({
+        x = left_column_x, y = current_y + 30,
+        w = 150, h = 40,
+        text = "弹出消息框",
+        size = 16,
+        on_click = function()
+            local message_box = ui.message_box({
+                x = 150, y = 150,
+                w = 500, h = 200,
+                wordWrap = true,
+                title = "综合演示",
+                message = "这是所有UI组件的综合演示页面,展示了exEasyUI的各种组件功能。",
+                buttons = { "确定", "取消" }
+            })
+            ui.add(message_box)
+        end
+    })
+    page1:add(btn_msgbox)
+
+    current_y = current_y + 90
+
+    -- 切换按钮
+    local toggle_label = ui.label({
+        x = left_column_x, y = current_y,
+        text = "3. 切换按钮",
+        color = ui.COLOR_BLACK,
+        size = 18
+    })
+    page1:add(toggle_label)
+
+    local btn_toggle = ui.button({
+        x = left_column_x, y = current_y + 30,
+        w = 80, h = 80,
+        src = "/luadb/4.jpg",
+        src_toggled = "/luadb/5.jpg",
+        toggle = true,
+    })
+    page1:add(btn_toggle)
+
+    current_y = current_y + 130
+
+    -- 复选框
+    local checkbox_label = ui.label({
+        x = left_column_x, y = current_y,
+        text = "4. 复选框组件",
+        color = ui.COLOR_BLACK,
+        size = 18
+    })
+    page1:add(checkbox_label)
+
+    local checkbox1 = ui.check_box({
+        x = left_column_x, y = current_y + 30,
+        text = "选项A",
+        checked = false,
+        size = 16,
+        on_change = function(checked)
+            log.info("checkbox", "选项A:", checked)
+        end
+    })
+    page1:add(checkbox1)
+
+    local checkbox2 = ui.check_box({
+        x = left_column_x + 120, y = current_y + 30,
+        text = "选项B",
+        checked = true,
+        size = 16,
+        on_change = function(checked)
+            log.info("checkbox", "选项B:", checked)
+        end
+    })
+    page1:add(checkbox2)
+
+    current_y = current_y + 80
+
+    -- 文本输入框
+    local input_label = ui.label({
+        x = left_column_x, y = current_y,
+        text = "5. 文本输入框",
+        color = ui.COLOR_BLACK,
+        size = 18
+    })
+    page1:add(input_label)
+
+    local text_input = ui.input({
+        x = left_column_x, y = current_y + 30,
+        w = 250, h = 35,
+        placeholder = "请输入文本...",
+        max_length = 20,
+        size = 16
+    })
+    page1:add(text_input)
+
+    current_y = current_y + 85
+
+    -- ==================== 右列:高级组件 ====================
+    local right_current_y = 80
+
+    -- 密码输入框
+    local password_label = ui.label({
+        x = right_column_x, y = right_current_y,
+        text = "6. 密码输入框",
+        color = ui.COLOR_BLACK,
+        size = 18
+    })
+    page1:add(password_label)
+
+    local password_input = ui.input({
+        x = right_column_x, y = right_current_y + 30,
+        w = 200, h = 35,
+        placeholder = "请输入密码...",
+        input_type = "password",
+        max_length = 16,
+        size = 16
+    })
+    page1:add(password_input)
+
+    local password_visible = false
+    local btn_password_toggle = ui.button({
+        x = right_column_x + 210, y = right_current_y + 30,
+        w = 80, h = 35,
+        text = "显示",
+        size = 14,
+        bg_color = ui.COLOR_BLUE,
+        text_color = ui.COLOR_WHITE,
+        on_click = function()
+            password_visible = not password_visible
+            password_input.input_type = password_visible and "text" or "password"
+            btn_password_toggle:set_text(password_visible and "隐藏" or "显示")
+        end
+    })
+    page1:add(btn_password_toggle)
+
+    right_current_y = right_current_y + 85
+
+    -- 数字输入框
+    local number_label = ui.label({
+        x = right_column_x, y = right_current_y,
+        text = "7. 数字输入框",
+        color = ui.COLOR_BLACK,
+        size = 18
+    })
+    page1:add(number_label)
+
+    local number_input = ui.input({
+        x = right_column_x, y = right_current_y + 30,
+        w = 120, h = 35,
+        placeholder = "0-100",
+        input_type = "number",
+        max_length = 3,
+        size = 16
+    })
+    page1:add(number_input)
+
+    local btn_number_minus = ui.button({
+        x = right_column_x + 130, y = right_current_y + 30,
+        w = 40, h = 35,
+        text = "-",
+        size = 16,
+        on_click = function()
+            local value = tonumber(number_input:get_text()) or 0
+            if value > 0 then
+                number_input:set_text(tostring(value - 1))
+            end
+        end
+    })
+    page1:add(btn_number_minus)
+
+    local btn_number_plus = ui.button({
+        x = right_column_x + 180, y = right_current_y + 30,
+        w = 40, h = 35,
+        text = "+",
+        size = 16,
+        on_click = function()
+            local value = tonumber(number_input:get_text()) or 0
+            if value < 100 then
+                number_input:set_text(tostring(value + 1))
+            end
+        end
+    })
+    page1:add(btn_number_plus)
+
+    right_current_y = right_current_y + 85
+
+    -- 下拉框
+    local combo_label = ui.label({
+        x = right_column_x, y = right_current_y,
+        text = "8. 下拉框组件",
+        color = ui.COLOR_BLACK,
+        size = 18
+    })
+    page1:add(combo_label)
+
+    local combo_box = ui.combo_box({
+        x = right_column_x, y = right_current_y + 30,
+        w = 200, h = 35,
+        options = { "选项1", "选项2", "选项3", "选项4" },
+        placeholder = "请选择",
+        selected = 1,
+        size = 16,
+        on_select = function(value, index, text)
+            log.info("combo_box", "选择了:", text, "索引:", index)
+        end
+    })
+    page1:add(combo_box)
+
+    right_current_y = right_current_y + 85
+
+    -- 图片轮播
+    local picture_label = ui.label({
+        x = right_column_x, y = right_current_y,
+        text = "9. 图片轮播",
+        color = ui.COLOR_BLACK,
+        size = 18
+    })
+    page1:add(picture_label)
+
+    local pic = ui.picture({
+        x = right_column_x, y = right_current_y + 30,
+        w = 200, h = 150,
+        sources = { "/luadb/1.jpg", "/luadb/2.jpg", "/luadb/3.jpg" },
+        autoplay = true,
+        interval = 2000
+    })
+    page1:add(pic)
+
+    local btn_picture_toggle = ui.button({
+        x = right_column_x, y = right_current_y + 190,
+        w = 100, h = 35,
+        text = "下一张",
+        size = 14,
+        on_click = function()
+            pic:next()
+        end
+    })
+    page1:add(btn_picture_toggle)
+
+    -- 底部提示
+    local hint = ui.label({
+        x = 250, y = 1650,
+        text = "上下滚动查看更多组件",
+        color = ui.COLOR_GRAY,
+        size = 16
+    })
+    page1:add(hint)
+
+    -- 注册窗口到UI系统
+    ui.add(page1)
+
+    -- 启动exeasyui刷新主循环
+    while true do
+        ui.refresh()
+        sys.wait(30) -- 约33FPS刷新率
+    end
+end
+
+sys.taskInit(ui_main)

+ 77 - 0
module/Air8101/demo/ui/easyui/single/win_autoplay_picture.lua

@@ -0,0 +1,77 @@
+--[[
+@module  win_autoplay_picture
+@summary 自动轮播图片演示模块
+@version 1.0.0
+@date    2025.12.9
+@author  江访
+@usage
+本文件为自动轮播图片演示模块,核心业务逻辑为:
+1、创建窗口容器并设置白色背景;
+2、添加图片轮播组件;
+3、配置自动播放和切换间隔;
+4、启动UI渲染循环持续刷新显示;
+
+本文件没有对外接口;
+]]
+
+local function ui_main()
+
+    -- 显示触摸初始化
+    hw_font_drv.init()
+
+    -- 设置主题
+    ui.sw_init({ theme = "light" })
+
+    -- 创建窗口容器
+    local page1 = ui.window({ background_color = ui.COLOR_WHITE })
+
+    local page_w, page_h = lcd.getSize()
+    -- 计算居中位置
+    local pic_width = 400
+    local pic_height = 300
+    local pic_x = (page_w - pic_width) / 2
+    local pic_y = 100
+
+    -- 创建自动轮播图片组件
+    local pic = ui.picture({ 
+        x = pic_x, y = pic_y, 
+        w = pic_width, h = pic_height,
+        sources = {"/luadb/1.jpg", "/luadb/2.jpg", "/luadb/3.jpg"}, 
+        autoplay = true, 
+        interval = 1500
+    })
+    
+    -- 添加标题
+    local title_label = ui.label({
+        x = pic_x, y = pic_y - 60,
+        text = "图片自动轮播演示",
+        color = ui.COLOR_BLACK,
+        size = 24
+    })
+
+    -- 添加说明标签
+    local hint_label = ui.label({
+        x = pic_x, y = pic_y + pic_height + 20,
+        text = "图片自动切换,间隔1.5秒",
+        color = ui.COLOR_GRAY,
+        size = 16
+    })
+
+    -- 添加组件到窗口
+    page1:add(title_label)
+    page1:add(pic)
+    page1:add(hint_label)
+
+    -- 注册窗口到UI系统
+    ui.add(page1)
+
+    -- 启动exeasyui刷新主循环
+    while true do
+        -- 刷新显示
+        ui.refresh()
+        -- 等待30ms
+        sys.wait(30)
+    end
+end
+
+sys.taskInit(ui_main)

+ 68 - 0
module/Air8101/demo/ui/easyui/single/win_button.lua

@@ -0,0 +1,68 @@
+--[[
+@module  win_button
+@summary 基础按钮组件演示模块
+@version 1.0.0
+@date    2025.12.9
+@author  江访
+@usage
+本文件为基础按钮组件演示模块,核心业务逻辑为:
+1、创建窗口容器并设置白色背景;
+2、添加基础按钮组件;
+3、启动UI渲染循环持续刷新显示;
+
+本文件没有对外接口;
+]]
+
+local function ui_main()
+
+    -- 显示触摸初始化
+    hw_font_drv.init()
+
+    -- 设置主题
+    ui.sw_init({ theme = "light" })
+
+    -- 创建窗口容器
+    local page1 = ui.window({ background_color = ui.COLOR_WHITE })
+
+    -- 计算居中位置
+    local page_w, page_h = lcd.getSize()
+    local button_width = 200
+    local button_height = 50
+    local button_x = (page_w - button_width) / 2
+    local button_y = (page_h - button_height) / 2
+
+    -- 创建按钮组件(文本模式)
+    local btn1 = ui.button({ 
+        x = button_x, y = button_y, 
+        w = button_width, h = button_height,
+        text = "基础按钮",
+        bg_color = ui.COLOR_BLUE,
+        text_color = ui.COLOR_WHITE,
+        size = 18
+    })
+    
+    -- 添加说明标签
+    local title_label = ui.label({
+        x = button_x, y = button_y - 60,
+        text = "基础按钮演示",
+        color = ui.COLOR_BLACK,
+        size = 24
+    })
+
+    -- 添加组件到窗口
+    page1:add(title_label)
+    page1:add(btn1)
+
+    -- 注册窗口到UI系统
+    ui.add(page1)
+
+    -- 启动exeasyui刷新主循环
+    while true do      
+        -- 刷新显示
+        ui.refresh()
+        -- 等待30ms
+        sys.wait(30) 
+    end
+end
+
+sys.taskInit(ui_main)

+ 73 - 0
module/Air8101/demo/ui/easyui/single/win_check_box.lua

@@ -0,0 +1,73 @@
+--[[
+@module  win_check_box
+@summary 复选框组件演示模块
+@version 1.0.0
+@date    2025.12.9
+@author  江访
+@usage
+本文件为复选框组件演示模块,核心业务逻辑为:
+1、创建窗口容器并设置白色背景;
+2、添加复选框组件;
+3、启动UI渲染循环持续刷新显示;
+
+本文件没有对外接口;
+]]
+
+local function ui_main()
+
+    -- 显示触摸初始化
+    hw_font_drv.init()
+
+    -- 设置主题
+    ui.sw_init({ theme = "light" })
+
+    -- 创建窗口容器
+    local page1 = ui.window({ background_color = ui.COLOR_WHITE })
+
+    -- 计算居中位置
+    local page_w, page_h = lcd.getSize()
+    local checkbox_width = 200
+    local checkbox_x = (page_w - checkbox_width) / 2
+
+    -- 创建复选框组件
+    local cb = ui.check_box({ 
+        x = checkbox_x, y = 200, 
+        w = 200, h = 30,
+        text = "选择我",
+        size = 18
+    })
+    
+    -- 添加标题
+    local title_label = ui.label({
+        x = checkbox_x, y = 120,
+        text = "复选框组件演示",
+        color = ui.COLOR_BLACK,
+        size = 24
+    })
+
+    -- 添加说明标签
+    local hint_label = ui.label({
+        x = checkbox_x, y = 250,
+        text = "点击复选框切换选中状态",
+        color = ui.COLOR_GRAY,
+        size = 14
+    })
+
+    -- 添加组件到窗口
+    page1:add(title_label)
+    page1:add(cb)
+    page1:add(hint_label)
+
+    -- 注册窗口到UI系统
+    ui.add(page1)
+
+    -- 启动exeasyui刷新主循环
+    while true do
+        -- 刷新显示
+        ui.refresh()
+        -- 等待30ms
+        sys.wait(30)
+    end
+end
+
+sys.taskInit(ui_main)

+ 79 - 0
module/Air8101/demo/ui/easyui/single/win_combo_box.lua

@@ -0,0 +1,79 @@
+--[[
+@module  win_combo_box
+@summary 下拉框组件演示模块
+@version 1.0.0
+@date    2025.12.9
+@author  江访
+@usage
+本文件为下拉框组件演示模块,核心业务逻辑为:
+1、创建窗口容器并设置白色背景;
+2、添加下拉框组件;
+3、配置选项列表和选择回调;
+4、启动UI渲染循环持续刷新显示;
+
+本文件没有对外接口;
+]]
+
+local function ui_main()
+
+    -- 显示触摸初始化
+    hw_font_drv.init()
+
+    -- 设置主题
+    ui.sw_init({ theme = "light" })
+
+    -- 创建窗口容器
+    local page1 = ui.window({ background_color = ui.COLOR_WHITE })
+
+    -- 计算居中位置
+    local page_w, page_h = lcd.getSize()
+    local container_width = 400
+    local container_x = (page_w - container_width) / 2
+
+    -- 创建下拉框组件
+    local combo_box = ui.combo_box({
+        x = container_x, y = 150,
+        w = 300, h = 40,
+        options = { "选项1", "选项2", "选项3", "选项4", "选项5" },
+        placeholder = "请选择",
+        selected = 1,
+        size = 16,
+        on_select = function(value, index, text)
+            log.info("combo_box", "选择了:", text, "索引:", index)
+        end
+    })
+
+    -- 添加标题
+    local title_label = ui.label({
+        x = container_x, y = 80,
+        text = "下拉框组件演示",
+        color = ui.COLOR_BLACK,
+        size = 24
+    })
+
+    -- 添加提示标签
+    local hint_label = ui.label({
+        x = container_x, y = 210,
+        text = "点击下拉箭头选择选项",
+        color = ui.COLOR_GRAY,
+        size = 14
+    })
+
+    -- 添加组件到窗口
+    page1:add(title_label)
+    page1:add(combo_box)
+    page1:add(hint_label)
+
+    -- 注册窗口到UI系统
+    ui.add(page1)
+
+    -- 启动exeasyui刷新主循环
+    while true do
+        -- 刷新显示
+        ui.refresh()
+        -- 等待30ms
+        sys.wait(30)
+    end
+end
+
+sys.taskInit(ui_main)

+ 89 - 0
module/Air8101/demo/ui/easyui/single/win_dyn_progress_bar.lua

@@ -0,0 +1,89 @@
+--[[
+@module  win_dyn_progress_bar
+@summary 动态进度条演示模块
+@version 1.0.0
+@date    2025.12.9
+@author  江访
+@usage
+本文件为动态进度条演示模块,核心业务逻辑为:
+1、创建窗口容器并设置白色背景;
+2、添加进度条组件;
+3、在主循环中动态更新进度条数值;
+4、实现进度条往复动画效果;
+5、启动UI渲染循环持续刷新显示;
+
+本文件没有对外接口;
+]]
+
+local direction = 1 
+local current = 0
+
+local function ui_main()
+
+    -- 显示触摸初始化
+    hw_font_drv.init()
+
+    -- 设置主题
+    ui.sw_init({ theme = "light" })
+
+    -- 创建窗口容器
+    local page1 = ui.window({ background_color = ui.COLOR_WHITE })
+
+    -- 计算居中位置
+    local page_w, page_h = lcd.getSize()
+    local progress_width = 600
+    local progress_height = 30
+    local progress_x = (page_w - progress_width) / 2
+    local progress_y = 200
+
+    -- 创建进度条组件
+    local pb = ui.progress_bar({ 
+        x = progress_x, y = progress_y, 
+        w = progress_width, h = progress_height
+    })
+    
+    -- 添加说明标签
+    local title_label = ui.label({
+        x = progress_x, y = progress_y - 60,
+        text = "动态进度条演示",
+        color = ui.COLOR_BLACK,
+        size = 24
+    })
+
+    -- 添加进度值显示标签
+    local value_label = ui.label({
+        x = progress_x, y = progress_y + 40,
+        text = "进度: 0%",
+        color = ui.COLOR_BLUE,
+        size = 18
+    })
+    
+    -- 添加组件到窗口
+    page1:add(title_label)
+    page1:add(pb)
+    page1:add(value_label)
+
+    -- 注册窗口到UI系统
+    ui.add(page1)
+
+    -- 启动exeasyui刷新主循环
+    while true do
+        -- 动态更新进度条数值
+        current = current + direction
+        if current >= 100 then
+            direction = -1
+        elseif current <= 0 then
+            direction = 1
+        end
+        
+        pb:set_progress(current)
+        value_label:set_text("进度: " .. current .. "%")
+        
+        -- 刷新显示
+        ui.refresh()
+        -- 等待30ms
+        sys.wait(30)
+    end
+end
+
+sys.taskInit(ui_main)

+ 81 - 0
module/Air8101/demo/ui/easyui/single/win_gtfont.lua

@@ -0,0 +1,81 @@
+--[[
+@module  win_gtfont
+@summary GTFont矢量字体演示模块
+@version 1.0.0
+@date    2025.12.9
+@author  江访
+@usage
+本文件为GTFont矢量字体演示模块,核心业务逻辑为:
+1、初始化GTFont矢量字体后端,使用外置SPI字库配件板;
+2、创建窗口容器并设置白色背景;
+3、添加多个标签组件展示不同字体样式;
+4、演示中英混排显示效果;
+5、启动UI渲染循环持续刷新显示;
+
+本文件没有对外接口;
+]]
+
+local function ui_main()
+
+    -- 启用GTFont矢量字体方式进行硬件初始化
+    hw_font_drv.init({ 
+        type = "gtfont", 
+        spi = { id = 1, cs = 3 }, 
+        size = 32 
+    })
+    
+    -- 设置主题
+    ui.sw_init({ theme = "light" })
+
+    -- 创建窗口容器
+    local win = ui.window({ background_color = ui.COLOR_WHITE })
+
+    -- 计算两列布局
+    local column_width = 380
+    local left_column_x = 40
+    local right_column_x = 420
+    local row_height = 60
+    local start_y = 80
+
+    -- 创建标题
+    local title = ui.label({
+        x = left_column_x + 200, y = start_y,
+        text = "GTFont矢量字体演示",
+        color = ui.COLOR_BLACK,
+        size = 24
+    })
+
+    -- 左列标签展示
+    local text1 = ui.label({ x = left_column_x, y = start_y + 60, text = "ABCDEFG", color = ui.COLOR_BLACK, size = 22 })
+    local text2 = ui.label({ x = left_column_x, y = start_y + 120, text = "abcdefg", color = ui.COLOR_RED, size = 24 })
+    local text3 = ui.label({ x = left_column_x, y = start_y + 180, text = "1234567", color = ui.COLOR_GREEN, size = 26 })
+    local text4 = ui.label({ x = left_column_x, y = start_y + 240, text = "!@#$%^&*", color = ui.COLOR_BLUE, size = 32 })
+
+    -- 右列标签展示
+    local text5 = ui.label({ x = right_column_x, y = start_y + 60, text = "Hello GTFont", color = ui.COLOR_ORANGE, size = 22 })
+    local text6 = ui.label({ x = right_column_x, y = start_y + 120, text = "中英混排 ABC 合宙", color = ui.COLOR_PINK, size = 24 })
+    local text7 = ui.label({ x = right_column_x, y = start_y + 180, text = "矢量字体清晰", color = ui.COLOR_PURPLE, size = 26 })
+    local text8 = ui.label({ x = right_column_x, y = start_y + 240, text = "支持10-192字号", color = ui.COLOR_DARK_GRAY, size = 32 })
+
+    -- 添加组件到窗口
+    win:add(title)
+    win:add(text1)
+    win:add(text2)
+    win:add(text3)
+    win:add(text4)
+    win:add(text5)
+    win:add(text6)
+    win:add(text7)
+    win:add(text8)
+
+    -- 注册窗口到UI系统
+    ui.add(win)
+
+    -- 启动exeasyui刷新主循环
+    while true do
+        ui.refresh()
+        sys.wait(200)
+    end
+end
+
+sys.taskInit(ui_main)

+ 106 - 0
module/Air8101/demo/ui/easyui/single/win_horizontal_slide.lua

@@ -0,0 +1,106 @@
+--[[
+@module  win_horizontal_slide
+@summary 横向滑动页面演示模块
+@version 1.0.0
+@date    2025.12.9
+@author  江访
+@usage
+本文件为横向滑动页面演示模块,核心业务逻辑为:
+1、创建窗口容器并设置白色背景;
+2、启用横向滑动功能;
+3、创建两页内容并水平排列;
+4、实现横向滑动切换页面效果;
+5、启动UI渲染循环持续刷新显示;
+
+本文件没有对外接口;
+]]
+
+local function ui_main()
+
+    -- 显示触摸初始化
+    hw_font_drv.init()
+
+    -- 设置主题
+    ui.sw_init({ theme = "light" })
+
+    -- 创建窗口容器
+    local win = ui.window({ background_color = ui.COLOR_WHITE })
+
+    -- 启用横向滚动,将两页内容并排布置
+    local page_w, page_h = lcd.getSize()
+    local totalW = page_w * 2
+
+    -- 创建横向滑动窗口
+    win:enable_scroll({ 
+        direction = "horizontal", 
+        content_width = totalW, 
+        threshold = 8, 
+        page_width = page_w 
+    })
+
+    -- 创建网格按钮函数
+    local function makeGrid(offset_x, label_prefix)
+        local cols, rows = 3, 4
+        local bw, bh = 200, 80
+        local mx, my = (page_w - cols * bw - (cols-1) * 20) / 2 + offset_x, 80
+        local gapx, gapy = 20, 20
+        local n = 1
+        
+        -- 添加页面标题
+        local page_title = ui.label({
+            x = mx, y = my - 50,
+            text = "页面 " .. label_prefix,
+            color = ui.COLOR_BLACK,
+            size = 22
+        })
+        win:add(page_title)
+        
+        for r = 0, rows - 1 do
+            for c = 0, cols - 1 do
+                local x = mx + c * (bw + gapx)
+                local y = my + r * (bh + gapy)
+                local btn = ui.button({ 
+                    x = x, y = y, 
+                    w = bw, h = bh, 
+                    text = string.format("%s-按钮%d", label_prefix, n),
+                    size = 16
+                })
+                win:add(btn)
+                n = n + 1
+            end
+        end
+    end
+
+    -- 创建总标题
+    local title = ui.label({
+        x = 250, y = 30,
+        text = "横向滑动页面演示",
+        color = ui.COLOR_BLACK,
+        size = 24
+    })
+    win:add(title)
+
+    -- 创建左页和右页内容
+    makeGrid(0, "一")  -- 第一页
+    makeGrid(page_w, "二")  -- 第二页
+
+    -- 添加提示标签
+    local hint = ui.label({
+        x = 600, y = 50,
+        text = "← 左右滑动切换页面 →",
+        color = ui.COLOR_GRAY,
+        size = 16
+    })
+    win:add(hint)
+
+    -- 注册窗口到UI系统
+    ui.add(win)
+
+    -- 启动exeasyui刷新主循环
+    while true do
+        ui.refresh()
+        sys.wait(30)
+    end
+end
+
+sys.taskInit(ui_main)

Некоторые файлы не были показаны из-за большого количества измененных файлов