Преглед изворни кода

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

马梦阳 пре 3 месеци
родитељ
комит
658684d300
100 измењених фајлова са 9523 додато и 2000 уклоњено
  1. 3 48
      components/airlink/src/task/luat_airlink_spi_master_task.c
  2. 36 375
      components/fatfs/luat_lib_fatfs.c
  3. 7 2
      components/hzfont/binding/luat_lib_hzfont.c
  4. 1 1
      components/hzfont/inc/luat_hzfont.h
  5. 121 12
      components/hzfont/src/luat_hzfont.c
  6. 58 56
      components/hzfont/src/ttf_parser.c
  7. 9 1
      components/network/iperf/binding/luat_lib_iperf.c
  8. 14 7
      components/network/iperf/include/luat_lwiperf.h
  9. 8 36
      components/network/iperf/src/luat_lwiperf.c
  10. 2 1
      components/network/libhttp/luat_http_client.c
  11. 12 0
      components/network/libsntp/luat_sntp.c
  12. 2 0
      components/pins/src/luat_pins.c
  13. 389 0
      components/rtmp/binding/luat_lib_rtmp.c
  14. 478 0
      components/rtmp/include/luat_rtmp_push.h
  15. 2689 0
      components/rtmp/src/luat_rtmp_push.c
  16. 531 0
      components/rtsp/binding/luat_lib_rtsp.c
  17. 362 0
      components/rtsp/include/luat_rtsp_push.h
  18. 1854 0
      components/rtsp/src/luat_rtsp_push.c
  19. 6 0
      components/u8g2/u8g2_font.c
  20. 2 9
      lua/src/liolib.c
  21. 7 0
      luat/include/luat_libs.h
  22. 2 0
      luat/include/luat_mem.h
  23. 2 0
      luat/include/luat_uart.h
  24. 43 0
      luat/include/luat_usb.h
  25. 7 7
      luat/modules/luat_lib_crypto.c
  26. 12 0
      luat/modules/luat_lib_fota.c
  27. 12 0
      luat/modules/luat_lib_rtos.c
  28. 5 1
      luat/modules/luat_lib_uart.c
  29. 346 0
      luat/modules/luat_lib_usb.c
  30. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_default_font_drv.lua
  31. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_gtfont_drv.lua
  32. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_hzfont_drv.lua
  33. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_default_font_drv.lua
  34. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_gtfont_drv.lua
  35. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_hzfont_drv.lua
  36. 65 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/main.lua
  37. 0 9
      module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/onewire_multi_18b20_swich_read/main.lua
  38. 0 125
      module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/onewire_multi_18b20_swich_read/switch_read.lua
  39. 367 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/onewire_multi_app.lua
  40. 0 108
      module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/onewire_single_18b20/main.lua
  41. 203 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/onewire_single_app.lua
  42. 212 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/readme.md
  43. 66 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/rtos/main.lua
  44. 59 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/rtos/readme.md
  45. 60 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/rtos/rtos_app.lua
  46. 366 291
      module/Air780EHM_Air780EHV_Air780EGH/demo/tf_card/tfcard_app.lua
  47. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/hw_font_drv.lua
  48. 5 5
      module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/main.lua
  49. 14 12
      module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/readme.md
  50. 15 14
      module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/win_horizontal_slide.lua
  51. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/ui/easyui/single/win_vertical_slide.lua
  52. 0 230
      module/Air780EPM/demo/accessory_board/AirLCD_1000/AirLCD_1000.lua
  53. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_12.bin
  54. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_22.bin
  55. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/font_drv/customer_font_drv.lua
  56. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/images/logo.jpg
  57. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/lcd_drv/exlcd_drv.lua
  58. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/lcd_drv/lcd_drv.lua
  59. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/main.lua
  60. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/readme.md
  61. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/tp_key_drv/key_drv.lua
  62. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/customer_font_page.lua
  63. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/home_page.lua
  64. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/lcd_page.lua
  65. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1000/lcd/ui/ui_main.lua
  66. BIN
      module/Air780EPM/demo/accessory_board/AirLCD_1000/logo.jpg
  67. 0 203
      module/Air780EPM/demo/accessory_board/AirLCD_1000/readme.md
  68. 0 187
      module/Air780EPM/demo/accessory_board/AirLCD_1000/ui_main.lua
  69. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_12.bin
  70. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_22.bin
  71. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/font_drv/customer_font_drv.lua
  72. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/images/logo.jpg
  73. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/lcd_drv/exlcd_drv.lua
  74. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/lcd_drv/lcd_drv.lua
  75. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/main.lua
  76. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/readme.md
  77. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/tp_key_drv/extp_drv.lua
  78. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/tp_key_drv/tp_drv.lua
  79. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/customer_font_page.lua
  80. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/home_page.lua
  81. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/lcd_page.lua
  82. 0 0
      module/Air780EPM/demo/accessory_board/AirLCD_1010/lcd/ui/ui_main.lua
  83. 65 0
      module/Air780EPM/demo/onewire/main.lua
  84. 0 9
      module/Air780EPM/demo/onewire/onewire_multi_18b20_swich_read/main.lua
  85. 0 125
      module/Air780EPM/demo/onewire/onewire_multi_18b20_swich_read/switch_read.lua
  86. 367 0
      module/Air780EPM/demo/onewire/onewire_multi_app.lua
  87. 0 107
      module/Air780EPM/demo/onewire/onewire_single_18b20/main.lua
  88. 203 0
      module/Air780EPM/demo/onewire/onewire_single_app.lua
  89. 212 0
      module/Air780EPM/demo/onewire/readme.md
  90. 66 0
      module/Air780EPM/demo/rtos/main.lua
  91. 54 0
      module/Air780EPM/demo/rtos/readme.md
  92. 60 0
      module/Air780EPM/demo/rtos/rtos_app.lua
  93. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_default_font_drv.lua
  94. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_gtfont_drv.lua
  95. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1000/exeasyui/hw_drv/hw_hzfont_drv.lua
  96. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_default_font_drv.lua
  97. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_gtfont_drv.lua
  98. 1 1
      module/Air8000/demo/accessory_board/AirLCD_1010/exeasyui/hw_drv/hw_hzfont_drv.lua
  99. 17 3
      module/Air8000/demo/accessory_board/AirSHT30_1000/readme.md
  100. 12 1
      module/Air8000/demo/accessory_board/AirSHT30_1000/sht30_app.lua

+ 3 - 48
components/airlink/src/task/luat_airlink_spi_master_task.c

@@ -13,9 +13,6 @@
 #include "luat_mem.h"
 #include "luat_mcu.h"
 #include "luat_fs.h"
-#ifdef __BK72XX__
-#include <os/os.h>
-#endif
 #define LUAT_LOG_TAG "airlink"
 #include "luat_log.h"
 
@@ -43,10 +40,6 @@ static uint8_t thread_rdy;
 static uint8_t spi_rdy;
 static luat_rtos_task_handle spi_task_handle;
 static luat_rtos_task_handle spi_irq_task_handle;
-#if defined(__BK72XX__) && defined(CONFIG_FREERTOS_SMP)
-static beken_thread_t bk_spi_task_handle = NULL;
-static beken_thread_t bk_spi_irq_task_handle = NULL;
-#endif
 
 #if defined(LUAT_USE_AIRLINK_EXEC_MOBILE)
 extern luat_airlink_dev_info_t g_airlink_self_dev_info;
@@ -77,13 +70,8 @@ __USER_FUNC_IN_RAM__ static int rdy_pin_irq_handler(void* param)
     }
     rdy_ready_flag = 1;
     // 发送通知事件,告知任务RDY已就绪
-#ifdef __BK72XX__
-    luat_event_t evt = {.id = 2};
-    luat_rtos_queue_send(rdy_task_evt_queue, &evt, sizeof(evt), 0);
-#else
     luat_event_t evt = {.id = 6};
     luat_rtos_queue_send(rdy_evt_queue, &evt, sizeof(evt), 0);
-#endif
     // LLOGD("RDY中断触发,设置就绪标志");
     return 0;
 }
@@ -156,12 +144,11 @@ void luat_airlink_spi_master_pin_setup(void)
         .mode = 1, // mode设置为1,全双工
 #ifdef __BK72XX__
         .bandrate = g_airlink_spi_conf.speed > 0 ? g_airlink_spi_conf.speed : 13000000,
-        .cs = AIRLINK_SPI_CS_PIN};
 #else
         .bandrate = g_airlink_spi_conf.speed > 0 ? g_airlink_spi_conf.speed : 31000000,
-        .cs = 255};
-    // luat_pm_iovolt_ctrl(0, 3300);
+        // luat_pm_iovolt_ctrl(0, 3300);
 #endif
+        .cs = 255};
     luat_spi_setup(&spi_conf);
     luat_gpio_cfg_t gpio_cfg = {0};
 
@@ -181,15 +168,13 @@ void luat_airlink_spi_master_pin_setup(void)
     gpio_cfg.pull = LUAT_GPIO_PULLUP;
     gpio_cfg.irq_cb = rdy_pin_irq_handler;
     luat_gpio_open(&gpio_cfg);
-#ifndef __BK72XX__
-    // CS片选脚
+//     // CS片选脚
     luat_gpio_set_default_cfg(&gpio_cfg);
     gpio_cfg.pin = AIRLINK_SPI_CS_PIN;
     gpio_cfg.mode = LUAT_GPIO_OUTPUT;
     gpio_cfg.pull = LUAT_GPIO_PULLUP;
     gpio_cfg.output_level = 1;
     luat_gpio_open(&gpio_cfg);
-#endif
 
     if (g_airlink_spi_conf.irq_pin != 255)
     {
@@ -237,9 +222,7 @@ __USER_FUNC_IN_RAM__ void airlink_transfer_and_exec(uint8_t *txbuff, uint8_t *rx
 
     g_airlink_statistic.tx_pkg.total++;
     // 拉低片选, 准备发送数据
-#ifndef __BK72XX__
     luat_gpio_set(AIRLINK_SPI_CS_PIN, 0);
-#endif
     
     // luat_spi_lock(MASTER_SPI_ID);
     // 发送数据
@@ -256,9 +239,7 @@ __USER_FUNC_IN_RAM__ void airlink_transfer_and_exec(uint8_t *txbuff, uint8_t *rx
     }
     rdy_ready_flag = 0;
     // 拉高片选, 数据发送完毕
-#ifndef __BK72XX__
     luat_gpio_set(AIRLINK_SPI_CS_PIN, 1);
-#endif
     // luat_airlink_print_buff("RX", rxbuff, 64);
     // 对接收到的数据进行解析
     link = luat_airlink_data_unpack(rxbuff, TEST_BUFF_SIZE);
@@ -451,24 +432,6 @@ __USER_FUNC_IN_RAM__ static void spi_master_task(void *param)
     }
 }
 
-#ifdef __BK72XX__
-__USER_FUNC_IN_RAM__ static void spi_irq_task(void *param)
-{
-    while (1)
-    {
-        uint64_t timeout = 0;
-        luat_event_t event = {0};
-        luat_rtos_queue_recv(rdy_task_evt_queue, &event, sizeof(luat_event_t), 10);
-        if (event.id == 2)
-        {
-            // LLOGD("从机通知IRQ中断");
-            // 发送通知事件,告知任务RDY已就绪
-            luat_event_t evt = {.id = 6};
-            luat_rtos_queue_send(rdy_evt_queue, &evt, sizeof(evt), 0);
-        }
-    }
-}
-#endif
 
 void luat_airlink_start_master(void)
 {
@@ -485,15 +448,7 @@ void luat_airlink_start_master(void)
     luat_rtos_queue_create(&evt_queue, 4 * 1024, sizeof(luat_event_t));
     // 创建专门的RDY事件队列 (id=6)
     luat_rtos_queue_create(&rdy_evt_queue, 10, sizeof(luat_event_t));
-#if defined(__BK72XX__) && defined(CONFIG_FREERTOS_SMP)
-// luat_rtos_task_create(&spi_irq_task_handle,  4 * 1024, 95, "spi_irq", spi_irq_task, NULL, 0);
-    rtos_core0_create_thread((beken_thread_t *)bk_spi_task_handle, BEKEN_APPLICATION_PRIORITY, "spi", (beken_thread_function_t)spi_master_task, 8 * 1024, 0);
-    rtos_core0_create_thread((beken_thread_t *)bk_spi_irq_task_handle, 10, "spi_irq", (beken_thread_function_t)spi_irq_task, 4 * 1024, 0);
-    // 创建专门用于处理从机通知中断的任务
-    luat_rtos_queue_create(&rdy_task_evt_queue, 1, sizeof(luat_event_t));
-#else
     luat_rtos_task_create(&spi_task_handle, 8 * 1024, 50, "spi", spi_master_task, NULL, 0);
-#endif
 }
 
 int luat_airlink_irqmode(luat_airlink_irq_ctx_t *ctx) {

+ 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)}
 };

+ 7 - 2
components/hzfont/binding/luat_lib_hzfont.c

@@ -25,9 +25,10 @@ hzfont.init("/sd/font.ttf")
 
 /**
 初始化HzFont字体库
-@api hzfont.init([ttf_path][, cache_size])
+@api hzfont.init([ttf_path][, cache_size][, load_to_psram])
 @string ttf_path TTF字体文件路径,可选;留空则回退到内置字库(若启用)
 @int cache_size 可选,位图与码点缓存容量(支持常量 HZFONT_CACHE_128/256/512/1024/2048),默认 HZFONT_CACHE_256
+@bool load_to_psram 可选,true 时将字库整包拷贝到 PSRAM 后再解析,减少后续 IO
 @return boolean 成功返回true,失败返回false
 @usage
 -- 从文件加载,使用默认缓存 256
@@ -52,7 +53,11 @@ static int l_hzfont_init(lua_State* L) {
     if (!lua_isnoneornil(L, 2)) {
         cache_size = (uint32_t)luaL_checkinteger(L, 2);
     }
-    int result = luat_hzfont_init(ttf_path, cache_size);
+    int load_to_psram = 0;
+    if (!lua_isnoneornil(L, 3)) {
+        load_to_psram = lua_toboolean(L, 3);
+    }
+    int result = luat_hzfont_init(ttf_path, cache_size, load_to_psram);
     lua_pushboolean(L, result);
     return 1;
 }

+ 1 - 1
components/hzfont/inc/luat_hzfont.h

@@ -14,7 +14,7 @@ typedef enum {
     LUAT_HZFONT_STATE_ERROR  = 2,
 } luat_hzfont_state_t;
 
-int luat_hzfont_init(const char *ttf_path, uint32_t cache_size);
+int luat_hzfont_init(const char *ttf_path, uint32_t cache_size, int load_to_psram);
 void luat_hzfont_deinit(void);
 luat_hzfont_state_t luat_hzfont_get_state(void);
 uint32_t luat_hzfont_get_str_width(const char *utf8, unsigned char font_size);

+ 121 - 12
components/hzfont/src/luat_hzfont.c

@@ -1,11 +1,14 @@
+// 负责:TTF 字体加载、缓存与渲染(HzFont)
 #include "luat_hzfont.h"
 
 #include "ttf_parser.h"
 #include "luat_lcd.h"
 #include "luat_mem.h"
 #include "luat_mcu.h"
+#include "luat_fs.h"
 
 #include <math.h>
+#include <stdio.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <string.h>
@@ -17,8 +20,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 +90,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 +164,56 @@ 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;
+}
+
+/* 将字体文件完整读入内存(PSRAM),用于后续内存解析 */
+static int hzfont_load_file_to_ram(const char *path, uint8_t **out_data, size_t *out_size) {
+    if (!path || !out_data || !out_size) {
+        return TTF_ERR_RANGE;
+    }
+    *out_data = NULL;
+    *out_size = 0;
+
+    FILE *fp = luat_fs_fopen(path, "rb");
+    if (!fp) {
+        return TTF_ERR_IO;
+    }
+
+    if (luat_fs_fseek(fp, 0, SEEK_END) != 0) {
+        luat_fs_fclose(fp);
+        return TTF_ERR_IO;
+    }
+    long vsize = luat_fs_ftell(fp);
+    if (vsize <= 0) {
+        luat_fs_fclose(fp);
+        return TTF_ERR_IO;
+    }
+    if (luat_fs_fseek(fp, 0, SEEK_SET) != 0) {
+        luat_fs_fclose(fp);
+        return TTF_ERR_IO;
+    }
+    uint8_t *buf = (uint8_t *)luat_heap_malloc((size_t)vsize);
+    if (!buf) {
+        luat_fs_fclose(fp);
+        return TTF_ERR_OOM;
+    }
+    size_t n = luat_fs_fread(buf, 1, (size_t)vsize, fp);
+    luat_fs_fclose(fp);
+    if (n != (size_t)vsize) {
+        luat_heap_free(buf);
+        return TTF_ERR_IO;
+    }
+    *out_data = buf;
+    *out_size = (size_t)vsize;
+    return TTF_OK;
 }
 
 static void hzfont_cache_destroy(void) {
@@ -183,6 +239,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,7 +578,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);
 }
 
-int luat_hzfont_init(const char *ttf_path, uint32_t cache_size) {
+/* 初始化字体库
+ * What: 加载 TTF(外部/内置),可选整包读入 PSRAM,建立码点与位图缓存。
+ * Pre: 需要有效的 ttf_path 或启用内置字库宏;cache_size 仅允许 128/256/512/1024/2048;load_to_psram=1 时需有足够 RAM。
+ * Post: 状态置为 READY,后续方可测宽/绘制;失败时状态为 ERROR。
+ */
+int luat_hzfont_init(const char *ttf_path, uint32_t cache_size, int load_to_psram) {
     if (g_ft_ctx.state == LUAT_HZFONT_STATE_READY) {
         LLOGE("font already initialized");
         return 0;
@@ -533,17 +598,53 @@ int luat_hzfont_init(const char *ttf_path, uint32_t cache_size) {
     memset(&g_ft_ctx.font, 0, sizeof(g_ft_ctx.font));
 
     int rc = TTF_ERR_RANGE;
+    uint8_t *ram_buf = NULL;
+    size_t ram_size = 0;
     if (ttf_path && ttf_path[0]) {
-        rc = ttf_load_from_file(ttf_path, &g_ft_ctx.font);
-        if (rc == TTF_OK) {
-            strncpy(g_ft_ctx.font_path, ttf_path, sizeof(g_ft_ctx.font_path) - 1);
-            g_ft_ctx.font_path[sizeof(g_ft_ctx.font_path) - 1] = 0;
+        if (load_to_psram) {
+            rc = hzfont_load_file_to_ram(ttf_path, &ram_buf, &ram_size);
+            if (rc == TTF_OK) {
+                rc = ttf_load_from_memory(ram_buf, ram_size, &g_ft_ctx.font);
+                if (rc == TTF_OK) {
+                    g_ft_ctx.font.ownsData = 1; /* 允许 ttf_unload 释放 */
+                    g_ft_ctx.font_path[0] = 0;
+                    strncpy(g_ft_ctx.font_path, ttf_path, sizeof(g_ft_ctx.font_path) - 1);
+                    g_ft_ctx.font_path[sizeof(g_ft_ctx.font_path) - 1] = 0;
+                } else {
+                    luat_heap_free(ram_buf);
+                    ram_buf = NULL;
+                }
+            }
+        } else {
+            rc = ttf_load_from_file(ttf_path, &g_ft_ctx.font);
+            if (rc == TTF_OK) {
+                strncpy(g_ft_ctx.font_path, ttf_path, sizeof(g_ft_ctx.font_path) - 1);
+                g_ft_ctx.font_path[sizeof(g_ft_ctx.font_path) - 1] = 0;
+            }
         }
     } else {
 #ifdef LUAT_CONF_USE_HZFONT_BUILTIN_TTF
-        rc = ttf_load_from_memory(hzfont_builtin_ttf, (size_t)hzfont_builtin_ttf_len, &g_ft_ctx.font);
-        if (rc == TTF_OK) {
-            g_ft_ctx.font_path[0] = '\0';
+        if (load_to_psram) {
+            ram_buf = (uint8_t *)luat_heap_malloc((size_t)hzfont_builtin_ttf_len);
+            if (!ram_buf) {
+                rc = TTF_ERR_OOM;
+            } else {
+                memcpy(ram_buf, hzfont_builtin_ttf, (size_t)hzfont_builtin_ttf_len);
+                ram_size = (size_t)hzfont_builtin_ttf_len;
+                rc = ttf_load_from_memory(ram_buf, ram_size, &g_ft_ctx.font);
+                if (rc == TTF_OK) {
+                    g_ft_ctx.font.ownsData = 1;
+                    g_ft_ctx.font_path[0] = '\0';
+                } else {
+                    luat_heap_free(ram_buf);
+                    ram_buf = NULL;
+                }
+            }
+        } else {
+            rc = ttf_load_from_memory(hzfont_builtin_ttf, (size_t)hzfont_builtin_ttf_len, &g_ft_ctx.font);
+            if (rc == TTF_OK) {
+                g_ft_ctx.font_path[0] = '\0';
+            }
         }
 #else
         LLOGE("empty ttf path and no builtin ttf");
@@ -671,6 +772,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 +792,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 +976,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 +1059,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));
 }

+ 9 - 1
components/network/iperf/binding/luat_lib_iperf.c

@@ -79,7 +79,15 @@ static void iperf_start_cb(void* args) {
         LLOGD("server listen %s:%d", buff, ctx->port);
     }
     else {
-        luat_lwiperf_start_tcp_client(remote_ip, ctx->port, LWIPERF_CLIENT, iperf_report_cb, NULL, &drv->netif->ip_addr);
+        lwiperf_client_conf_t conf = {0};
+        conf.remote_addr = remote_ip;
+        conf.remote_port = ctx->port;
+        conf.type = LWIPERF_CLIENT;
+        conf.report_fn = iperf_report_cb;
+        conf.report_arg = NULL;
+        conf.local_addr = &drv->netif->ip_addr;
+        conf.amount = htonl((u32_t)-6000); // 默认测试60秒
+        luat_lwiperf_start_tcp_client(&conf);
         ipaddr_ntoa_r(remote_ip, buff2, sizeof(buff2));
         LLOGD("client connect %s --> %s:%d", buff, buff2, ctx->port);
     }

+ 14 - 7
components/network/iperf/include/luat_lwiperf.h

@@ -74,6 +74,7 @@ enum lwiperf_client_type
   LWIPERF_TRADEOFF
 };
 
+
 /** Prototype of a report function that is called when a session is finished.
     This report function can show the test results.
     @param report_type contains the test result */
@@ -81,15 +82,21 @@ typedef void (*lwiperf_report_fn)(void *arg, enum lwiperf_report_type report_typ
   const ip_addr_t* local_addr, u16_t local_port, const ip_addr_t* remote_addr, u16_t remote_port,
   u32_t bytes_transferred, u32_t ms_duration, u32_t bandwidth_kbitpsec);
 
+
+typedef struct lwiperf_client_conf
+{
+  const ip_addr_t* remote_addr;
+  u16_t remote_port;
+  enum lwiperf_client_type type;
+  lwiperf_report_fn report_fn;
+  void* report_arg;
+  const ip_addr_t* local_addr;
+  u32_t amount; /* in bytes, 0 means infinite */
+}lwiperf_client_conf_t;
+
 void* luat_lwiperf_start_tcp_server(const ip_addr_t* local_addr, u16_t local_port,
                                lwiperf_report_fn report_fn, void* report_arg);
-void* luat_lwiperf_start_tcp_server_default(lwiperf_report_fn report_fn, void* report_arg);
-void* luat_lwiperf_start_tcp_client(const ip_addr_t* remote_addr, u16_t remote_port,
-                               enum lwiperf_client_type type,
-                               lwiperf_report_fn report_fn, void* report_arg,
-                               const ip_addr_t* local_addr);
-void* luat_lwiperf_start_tcp_client_default(const ip_addr_t* remote_addr,
-                               lwiperf_report_fn report_fn, void* report_arg);
+void* luat_lwiperf_start_tcp_client(lwiperf_client_conf_t* client_conf);
 
 void  luat_lwiperf_abort(void* lwiperf_session);
 

+ 8 - 36
components/network/iperf/src/luat_lwiperf.c

@@ -673,21 +673,7 @@ static void iperf_free(size_t line, void* ptr);
    lwiperf_list_add(&conn->base);
    return ERR_OK;
  }
- 
- /**
-  * @ingroup iperf
-  * Start a TCP iperf server on the default TCP port (5001) and listen for
-  * incoming connections from iperf clients.
-  *
-  * @returns a connection handle that can be used to abort the server
-  *          by calling @ref lwiperf_abort()
-  */
- void *
- luat_lwiperf_start_tcp_server_default(lwiperf_report_fn report_fn, void *report_arg)
- {
-   return luat_lwiperf_start_tcp_server(IP_ADDR_ANY, LWIPERF_TCP_PORT_DEFAULT,
-                                   report_fn, report_arg);
- }
+
  
  /**
   * @ingroup iperf
@@ -765,19 +751,6 @@ static void iperf_free(size_t line, void* ptr);
    return ERR_OK;
  }
  
- /**
-  * @ingroup iperf
-  * Start a TCP iperf client to the default TCP port (5001).
-  *
-  * @returns a connection handle that can be used to abort the client
-  *          by calling @ref lwiperf_abort()
-  */
- void*  luat_lwiperf_start_tcp_client_default(const ip_addr_t* remote_addr,
-                                lwiperf_report_fn report_fn, void* report_arg)
- {
-   return  luat_lwiperf_start_tcp_client(remote_addr, LWIPERF_TCP_PORT_DEFAULT, LWIPERF_CLIENT,
-                                   report_fn, report_arg, NULL);
- }
  
  /**
   * @ingroup iperf
@@ -786,15 +759,14 @@ static void iperf_free(size_t line, void* ptr);
   * @returns a connection handle that can be used to abort the client
   *          by calling @ref lwiperf_abort()
   */
- void*  luat_lwiperf_start_tcp_client(const ip_addr_t* remote_addr, u16_t remote_port,
-   enum lwiperf_client_type type, lwiperf_report_fn report_fn, void* report_arg, const ip_addr_t* local_addr)
+ void*  luat_lwiperf_start_tcp_client(lwiperf_client_conf_t* client_conf)
  {
    err_t ret;
    lwiperf_settings_t settings;
    lwiperf_state_tcp_t *state = NULL;
  
    memset(&settings, 0, sizeof(settings));
-   switch (type) {
+   switch (client_conf->type) {
    case LWIPERF_CLIENT:
      /* Unidirectional tx only test */
      settings.flags = 0;
@@ -814,16 +786,16 @@ static void iperf_free(size_t line, void* ptr);
    settings.num_threads = htonl(1);
    settings.remote_port = htonl(LWIPERF_TCP_PORT_DEFAULT);
    /* TODO: implement passing duration/amount of bytes to transfer */
-   settings.amount = htonl((u32_t)-1000);
+   settings.amount = client_conf->amount;
    LLOGD("准备启动iperf客户端");
-   ret = lwiperf_tx_start_impl(remote_addr, remote_port, &settings, report_fn, report_arg, NULL, &state, local_addr);
+   ret = lwiperf_tx_start_impl(client_conf->remote_addr, client_conf->remote_port, &settings, client_conf->report_fn, client_conf->report_arg, NULL, &state, client_conf->local_addr);
    if (ret == ERR_OK) {
      LWIP_ASSERT("state != NULL", state != NULL);
-     if (type != LWIPERF_CLIENT) {
+     if (client_conf->type != LWIPERF_CLIENT) {
        /* start corresponding server now */
        lwiperf_state_tcp_t *server = NULL;
        ret = lwiperf_start_tcp_server_impl(&state->conn_pcb->local_ip, LWIPERF_TCP_PORT_DEFAULT,
-         report_fn, report_arg, (lwiperf_state_base_t *)state, &server);
+         client_conf->report_fn, client_conf->report_arg, (lwiperf_state_base_t *)state, &server);
        if (ret != ERR_OK) {
          /* starting server failed, abort client */
          luat_lwiperf_abort(state);
@@ -832,7 +804,7 @@ static void iperf_free(size_t line, void* ptr);
        /* make this server accept one connection only */
        server->specific_remote = 1;
        server->remote_addr = state->conn_pcb->remote_ip;
-       if (type == LWIPERF_TRADEOFF) {
+       if (client_conf->type == LWIPERF_TRADEOFF) {
          /* tradeoff means that the remote host connects only after the client is done,
             so keep the listen pcb open until the client is done */
          server->client_tradeoff_mode = 1;

+ 2 - 1
components/network/libhttp/luat_http_client.c

@@ -551,6 +551,7 @@ static void http_send_message(luat_http_ctrl_t *http_ctrl){
 	// 发送请求行, 主要,这里都借用了resp_buff,但这并不会与resp冲突
 	int result;
 	http_send(http_ctrl, (uint8_t *)http_ctrl->request_line, strlen((char*)http_ctrl->request_line));
+	http_send(http_ctrl, (uint8_t*)"Connection: Close\r\n", strlen("Connection: Close\r\n"));
 	// 判断自定义headers是否有host	
 	if (http_ctrl->custom_host == 0) {
 		result = snprintf_(http_ctrl->resp_buff, HTTP_RESP_BUFF_SIZE,  "Host: %s:%d\r\n", http_ctrl->host, http_ctrl->remote_port);
@@ -646,7 +647,7 @@ LUAT_RT_RET_TYPE luat_http_timer_callback(LUAT_RT_CB_PARAM){
 }
 
 static void on_tcp_closed(luat_http_ctrl_t *http_ctrl) {
-	LLOGI("on_tcp_closed %p", http_ctrl);
+	LLOGI("on_tcp_closed %p body is done %d header is complete %d", http_ctrl, http_ctrl->http_body_is_finally, http_ctrl->headers_complete);
 	int ret = 0;
 	http_ctrl->tcp_closed = 1;
 	if (http_ctrl->http_body_is_finally == 0) { // 当没有解析完成

+ 12 - 0
components/network/libsntp/luat_sntp.c

@@ -358,3 +358,15 @@ int ntp_get(int adapter_index){
     }
     return ret;
 }
+
+uint64_t luat_sntp_time64_ms() {
+    uint64_t tick64 = luat_mcu_tick64();
+    uint32_t us_period = luat_mcu_us_period();
+    uint64_t ll_sec = tick64 /us_period/ 1000 / 1000;
+    uint64_t ll_ms  = (tick64 /us_period/ 1000) % 1000;
+    uint64_t tmp = ll_sec + g_sntp_ctx.sysboot_diff_sec;
+    //LLOGD("ntp sec: sec=%llu, ms=%u", tmp, ll_ms);
+    tmp *= 1000;
+    tmp += ll_ms + g_sntp_ctx.sysboot_diff_ms;
+    return tmp;
+}

+ 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;
+}

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

@@ -0,0 +1,478 @@
+/**
+ * @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 {
+    uint64_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;  /**< 最后音频时间戳(毫秒) */
+
+    /* 细分统计 */
+    uint32_t i_frames;              /**< 发送的I帧数量 */
+    uint32_t p_frames;              /**< 发送的P帧数量 */
+    uint64_t i_bytes;               /**< 发送的I帧字节数(NAL数据长度累加) */
+    uint64_t p_bytes;               /**< 发送的P帧字节数(NAL数据长度累加) */
+    uint64_t audio_bytes;           /**< 发送的音频字节数 */
+
+    uint32_t dropped_frames;        /**< 被丢弃的帧数量 */
+    uint64_t dropped_bytes;         /**< 被丢弃的帧字节数 */
+} 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;          /**< 已发送的包数 */
+    uint64_t bytes_sent;            /**< 已发送的字节数 */
+    uint32_t command_id;            /**< 当前命令ID */
+
+    /* 帧统计 */
+    uint32_t i_frames;              /**< 发送的I帧数量 */
+    uint32_t p_frames;              /**< 发送的P帧数量 */
+    uint64_t i_bytes;               /**< 发送的I帧字节数 */
+    uint64_t p_bytes;               /**< 发送的P帧字节数 */
+    uint32_t audio_frames_sent;     /**< 发送的音频帧数量 */
+    uint64_t audio_bytes;           /**< 发送的音频字节数 */
+    uint32_t dropped_frames;        /**< 被丢弃的帧数量 */
+    uint64_t dropped_bytes;         /**< 被丢弃的帧字节数 */
+    uint32_t last_stats_log_ms;     /**< 上次统计日志输出时间 */
+    uint64_t last_stats_bytes;      /**< 上次统计日志输出时的总字节数 */
+    uint32_t stats_interval_ms;     /**< 统计输出间隔(毫秒),默认10000 */
+    uint32_t stats_window_ms;       /**< 统计窗口长度(毫秒),默认与间隔相同 */
+    uint32_t last_window_ms;        /**< 上次窗口采样时间戳(ms) */
+    uint64_t last_window_bytes;     /**< 上次窗口采样时的总字节数 */
+    
+    /** ============ 用户数据 ============ */
+    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);
+
+/**
+ * 发送音频数据帧
+ * 
+ * 将音频数据打包为RTMP音频消息并发送。
+ * 支持AAC等多种音频格式。
+ * 
+ * 使用示例(AAC):
+ * @code
+ * // 发送AAC Sequence Header
+ * uint8_t aac_header[] = {0xAF, 0x00, 0x12, 0x10}; // AAC-LC, 44.1kHz, Stereo
+ * rtmp_send_audio(ctx, aac_header, sizeof(aac_header), 0);
+ * 
+ * // 发送AAC音频帧
+ * uint8_t audio_frame[256];
+ * audio_frame[0] = 0xAF; // AAC, 44.1kHz, 16bit, Stereo
+ * audio_frame[1] = 0x01; // AAC raw data
+ * memcpy(&audio_frame[2], aac_raw_data, aac_raw_len);
+ * rtmp_send_audio(ctx, audio_frame, 2 + aac_raw_len, timestamp);
+ * @endcode
+ * 
+ * 音频标签头格式(第1字节):
+ * - Bit[7:4]: SoundFormat (10=AAC, 2=MP3, 3=PCM等)
+ * - Bit[3:2]: SoundRate (0=5.5kHz, 1=11kHz, 2=22kHz, 3=44kHz)
+ * - Bit[1]: SoundSize (0=8bit, 1=16bit)
+ * - Bit[0]: SoundType (0=Mono, 1=Stereo)
+ * 
+ * AAC格式需要第2字节指定AACPacketType:
+ * - 0 = AAC sequence header (AudioSpecificConfig)
+ * - 1 = AAC raw data
+ * 
+ * @param ctx RTMP上下文指针
+ * @param audio_data 音频数据指针,应包含完整的音频标签(标签头+数据)
+ * @param audio_len 音频数据总长度
+ * @param timestamp 音频时间戳(毫秒),从0开始递增
+ * @return RTMP_OK表示发送成功,其他值表示错误
+ *         - RTMP_ERR_INVALID_PARAM: 参数无效
+ *         - RTMP_ERR_NO_MEMORY: 内存不足
+ *         - RTMP_ERR_FAILED: 发送失败
+ */
+int rtmp_send_audio(rtmp_ctx_t *ctx, const uint8_t *audio_data,
+                    uint32_t audio_len, 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);
+
+/**
+ * 设置统计输出间隔
+ * 
+ * @param ctx RTMP上下文指针
+ * @param interval_ms 间隔毫秒数(例如10000表示10秒)
+ * @return RTMP_OK表示成功
+ */
+int rtmp_set_stats_interval(rtmp_ctx_t *ctx, uint32_t interval_ms);
+
+/**
+ * 设置统计窗口长度
+ * 
+ * @param ctx RTMP上下文指针
+ * @param window_ms 窗口毫秒数(例如10000表示10秒)
+ * @return RTMP_OK表示成功
+ */
+int rtmp_set_stats_window(rtmp_ctx_t *ctx, uint32_t window_ms);
+
+/* ======================== 回调函数定义 ======================== */
+
+/**
+ * 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__ */

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

@@ -0,0 +1,2689 @@
+/**
+ * @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;                /* 是否关键帧 */
+    uint32_t enqueue_ms;        /* 入队时间戳(ms) */
+    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;
+
+    /* 统计初始化 */
+    ctx->last_stats_log_ms = (uint32_t)luat_mcu_tick64_ms();
+    ctx->last_stats_bytes = 0;
+    ctx->stats_interval_ms = 10000;
+    ctx->stats_window_ms = 10000;
+    ctx->last_window_ms = ctx->last_stats_log_ms;
+    ctx->last_window_bytes = 0;
+    
+    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->enqueue_ms = (uint32_t)luat_mcu_tick64_ms();
+    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->enqueue_ms = (uint32_t)luat_mcu_tick64_ms();
+    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 += (uint64_t)nalu_len;
+    if (is_key_frame) {
+        ctx->i_frames++;
+        ctx->i_bytes += nalu_len;
+    } else {
+        ctx->p_frames++;
+        ctx->p_bytes += 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;
+}
+
+/**
+ * 发送音频数据帧
+ */
+int rtmp_send_audio(rtmp_ctx_t *ctx, const uint8_t *audio_data,
+                    uint32_t audio_len, uint32_t timestamp) {
+    if (!ctx || !audio_data || audio_len == 0) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    
+    if (ctx->state != RTMP_STATE_PUBLISHING) {
+        LLOGE("RTMP: Cannot send audio, not in publishing state");
+        return RTMP_ERR_FAILED;
+    }
+    
+    /* 构建RTMP音频消息 (消息类型8=音频, 流ID=1) */
+    uint8_t *rtmp_buf = NULL;
+    uint32_t rtmp_len = 0;
+    int ret = rtmp_build_rtmp_message(ctx, RTMP_MSG_AUDIO, audio_data, audio_len, 
+                                     timestamp, ctx->audio_stream_id, 
+                                     &rtmp_buf, &rtmp_len);
+    if (ret != RTMP_OK) {
+        LLOGE("RTMP: Failed to build RTMP message for audio");
+        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);
+        LLOGE("RTMP: Failed to allocate frame node for audio");
+        return RTMP_ERR_NO_MEMORY;
+    }
+    
+    node->data = rtmp_buf;
+    node->len = rtmp_len;
+    node->sent = 0;
+    node->is_key = false; /* 音频帧不是关键帧 */
+    node->enqueue_ms = (uint32_t)luat_mcu_tick64_ms();
+    node->next = NULL;
+    
+    ret = rtmp_queue_frame(ctx, node);
+    if (ret != RTMP_OK) {
+        rtmp_free_frame_node(node);
+        LLOGE("RTMP: Failed to queue audio frame");
+        return ret;
+    }
+    
+    /* 更新音频时间戳和统计 */
+    ctx->audio_timestamp = timestamp;
+    ctx->audio_frames_sent++;
+    ctx->audio_bytes += (uint64_t)audio_len;
+    
+    RTMP_LOGV("RTMP: Audio frame queued, len=%u, ts=%u", audio_len, timestamp);
+    
+    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;
+        }
+    }
+
+    /* 周期性打印统计信息(每10秒) */
+    uint32_t now_ms = (uint32_t)luat_mcu_tick64_ms();
+    /* 独立刷新窗口采样 */
+    if (now_ms - ctx->last_window_ms >= ctx->stats_window_ms) {
+        ctx->last_window_ms = now_ms;
+        ctx->last_window_bytes = ctx->bytes_sent;
+    }
+
+    if (now_ms - ctx->last_stats_log_ms >= ctx->stats_interval_ms) {
+        uint32_t elapsed_ms = 0;
+        if (ctx->base_timestamp > 0 && now_ms > ctx->base_timestamp) {
+            elapsed_ms = now_ms - ctx->base_timestamp;
+        }
+        /* 平均码率(单位kbps): 总比特 / 秒 / 1000 */
+        uint32_t avg_kbps = 0;
+        if (elapsed_ms > 0) {
+            double kbps = ((double)ctx->bytes_sent * 8.0) / ((double)elapsed_ms / 1000.0) / 1000.0;
+            avg_kbps = (uint32_t)(kbps + 0.5);
+        }
+        /* 区间平均码率(最近10秒) */
+        uint32_t win_kbps = 0;
+        /* 统计窗口以 stats_window_ms 为准;若比实际间隔更长,则按实际间隔计算 */
+        uint32_t win_ms = (now_ms >= ctx->last_window_ms) ? (now_ms - ctx->last_window_ms) : 0;
+        uint64_t win_bytes = (ctx->bytes_sent >= ctx->last_window_bytes) ? (ctx->bytes_sent - ctx->last_window_bytes) : 0;
+        if (win_ms > 0 && win_bytes > 0) {
+            double wkbps = ((double)win_bytes * 8.0) / ((double)win_ms / 1000.0) / 1000.0;
+            win_kbps = (uint32_t)(wkbps + 0.5);
+        }
+        /* 以kB为单位显示总字节和各类字节(四舍五入) */
+        uint64_t total_kB = ctx->bytes_sent / 1024ULL;
+        uint64_t i_kB = ctx->i_bytes / 1024ULL;
+        uint64_t p_kB = ctx->p_bytes / 1024ULL;
+        uint64_t drop_kB = ctx->dropped_bytes / 1024ULL;
+
+        LLOGI("RTMP stats: total=%llu kB packets=%u I=%u (%llukB) P=%u (%llukB) dropped=%u (%llukB) queue=%u avg=%u kbps win=%u kbps",
+              (unsigned long long)total_kB,
+              ctx->packets_sent,
+              ctx->i_frames,
+              (unsigned long long)i_kB,
+              ctx->p_frames,
+              (unsigned long long)p_kB,
+              ctx->dropped_frames,
+              (unsigned long long)drop_kB,
+              ctx->frame_queue_bytes,
+              avg_kbps,
+              win_kbps);
+
+        /* 更新日志基准 */
+        ctx->last_stats_log_ms = now_ms;
+        ctx->last_stats_bytes = ctx->bytes_sent;
+    }
+    
+    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->i_frames + ctx->p_frames;
+    stats->audio_frames_sent = ctx->audio_frames_sent;
+    stats->last_video_timestamp = ctx->video_timestamp;
+    stats->last_audio_timestamp = ctx->audio_timestamp;
+
+    stats->i_frames = ctx->i_frames;
+    stats->p_frames = ctx->p_frames;
+    stats->i_bytes = ctx->i_bytes;
+    stats->p_bytes = ctx->p_bytes;
+    stats->audio_bytes = ctx->audio_bytes;
+    stats->dropped_frames = ctx->dropped_frames;
+    stats->dropped_bytes = ctx->dropped_bytes;
+    
+    // 计算连接持续时间
+    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;
+}
+
+/**
+ * 设置统计输出间隔
+ */
+int rtmp_set_stats_interval(rtmp_ctx_t *ctx, uint32_t interval_ms) {
+    if (!ctx) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    if (interval_ms == 0) {
+        /* 防止除零/过于频繁,设定最小1000ms */
+        interval_ms = 1000;
+    }
+    ctx->stats_interval_ms = interval_ms;
+    return RTMP_OK;
+}
+
+/**
+ * 设置统计窗口长度
+ */
+int rtmp_set_stats_window(rtmp_ctx_t *ctx, uint32_t window_ms) {
+    if (!ctx) {
+        return RTMP_ERR_INVALID_PARAM;
+    }
+    if (window_ms == 0) {
+        window_ms = 1000;
+    }
+    ctx->stats_window_ms = window_ms;
+    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 += (uint64_t)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;
+
+    /* 拥堵且来了关键帧,优先丢弃缓存超过1秒且未开始发送的帧(sent==0) */
+    if (node->is_key && ctx->frame_head) {
+        uint32_t now_ms = (uint32_t)luat_mcu_tick64_ms();
+        rtmp_frame_node_t *cur = ctx->frame_head;
+        rtmp_frame_node_t *prev = NULL;
+        while (cur) {
+            if (cur->sent == 0 && (now_ms - cur->enqueue_ms) > 2000) {
+                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;
+                ctx->dropped_frames++;
+                ctx->dropped_bytes += (uint64_t)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;
+            ctx->dropped_frames++;
+            ctx->dropped_bytes += (uint64_t)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;
+            ctx->dropped_frames++;
+            ctx->dropped_bytes += (uint64_t)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);
+        ctx->dropped_frames++;
+        ctx->dropped_bytes += (uint64_t)node->len;
+        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;
+}

+ 531 - 0
components/rtsp/binding/luat_lib_rtsp.c

@@ -0,0 +1,531 @@
+/*
+@module  rtsp
+@summary RTSP 直播推流
+@version 1.0
+@date    2025.12.11
+@tag     LUAT_USE_RTSP
+@usage
+-- RTSP推流示例
+local rtsp = rtsp.create("rtsp://example.com:554/stream")
+rtsp:setCallback(function(state, ...)
+    if state == rtsp.STATE_CONNECTED then
+        print("已连接到推流服务器")
+    elseif state == rtsp.STATE_PLAYING then
+        print("已开始推流")
+    elseif state == rtsp.STATE_ERROR then
+        print("出错:", ...)
+    end
+end)
+rtsp:connect()
+
+-- 开始处理
+rtsp:start()
+
+-- 30秒后停止
+sys.wait(30000)
+rtsp:stop()
+
+-- 断开连接
+rtsp:disconnect()
+rtsp:destroy()
+*/
+
+#include "luat_base.h"
+#include "luat_rtsp_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 "rtsp"
+#include "luat_log.h"
+
+typedef struct {
+    rtsp_ctx_t *rtsp;
+    int callback_ref;
+} luat_rtsp_userdata_t;
+
+/**
+创建RTSP推流上下文
+@api rtsp.create(url)
+@string url RTSP服务器地址, 格式: rtsp://host:port/stream
+@return userdata RTSP上下文对象
+@usage
+local rtsp = rtsp.create("rtsp://example.com:554/stream")
+*/
+static int l_rtsp_create(lua_State *L) {
+    const char *url = luaL_checkstring(L, 1);
+    
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)lua_newuserdata(L, sizeof(luat_rtsp_userdata_t));
+    if (!ud) {
+        LLOGE("内存分配失败");
+        lua_pushnil(L);
+        return 1;
+    }
+    
+    ud->rtsp = rtsp_create();
+    if (!ud->rtsp) {
+        LLOGE("RTSP上下文创建失败");
+        lua_pushnil(L);
+        return 1;
+    }
+    ud->rtsp->user_data = (void *)ud;
+    
+    ud->callback_ref = LUA_NOREF;
+    
+    if (rtsp_set_url(ud->rtsp, url) != 0) {
+        LLOGE("RTSP URL设置失败");
+        rtsp_destroy(ud->rtsp);
+        lua_pushnil(L);
+        return 1;
+    }
+    
+    luaL_getmetatable(L, "rtsp_ctx");
+    lua_setmetatable(L, -2);
+    
+    LLOGD("RTSP上下文创建成功: %s", url);
+    return 1;
+}
+
+/**
+设置RTSP状态回调函数
+@api rtsp:setCallback(func)
+@function func 回调函数, 参数为 (state, ...) 
+@return nil 无返回值
+@usage
+rtsp:setCallback(function(state, ...)
+    if state == rtsp.STATE_IDLE then
+        print("空闲状态")
+    elseif state == rtsp.STATE_CONNECTING then
+        print("正在连接")
+    elseif state == rtsp.STATE_OPTIONS then
+        print("发送OPTIONS")
+    elseif state == rtsp.STATE_DESCRIBE then
+        print("发送DESCRIBE")
+    elseif state == rtsp.STATE_SETUP then
+        print("发送SETUP")
+    elseif state == rtsp.STATE_PLAY then
+        print("发送PLAY请求")
+    elseif state == rtsp.STATE_PLAYING then
+        print("正在推流")
+    elseif state == rtsp.STATE_DISCONNECTING then
+        print("正在断开")
+    elseif state == rtsp.STATE_ERROR then
+        print("错误:", ...)
+    end
+end)
+*/
+static int l_rtsp_set_callback(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        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("RTSP回调函数已设置");
+    } 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("RTSP回调函数已清除");
+    } else {
+        LLOGE("参数错误,需要function或nil");
+        lua_pushboolean(L, 0);
+        return 1;
+    }
+    
+    lua_pushboolean(L, 1);
+    return 1;
+}
+
+static int l_rtsp_handler(lua_State *L, void *udata) {
+    rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
+    luat_rtsp_userdata_t *ud = (luat_rtsp_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(rtsp_ctx_t *ctx, rtsp_state_t oldstate, rtsp_state_t newstate, int error_code) {
+    rtos_msg_t msg = {0};
+    msg.handler = l_rtsp_handler;
+    msg.ptr = ctx->user_data;
+    msg.arg1 = (int)newstate;
+    msg.arg2 = (int)oldstate;
+    LLOGD("RTSP状态(%d)回调消息入队 %p %p", (int)newstate, &msg, ctx->user_data);
+    // luat_msgbus_put(&msg, 0);
+}
+
+/**
+连接到RTSP服务器
+@api rtsp:connect()
+@return boolean 成功返回true, 失败返回false
+@usage
+local ok = rtsp:connect()
+if ok then
+    print("连接请求已发送")
+else
+    print("连接失败")
+end
+*/
+static int l_rtsp_connect(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        lua_pushboolean(L, 0);
+        return 1;
+    }
+    
+    rtsp_set_state_callback(ud->rtsp, l_state_callback);
+    
+    int ret = tcpip_callback_with_block(rtsp_connect, (void *)ud->rtsp, 0);
+    LLOGD("RTSP连接请求: %s", ret == 0 ? "成功" : "失败");
+    lua_pushboolean(L, ret == 0 ? 1 : 0);
+    return 1;
+}
+
+/**
+断开RTSP连接
+@api rtsp:disconnect()
+@return boolean 成功返回true, 失败返回false
+@usage
+rtsp:disconnect()
+*/
+static int l_rtsp_disconnect(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        lua_pushboolean(L, 0);
+        return 1;
+    }
+    
+    int ret = rtsp_disconnect(ud->rtsp);
+    LLOGD("RTSP断开连接: %s", ret == 0 ? "成功" : "失败");
+    lua_pushboolean(L, ret == 0 ? 1 : 0);
+    return 1;
+}
+
+static void t_rtsp_poll(void *arg) {
+    rtsp_ctx_t *ctx = (rtsp_ctx_t *)arg;
+    rtsp_poll(ctx);
+    sys_timeout(20, t_rtsp_poll, ctx);
+}
+
+/**
+处理RTSP事件
+@api rtsp:start()
+@return nil 无返回值
+@usage
+rtsp:start()
+*/
+static int l_rtsp_start(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        return 0;
+    }
+    sys_timeout(20, t_rtsp_poll, ud->rtsp);
+    return 0;
+}
+
+/**
+停止处理RTSP事件
+@api rtsp:stop()
+@return nil 无返回值
+@usage
+rtsp:stop()
+*/
+static int l_rtsp_stop(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        return 0;
+    }
+    // 移除定时器回调
+    sys_untimeout(t_rtsp_poll, ud->rtsp);
+    return 0;
+}
+
+/**
+获取RTSP连接状态
+@api rtsp:getState()
+@return int 当前状态值
+@usage
+local state = rtsp:getState()
+if state == rtsp.STATE_CONNECTED then
+    print("已连接")
+elseif state == rtsp.STATE_PLAYING then
+    print("正在推流")
+end
+*/
+static int l_rtsp_get_state(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        lua_pushinteger(L, -1);
+        return 1;
+    }
+    
+    rtsp_state_t state = rtsp_get_state(ud->rtsp);
+    lua_pushinteger(L, (lua_Integer)state);
+    return 1;
+}
+
+/**
+设置H.264 SPS参数
+@api rtsp:setSPS(sps_data)
+@string sps_data 或 
+@userdata sps_data H.264序列参数集数据
+@return boolean 成功返回true, 失败返回false
+@usage
+local sps = string.fromBinary("\x67\x42...") -- H.264 SPS数据
+rtsp:setSPS(sps)
+*/
+static int l_rtsp_set_sps(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        lua_pushboolean(L, 0);
+        return 1;
+    }
+    
+    size_t len = 0;
+    const uint8_t *data = (const uint8_t *)luaL_checklstring(L, 2, &len);
+    
+    if (rtsp_set_sps(ud->rtsp, data, (uint32_t)len) == RTSP_OK) {
+        LLOGD("SPS已设置: %u字节", (uint32_t)len);
+        lua_pushboolean(L, 1);
+    } else {
+        lua_pushboolean(L, 0);
+    }
+    return 1;
+}
+
+/**
+设置H.264 PPS参数
+@api rtsp:setPPS(pps_data)
+@string pps_data 或 
+@userdata pps_data H.264图像参数集数据
+@return boolean 成功返回true, 失败返回false
+@usage
+local pps = string.fromBinary("\x68\xCB...") -- H.264 PPS数据
+rtsp:setPPS(pps)
+*/
+static int l_rtsp_set_pps(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        lua_pushboolean(L, 0);
+        return 1;
+    }
+    
+    size_t len = 0;
+    const uint8_t *data = (const uint8_t *)luaL_checklstring(L, 2, &len);
+    
+    if (rtsp_set_pps(ud->rtsp, data, (uint32_t)len) == RTSP_OK) {
+        LLOGD("PPS已设置: %u字节", (uint32_t)len);
+        lua_pushboolean(L, 1);
+    } else {
+        lua_pushboolean(L, 0);
+    }
+    return 1;
+}
+
+/**
+推送H.264视频帧
+@api rtsp:pushFrame(frame_data, timestamp)
+@string frame_data 或 
+@userdata frame_data H.264编码的视频帧数据
+@int timestamp 时间戳(毫秒), 可选,为0则使用内部时间戳
+@return int 成功时返回已发送或已入队的字节数, 失败返回负数
+@usage
+-- 持续推送H.264帧
+local frame_data = ... -- 获取H.264帧数据
+local timestamp = sys.now() % 0x100000000
+local ret = rtsp:pushFrame(frame_data, timestamp)
+if ret >= 0 then
+    print("已发送", ret, "字节")
+else
+    print("发送失败:", ret)
+end
+*/
+static int l_rtsp_push_frame(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        lua_pushinteger(L, -1);
+        return 1;
+    }
+    
+    size_t len = 0;
+    const uint8_t *data = (const uint8_t *)luaL_checklstring(L, 2, &len);
+    uint32_t timestamp = 0;
+    
+    if (!lua_isnil(L, 3)) {
+        timestamp = (uint32_t)luaL_checkinteger(L, 3);
+    }
+    
+    if (len == 0 || len > (1024 * 1024)) {
+        LLOGE("帧数据大小无效: %u", (uint32_t)len);
+        lua_pushinteger(L, RTSP_ERR_INVALID_PARAM);
+        return 1;
+    }
+    
+    int ret = rtsp_push_h264_frame(ud->rtsp, data, (uint32_t)len, timestamp);
+    lua_pushinteger(L, ret);
+    return 1;
+}
+
+/**
+获取RTSP统计信息
+@api rtsp:getStats()
+@return table 统计信息表
+@usage
+local stats = rtsp:getStats()
+print("已发送字节数:", stats.bytes_sent)
+print("已发送视频帧数:", stats.video_frames_sent)
+print("已发送RTP包数:", stats.rtp_packets_sent)
+*/
+static int l_rtsp_get_stats(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        lua_pushnil(L);
+        return 1;
+    }
+    
+    rtsp_stats_t stats = {0};
+    rtsp_get_stats(ud->rtsp, &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.rtp_packets_sent);
+    lua_setfield(L, -2, "rtp_packets_sent");
+    lua_pushinteger(L, stats.connection_time);
+    lua_setfield(L, -2, "connection_time");
+    lua_pushinteger(L, stats.last_video_timestamp);
+    lua_setfield(L, -2, "last_video_timestamp");
+    
+    return 1;
+}
+
+/**
+销毁RTSP上下文,释放所有资源
+@api rtsp:destroy()
+@return nil 无返回值
+@usage
+rtsp:destroy()
+*/
+static int l_rtsp_destroy(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (!ud || !ud->rtsp) {
+        return 0;
+    }
+    
+    if (ud->callback_ref != LUA_NOREF) {
+        luaL_unref(L, LUA_REGISTRYINDEX, ud->callback_ref);
+        ud->callback_ref = LUA_NOREF;
+    }
+    
+    rtsp_destroy(ud->rtsp);
+    ud->rtsp = NULL;
+    
+    LLOGD("RTSP上下文已销毁");
+    return 0;
+}
+
+static int l_rtsp_gc(lua_State *L) {
+    luat_rtsp_userdata_t *ud = (luat_rtsp_userdata_t *)luaL_checkudata(L, 1, "rtsp_ctx");
+    if (ud && ud->rtsp) {
+        if (ud->callback_ref != LUA_NOREF) {
+            luaL_unref(L, LUA_REGISTRYINDEX, ud->callback_ref);
+        }
+        rtsp_destroy(ud->rtsp);
+        ud->rtsp = NULL;
+    }
+    return 0;
+}
+
+#include "rotable2.h"
+
+static const rotable_Reg_t reg_rtsp_ctx[] = {
+    {"setCallback",   ROREG_FUNC(l_rtsp_set_callback)},
+    {"connect",       ROREG_FUNC(l_rtsp_connect)},
+    {"disconnect",    ROREG_FUNC(l_rtsp_disconnect)},
+    {"start",         ROREG_FUNC(l_rtsp_start)},
+    {"stop",          ROREG_FUNC(l_rtsp_stop)},
+    {"getState",      ROREG_FUNC(l_rtsp_get_state)},
+    {"setSPS",        ROREG_FUNC(l_rtsp_set_sps)},
+    {"setPPS",        ROREG_FUNC(l_rtsp_set_pps)},
+    {"pushFrame",     ROREG_FUNC(l_rtsp_push_frame)},
+    {"getStats",      ROREG_FUNC(l_rtsp_get_stats)},
+    {"destroy",       ROREG_FUNC(l_rtsp_destroy)},
+    {"__gc",          ROREG_FUNC(l_rtsp_gc)},
+    {NULL,            ROREG_INT(0)}
+};
+
+static const rotable_Reg_t reg_rtsp[] = {
+    {"create",            ROREG_FUNC(l_rtsp_create)},
+    
+    // RTSP状态常量
+    {"STATE_IDLE",        ROREG_INT(RTSP_STATE_IDLE)},
+    {"STATE_CONNECTING",  ROREG_INT(RTSP_STATE_CONNECTING)},
+    {"STATE_OPTIONS",     ROREG_INT(RTSP_STATE_OPTIONS)},
+    {"STATE_DESCRIBE",    ROREG_INT(RTSP_STATE_DESCRIBE)},
+    {"STATE_SETUP",       ROREG_INT(RTSP_STATE_SETUP)},
+    {"STATE_PLAY",        ROREG_INT(RTSP_STATE_PLAY)},
+    {"STATE_PLAYING",     ROREG_INT(RTSP_STATE_PLAYING)},
+    {"STATE_DISCONNECTING",ROREG_INT(RTSP_STATE_DISCONNECTING)},
+    {"STATE_ERROR",       ROREG_INT(RTSP_STATE_ERROR)},
+    
+    // 返回值常量
+    {"OK",                ROREG_INT(RTSP_OK)},
+    {"ERR_FAILED",        ROREG_INT(RTSP_ERR_FAILED)},
+    {"ERR_INVALID_PARAM", ROREG_INT(RTSP_ERR_INVALID_PARAM)},
+    {"ERR_NO_MEMORY",     ROREG_INT(RTSP_ERR_NO_MEMORY)},
+    {"ERR_CONNECT_FAILED",ROREG_INT(RTSP_ERR_CONNECT_FAILED)},
+    {"ERR_HANDSHAKE_FAILED",ROREG_INT(RTSP_ERR_HANDSHAKE_FAILED)},
+    {"ERR_NETWORK",       ROREG_INT(RTSP_ERR_NETWORK)},
+    {"ERR_TIMEOUT",       ROREG_INT(RTSP_ERR_TIMEOUT)},
+    {"ERR_BUFFER_OVERFLOW",ROREG_INT(RTSP_ERR_BUFFER_OVERFLOW)},
+    
+    {NULL,                ROREG_INT(0)}
+};
+
+static int _rtsp_struct_newindex(lua_State *L) {
+	const rotable_Reg_t* reg = reg_rtsp_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_rtsp(lua_State *L) {
+    luat_newlib2(L, reg_rtsp);
+    
+    luaL_newmetatable(L, "rtsp_ctx");
+    lua_pushcfunction(L, _rtsp_struct_newindex);
+    lua_setfield(L, -2, "__index");
+    lua_pop(L, 1);
+    
+    return 1;
+}

+ 362 - 0
components/rtsp/include/luat_rtsp_push.h

@@ -0,0 +1,362 @@
+/**
+ * @file luat_rtsp_push.h
+ * @brief RTSP推流组件 - 基于lwip raw API实现
+ * @author LuatOS Team
+ * 
+ * 该组件实现了RTSP(Real Time Streaming Protocol)推流功能,
+ * 支持将H.264视频流推送到RTSP服务器。
+ * 
+ * 主要特性:
+ * - 基于lwip raw socket API,适应嵌入式环境
+ * - 支持自定义H.264帧来源,灵活的NALU帧注入
+ * - 完整的RTSP握手和连接管理
+ * - 支持RTP/RTCP协议实现
+ * - 支持H.264基础配置文件
+ * - C99语法,内存使用优化
+ * 
+ * 调试说明:
+ * - 在 luat_rtsp_push.c 中修改 RTSP_DEBUG_VERBOSE 宏来控制详细日志输出
+ * - 设置为 1 开启详细调试信息,设置为 0 关闭(仅保留关键日志)
+ */
+
+#ifndef __LUAT_RTSP_PUSH_H__
+#define __LUAT_RTSP_PUSH_H__
+
+#include "luat_base.h"
+#include "lwip/tcp.h"
+#include "lwip/udp.h"
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ======================== RTSP常量定义 ======================== */
+
+/** RTSP默认端口 */
+#define RTSP_DEFAULT_PORT 554
+
+/** RTSP缓冲区大小(字节) - 需要足够大以容纳RTSP信令 */
+#define RTSP_BUFFER_SIZE (64 * 1024)
+
+/** RTP缓冲区大小(字节) */
+#define RTP_BUFFER_SIZE (256 * 1024)
+
+/** RTP UDP缓冲区最大包大小 */
+#define RTP_MAX_PACKET_SIZE 1400
+
+/** 发送帧队列最大字节数上限,超出将丢弃未发送帧 */
+#define RTSP_MAX_QUEUE_BYTES (1024 * 1024)
+
+/** RTSP命令超时时间(毫秒) */
+#define RTSP_CMD_TIMEOUT 5000
+
+/* ======================== 返回值定义 ======================== */
+
+/** 操作成功 */
+#define RTSP_OK 0
+
+/** 通用错误 */
+#define RTSP_ERR_FAILED (-1)
+
+/** 参数无效 */
+#define RTSP_ERR_INVALID_PARAM (-2)
+
+/** 内存不足 */
+#define RTSP_ERR_NO_MEMORY (-3)
+
+/** 连接错误 */
+#define RTSP_ERR_CONNECT_FAILED (-4)
+
+/** 握手失败 */
+#define RTSP_ERR_HANDSHAKE_FAILED (-5)
+
+/** 网络错误 */
+#define RTSP_ERR_NETWORK (-6)
+
+/** 超时 */
+#define RTSP_ERR_TIMEOUT (-7)
+
+/** 缓冲区溢出 */
+#define RTSP_ERR_BUFFER_OVERFLOW (-8)
+
+/* ======================== 数据类型定义 ======================== */
+
+/**
+ * RTSP连接状态枚举
+ */
+typedef enum {
+    RTSP_STATE_IDLE = 0,            /**< 空闲状态 */
+    RTSP_STATE_CONNECTING = 1,      /**< 正在连接 */
+    RTSP_STATE_OPTIONS = 2,         /**< 发送OPTIONS请求 */
+    RTSP_STATE_DESCRIBE = 3,        /**< 发送DESCRIBE请求 */
+    RTSP_STATE_SETUP = 4,           /**< 发送SETUP请求 */
+    RTSP_STATE_PLAY = 5,            /**< 发送PLAY请求,准备推流 */
+    RTSP_STATE_PLAYING = 6,         /**< 正在推流 */
+    RTSP_STATE_DISCONNECTING = 7,   /**< 正在断开连接 */
+    RTSP_STATE_ERROR = 8            /**< 错误状态 */
+} rtsp_state_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视频帧信息结构体
+ */
+typedef struct {
+    uint8_t *data;                  /**< 视频数据指针 */
+    uint32_t len;                   /**< 视频数据长度 */
+    uint32_t timestamp;             /**< 时间戳(ms) */
+    uint8_t nalu_type;              /**< NALU类型 */
+    uint8_t is_keyframe;            /**< 是否为关键帧 */
+} h264_frame_t;
+
+/**
+ * RTSP推流统计信息结构体
+ */
+typedef struct {
+    uint32_t bytes_sent;            /**< 已发送的字节数 */
+    uint32_t video_frames_sent;     /**< 已发送的视频帧数 */
+    uint32_t rtp_packets_sent;      /**< 已发送的RTP包数 */
+    uint32_t connection_time;       /**< 连接持续时间(毫秒) */
+    uint32_t last_video_timestamp;  /**< 最后视频时间戳(毫秒) */
+} rtsp_stats_t;
+
+/**
+ * RTSP推流上下文结构体
+ * 管理单个RTSP连接的所有状态和缓冲区
+ */
+typedef struct {
+    /** ============ 连接信息 ============ */
+    char *url;                      /**< RTSP服务器URL */
+    char *host;                     /**< RTSP服务器主机名/IP地址 */
+    char *stream;                   /**< 推流名 */
+    char *auth;                     /**< 认证信息(用户名:密码) */
+    uint16_t port;                  /**< 连接端口 */
+    
+    /** ============ TCP连接状态(RTSP控制通道) ============ */
+    struct tcp_pcb *control_pcb;    /**< lwip TCP控制块(RTSP信令) */
+    rtsp_state_t state;             /**< 当前连接状态 */
+    uint32_t last_activity_time;    /**< 最后活动时间戳 */
+    
+    /** ============ UDP连接状态(RTP媒体通道) ============ */
+    struct udp_pcb *rtp_pcb;        /**< RTP UDP控制块 */
+    struct udp_pcb *rtcp_pcb;       /**< RTCP UDP控制块 */
+    ip_addr_t remote_ip;            /**< 远端IP地址 */
+    uint16_t remote_rtp_port;       /**< 远端RTP端口 */
+    uint16_t remote_rtcp_port;      /**< 远端RTCP端口 */
+    uint16_t local_rtp_port;        /**< 本地RTP端口 */
+    uint16_t local_rtcp_port;       /**< 本地RTCP端口 */
+    
+    /** ============ RTSP协议状态 ============ */
+    uint32_t cseq;                  /**< RTSP序列号 */
+    char *session_id;               /**< RTSP会话ID */
+    uint32_t video_stream_id;       /**< 视频流ID(SSRC) */
+    
+    /** ============ RTP状态 ============ */
+    uint32_t rtp_sequence;          /**< RTP序列号 */
+    uint32_t rtp_timestamp;         /**< RTP时间戳 */
+    uint32_t rtp_ssrc;              /**< RTP同步源标识符 */
+    
+    /** ============ 缓冲区管理 ============ */
+    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;              /**< 发送缓冲区写位置 */
+    
+    uint8_t *rtp_buf;               /**< RTP发送缓冲区 */
+    uint32_t rtp_buf_size;          /**< RTP缓冲区大小 */
+
+    /** ============ 帧发送队列 ============ */
+    struct rtsp_frame_node *frame_head; /**< 待发送帧队列头 */
+    struct rtsp_frame_node *frame_tail; /**< 待发送帧队列尾 */
+    uint32_t frame_queue_bytes;          /**< 队列占用的总字节数 */
+    
+    /** ============ 时间戳管理 ============ */
+    uint32_t video_timestamp;       /**< 当前视频时间戳(ms) */
+    uint32_t base_timestamp;        /**< 基准时间戳 */
+    uint32_t start_tick;            /**< 启动时刻的系统tick */
+    
+    /** ============ H.264编码信息 ============ */
+    uint8_t *sps_data;              /**< SPS(序列参数集)数据 */
+    uint32_t sps_len;               /**< SPS长度 */
+    uint8_t *pps_data;              /**< PPS(图像参数集)数据 */
+    uint32_t pps_len;               /**< PPS长度 */
+    char *sprop_parameter_sets;     /**< SDP中的sprop-parameter-sets */
+    
+    /** ============ 统计信息 ============ */
+    uint32_t packets_sent;          /**< 已发送的包数 */
+    uint32_t bytes_sent;            /**< 已发送的字节数 */
+    uint32_t video_frames_sent;     /**< 已发送的视频帧数 */
+    uint32_t rtp_packets_sent;      /**< 已发送的RTP包数 */
+    uint32_t last_rtcp_time;        /**< 上次发送RTCP SR的时间(tick) */
+    
+    /** ============ 用户数据 ============ */
+    void *user_data;                /**< 用户自定义数据指针 */
+} rtsp_ctx_t;
+
+/**
+ * RTSP状态变化回调函数类型
+ * 
+ * @param ctx RTSP上下文指针
+ * @param old_state 旧状态
+ * @param new_state 新状态
+ * @param error_code 错误代码(仅在STATE_ERROR时有效)
+ */
+typedef void (*rtsp_state_callback_t)(rtsp_ctx_t *ctx, rtsp_state_t old_state, 
+                                      rtsp_state_t new_state, int error_code);
+
+/* ======================== 核心接口函数 ======================== */
+
+/**
+ * 创建RTSP推流上下文
+ * 
+ * @return RTSP上下文指针,失败返回NULL
+ * @note 返回的指针需要使用rtsp_destroy()释放
+ */
+rtsp_ctx_t* rtsp_create(void);
+
+/**
+ * 销毁RTSP推流上下文,释放所有资源
+ * 
+ * @param ctx RTSP上下文指针
+ * @return 0=成功, 负数=失败
+ */
+int rtsp_destroy(rtsp_ctx_t *ctx);
+
+/**
+ * 设置RTSP服务器URL
+ * 
+ * @param ctx RTSP上下文指针
+ * @param url RTSP服务器地址,格式: rtsp://host:port/stream
+ * @return 0=成功, 负数=失败
+ */
+int rtsp_set_url(rtsp_ctx_t *ctx, const char *url);
+
+/**
+ * 设置状态变化回调函数
+ * 
+ * @param ctx RTSP上下文指针
+ * @param callback 回调函数指针
+ * @return 0=成功, 负数=失败
+ */
+int rtsp_set_state_callback(rtsp_ctx_t *ctx, rtsp_state_callback_t callback);
+
+/**
+ * 连接到RTSP服务器
+ * 
+ * @param ctx RTSP上下文指针
+ * @return 0=成功, 负数=失败
+ * @note 此函数应在lwip tcpip线程中调用
+ */
+int rtsp_connect(rtsp_ctx_t *ctx);
+
+/**
+ * 断开RTSP连接
+ * 
+ * @param ctx RTSP上下文指针
+ * @return 0=成功, 负数=失败
+ */
+int rtsp_disconnect(rtsp_ctx_t *ctx);
+
+/**
+ * 获取当前连接状态
+ * 
+ * @param ctx RTSP上下文指针
+ * @return 当前状态值
+ */
+rtsp_state_t rtsp_get_state(rtsp_ctx_t *ctx);
+
+/**
+ * 设置H.264 SPS数据(序列参数集)
+ * 
+ * @param ctx RTSP上下文指针
+ * @param sps_data SPS数据指针
+ * @param sps_len SPS数据长度
+ * @return 0=成功, 负数=失败
+ * @note 数据会被复制,调用者可以释放原数据
+ */
+int rtsp_set_sps(rtsp_ctx_t *ctx, const uint8_t *sps_data, uint32_t sps_len);
+
+/**
+ * 设置H.264 PPS数据(图像参数集)
+ * 
+ * @param ctx RTSP上下文指针
+ * @param pps_data PPS数据指针
+ * @param pps_len PPS数据长度
+ * @return 0=成功, 负数=失败
+ * @note 数据会被复制,调用者可以释放原数据
+ */
+int rtsp_set_pps(rtsp_ctx_t *ctx, const uint8_t *pps_data, uint32_t pps_len);
+
+/**
+ * 推送H.264视频帧数据
+ * 
+ * 该函数接收H.264编码的视频帧数据(可以是完整的访问单元或单个NALU)
+ * 内部会自动进行RTP打包并通过UDP发送到服务器。
+ * 
+ * @param ctx RTSP上下文指针
+ * @param frame_data 视频帧数据指针(包含起始码或不包含均可)
+ * @param frame_len 视频帧数据长度(字节数)
+ * @param timestamp 时间戳(毫秒),如果为0则使用内部时间戳
+ * @return 0=成功,正数=已入队,负数=失败
+ * 
+ * @note 
+ * - frame_data 可以包含H.264起始码(0x00000001或0x000001)或不包含
+ * - 支持单个NALU或多个NALU的访问单元
+ * - 如果在PLAYING状态下调用,数据会立即发送;否则进入队列
+ * - 返回值为正数时表示字节数已加入队列
+ * 
+ * @example
+ * // 推送一帧H.264视频
+ * uint8_t *frame_data = ...;  // H.264编码帧数据
+ * uint32_t frame_len = ...;   // 帧长度
+ * uint32_t timestamp = 0;     // 使用内部时间戳
+ * 
+ * int ret = rtsp_push_h264_frame(ctx, frame_data, frame_len, timestamp);
+ * if (ret >= 0) {
+ *     printf("成功推送%d字节\n", ret);
+ * } else {
+ *     printf("推送失败: %d\n", ret);
+ * }
+ */
+int rtsp_push_h264_frame(rtsp_ctx_t *ctx, const uint8_t *frame_data, 
+                         uint32_t frame_len, uint32_t timestamp);
+
+/**
+ * 处理RTSP连接事件,应定期调用此函数
+ * 
+ * @param ctx RTSP上下文指针
+ * @return 0=成功, 负数=失败
+ * @note 建议每10-20ms调用一次此函数
+ */
+int rtsp_poll(rtsp_ctx_t *ctx);
+
+/**
+ * 获取推流统计信息
+ * 
+ * @param ctx RTSP上下文指针
+ * @param stats 统计信息指针
+ * @return 0=成功, 负数=失败
+ */
+int rtsp_get_stats(rtsp_ctx_t *ctx, rtsp_stats_t *stats);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LUAT_RTSP_PUSH_H__ */

+ 1854 - 0
components/rtsp/src/luat_rtsp_push.c

@@ -0,0 +1,1854 @@
+/**
+ * @file luat_rtsp_push.c
+ * @brief RTSP推流组件实现 - 基于lwip raw API
+ * @author LuatOS Team
+ * 
+ * 实现了RTSP协议的核心功能,包括:
+ * - TCP连接管理(RTSP控制通道)
+ * - UDP连接管理(RTP媒体通道)
+ * - RTSP握手流程(OPTIONS, DESCRIBE, SETUP, PLAY)
+ * - RTP打包和发送
+ * - H.264视频数据处理
+ * - 网络数据收发
+ */
+
+#include "luat_rtsp_push.h"
+#include "luat_debug.h"
+#include "luat_mcu.h"
+#include "luat_mem.h"
+#include "luat_rtos.h"
+#include "luat_sntp.h"
+#include "lwip/tcp.h"
+#include "lwip/udp.h"
+#include "lwip/tcpip.h"
+#include "lwip/timeouts.h"
+#include "lwip/ip_addr.h"
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#define LUAT_LOG_TAG "rtsp_push"
+#include "luat_log.h"
+
+/* ======================== 调试开关 ======================== */
+
+/** 启用详细调试日志 (0=关闭, 1=开启) */
+#define RTSP_DEBUG_VERBOSE 0
+
+#if RTSP_DEBUG_VERBOSE
+    #define RTSP_LOGV(...) LLOGD(__VA_ARGS__)
+#else
+    #define RTSP_LOGV(...)
+#endif
+
+/* ======================== 内部常量定义 ======================== */
+
+/** 内部帧队列节点结构体 */
+struct rtsp_frame_node {
+    uint8_t *data;
+    uint32_t len;
+    uint32_t timestamp;
+    struct rtsp_frame_node *next;
+};
+
+/** RTP固定头大小 */
+#define RTP_HEADER_SIZE 12
+
+/** RTP H.264 Payload Type */
+#define RTP_PAYLOAD_TYPE_H264 96
+
+/** RTP时间戳基数(90000 Hz for video) */
+#define RTP_TIMESTAMP_BASE 90000
+
+/** H.264 NALU类型定义 */
+#define NALU_TYPE_SLICE    1
+#define NALU_TYPE_DPA      2
+#define NALU_TYPE_DPB      3
+#define NALU_TYPE_DPC      4
+#define NALU_TYPE_IDR      5
+#define NALU_TYPE_SEI      6
+#define NALU_TYPE_SPS      7
+#define NALU_TYPE_PPS      8
+#define NALU_TYPE_AUD      9
+
+/** FU-A分片标识 */
+#define FU_A_TYPE          28
+
+rtsp_ctx_t *g_rtsp_ctx;
+
+/* ======================== 内部函数声明 ======================== */
+
+/**
+ * 解析URL并提取主机名、端口、流名
+ */
+static int rtsp_parse_url(rtsp_ctx_t *ctx, const char *url);
+
+/**
+ * 详细解析SPS数据
+ */
+static void rtsp_parse_sps_detail(const uint8_t *sps_data, uint32_t sps_len);
+
+/**
+ * 详细解析PPS数据
+ */
+static void rtsp_parse_pps_detail(const uint8_t *pps_data, uint32_t pps_len);
+
+/**
+ * 详细解析SPS数据
+ */
+static void rtsp_parse_sps_detail(const uint8_t *sps_data, uint32_t sps_len);
+
+/**
+ * 详细解析PPS数据
+ */
+static void rtsp_parse_pps_detail(const uint8_t *pps_data, uint32_t pps_len);
+
+/**
+ * TCP连接回调函数
+ */
+static err_t rtsp_tcp_connect_callback(void *arg, struct tcp_pcb *pcb, err_t err);
+
+/**
+ * TCP接收回调函数
+ */
+static err_t rtsp_tcp_recv_callback(void *arg, struct tcp_pcb *pcb, 
+                                   struct pbuf *p, err_t err);
+
+/**
+ * TCP错误回调函数
+ */
+static void rtsp_tcp_error_callback(void *arg, err_t err);
+
+/**
+ * 状态转移函数
+ */
+static void rtsp_set_state(rtsp_ctx_t *ctx, rtsp_state_t new_state, int error_code);
+
+/**
+ * 发送RTSP命令
+ */
+static int rtsp_send_command(rtsp_ctx_t *ctx, const char *method, 
+                             const char *resource, const char *extra_headers,
+                             const char *data, uint32_t data_len);
+
+/**
+ * 处理RTSP响应
+ */
+static int rtsp_handle_response(rtsp_ctx_t *ctx);
+
+/**
+ * 发送RTP包
+ */
+static int rtsp_send_rtp_packet(rtsp_ctx_t *ctx, const uint8_t *data, 
+                                uint32_t len, uint32_t timestamp, uint8_t marker);
+
+/**
+ * 解析H.264帧并构建RTP包
+ */
+static int rtsp_build_rtp_payload(rtsp_ctx_t *ctx, const uint8_t *frame_data, 
+                                  uint32_t frame_len, uint32_t timestamp);
+
+/**
+ * 从队列中提取并发送帧
+ */
+static int rtsp_send_queued_frames(rtsp_ctx_t *ctx);
+
+/**
+ * 清空帧队列
+ */
+static void rtsp_clear_frame_queue(rtsp_ctx_t *ctx);
+
+/** 简单的大小写不敏感比较 */
+static int rtsp_strncasecmp(const char *a, const char *b, size_t n);
+
+/**
+ * Base64编码函数
+ */
+static int rtsp_base64_encode(const uint8_t *src, uint32_t src_len, char *dst, uint32_t dst_len);
+
+/**
+ * 查找下一个NALU起始码位置
+ */
+static const uint8_t* rtsp_find_next_nalu(const uint8_t *data, uint32_t len, uint32_t *start_code_len);
+
+/**
+ * 发送单个NALU(支持FU-A分片)
+ */
+static int rtsp_send_nalu(rtsp_ctx_t *ctx, const uint8_t *nalu_data, 
+                          uint32_t nalu_len, uint32_t timestamp, uint8_t is_last_nalu);
+
+/**
+ * 转换系统时间到NTP时间戳
+ */
+static void rtsp_get_ntp_timestamp(uint32_t *ntp_sec, uint32_t *ntp_frac);
+
+/**
+ * 发送RTCP Sender Report
+ */
+static int rtsp_send_rtcp_sr(rtsp_ctx_t *ctx);
+
+/* ======================== 核心实现 ======================== */
+
+/**
+ * 创建RTSP推流上下文
+ */
+rtsp_ctx_t* rtsp_create(void) {
+    rtsp_ctx_t *ctx = (rtsp_ctx_t *)luat_heap_malloc(sizeof(rtsp_ctx_t));
+    if (!ctx) {
+        LLOGE("内存分配失败");
+        return NULL;
+    }
+    
+    memset(ctx, 0, sizeof(rtsp_ctx_t));
+    
+    // 初始化缓冲区
+    ctx->recv_buf = (uint8_t *)luat_heap_malloc(RTSP_BUFFER_SIZE);
+    if (!ctx->recv_buf) {
+        LLOGE("接收缓冲区分配失败");
+        luat_heap_free(ctx);
+        return NULL;
+    }
+    ctx->recv_buf_size = RTSP_BUFFER_SIZE;
+    ctx->recv_pos = 0;
+    
+    ctx->send_buf = (uint8_t *)luat_heap_malloc(RTSP_BUFFER_SIZE);
+    if (!ctx->send_buf) {
+        LLOGE("发送缓冲区分配失败");
+        luat_heap_free(ctx->recv_buf);
+        luat_heap_free(ctx);
+        return NULL;
+    }
+    ctx->send_buf_size = RTSP_BUFFER_SIZE;
+    ctx->send_pos = 0;
+    
+    ctx->rtp_buf = (uint8_t *)luat_heap_malloc(RTP_BUFFER_SIZE);
+    if (!ctx->rtp_buf) {
+        LLOGE("RTP缓冲区分配失败");
+        luat_heap_free(ctx->send_buf);
+        luat_heap_free(ctx->recv_buf);
+        luat_heap_free(ctx);
+        return NULL;
+    }
+    ctx->rtp_buf_size = RTP_BUFFER_SIZE;
+    
+    // 初始化RTSP状态
+    ctx->state = RTSP_STATE_IDLE;
+    ctx->cseq = 1;
+    ctx->rtp_sequence = (uint32_t)luat_mcu_ticks();
+    ctx->rtp_ssrc = (uint32_t)luat_mcu_ticks();
+    ctx->start_tick = luat_mcu_ticks();
+    ctx->last_rtcp_time = luat_mcu_ticks();
+    
+    // 初始化帧队列
+    ctx->frame_head = NULL;
+    ctx->frame_tail = NULL;
+    ctx->frame_queue_bytes = 0;
+    
+    g_rtsp_ctx = ctx;
+    LLOGD("RTSP上下文创建成功");
+    return ctx;
+}
+
+/**
+ * 销毁RTSP推流上下文
+ */
+int rtsp_destroy(rtsp_ctx_t *ctx) {
+    if (!ctx) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    // 断开连接
+    if (ctx->state != RTSP_STATE_IDLE) {
+        rtsp_disconnect(ctx);
+    }
+    
+    // 清空队列
+    rtsp_clear_frame_queue(ctx);
+    
+    // 释放TCP连接
+    if (ctx->control_pcb) {
+        tcp_abort(ctx->control_pcb);
+        ctx->control_pcb = NULL;
+    }
+    
+    // 释放UDP连接
+    if (ctx->rtp_pcb) {
+        udp_remove(ctx->rtp_pcb);
+        ctx->rtp_pcb = NULL;
+    }
+    if (ctx->rtcp_pcb) {
+        udp_remove(ctx->rtcp_pcb);
+        ctx->rtcp_pcb = NULL;
+    }
+    
+    // 释放缓冲区
+    if (ctx->recv_buf) {
+        luat_heap_free(ctx->recv_buf);
+        ctx->recv_buf = NULL;
+    }
+    if (ctx->send_buf) {
+        luat_heap_free(ctx->send_buf);
+        ctx->send_buf = NULL;
+    }
+    if (ctx->rtp_buf) {
+        luat_heap_free(ctx->rtp_buf);
+        ctx->rtp_buf = NULL;
+    }
+    
+    // 释放字符串
+    if (ctx->url) {
+        luat_heap_free(ctx->url);
+        ctx->url = NULL;
+    }
+    if (ctx->host) {
+        luat_heap_free(ctx->host);
+        ctx->host = NULL;
+    }
+    if (ctx->stream) {
+        luat_heap_free(ctx->stream);
+        ctx->stream = NULL;
+    }
+    if (ctx->auth) {
+        luat_heap_free(ctx->auth);
+        ctx->auth = NULL;
+    }
+    if (ctx->session_id) {
+        luat_heap_free(ctx->session_id);
+        ctx->session_id = NULL;
+    }
+    if (ctx->sps_data) {
+        luat_heap_free(ctx->sps_data);
+        ctx->sps_data = NULL;
+    }
+    if (ctx->pps_data) {
+        luat_heap_free(ctx->pps_data);
+        ctx->pps_data = NULL;
+    }
+    if (ctx->sprop_parameter_sets) {
+        luat_heap_free(ctx->sprop_parameter_sets);
+        ctx->sprop_parameter_sets = NULL;
+    }
+    
+    luat_heap_free(ctx);
+    LLOGD("RTSP上下文已销毁");
+    return RTSP_OK;
+}
+
+/**
+ * 设置RTSP服务器URL
+ */
+int rtsp_set_url(rtsp_ctx_t *ctx, const char *url) {
+    if (!ctx || !url) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    if (ctx->url) {
+        luat_heap_free(ctx->url);
+    }
+    
+    ctx->url = (char *)luat_heap_malloc(strlen(url) + 1);
+    if (!ctx->url) {
+        LLOGE("URL内存分配失败");
+        return RTSP_ERR_NO_MEMORY;
+    }
+    
+    strcpy(ctx->url, url);
+    
+    if (rtsp_parse_url(ctx, url) != RTSP_OK) {
+        LLOGE("URL解析失败: %s", url);
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    LLOGD("RTSP URL设置: %s", url);
+    return RTSP_OK;
+}
+
+/**
+ * 设置状态变化回调函数
+ */
+int rtsp_set_state_callback(rtsp_ctx_t *ctx, rtsp_state_callback_t callback) {
+    if (!ctx) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    ctx->user_data = (void *)callback;
+    return RTSP_OK;
+}
+
+/**
+ * 设置H.264 SPS数据
+ */
+int rtsp_set_sps(rtsp_ctx_t *ctx, const uint8_t *sps_data, uint32_t sps_len) {
+    if (!ctx || !sps_data || sps_len == 0 || sps_len > 1024) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    if (ctx->sps_data) {
+        luat_heap_free(ctx->sps_data);
+    }
+    
+    ctx->sps_data = (uint8_t *)luat_heap_malloc(sps_len);
+    if (!ctx->sps_data) {
+        LLOGE("SPS数据内存分配失败");
+        return RTSP_ERR_NO_MEMORY;
+    }
+    
+    memcpy(ctx->sps_data, sps_data, sps_len);
+    ctx->sps_len = sps_len;
+    
+    LLOGD("SPS设置完毕: %u字节", sps_len);
+    return RTSP_OK;
+}
+
+/**
+ * 设置H.264 PPS数据
+ */
+int rtsp_set_pps(rtsp_ctx_t *ctx, const uint8_t *pps_data, uint32_t pps_len) {
+    if (!ctx || !pps_data || pps_len == 0 || pps_len > 1024) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    if (ctx->pps_data) {
+        luat_heap_free(ctx->pps_data);
+    }
+    
+    ctx->pps_data = (uint8_t *)luat_heap_malloc(pps_len);
+    if (!ctx->pps_data) {
+        LLOGE("PPS数据内存分配失败");
+        return RTSP_ERR_NO_MEMORY;
+    }
+    
+    memcpy(ctx->pps_data, pps_data, pps_len);
+    ctx->pps_len = pps_len;
+    
+    LLOGD("PPS设置完毕: %u字节", pps_len);
+    return RTSP_OK;
+}
+
+/**
+ * 推送H.264视频帧
+ */
+int rtsp_push_h264_frame(rtsp_ctx_t *ctx, const uint8_t *frame_data, 
+                         uint32_t frame_len, uint32_t timestamp) {
+    if (!ctx || !frame_data || frame_len == 0) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    // 如果在PLAYING状态,直接处理;否则进入队列
+    if (ctx->state == RTSP_STATE_PLAYING) {
+        // 直接构建RTP包并发送
+        return rtsp_build_rtp_payload(ctx, frame_data, frame_len, timestamp);
+    } else if (1) {
+        return 0; // 丢弃非PLAYING状态下的帧
+    } else {
+        LLOGI("当前状态非PLAYING,帧加入发送队列 %d %d", frame_len, ctx->state);
+        // 加入队列
+        struct rtsp_frame_node *node = (struct rtsp_frame_node *)
+            luat_heap_malloc(sizeof(struct rtsp_frame_node));
+        if (!node) {
+            LLOGE("帧队列节点内存分配失败");
+            return RTSP_ERR_NO_MEMORY;
+        }
+        
+        node->data = (uint8_t *)luat_heap_malloc(frame_len);
+        if (!node->data) {
+            LLOGE("帧数据内存分配失败");
+            luat_heap_free(node);
+            return RTSP_ERR_NO_MEMORY;
+        }
+        
+        memcpy(node->data, frame_data, frame_len);
+        node->len = frame_len;
+        node->timestamp = timestamp;
+        node->next = NULL;
+        
+        // 检查队列大小
+        if (ctx->frame_queue_bytes + frame_len > RTSP_MAX_QUEUE_BYTES) {
+            // 丢弃最早的非关键帧
+            if (ctx->frame_head && ctx->frame_head != ctx->frame_tail) {
+                struct rtsp_frame_node *temp = ctx->frame_head;
+                ctx->frame_head = ctx->frame_head->next;
+                ctx->frame_queue_bytes -= temp->len;
+                
+                luat_heap_free(temp->data);
+                luat_heap_free(temp);
+                LLOGD("队列溢出,丢弃一帧");
+            }
+        }
+        
+        if (!ctx->frame_head) {
+            ctx->frame_head = node;
+        } else {
+            ctx->frame_tail->next = node;
+        }
+        ctx->frame_tail = node;
+        ctx->frame_queue_bytes += frame_len;
+        
+        //RTSP_LOGV("帧已加入队列: %u字节, 队列总大小: %u字节", frame_len, ctx->frame_queue_bytes);
+        return (int)frame_len;
+    }
+}
+
+/**
+ * 获取当前连接状态
+ */
+rtsp_state_t rtsp_get_state(rtsp_ctx_t *ctx) {
+    if (!ctx) {
+        return RTSP_STATE_IDLE;
+    }
+    return ctx->state;
+}
+
+/**
+ * 处理RTSP事件轮询
+ */
+int rtsp_poll(rtsp_ctx_t *ctx) {
+    if (!ctx) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    uint32_t now = luat_mcu_ticks();
+    
+    // 检查连接超时
+    if (ctx->state != RTSP_STATE_IDLE && ctx->state != RTSP_STATE_ERROR && ctx->state != RTSP_STATE_PLAYING) {
+        if (now - ctx->last_activity_time > RTSP_CMD_TIMEOUT) {
+            LLOGE("RTSP连接超时");
+            rtsp_set_state(ctx, RTSP_STATE_ERROR, RTSP_ERR_TIMEOUT);
+            return RTSP_ERR_TIMEOUT;
+        }
+    }
+    
+    // 发送队列中的帧
+    if (ctx->state == RTSP_STATE_PLAYING) {
+        rtsp_send_queued_frames(ctx);
+        
+        // 定期发送RTCP Sender Report (每5秒一次)
+        if (now - ctx->last_rtcp_time >= 5000) {
+            LLOGD("触发RTCP SR发送: 距上次%u毫秒", now - ctx->last_rtcp_time);
+            int ret = rtsp_send_rtcp_sr(ctx);
+            if (ret == RTSP_OK) {
+                ctx->last_rtcp_time = now;
+            } else {
+                LLOGE("RTCP SR发送失败: %d", ret);
+            }
+        }
+    }
+    
+    return RTSP_OK;
+}
+
+/**
+ * 获取推流统计信息
+ */
+int rtsp_get_stats(rtsp_ctx_t *ctx, rtsp_stats_t *stats) {
+    if (!ctx || !stats) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    stats->bytes_sent = ctx->bytes_sent;
+    stats->video_frames_sent = ctx->video_frames_sent;
+    stats->rtp_packets_sent = ctx->rtp_packets_sent;
+    stats->connection_time = luat_mcu_ticks() - ctx->start_tick;
+    stats->last_video_timestamp = ctx->video_timestamp;
+    
+    return RTSP_OK;
+}
+
+/* ======================== 内部函数实现 ======================== */
+
+/**
+ * 解析RTSP URL
+ */
+static int rtsp_parse_url(rtsp_ctx_t *ctx, const char *url) {
+    if (strncmp(url, "rtsp://", 7) != 0) {
+        LLOGE("非法的RTSP URL格式");
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    const char *ptr = url + 7;
+    const char *host_end = strchr(ptr, ':');
+    const char *stream_start;
+    
+    // 解析主机名
+    if (!host_end) {
+        host_end = strchr(ptr, '/');
+        if (!host_end) {
+            host_end = ptr + strlen(ptr);
+        }
+    }
+    
+    int host_len = host_end - ptr;
+    ctx->host = (char *)luat_heap_malloc(host_len + 1);
+    if (!ctx->host) {
+        return RTSP_ERR_NO_MEMORY;
+    }
+    strncpy(ctx->host, ptr, host_len);
+    ctx->host[host_len] = '\0';
+    
+    // 解析端口
+    if (*host_end == ':') {
+        ctx->port = (uint16_t)atoi(host_end + 1);
+        stream_start = strchr(host_end + 1, '/');
+    } else {
+        ctx->port = RTSP_DEFAULT_PORT;
+        stream_start = host_end;
+    }
+    
+    // 解析流名
+    if (stream_start && *stream_start == '/') {
+        stream_start++;
+        int stream_len = strlen(stream_start);
+        ctx->stream = (char *)luat_heap_malloc(stream_len + 1);
+        if (!ctx->stream) {
+            return RTSP_ERR_NO_MEMORY;
+        }
+        strcpy(ctx->stream, stream_start);
+    } else {
+        ctx->stream = (char *)luat_heap_malloc(1);
+        if (!ctx->stream) {
+            return RTSP_ERR_NO_MEMORY;
+        }
+        ctx->stream[0] = '\0';
+    }
+    
+    LLOGD("RTSP URL解析: host=%s, port=%u, stream=%s", 
+          ctx->host, ctx->port, ctx->stream);
+    
+    return RTSP_OK;
+}
+
+/**
+ * 状态转移
+ */
+static void rtsp_set_state(rtsp_ctx_t *ctx, rtsp_state_t new_state, int error_code) {
+    if (ctx->state == new_state) {
+        return;
+    }
+    
+    rtsp_state_t old_state = ctx->state;
+    ctx->state = new_state;
+    ctx->last_activity_time = luat_mcu_ticks();
+    
+    LLOGD("RTSP状态转移: %d -> %d", (int)old_state, (int)new_state);
+    
+    // 调用回调函数
+    rtsp_state_callback_t callback = (rtsp_state_callback_t)ctx->user_data;
+    if (callback) {
+        callback(ctx, old_state, new_state, error_code);
+    }
+}
+
+/**
+ * 发送RTSP命令
+ */
+static int rtsp_send_command(rtsp_ctx_t *ctx, const char *method, 
+                             const char *resource, const char *extra_headers,
+                             const char *data, uint32_t data_len) {
+    if (!ctx->control_pcb) {
+        LLOGE("RTSP TCP连接未建立");
+        return RTSP_ERR_CONNECT_FAILED;
+    }
+    
+    // 构建RTSP请求行
+    int len = snprintf((char *)ctx->send_buf, ctx->send_buf_size,
+        "%s %s RTSP/1.0\r\n"
+        "CSeq: %u\r\n"
+        "User-Agent: LuatOS-RTSP/1.0\r\n",
+        method, resource, ctx->cseq++);
+    
+    if (len < 0 || len >= (int)ctx->send_buf_size) {
+        LLOGE("发送缓冲区溢出");
+        return RTSP_ERR_BUFFER_OVERFLOW;
+    }
+    
+    // 添加Session头(如果已建立会话)
+    if (ctx->session_id) {
+        int n = snprintf((char *)ctx->send_buf + len, ctx->send_buf_size - len,
+            "Session: %s\r\n", ctx->session_id);
+        if (n < 0 || len + n >= (int)ctx->send_buf_size) {
+            LLOGE("发送缓冲区溢出");
+            return RTSP_ERR_BUFFER_OVERFLOW;
+        }
+        len += n;
+    }
+    
+    // 添加额外头部
+    if (extra_headers) {
+        int n = strlen(extra_headers);
+        if (len + n >= (int)ctx->send_buf_size) {
+            LLOGE("发送缓冲区溢出");
+            return RTSP_ERR_BUFFER_OVERFLOW;
+        }
+        memcpy(ctx->send_buf + len, extra_headers, n);
+        len += n;
+    }
+    
+    // 添加空行分隔头部和body(仅当没有data时自动添加)
+    if (data_len == 0) {
+        if (len + 2 >= (int)ctx->send_buf_size) {
+            LLOGE("发送缓冲区溢出");
+            return RTSP_ERR_BUFFER_OVERFLOW;
+        }
+        memcpy(ctx->send_buf + len, "\r\n", 2);
+        len += 2;
+    }
+    
+    RTSP_LOGV("RTSP命令:\n%.*s", len, (char *)ctx->send_buf);
+    
+    // 发送TCP头部数据
+    err_t err = tcp_write(ctx->control_pcb, ctx->send_buf, len, TCP_WRITE_FLAG_COPY);
+    if (err != ERR_OK) {
+        LLOGE("TCP头部发送失败: %d", err);
+        return RTSP_ERR_NETWORK;
+    }
+    
+    // 发送body数据(如果有)
+    if (data && data_len > 0) {
+        // 需要发送空行 + body
+        err = tcp_write(ctx->control_pcb, (const void *)"\r\n", 2, TCP_WRITE_FLAG_COPY);
+        if (err != ERR_OK) {
+            LLOGE("TCP空行发送失败: %d", err);
+            return RTSP_ERR_NETWORK;
+        }
+        
+        err = tcp_write(ctx->control_pcb, (const void *)data, data_len, TCP_WRITE_FLAG_COPY);
+        if (err != ERR_OK) {
+            LLOGE("TCP数据体发送失败: %d", err);
+            return RTSP_ERR_NETWORK;
+        }
+    }
+    
+    tcp_output(ctx->control_pcb);
+    return RTSP_OK;
+}
+
+/**
+ * TCP连接回调
+ */
+static err_t rtsp_tcp_connect_callback(void *arg, struct tcp_pcb *pcb, err_t err) {
+    rtsp_ctx_t *ctx = (rtsp_ctx_t *)arg;
+    
+    if (err != ERR_OK) {
+        LLOGE("TCP连接失败: %d", err);
+        rtsp_set_state(ctx, RTSP_STATE_ERROR, RTSP_ERR_CONNECT_FAILED);
+        return ERR_ABRT;
+    }
+    
+    LLOGD("TCP连接成功");
+    rtsp_set_state(ctx, RTSP_STATE_OPTIONS, 0);
+    
+    // 设置TCP回调函数
+    tcp_recv(pcb, rtsp_tcp_recv_callback);
+    tcp_err(pcb, rtsp_tcp_error_callback);
+    tcp_arg(pcb, ctx);
+    
+    // 发送OPTIONS请求(使用具体的stream URI)
+    if (rtsp_send_command(ctx, "OPTIONS", ctx->url, NULL, NULL, 0) == RTSP_OK) {
+        rtsp_set_state(ctx, RTSP_STATE_OPTIONS, 0);
+    }
+    
+    return ERR_OK;
+}
+
+/**
+ * TCP接收回调
+ */
+static err_t rtsp_tcp_recv_callback(void *arg, struct tcp_pcb *pcb, 
+                                   struct pbuf *p, err_t err) {
+    rtsp_ctx_t *ctx = (rtsp_ctx_t *)arg;
+    
+    if (err != ERR_OK) {
+        LLOGE("TCP接收错误: %d", err);
+        if (p) pbuf_free(p);
+        return ERR_ABRT;
+    }
+    
+    if (!p) {
+        LLOGD("TCP连接已关闭");
+        rtsp_set_state(ctx, RTSP_STATE_IDLE, 0);
+        return ERR_OK;
+    }
+    
+    // 复制数据到接收缓冲区
+    if (ctx->recv_pos + p->tot_len > ctx->recv_buf_size) {
+        LLOGE("接收缓冲区溢出");
+        pbuf_free(p);
+        return ERR_ABRT;
+    }
+    
+    pbuf_copy_partial(p, ctx->recv_buf + ctx->recv_pos, p->tot_len, 0);
+    ctx->recv_pos += p->tot_len;
+    
+    // 更新活动时间
+    ctx->last_activity_time = luat_mcu_ticks();
+    
+    RTSP_LOGV("接收RTSP数据: %u字节", p->tot_len);
+    pbuf_free(p);
+    
+    // 处理RTSP响应
+    rtsp_handle_response(ctx);
+    tcp_recved(pcb, p->tot_len);
+    
+    return ERR_OK;
+}
+
+/**
+ * TCP错误回调
+ */
+static void rtsp_tcp_error_callback(void *arg, err_t err) {
+    rtsp_ctx_t *ctx = (rtsp_ctx_t *)arg;
+    LLOGE("TCP错误: %d", err);
+    rtsp_set_state(ctx, RTSP_STATE_ERROR, RTSP_ERR_NETWORK);
+}
+
+/**
+ * 处理RTSP响应
+ */
+static int rtsp_handle_response(rtsp_ctx_t *ctx) {
+    // 检查是否已收到完整的响应
+    char *resp_end = strstr((char *)ctx->recv_buf, "\r\n\r\n");
+    if (!resp_end) {
+        // 尚未接收完整响应
+        return RTSP_OK;
+    }
+    
+    // 打印完整的RTSP响应以便调试
+    int resp_len = (resp_end - (char *)ctx->recv_buf) + 4;
+    LLOGD("RTSP响应内容:\n%.*s", resp_len, (char *)ctx->recv_buf);
+    
+    // 解析响应行: RTSP/1.0 <status> <reason>
+    char *line_start = (char *)ctx->recv_buf;
+    char *line_end = strstr(line_start, "\r\n");
+    if (!line_end) {
+        LLOGE("无效的RTSP响应");
+        rtsp_set_state(ctx, RTSP_STATE_ERROR, RTSP_ERR_HANDSHAKE_FAILED);
+        return RTSP_ERR_HANDSHAKE_FAILED;
+    }
+    
+    *line_end = '\0';
+    int status_code = 0;
+    if (sscanf(line_start, "RTSP/1.0 %d", &status_code) != 1) {
+        LLOGE("RTSP响应行解析失败");
+        *line_end = '\r';
+        rtsp_set_state(ctx, RTSP_STATE_ERROR, RTSP_ERR_HANDSHAKE_FAILED);
+        return RTSP_ERR_HANDSHAKE_FAILED;
+    }
+    *line_end = '\r';
+    
+    LLOGD("RTSP响应: 状态码=%d", status_code);
+    
+    if (status_code != 200) {
+        LLOGE("RTSP服务器错误: %d", status_code);
+        rtsp_set_state(ctx, RTSP_STATE_ERROR, RTSP_ERR_HANDSHAKE_FAILED);
+        return RTSP_ERR_HANDSHAKE_FAILED;
+    }
+    
+    // 解析响应头
+    char *header_start = line_end + 2;
+    char *session_id = NULL;
+    
+    while (header_start < resp_end) {
+        line_end = strstr(header_start, "\r\n");
+        if (!line_end) break;
+        
+        *line_end = '\0';
+        
+        // 提取Session头
+        if (rtsp_strncasecmp(header_start, "Session:", 8) == 0) {
+            char *value = header_start + 8;
+            while (*value == ' ' || *value == '\t') value++;
+            
+            // Session ID可能包含超时参数 (e.g. "12345678;timeout=60")
+            char *semicolon = strchr(value, ';');
+            int len = semicolon ? (semicolon - value) : strlen(value);
+            
+            if (len > 0) {
+                session_id = (char *)luat_heap_malloc(len + 1);
+                if (session_id) {
+                    strncpy(session_id, value, len);
+                    session_id[len] = '\0';
+                    LLOGD("提取Session ID: %s", session_id);
+                }
+            }
+        }
+        
+        // 提取Transport头
+        if (rtsp_strncasecmp(header_start, "Transport:", 10) == 0) {
+            char *value = header_start + 10;
+            while (*value == ' ' || *value == '\t') value++;
+            
+            // 解析server_port参数
+            char *server_port = strstr(value, "server_port=");
+            if (server_port) {
+                server_port += 12;  // strlen("server_port=")
+                int rtp_port = 0, rtcp_port = 0;
+                if (sscanf(server_port, "%d-%d", &rtp_port, &rtcp_port) == 2) {
+                    ctx->remote_rtp_port = (uint16_t)rtp_port;
+                    ctx->remote_rtcp_port = (uint16_t)rtcp_port;
+                    LLOGD("服务器端口: RTP=%u, RTCP=%u", 
+                          ctx->remote_rtp_port, ctx->remote_rtcp_port);
+                }
+            }
+            
+            // 解析client_port参数(如果服务器返回)
+            char *client_port = strstr(value, "client_port=");
+            if (client_port) {
+                client_port += 12;  // strlen("client_port=")
+                int rtp_port = 0, rtcp_port = 0;
+                if (sscanf(client_port, "%d-%d", &rtp_port, &rtcp_port) == 2) {
+                    ctx->local_rtp_port = (uint16_t)rtp_port;
+                    ctx->local_rtcp_port = (uint16_t)rtcp_port;
+                    LLOGD("本地端口: RTP=%u, RTCP=%u", 
+                          ctx->local_rtp_port, ctx->local_rtcp_port);
+                }
+            }
+        }
+        
+        *line_end = '\r';
+        header_start = line_end + 2;
+    }
+    
+    // 保存Session ID
+    if (session_id && !ctx->session_id) {
+        ctx->session_id = session_id;
+    } else if (session_id) {
+        luat_heap_free(session_id);
+    }
+    
+    // 根据当前状态处理下一步
+    switch (ctx->state) {
+        case RTSP_STATE_OPTIONS:
+            // OPTIONS成功,发送ANNOUNCE
+            {
+                char sdp[2048];
+                int sdp_len = 0;
+                
+                // 如果有SPS/PPS,构建完整的SDP;否则使用基础SDP
+                if (ctx->sps_data && ctx->sps_len > 0 && ctx->pps_data && ctx->pps_len > 0) {
+                    // 计算profile-level-id (从SPS中提取)
+                    char profile_level_id[16] = {0};
+                    if (ctx->sps_len >= 4) {
+                        snprintf(profile_level_id, sizeof(profile_level_id), "%02X%02X%02X",
+                                ctx->sps_data[1], ctx->sps_data[2], ctx->sps_data[3]);
+                    } else {
+                        strcpy(profile_level_id, "42C01E");  // 默认Baseline Profile Level 3.0
+                    }
+                    
+                    // Base64编码SPS
+                    char sps_b64[512] = {0};
+                    int sps_b64_len = rtsp_base64_encode(ctx->sps_data, ctx->sps_len, 
+                                                         sps_b64, sizeof(sps_b64));
+                    
+                    // Base64编码PPS
+                    char pps_b64[512] = {0};
+                    int pps_b64_len = rtsp_base64_encode(ctx->pps_data, ctx->pps_len,
+                                                         pps_b64, sizeof(pps_b64));
+                    
+                    if (sps_b64_len > 0 && pps_b64_len > 0) {
+                        // 构建完整的SDP
+                        sdp_len = snprintf(sdp, sizeof(sdp),
+                            "v=0\r\n"
+                            "o=- 0 0 IN IP4 127.0.0.1\r\n"
+                            "s=LuatOS RTSP Stream\r\n"
+                            "c=IN IP4 0.0.0.0\r\n"
+                            "t=0 0\r\n"
+                            "m=video 0 RTP/AVP 96\r\n"
+                            "a=rtpmap:96 H264/90000\r\n"
+                            "a=fmtp:96 packetization-mode=1;profile-level-id=%s;sprop-parameter-sets=%s\r\n"
+                            "a=control:streamid=0\r\n",
+                            profile_level_id, (ctx->sprop_parameter_sets ? ctx->sprop_parameter_sets : "") );
+                        
+                        LLOGD("完整SDP生成: profile-level-id=%s", profile_level_id);
+                    }
+                }
+                
+                // 如果没有生成完整SDP,使用基础SDP
+                if (sdp_len <= 0) {
+                    sdp_len = snprintf(sdp, sizeof(sdp),
+                        "v=0\r\n"
+                        "o=- 0 0 IN IP4 127.0.0.1\r\n"
+                        "s=LuatOS RTSP Stream\r\n"
+                        "c=IN IP4 %s\r\n"
+                        "t=0 0\r\n"
+                        "m=video 0 RTP/AVP 96\r\n"
+                        "a=rtpmap:96 H264/90000\r\n"
+                        "a=fmtp:96 profile-level-id=1; sprop-parameter-sets=Z0IAKeNQCgC2QgAAB9AAAOpg0YAAmJgAL/L3gAE=,aM48gA==\r\n"
+                        "a=control:streamid=0\r\n", ctx->host);
+                    
+                    LLOGD("使用基础SDP(无SPS/PPS)");
+                }
+                
+                if (sdp_len > 0 && sdp_len < (int)sizeof(sdp)) {
+                    char headers[512] = {0};
+                    snprintf(headers, sizeof(headers),
+                        "Content-Type: application/sdp\r\n"
+                        "Content-Length: %d\r\n", sdp_len);
+                    
+                    if (rtsp_send_command(ctx, "ANNOUNCE", ctx->url, headers, sdp, sdp_len) == RTSP_OK) {
+                        rtsp_set_state(ctx, RTSP_STATE_DESCRIBE, 0);
+                    }
+                }
+            }
+            break;
+            
+        case RTSP_STATE_DESCRIBE:
+            // ANNOUNCE/DESCRIBE成功,发送SETUP
+            {
+                char setup_headers[512] = {0};
+                snprintf(setup_headers, sizeof(setup_headers),
+                    "Transport: RTP/AVP/UDP;unicast;client_port=%u-%u;mode=record\r\n",
+                    ctx->local_rtp_port ? ctx->local_rtp_port : 19030,
+                    ctx->local_rtcp_port ? ctx->local_rtcp_port : 19031);
+                
+                char resource[512] = {0};
+                snprintf(resource, sizeof(resource), "%s/streamid=0", ctx->url);
+                
+                if (rtsp_send_command(ctx, "SETUP", resource, setup_headers, NULL, 0) == RTSP_OK) {
+                    rtsp_set_state(ctx, RTSP_STATE_SETUP, 0);
+                }
+            }
+            break;
+            
+        case RTSP_STATE_SETUP:
+            // SETUP成功,发送PLAY
+            if (rtsp_send_command(ctx, "RECORD", ctx->url, NULL, NULL, 0) == RTSP_OK) {
+                rtsp_set_state(ctx, RTSP_STATE_PLAY, 0);
+            }
+            break;
+            
+        case RTSP_STATE_PLAY:
+            // RECORD/PLAY成功,进入推流状态
+            rtsp_set_state(ctx, RTSP_STATE_PLAYING, 0);
+            LLOGD("RTSP握手完成,开始推流");
+            
+            // 立即发送一次RTCP Sender Report
+            LLOGD("RTSP就绪,发送初始RTCP SR");
+            rtsp_send_rtcp_sr(ctx);
+            ctx->last_rtcp_time = luat_mcu_ticks();
+            
+            // 通知摄像头开始采集
+            extern int luat_camera_capture(int id, uint8_t quality, const char *path);
+            luat_camera_capture(0, 80, "rtsp");
+            break;
+            
+        default:
+            break;
+    }
+    
+    // 清空接收缓冲区
+    resp_len = (resp_end - (char *)ctx->recv_buf) + 4;
+    if (ctx->recv_pos > (uint32_t)resp_len) {
+        memmove(ctx->recv_buf, ctx->recv_buf + resp_len, ctx->recv_pos - resp_len);
+        ctx->recv_pos -= resp_len;
+    } else {
+        ctx->recv_pos = 0;
+    }
+    
+    return RTSP_OK;
+}
+
+/**
+ * 发送RTP包
+ */
+static int rtsp_send_rtp_packet(rtsp_ctx_t *ctx, const uint8_t *data, 
+                                uint32_t len, uint32_t timestamp, uint8_t marker) {
+    if (!ctx->rtp_pcb) {
+        LLOGE("RTP UDP连接未建立");
+        return RTSP_ERR_CONNECT_FAILED;
+    }
+    
+    // 构建RTP头
+    uint8_t rtp_header[RTP_HEADER_SIZE];
+    uint32_t rtp_ts = (timestamp * RTP_TIMESTAMP_BASE) / 1000;
+    
+    // 计算与上个包的时间戳差值(用于诊断时间戳跳跃)
+    static uint32_t last_rtp_ts = 0;
+    int32_t ts_delta = (int32_t)(rtp_ts - last_rtp_ts);
+    
+    RTSP_LOGV("RTP包: seq=%u, ts=%u, ts_delta=%d, marker=%u, len=%u, timestamp_ms=%u",
+              ctx->rtp_sequence, rtp_ts, ts_delta, marker, len, timestamp);
+    
+    // 如果时间戳跳跃异常大(可能导致抖动),记录警告
+    if (last_rtp_ts > 0 && (ts_delta < -90000 || ts_delta > 270000)) {
+        LLOGW("时间戳异常跳跃: seq=%u, last_ts=%u, curr_ts=%u, delta=%d (应在-90000~270000范围内,对应帧率25~120fps)",
+              ctx->rtp_sequence, last_rtp_ts, rtp_ts, ts_delta);
+    }
+    last_rtp_ts = rtp_ts;
+    
+    // V=2, P=0, X=0, CC=0
+    rtp_header[0] = (2 << 6) | 0;
+    // M=marker, PT=96(H.264)
+    rtp_header[1] = (marker << 7) | RTP_PAYLOAD_TYPE_H264;
+    // Sequence number (big-endian)
+    rtp_header[2] = (ctx->rtp_sequence >> 8) & 0xFF;
+    rtp_header[3] = ctx->rtp_sequence & 0xFF;
+    // Timestamp (big-endian)
+    rtp_header[4] = (rtp_ts >> 24) & 0xFF;
+    rtp_header[5] = (rtp_ts >> 16) & 0xFF;
+    rtp_header[6] = (rtp_ts >> 8) & 0xFF;
+    rtp_header[7] = rtp_ts & 0xFF;
+    // SSRC (big-endian)
+    rtp_header[8] = (ctx->rtp_ssrc >> 24) & 0xFF;
+    rtp_header[9] = (ctx->rtp_ssrc >> 16) & 0xFF;
+    rtp_header[10] = (ctx->rtp_ssrc >> 8) & 0xFF;
+    rtp_header[11] = ctx->rtp_ssrc & 0xFF;
+    
+    ctx->rtp_sequence++;
+    
+    // 构建完整RTP包
+    if (RTP_HEADER_SIZE + len > ctx->rtp_buf_size) {
+        LLOGE("RTP缓冲区溢出");
+        return RTSP_ERR_BUFFER_OVERFLOW;
+    }
+    
+    memcpy(ctx->rtp_buf, rtp_header, RTP_HEADER_SIZE);
+    memcpy(ctx->rtp_buf + RTP_HEADER_SIZE, data, len);
+    
+    // 发送UDP包
+    struct pbuf *pb = pbuf_alloc(PBUF_TRANSPORT, RTP_HEADER_SIZE + len, PBUF_RAM);
+    if (!pb) {
+        LLOGE("UDP数据包分配失败");
+        return RTSP_ERR_NO_MEMORY;
+    }
+    
+    memcpy(pb->payload, ctx->rtp_buf, RTP_HEADER_SIZE + len);
+    
+    err_t err = udp_sendto(ctx->rtp_pcb, pb, &ctx->remote_ip, ctx->remote_rtp_port);
+    pbuf_free(pb);
+    
+    if (err != ERR_OK) {
+        LLOGE("RTP UDP发送失败: err=%d, remote_ip=%s, remote_rtp_port=%u, len=%u", 
+              err, ipaddr_ntoa(&ctx->remote_ip), ctx->remote_rtp_port, RTP_HEADER_SIZE + len);
+        return RTSP_ERR_NETWORK;
+    }
+    
+    ctx->bytes_sent += RTP_HEADER_SIZE + len;
+    ctx->rtp_packets_sent++;
+    
+    // RTSP_LOGV("RTP包已发送: %u字节, 序列号: %u", RTP_HEADER_SIZE + len, ctx->rtp_sequence - 1);
+    
+    return RTSP_OK;
+}
+
+/**
+ * 构建RTP载荷 - 支持多NALU解析和FU-A分片
+ */
+static int rtsp_build_rtp_payload(rtsp_ctx_t *ctx, const uint8_t *frame_data, 
+                                  uint32_t frame_len, uint32_t timestamp) {
+    if (!frame_data || frame_len == 0) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    // 如果传入的 timestamp 为 0,使用上下文中的时间戳
+    if (timestamp == 0) {
+        timestamp = ctx->video_timestamp;
+    }
+    const uint8_t *ptr = frame_data;
+    uint32_t remaining = frame_len;
+    int nalu_count = 0;
+    int total_sent = 0;
+    
+    RTSP_LOGV("处理帧数据: %u字节", frame_len);
+    
+    // 解析并发送所有NALU
+    while (remaining > 0) {
+        // 查找当前NALU的起始码
+        uint32_t start_code_len = 0;
+        const uint8_t *nalu_start = rtsp_find_next_nalu(ptr, remaining, &start_code_len);
+        
+        if (!nalu_start) {
+            // 没有找到起始码,可能整个数据就是一个NALU
+            if (nalu_count == 0 && remaining > 0) {
+                nalu_start = ptr;
+                start_code_len = 0;
+            } else {
+                break;
+            }
+        }
+        
+        // 跳过起始码
+        const uint8_t *nalu_data = nalu_start + start_code_len;
+        uint32_t nalu_available = remaining - (nalu_start - ptr) - start_code_len;
+        
+        if (nalu_available == 0) {
+            break;
+        }
+        
+        // 查找下一个NALU起始码,确定当前NALU长度
+        uint32_t next_start_code_len = 0;
+        const uint8_t *next_nalu = rtsp_find_next_nalu(nalu_data + 1, nalu_available - 1, &next_start_code_len);
+        
+        uint32_t nalu_len;
+        if (next_nalu) {
+            nalu_len = next_nalu - nalu_data;
+        } else {
+            nalu_len = nalu_available;
+        }
+        
+        if (nalu_len > 0) {
+            uint8_t nalu_type = nalu_data[0] & 0x1F;
+            RTSP_LOGV("发现NALU: 类型=%u, 长度=%u", nalu_type, nalu_len);
+            
+            // 自动保存SPS/PPS
+            if (nalu_type == NALU_TYPE_SPS && (!ctx->sps_data || ctx->sps_len != nalu_len)) {
+                if (ctx->sps_data) {
+                    luat_heap_free(ctx->sps_data);
+                }
+                ctx->sps_data = (uint8_t *)luat_heap_malloc(nalu_len);
+                if (ctx->sps_data) {
+                    memcpy(ctx->sps_data, nalu_data, nalu_len);
+                    ctx->sps_len = nalu_len;
+                    LLOGD("自动提取SPS: %u字节", nalu_len);
+                    // 详细解析SPS
+                    rtsp_parse_sps_detail(nalu_data, nalu_len);
+                    // 打印sprop-sps(Base64)
+                    char b64_sps[256];
+                    if (rtsp_base64_encode(nalu_data, nalu_len, b64_sps, sizeof(b64_sps)) >= 0) {
+                        LLOGD("sprop-sps=%s", b64_sps);
+                        // 如果已有PPS,合成sprop-parameter-sets
+                        if (ctx->pps_data && ctx->pps_len > 0) {
+                            char b64_pps[256];
+                            if (rtsp_base64_encode(ctx->pps_data, ctx->pps_len, b64_pps, sizeof(b64_pps)) >= 0) {
+                                size_t total_len = strlen(b64_sps) + 1 + strlen(b64_pps) + 1;
+                                if (ctx->sprop_parameter_sets) {
+                                    luat_heap_free(ctx->sprop_parameter_sets);
+                                    ctx->sprop_parameter_sets = NULL;
+                                }
+                                ctx->sprop_parameter_sets = (char*)luat_heap_malloc(total_len);
+                                if (ctx->sprop_parameter_sets) {
+                                    snprintf(ctx->sprop_parameter_sets, total_len, "%s,%s", b64_sps, b64_pps);
+                                    LLOGD("sprop-parameter-sets=%s", ctx->sprop_parameter_sets);
+                                } else {
+                                    LLOGW("分配sprop-parameter-sets失败");
+                                }
+                            }
+                        }
+                    } else {
+                        LLOGW("sprop-sps Base64编码失败");
+                    }
+                }
+            } else if (nalu_type == NALU_TYPE_PPS && (!ctx->pps_data || ctx->pps_len != nalu_len)) {
+                if (ctx->pps_data) {
+                    luat_heap_free(ctx->pps_data);
+                }
+                ctx->pps_data = (uint8_t *)luat_heap_malloc(nalu_len);
+                if (ctx->pps_data) {
+                    memcpy(ctx->pps_data, nalu_data, nalu_len);
+                    ctx->pps_len = nalu_len;
+                    LLOGD("自动提取PPS: %u字节", nalu_len);
+                    // 详细解析PPS
+                    rtsp_parse_pps_detail(nalu_data, nalu_len);
+                    // 打印sprop-pps(Base64)
+                    char b64_pps[256];
+                    if (rtsp_base64_encode(nalu_data, nalu_len, b64_pps, sizeof(b64_pps)) >= 0) {
+                        LLOGD("sprop-pps=%s", b64_pps);
+                        // 如果已有SPS,合成sprop-parameter-sets
+                        if (ctx->sps_data && ctx->sps_len > 0) {
+                            char b64_sps[256];
+                            if (rtsp_base64_encode(ctx->sps_data, ctx->sps_len, b64_sps, sizeof(b64_sps)) >= 0) {
+                                size_t total_len = strlen(b64_sps) + 1 + strlen(b64_pps) + 1;
+                                if (ctx->sprop_parameter_sets) {
+                                    luat_heap_free(ctx->sprop_parameter_sets);
+                                    ctx->sprop_parameter_sets = NULL;
+                                }
+                                ctx->sprop_parameter_sets = (char*)luat_heap_malloc(total_len);
+                                if (ctx->sprop_parameter_sets) {
+                                    snprintf(ctx->sprop_parameter_sets, total_len, "%s,%s", b64_sps, b64_pps);
+                                    LLOGD("sprop-parameter-sets=%s", ctx->sprop_parameter_sets);
+                                } else {
+                                    LLOGW("分配sprop-parameter-sets失败");
+                                }
+                            }
+                        }
+                    } else {
+                        LLOGW("sprop-pps Base64编码失败");
+                    }
+                }
+            }
+            
+            // 发送NALU(非SPS/PPS或在推流状态下也发送SPS/PPS)
+            if (nalu_type != NALU_TYPE_AUD) {  // 跳过AUD
+                // 判断是否为最后一个NALU
+                uint8_t is_last = (next_nalu == NULL) ? 1 : 0;
+                
+                int ret = rtsp_send_nalu(ctx, nalu_data, nalu_len, timestamp, is_last);
+                if (ret >= 0) {
+                    total_sent += ret;
+                    nalu_count++;
+                } else {
+                    LLOGE("NALU发送失败: %d", ret);
+                    return ret;
+                }
+            }
+        }
+        
+        // 移动到下一个NALU
+        if (next_nalu) {
+            uint32_t consumed = next_nalu - ptr;
+            ptr = next_nalu;
+            remaining -= consumed;
+        } else {
+            break;
+        }
+    }
+    
+    if (nalu_count > 0) {
+        ctx->video_frames_sent++;
+        ctx->video_timestamp = timestamp;
+        RTSP_LOGV("帧发送完成: %d个NALU, 总计%d字节", nalu_count, total_sent);
+    }
+    
+    return total_sent > 0 ? total_sent : RTSP_ERR_FAILED;
+}
+
+/**
+ * 发送队列中的帧
+ */
+static int rtsp_send_queued_frames(rtsp_ctx_t *ctx) {
+    int sent_count = 0;
+    
+    while (ctx->frame_head && sent_count < 5) {  // 每次轮询最多发送5帧
+        struct rtsp_frame_node *node = ctx->frame_head;
+        
+        if (rtsp_build_rtp_payload(ctx, node->data, node->len, node->timestamp) == RTSP_OK) {
+            ctx->frame_head = node->next;
+            ctx->frame_queue_bytes -= node->len;
+            
+            luat_heap_free(node->data);
+            luat_heap_free(node);
+            
+            sent_count++;
+        } else {
+            break;  // 发送失败,暂停发送
+        }
+    }
+    
+    if (!ctx->frame_head) {
+        ctx->frame_tail = NULL;
+    }
+    
+    //RTSP_LOGV("队列帧发送: %d帧, 剩余队列: %u字节", sent_count, ctx->frame_queue_bytes);
+    
+    return RTSP_OK;
+}
+
+/**
+ * 清空帧队列
+ */
+static void rtsp_clear_frame_queue(rtsp_ctx_t *ctx) {
+    struct rtsp_frame_node *node = ctx->frame_head;
+    
+    while (node) {
+        struct rtsp_frame_node *next = node->next;
+        if (node->data) {
+            luat_heap_free(node->data);
+        }
+        luat_heap_free(node);
+        node = next;
+    }
+    
+    ctx->frame_head = NULL;
+    ctx->frame_tail = NULL;
+    ctx->frame_queue_bytes = 0;
+}
+
+/**
+ * 大小写不敏感字符串比较(用于HTTP头解析)
+ */
+static int rtsp_strncasecmp(const char *a, const char *b, size_t n) {
+    for (size_t i = 0; i < n; i++) {
+        int ca = tolower((unsigned char)a[i]);
+        int cb = tolower((unsigned char)b[i]);
+        if (ca != cb || ca == 0) {
+            return ca - cb;
+        }
+    }
+    return 0;
+}
+
+/**
+ * Base64编码函数
+ */
+static int rtsp_base64_encode(const uint8_t *src, uint32_t src_len, char *dst, uint32_t dst_len) {
+    static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+    uint32_t i = 0, j = 0;
+    
+    // 计算所需的输出长度
+    uint32_t required_len = ((src_len + 2) / 3) * 4;
+    if (required_len >= dst_len) {
+        return -1;  // 缓冲区不足
+    }
+    
+    while (i < src_len) {
+        uint32_t octet_a = i < src_len ? src[i++] : 0;
+        uint32_t octet_b = i < src_len ? src[i++] : 0;
+        uint32_t octet_c = i < src_len ? src[i++] : 0;
+        
+        uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;
+        
+        dst[j++] = base64_table[(triple >> 18) & 0x3F];
+        dst[j++] = base64_table[(triple >> 12) & 0x3F];
+        dst[j++] = base64_table[(triple >> 6) & 0x3F];
+        dst[j++] = base64_table[triple & 0x3F];
+    }
+    
+    // 添加填充
+    uint32_t mod = src_len % 3;
+    if (mod == 1) {
+        dst[j - 1] = '=';
+        dst[j - 2] = '=';
+    } else if (mod == 2) {
+        dst[j - 1] = '=';
+    }
+    
+    dst[j] = '\0';
+    return j;
+}
+
+/**
+ * 查找下一个NALU起始码
+ * @return 起始码的位置,如果未找到返回NULL
+ */
+static const uint8_t* rtsp_find_next_nalu(const uint8_t *data, uint32_t len, uint32_t *start_code_len) {
+    if (len < 3) {
+        return NULL;
+    }
+    
+    for (uint32_t i = 0; i < len - 2; i++) {
+        // 查找0x000001或0x00000001
+        if (data[i] == 0 && data[i+1] == 0) {
+            if (data[i+2] == 1) {
+                *start_code_len = 3;
+                return &data[i];
+            } else if (i < len - 3 && data[i+2] == 0 && data[i+3] == 1) {
+                *start_code_len = 4;
+                return &data[i];
+            }
+        }
+    }
+    
+    return NULL;
+}
+
+/**
+ * 发送单个NALU(支持FU-A分片)
+ */
+static int rtsp_send_nalu(rtsp_ctx_t *ctx, const uint8_t *nalu_data, 
+                          uint32_t nalu_len, uint32_t timestamp, uint8_t is_last_nalu) {
+    if (!nalu_data || nalu_len == 0) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    uint8_t nalu_header = nalu_data[0];
+    uint8_t nalu_type = nalu_header & 0x1F;
+    uint8_t nri = nalu_header & 0x60;
+    
+    // 如果NALU小于最大载荷,直接发送
+    if (nalu_len <= RTP_MAX_PACKET_SIZE) {
+        int ret = rtsp_send_rtp_packet(ctx, nalu_data, nalu_len, timestamp, is_last_nalu);
+        return (ret == RTSP_OK) ? (int)nalu_len : ret;
+    }
+    
+    // 需要FU-A分片
+    RTSP_LOGV("NALU过大(%u字节),使用FU-A分片", nalu_len);
+    
+    uint32_t nalu_payload_len = nalu_len - 1;  // 去掉NALU头
+    const uint8_t *nalu_payload = nalu_data + 1;
+    uint32_t fragment_size = RTP_MAX_PACKET_SIZE - 2;  // FU indicator + FU header
+    uint32_t offset = 0;
+    uint32_t fragment_count = 0;
+    int total_sent = 0;
+    
+    while (offset < nalu_payload_len) {
+        uint32_t this_fragment_size = (nalu_payload_len - offset > fragment_size) ? 
+                                      fragment_size : (nalu_payload_len - offset);
+        
+        uint8_t fu_packet[RTP_MAX_PACKET_SIZE];
+        
+        // FU indicator: F(1bit) + NRI(2bits) + Type(5bits=28)
+        fu_packet[0] = nri | FU_A_TYPE;
+        
+        // FU header: S(1bit) + E(1bit) + R(1bit) + Type(5bits)
+        uint8_t fu_header = nalu_type;
+        if (offset == 0) {
+            fu_header |= 0x80;  // S bit (start)
+        }
+        if (offset + this_fragment_size >= nalu_payload_len) {
+            fu_header |= 0x40;  // E bit (end)
+        }
+        fu_packet[1] = fu_header;
+        
+        // 复制分片数据
+        memcpy(fu_packet + 2, nalu_payload + offset, this_fragment_size);
+        
+        // 发送分片,只有最后一个分片且是最后一个NALU时才设置marker
+        uint8_t marker = ((fu_header & 0x40) && is_last_nalu) ? 1 : 0;
+        
+        int ret = rtsp_send_rtp_packet(ctx, fu_packet, 2 + this_fragment_size, timestamp, marker);
+        if (ret != RTSP_OK) {
+            LLOGE("FU-A分片发送失败: %d", ret);
+            return ret;
+        }
+        
+        offset += this_fragment_size;
+        fragment_count++;
+        total_sent += (2 + this_fragment_size);
+    }
+    
+    RTSP_LOGV("FU-A分片完成: %u个分片", fragment_count);
+    return total_sent;
+}
+
+/**
+ * 转换系统时间到NTP时间戳
+ * NTP时间戳: 64位, 高32位是秒数(从1900年1月1日开始), 低32位是小数秒
+ */
+static void rtsp_get_ntp_timestamp(uint32_t *ntp_sec, uint32_t *ntp_frac) {
+    // 使用 SNTP 获取准确的 NTP 时间戳(毫秒单位,从1970年开始)
+    extern uint64_t luat_sntp_time64_ms();
+    uint64_t unix_ms = luat_sntp_time64_ms();
+    
+    // NTP时间戳从1900年开始,1970年1月1日对应的秒数为2208988800秒
+    const uint64_t NTP_OFFSET_SEC = 2208988800UL;
+    
+    // 将UNIX时间(毫秒)转换为NTP时间
+    // 1. UNIX秒数 = unix_ms / 1000
+    // 2. NTP秒数 = UNIX秒数 + NTP_OFFSET
+    uint64_t unix_sec = unix_ms / 1000;
+    uint32_t unix_ms_frac = (uint32_t)(unix_ms % 1000);
+    
+    // NTP秒数
+    *ntp_sec = (uint32_t)(unix_sec + NTP_OFFSET_SEC);
+    
+    // NTP小数部分: 将毫秒转换为 NTP 小数格式 (2^32 * ms / 1000)
+    *ntp_frac = (unix_ms_frac * 4294967296ULL) / 1000;
+    
+    RTSP_LOGV("NTP时间戳: unix_ms=%llu, unix_sec=%llu, ntp_sec=%u, ntp_frac=%u", 
+              unix_ms, unix_sec, *ntp_sec, *ntp_frac);
+}
+
+/**
+ * 发送RTCP Sender Report
+ * RTCP SR格式 (RFC 3550):
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |V=2|P|    RC   |   PT=SR=200   |             length            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                         SSRC of sender                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |              NTP timestamp, most significant word             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |             NTP timestamp, least significant word             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                         RTP timestamp                         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                     sender's packet count                     |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                      sender's octet count                     |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+static int rtsp_send_rtcp_sr(rtsp_ctx_t *ctx) {
+    if (!ctx->rtcp_pcb || ctx->remote_rtcp_port == 0) {
+        LLOGD("RTCP未配置,跳过SR发送: rtcp_pcb=%p, remote_rtcp_port=%u", 
+              ctx->rtcp_pcb, ctx->remote_rtcp_port);
+        return RTSP_OK;
+    }
+    
+    LLOGD("准备发送RTCP SR: packets=%u, bytes=%u, remote_ip=%s, remote_rtcp_port=%u",
+          ctx->rtp_packets_sent, ctx->bytes_sent, 
+          ipaddr_ntoa(&ctx->remote_ip), ctx->remote_rtcp_port);
+    
+    uint8_t rtcp_packet[28];  // RTCP SR固定头部28字节(不含接收报告块)
+    uint32_t ntp_sec, ntp_frac;
+    
+    // 获取NTP时间戳
+    rtsp_get_ntp_timestamp(&ntp_sec, &ntp_frac);
+    
+    // 计算RTP时间戳
+    uint32_t rtp_ts = (ctx->video_timestamp * RTP_TIMESTAMP_BASE) / 1000;
+    
+    // 构建RTCP SR包
+    // V=2, P=0, RC=0 (无接收报告块)
+    rtcp_packet[0] = (2 << 6) | 0;
+    // PT=200 (SR)
+    rtcp_packet[1] = 200;
+    // Length = 6 (28字节 / 4 - 1)
+    rtcp_packet[2] = 0;
+    rtcp_packet[3] = 6;
+    
+    // SSRC (big-endian)
+    rtcp_packet[4] = (ctx->rtp_ssrc >> 24) & 0xFF;
+    rtcp_packet[5] = (ctx->rtp_ssrc >> 16) & 0xFF;
+    rtcp_packet[6] = (ctx->rtp_ssrc >> 8) & 0xFF;
+    rtcp_packet[7] = ctx->rtp_ssrc & 0xFF;
+    
+    // NTP timestamp - 高32位 (big-endian)
+    rtcp_packet[8] = (ntp_sec >> 24) & 0xFF;
+    rtcp_packet[9] = (ntp_sec >> 16) & 0xFF;
+    rtcp_packet[10] = (ntp_sec >> 8) & 0xFF;
+    rtcp_packet[11] = ntp_sec & 0xFF;
+    
+    // NTP timestamp - 低32位 (big-endian)
+    rtcp_packet[12] = (ntp_frac >> 24) & 0xFF;
+    rtcp_packet[13] = (ntp_frac >> 16) & 0xFF;
+    rtcp_packet[14] = (ntp_frac >> 8) & 0xFF;
+    rtcp_packet[15] = ntp_frac & 0xFF;
+    
+    // RTP timestamp (big-endian)
+    rtcp_packet[16] = (rtp_ts >> 24) & 0xFF;
+    rtcp_packet[17] = (rtp_ts >> 16) & 0xFF;
+    rtcp_packet[18] = (rtp_ts >> 8) & 0xFF;
+    rtcp_packet[19] = rtp_ts & 0xFF;
+    
+    // Sender's packet count (big-endian)
+    rtcp_packet[20] = (ctx->rtp_packets_sent >> 24) & 0xFF;
+    rtcp_packet[21] = (ctx->rtp_packets_sent >> 16) & 0xFF;
+    rtcp_packet[22] = (ctx->rtp_packets_sent >> 8) & 0xFF;
+    rtcp_packet[23] = ctx->rtp_packets_sent & 0xFF;
+    
+    // Sender's octet count (big-endian)
+    rtcp_packet[24] = (ctx->bytes_sent >> 24) & 0xFF;
+    rtcp_packet[25] = (ctx->bytes_sent >> 16) & 0xFF;
+    rtcp_packet[26] = (ctx->bytes_sent >> 8) & 0xFF;
+    rtcp_packet[27] = ctx->bytes_sent & 0xFF;
+    
+    // 发送RTCP SR包
+    struct pbuf *pb = pbuf_alloc(PBUF_TRANSPORT, 28, PBUF_RAM);
+    if (!pb) {
+        LLOGE("RTCP SR包分配失败");
+        return RTSP_ERR_NO_MEMORY;
+    }
+    
+    memcpy(pb->payload, rtcp_packet, 28);
+    
+    LLOGD("执行udp_sendto: rtcp_pcb=%p, remote_ip=%s, remote_rtcp_port=%u, len=28",
+          ctx->rtcp_pcb, ipaddr_ntoa(&ctx->remote_ip), ctx->remote_rtcp_port);
+    
+    err_t err = udp_sendto(ctx->rtcp_pcb, pb, &ctx->remote_ip, ctx->remote_rtcp_port);
+    
+    LLOGD("udp_sendto执行完毕: err=%d (%s)", err, 
+          err == ERR_OK ? "成功" : (err == ERR_MEM ? "内存不足" : 
+          (err == ERR_RTE ? "路由错误" : "其他错误")));
+    
+    pbuf_free(pb);
+    
+    if (err != ERR_OK) {
+        LLOGE("RTCP SR发送失败: err=%d, remote_ip=%s, remote_rtcp_port=%u", 
+              err, ipaddr_ntoa(&ctx->remote_ip), ctx->remote_rtcp_port);
+        return RTSP_ERR_NETWORK;
+    }
+    
+    LLOGD("RTCP SR已成功发送: SSRC=0x%08X, packets=%u, bytes=%u, NTP=%u.%u, RTP_TS=%u", 
+          ctx->rtp_ssrc, ctx->rtp_packets_sent, ctx->bytes_sent, ntp_sec, ntp_frac, rtp_ts);
+    
+    return RTSP_OK;
+}
+
+/**
+ * 连接到RTSP服务器
+ */
+int rtsp_connect(rtsp_ctx_t *ctx) {
+    if (!ctx || !ctx->host || ctx->port == 0) {
+        LLOGE("RTSP上下文无效或URL未设置");
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    if (ctx->state != RTSP_STATE_IDLE) {
+        LLOGE("RTSP已处于连接状态");
+        return RTSP_ERR_FAILED;
+    }
+    
+    // 创建TCP控制连接
+    ctx->control_pcb = tcp_new();
+    if (!ctx->control_pcb) {
+        LLOGE("TCP控制块创建失败");
+        return RTSP_ERR_NO_MEMORY;
+    }
+    
+    // 创建RTP UDP连接
+    ctx->rtp_pcb = udp_new();
+    if (!ctx->rtp_pcb) {
+        LLOGE("RTP UDP块创建失败");
+        tcp_abort(ctx->control_pcb);
+        ctx->control_pcb = NULL;
+        return RTSP_ERR_NO_MEMORY;
+    }
+    
+    // 创建RTCP UDP连接
+    ctx->rtcp_pcb = udp_new();
+    if (!ctx->rtcp_pcb) {
+        LLOGE("RTCP UDP块创建失败");
+        tcp_abort(ctx->control_pcb);
+        udp_remove(ctx->rtp_pcb);
+        ctx->control_pcb = NULL;
+        ctx->rtp_pcb = NULL;
+        return RTSP_ERR_NO_MEMORY;
+    }
+    
+    rtsp_set_state(ctx, RTSP_STATE_CONNECTING, 0);
+    
+    // 发起TCP连接
+    ip_addr_t remote_ip;
+    if (ipaddr_aton(ctx->host, &remote_ip) == 0) {
+        // TODO: DNS解析
+        LLOGE("IP地址转换失败: %s", ctx->host);
+        tcp_abort(ctx->control_pcb);
+        udp_remove(ctx->rtp_pcb);
+        udp_remove(ctx->rtcp_pcb);
+        ctx->control_pcb = NULL;
+        ctx->rtp_pcb = NULL;
+        ctx->rtcp_pcb = NULL;
+        rtsp_set_state(ctx, RTSP_STATE_ERROR, RTSP_ERR_CONNECT_FAILED);
+        return RTSP_ERR_CONNECT_FAILED;
+    }
+    
+    // 保存远端IP地址
+    ip_addr_copy(ctx->remote_ip, remote_ip);
+    LLOGD("解析服务器地址: %s -> %s", ctx->host, ipaddr_ntoa(&ctx->remote_ip));
+    
+    tcp_arg(ctx->control_pcb, (void *)ctx);
+    tcp_connect(ctx->control_pcb, &remote_ip, ctx->port, rtsp_tcp_connect_callback);
+    
+    LLOGD("RTSP连接请求已发送: %s:%u", ctx->host, ctx->port);
+    return RTSP_OK;
+}
+
+/**
+ * 断开RTSP连接
+ */
+int rtsp_disconnect(rtsp_ctx_t *ctx) {
+    if (!ctx) {
+        return RTSP_ERR_INVALID_PARAM;
+    }
+    
+    rtsp_set_state(ctx, RTSP_STATE_DISCONNECTING, 0);
+    
+    // 关闭TCP连接
+    if (ctx->control_pcb) {
+        tcp_close(ctx->control_pcb);
+        ctx->control_pcb = NULL;
+    }
+    
+    // 关闭UDP连接
+    if (ctx->rtp_pcb) {
+        udp_remove(ctx->rtp_pcb);
+        ctx->rtp_pcb = NULL;
+    }
+    if (ctx->rtcp_pcb) {
+        udp_remove(ctx->rtcp_pcb);
+        ctx->rtcp_pcb = NULL;
+    }
+    
+    rtsp_clear_frame_queue(ctx);
+    rtsp_set_state(ctx, RTSP_STATE_IDLE, 0);
+    
+    LLOGD("RTSP连接已断开");
+    return RTSP_OK;
+}
+
+/**
+ * 详细解析SPS数据
+ */
+static void rtsp_parse_sps_detail(const uint8_t *sps_data, uint32_t sps_len) {
+    if (!sps_data || sps_len < 4) {
+        LLOGE("SPS数据无效");
+        return;
+    }
+    
+    LLOGD("========== SPS详细信息 ==========");
+    
+    // NALU头 (1字节)
+    uint8_t nalu_header = sps_data[0];
+    uint8_t forbidden_zero = (nalu_header >> 7) & 0x01;
+    uint8_t nal_ref_idc = (nalu_header >> 5) & 0x03;
+    uint8_t nal_unit_type = nalu_header & 0x1F;
+    
+    LLOGD("NALU Header: 0x%02X", nalu_header);
+    LLOGD("  forbidden_zero_bit: %u", forbidden_zero);
+    LLOGD("  nal_ref_idc: %u", nal_ref_idc);
+    LLOGD("  nal_unit_type: %u (SPS)", nal_unit_type);
+    
+    if (sps_len < 4) {
+        LLOGD("SPS数据太短,无法解析详细参数");
+        return;
+    }
+    
+    // Profile & Level (3字节)
+    uint8_t profile_idc = sps_data[1];
+    uint8_t constraint_flags = sps_data[2];
+    uint8_t level_idc = sps_data[3];
+    
+    LLOGD("Profile IDC: %u (0x%02X)", profile_idc, profile_idc);
+    const char *profile_name = "Unknown";
+    switch (profile_idc) {
+        case 66: profile_name = "Baseline"; break;
+        case 77: profile_name = "Main"; break;
+        case 88: profile_name = "Extended"; break;
+        case 100: profile_name = "High"; break;
+        case 110: profile_name = "High 10"; break;
+        case 122: profile_name = "High 4:2:2"; break;
+        case 244: profile_name = "High 4:4:4"; break;
+    }
+    LLOGD("  Profile: %s", profile_name);
+    
+    LLOGD("Constraint Flags: 0x%02X", constraint_flags);
+    LLOGD("  constraint_set0_flag: %u", (constraint_flags >> 7) & 0x01);
+    LLOGD("  constraint_set1_flag: %u", (constraint_flags >> 6) & 0x01);
+    LLOGD("  constraint_set2_flag: %u", (constraint_flags >> 5) & 0x01);
+    LLOGD("  constraint_set3_flag: %u", (constraint_flags >> 4) & 0x01);
+    LLOGD("  constraint_set4_flag: %u", (constraint_flags >> 3) & 0x01);
+    LLOGD("  constraint_set5_flag: %u", (constraint_flags >> 2) & 0x01);
+    
+    LLOGD("Level IDC: %u (Level %.1f)", level_idc, level_idc / 10.0);
+    
+    // 打印原始数据 (前16字节或全部)
+    uint32_t print_len = sps_len > 16 ? 16 : sps_len;
+    char hex_str[128];
+    int pos = 0;
+    for (uint32_t i = 0; i < print_len && pos < 120; i++) {
+        pos += snprintf(hex_str + pos, sizeof(hex_str) - pos, "%02X ", sps_data[i]);
+    }
+    if (sps_len > 16) {
+        snprintf(hex_str + pos, sizeof(hex_str) - pos, "...");
+    }
+    LLOGD("SPS Raw Data (%u bytes): %s", sps_len, hex_str);
+    LLOGD("==================================\n");
+}
+
+/**
+ * 详细解析PPS数据
+ */
+static void rtsp_parse_pps_detail(const uint8_t *pps_data, uint32_t pps_len) {
+    if (!pps_data || pps_len < 1) {
+        LLOGE("PPS数据无效");
+        return;
+    }
+    
+    LLOGD("========== PPS详细信息 ==========");
+    
+    // NALU头 (1字节)
+    uint8_t nalu_header = pps_data[0];
+    uint8_t forbidden_zero = (nalu_header >> 7) & 0x01;
+    uint8_t nal_ref_idc = (nalu_header >> 5) & 0x03;
+    uint8_t nal_unit_type = nalu_header & 0x1F;
+    
+    LLOGD("NALU Header: 0x%02X", nalu_header);
+    LLOGD("  forbidden_zero_bit: %u", forbidden_zero);
+    LLOGD("  nal_ref_idc: %u", nal_ref_idc);
+    LLOGD("  nal_unit_type: %u (PPS)", nal_unit_type);
+    
+    // 打印原始数据 (前16字节或全部)
+    uint32_t print_len = pps_len > 16 ? 16 : pps_len;
+    char hex_str[128];
+    int pos = 0;
+    for (uint32_t i = 0; i < print_len && pos < 120; i++) {
+        pos += snprintf(hex_str + pos, sizeof(hex_str) - pos, "%02X ", pps_data[i]);
+    }
+    if (pps_len > 16) {
+        snprintf(hex_str + pos, sizeof(hex_str) - pos, "...");
+    }
+    LLOGD("PPS Raw Data (%u bytes): %s", pps_len, hex_str);
+    LLOGD("==================================\n");
+}

+ 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;
 }
 

+ 7 - 0
luat/include/luat_libs.h

@@ -21,6 +21,8 @@ LUAMOD_API int luaopen_adc( lua_State *L );
 LUAMOD_API int luaopen_pwm( lua_State *L );
 /** uart库*/
 LUAMOD_API int luaopen_uart( lua_State *L );
+/** usb流库*/
+LUAMOD_API int luaopen_usb(lua_State *L);
 /** pm库*/
 LUAMOD_API int luaopen_pm( lua_State *L );
 /** fs库*/
@@ -231,4 +233,9 @@ 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);
+/** rtsp推流库*/
+LUAMOD_API int luaopen_rtsp(lua_State *L);
+
 #endif

+ 2 - 0
luat/include/luat_mem.h

@@ -22,5 +22,7 @@ void luat_meminfo_opt_sys(LUAT_HEAP_TYPE_E type,size_t* total, size_t* used, siz
 #define LUAT_MEM_REALLOC luat_heap_realloc
 #define LUAT_MEM_CALLOC luat_heap_calloc
 
+// 查询并输出内存使用情况到日志
+void luat_meminfo_query(LUAT_HEAP_TYPE_E type,size_t* total, size_t* used, size_t* max_used, int log_out);
 
 #endif

+ 2 - 0
luat/include/luat_uart.h

@@ -45,6 +45,8 @@
 #define LUAT_1_5_STOP_BITS                   0xf1   /**< 1.5 */
 
 #define LUAT_VUART_ID_0						0x20    
+#define LUAT_VUART_ID_1						0x21
+#define LUAT_VUART_ID_2						0x22
 
 #define LUAT_UART_RX_ERROR_DROP_DATA		(0xD6)
 #define LUAT_UART_DEBUG_ENABLE				(0x3E)

+ 43 - 0
luat/include/luat_usb.h

@@ -0,0 +1,43 @@
+#ifndef LUAT_USB_H
+#define LUAT_USB_H
+
+#include "luat_base.h"
+enum
+{
+	LUAT_USB_MODE_DEVICE,
+	LUAT_USB_MODE_HOST,
+	LUAT_USB_MODE_OTG,
+	LUAT_USB_CLASS_CDC_ACM = 0,
+	LUAT_USB_CLASS_AUDIO,
+	LUAT_USB_CLASS_CAMERA,
+	LUAT_USB_CLASS_HID,
+	LUAT_USB_CLASS_MSC,
+	LUAT_USB_CLASS_WINUSB,
+	LUAT_USB_EVENT_NEW_RX	= 0,
+	LUAT_USB_EVENT_TX_DONE,
+	LUAT_USB_EVENT_CONNECT,
+	LUAT_USB_EVENT_DISCONNECT,
+	LUAT_USB_EVENT_SUSPEND,
+	LUAT_USB_EVENT_RESUME,
+};
+
+typedef void (*usb_callback_t)(int id, int event, uint8_t *data, uint32_t len);
+
+int luat_usb_set_vid(int id, uint16_t vid);
+int luat_usb_get_vid(int id, uint16_t *vid);
+
+int luat_usb_set_pid(int id, uint16_t pid);
+int luat_usb_get_pid(int id, uint16_t *pid);
+
+int luat_usb_set_mode(int id, uint8_t mode);
+
+int luat_usb_add_class(int id, uint8_t class, uint8_t num);
+int luat_usb_get_free_ep_num(int id);
+int luat_usb_clear_class(int id);
+
+int luat_usb_set_callback(int id, usb_callback_t callback);
+
+int luat_usb_tx(int id, uint8_t class, const void *data, uint32_t len);
+int luat_usb_hid_tx(int id, const char *string, uint32_t len);
+int luat_usb_rx(int id, uint8_t class, void *data, uint32_t len);
+#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},
 };
 

+ 12 - 0
luat/modules/luat_lib_fota.c

@@ -221,6 +221,18 @@ static int l_fota_file(lua_State* L)
     luat_heap_free(buff);
     luat_fs_fclose(fd);
 
+    if (result == 0) {
+        LLOGI("fota file write done, call fota.done()");
+        result = luat_fota_done();
+        if (result == 0) {
+            LLOGI("fota done success, call fota.end()");
+            result = luat_fota_end(1);
+        }
+        else {
+            LLOGE("fota done fail %d", result);
+        }
+    }
+
     if (result > 0)
     {
     	lua_pushboolean(L, 1);

+ 12 - 0
luat/modules/luat_lib_rtos.c

@@ -299,13 +299,25 @@ static int l_rtos_meminfo(lua_State *L) {
     size_t max_used = 0;
     const char * str = luaL_optlstring(L, 1, "lua", &len);
     if (strcmp("sys", str) == 0) {
+        #ifdef LUAT_USE_MEM_LOGOUT
+        luat_meminfo_query(LUAT_HEAP_SRAM, &total, &used, &max_used, 1);
+        #else
         luat_meminfo_opt_sys(LUAT_HEAP_SRAM, &total, &used, &max_used);
+        #endif
     }
     else if(strcmp("psram", str) == 0){
+        #ifdef LUAT_USE_MEM_LOGOUT
+        luat_meminfo_query(LUAT_HEAP_PSRAM, &total, &used, &max_used, 1);
+        #else
         luat_meminfo_opt_sys(LUAT_HEAP_PSRAM, &total, &used, &max_used);
+        #endif
     }
     else {
+        #ifdef LUAT_USE_MEM_LOGOUT
+        luat_meminfo_query(0, &total, &used, &max_used, 1);
+        #else
         luat_meminfo_luavm(&total, &used, &max_used);
+        #endif
     }
     
     lua_pushinteger(L, total);

+ 5 - 1
luat/modules/luat_lib_uart.c

@@ -37,7 +37,7 @@
 #define MAX_DEVICE_COUNT 9
 #endif
 
-#define MAX_USB_DEVICE_COUNT 1
+#define MAX_USB_DEVICE_COUNT 3
 
 typedef struct luat_uart_cb {
     int received;//回调函数
@@ -1425,6 +1425,10 @@ static const rotable_Reg_t reg_uart[] =
 
     //@const VUART_0 number 虚拟串口0
 	{ "VUART_0",       ROREG_INT(LUAT_VUART_ID_0)},
+    //@const VUART_1 number 虚拟串口1
+	{ "VUART_1",       ROREG_INT(LUAT_VUART_ID_1)},
+    //@const VUART_2 number 虚拟串口2
+	{ "VUART_2",       ROREG_INT(LUAT_VUART_ID_2)},
     //@const ERROR_DROP number 遇到错误时抛弃缓存的数据
 	{ "ERROR_DROP",       ROREG_INT(LUAT_UART_RX_ERROR_DROP_DATA)},
     //@const DEBUG number 开启调试功能

+ 346 - 0
luat/modules/luat_lib_usb.c

@@ -0,0 +1,346 @@
+/*
+@module  usb
+@summary usb操作库
+@version 1.0
+@date    2025.12.15
+@demo usb
+@tag LUAT_USE_USB
+@usage
+--[[
+--简单举例
+pm.power(pm.USB, false)		--确保USB外设是掉电状态
+--usb.vid(0, 0x1234)		--配置VID,不是必须的
+--usb.pvid(0, 0x5678)		--配置PID,不是必须的
+usb.clear_all_class(0)				--清除掉之前配置的设备类
+usb.mode(0, usb.DEVICE)		--usb设置成从机模式
+usb.add_class(0, usb.CDC_ACM, 1)	--使用1个CDC-ACM虚拟串口功能
+usb.add_class(0, usb.WINUSB, 1)		--使用1个WINUSB功能
+pm.power(pm.USB, true)		--USB上电初始化开始工作
+--说明
+目前设备类只有usb.HID和usb.WINUSB可以通过usb操作库api和对端通讯,usb.CDC-ACM虚拟串口直接使用uart api
+]]
+*/
+#include "luat_base.h"
+#include "luat_sys.h"
+#include "luat_msgbus.h"
+#include "luat_mem.h"
+#include "luat_usb.h"
+#include "luat_zbuff.h"
+#define LUAT_LOG_TAG "usb"
+#include "luat_log.h"
+#include "rotable2.h"
+
+#define MAX_USB_DEVICE_COUNT 2
+static int l_usb_cb[MAX_USB_DEVICE_COUNT];
+
+int l_usb_handler(lua_State *L, void* ptr) {
+    (void)ptr;
+    rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
+    lua_pop(L, 1);
+    int usb_id = msg->arg1 & 0x000000ff;
+    int class_id = (msg->arg1 & 0x0000ff00) >> 8;
+    if (l_usb_cb[usb_id] && usb_id < MAX_USB_DEVICE_COUNT)
+    {
+        lua_geti(L, LUA_REGISTRYINDEX, l_usb_cb[usb_id]);
+        lua_pushinteger(L, usb_id);
+        lua_pushinteger(L, msg->arg2);
+        if (class_id != 0x000000ff)
+        {
+        	lua_pushinteger(L, class_id);
+        }
+        else
+        {
+        	lua_pushnil(L);
+        }
+        lua_call(L, 3, 0);
+    }
+    // 给rtos.recv方法返回个空数据
+    lua_pushinteger(L, 0);
+    return 1;
+}
+
+
+/*
+USB发送数据,目前仅限于HID和WINUSB设备,CDC-ACM虚拟串口直接使用串口API操作
+@api usb.tx(id, data, class)
+@int 设备id,默认为0
+@zbuff or string 需要发送的数据
+@int 设备类
+@return bool 成功返回true,否则返回false,总线id填错,所填设备类不支持直接发送数据等情况下返回错误
+@usage
+-- HID上传数据
+usb.tx(0, "1234", usb.HID) -- usb hid上传0x31 0x32 0x33 0x34  + N个0
+*/
+static int l_usb_tx(lua_State* L) {
+	int result;
+    uint8_t class = luaL_optinteger(L, 3, LUAT_USB_CLASS_WINUSB);
+    int usb_id = luaL_optinteger(L, 1, 0);
+    const char *buf;
+    luat_zbuff_t *buff = NULL;
+    if(lua_isuserdata(L, 2)) {
+        buff = ((luat_zbuff_t *)luaL_checkudata(L, 2, LUAT_ZBUFF_TYPE));
+        result = luat_usb_tx(usb_id, class, buff->addr, buff->used);
+    } else {
+    	size_t len;
+    	buf = luaL_checklstring(L, 2, &len);
+    	if (LUAT_USB_CLASS_HID == class)
+    	{
+    		result = luat_usb_hid_tx(usb_id, buf, len);
+    	}
+    	else
+    	{
+    		result = luat_usb_tx(usb_id, class, buf, len);
+    	}
+        lua_pushboolean(L, !result);
+    }
+    return 1;
+}
+
+/*
+buff形式读接收到的数据,一次读出全部数据存入buff中,如果buff空间不够会自动扩展
+@api usb.rx(id, buff, class)
+@int 设备id,默认为0
+@zbuff zbuff对象
+@int 设备类
+@return int 返回读到的长度,并把zbuff指针后移
+@usage
+usb.rx(0, buff, usb.HID)
+*/
+static int l_usb_rx(lua_State *L)
+{
+    uint8_t class = luaL_optinteger(L, 3, LUAT_USB_CLASS_WINUSB);
+    int usb_id = luaL_optinteger(L, 1, 0);
+
+    if(lua_isuserdata(L, 2)){//zbuff对象特殊处理
+    	luat_zbuff_t *buff = ((luat_zbuff_t *)luaL_checkudata(L, 2, LUAT_ZBUFF_TYPE));
+        int result = luat_usb_rx(usb_id, class, NULL, 0);	//读出当前缓存的长度
+        if (result > (buff->len - buff->used))
+        {
+        	__zbuff_resize(buff, buff->len + result);
+        }
+        result = luat_usb_rx(usb_id, class, buff->addr + buff->used, result);
+        lua_pushinteger(L, result);
+        buff->used += result;
+        return 1;
+    }
+    else
+    {
+        lua_pushinteger(L, 0);
+        return 1;
+    }
+    return 1;
+}
+
+
+/*
+设置USB工作模式,必须在USB外设掉电不工作时进行设置
+@api usb.mode(id, mode)
+@int usb总线id,默认0,如果芯片只有1条USB线,填0
+@int 工作模式,只有3种,usb.HOST主机模式,usb.DEVICE从机模式,usb.OTG协商模式,默认是从机模式
+@return bool 成功返回true,否则返回false,总线id填错,所选模式不支持时,USB外设正在工作等情况下返回失败
+@usage
+pm.power(pm.USB, false)
+usb.mode(0, usb.DEVICE)
+pm.power(pm.USB, true)
+*/
+static int l_usb_mode(lua_State* L) {
+	int result = luat_usb_set_mode(luaL_optinteger(L, 1, 0), luaL_optinteger(L, 2, LUAT_USB_MODE_DEVICE));
+	lua_pushboolean(L, !result);
+    return 1;
+}
+
+/*
+注册USB事件回调
+@api usb.on(id, func)
+@int usb总线id,默认0,如果芯片只有1条USB线,填0
+@function 回调方法
+@return nil 无返回值
+@usage
+usb.on(0, function(id, class, event)
+    log.info("usb", id, class, event)
+end)
+--回调参数有3个
+1、usb总线id
+2、event,见usb.EV_XXX
+3、如果event是usb.EV_RX或usb.EV_TX,则第三个参数表示哪个设备类,目前只有usb.HID和usb.WINUSB
+*/
+static int l_usb_on(lua_State *L) {
+    int usb_id = luaL_optinteger(L, 1, 0);
+    if (usb_id >= MAX_USB_DEVICE_COUNT) return 0;
+    if (l_usb_cb[usb_id])
+    {
+    	luaL_unref(L, LUA_REGISTRYINDEX, l_usb_cb[usb_id]);
+    }
+	if (lua_isfunction(L, 2)) {
+		lua_pushvalue(L, 2);
+		l_usb_cb[usb_id] = luaL_ref(L, LUA_REGISTRYINDEX);
+	}
+    return 0;
+}
+
+
+
+/*
+设置/获取USB的VID,必须在USB外设掉电不工作时进行设置,获取没有限制
+@api usb.vid(id, vid)
+@int usb总线id,默认0,如果芯片只有1条USB线,填0
+@int 想要设置的VID值,留空则不做设置
+@return bool 成功或者获取VID时返回true,否则返回false,总线id填错,芯片不支持设置,USB外设正在工作等情况下返回失败
+@return int 当前VID值
+@usage
+pm.power(pm.USB, false)
+usb.vid(0, 0x1234)
+pm.power(pm.USB, true)
+*/
+static int l_usb_vid(lua_State* L) {
+	int result1 = 0;
+	int result2 = -1;
+	int bus_id = luaL_optinteger(L, 1, 0);
+	uint16_t vid;
+	if (lua_isinteger(L, 2))
+	{
+		result1 = luat_usb_set_vid(bus_id, lua_tointeger(L, 2));
+	}
+	result2 = luat_usb_get_vid(bus_id, &vid);
+
+	lua_pushboolean(L, !(result1 || result2));
+	lua_pushinteger(L, vid);
+    return 2;
+}
+
+/*
+设置/获取USB的PID,必须在USB外设掉电不工作时进行设置,获取没有限制
+@api usb.pid(id, pid)
+@int usb总线id,默认0,如果芯片只有1条USB线,填0
+@int 想要设置的PID值,留空则不做设置
+@return bool 成功或者获取VID时返回true,否则返回false,总线id填错,芯片不支持设置,USB外设正在工作等情况下返回失败
+@return int 当前PID值
+@usage
+pm.power(pm.USB, false)
+usb.pid(0, 0x1234)
+pm.power(pm.USB, true)
+*/
+static int l_usb_pid(lua_State* L) {
+	int result1 = 0;
+	int result2 = -1;
+	int bus_id = luaL_optinteger(L, 1, 0);
+	uint16_t pid;
+	if (lua_isinteger(L, 2))
+	{
+		result1 = luat_usb_set_pid(bus_id, lua_tointeger(L, 2));
+	}
+	result2 = luat_usb_get_pid(bus_id, &pid);
+
+	lua_pushboolean(L, !(result1 || result2));
+	lua_pushinteger(L, pid);
+    return 2;
+}
+
+/*
+设置USB支持的设备类和数量,必须在USB外设掉电不工作时进行设置
+@api usb.add_class(id, class, num)
+@int usb总线id,默认0,如果芯片只有1条USB线,填0
+@int 设备类,从机模式支持usb.CDC_ACM,usb.HID,usb.MSC,usb.WINUSB,主机模式支持usb.CAMERA
+@int 数量,目前只有从机的usb.CDC_ACM允许至多3个,其他只允许1个,超过时会强制改成所允许的最大值
+@return bool 成功返回true,否则返回false,总线id填错,所选设备类不支持时,端点数量超过芯片允许的最大值,USB外设正在工作等情况下返回失败
+@usage
+pm.power(pm.USB, false)
+usb.add_class(0, usb.CDC_ACM, 3)	--使用3个CDC-ACM虚拟串口功能
+usb.add_class(0, usb.WINUSB, 1)		--使用1个WINUSB功能
+pm.power(pm.USB, true)
+*/
+static int l_usb_add_class(lua_State* L) {
+	int result = luat_usb_add_class(luaL_optinteger(L, 1, 0), luaL_optinteger(L, 2, LUAT_USB_CLASS_CDC_ACM), luaL_optinteger(L, 3, 1));
+	lua_pushboolean(L, !result);
+    return 1;
+}
+
+/*
+清除掉当前配置的设备类,必须在USB外设掉电不工作时进行设置
+@api usb.clear_all_class(id)
+@int usb总线id,默认0,如果芯片只有1条USB线,填0
+@return bool 成功返回true,否则返回false,总线id填错,USB外设正在工作等情况下返回失败
+@usage
+pm.power(pm.USB, false)
+usb.clear_all_class(0)				--清除掉之前配置的设备类
+usb.add_class(0, usb.CDC_ACM, 3)	--使用3个CDC-ACM虚拟串口功能
+usb.add_class(0, usb.WINUSB, 1)		--使用1个WINUSB功能
+pm.power(pm.USB, true)
+*/
+static int l_usb_clear_all_class(lua_State* L) {
+	int result = luat_usb_clear_class(luaL_optinteger(L, 1, 0));
+	lua_pushboolean(L, !result);
+    return 1;
+}
+
+/*
+返回当前剩余的端点数
+@api usb.get_free_ep_num(id)
+@int usb总线id,默认0,如果芯片只有1条USB线,填0
+@return int 剩余的端点数,总线id填错时直接返回0
+@usage
+log.info(usb.get_free_ep_num(0))
+*/
+static int l_usb_get_free_ep_num(lua_State* L) {
+	int result = luat_usb_get_free_ep_num(luaL_optinteger(L, 1, 0));
+	if (result < 0)
+	{
+		lua_pushinteger(L, 0);
+	}
+	else
+	{
+		lua_pushinteger(L, result);
+	}
+    return 1;
+}
+
+static const rotable_Reg_t reg_usb[] =
+{
+	{ "tx",					ROREG_FUNC(l_usb_tx)},
+	{ "rx",					ROREG_FUNC(l_usb_rx)},
+	{ "mode",				ROREG_FUNC(l_usb_mode)},
+	{ "add_class",      	ROREG_FUNC(l_usb_add_class)},
+	{ "on",         		ROREG_FUNC(l_usb_on)},
+	{ "vid",         		ROREG_FUNC(l_usb_vid)},
+	{ "pid",         		ROREG_FUNC(l_usb_pid)},
+	{ "clear_all_class" ,   ROREG_FUNC(l_usb_clear_all_class)},
+	{ "get_free_ep_num" ,   ROREG_FUNC(l_usb_get_free_ep_num)},
+	//@const HOST number USB主机模式
+    { "HOST",        		ROREG_INT(LUAT_USB_MODE_HOST)},
+	//@const DEVICE number USB从机模式
+    { "DEVICE",       		ROREG_INT(LUAT_USB_MODE_DEVICE)},
+	//@const OTG number USB otg模式
+    { "OTG",       			ROREG_INT(LUAT_USB_MODE_OTG)},
+	//@const CDC_ACM number cdc_acm 虚拟串口类
+    { "CDC_ACM",        	ROREG_INT(LUAT_USB_CLASS_CDC_ACM)},
+	//@const AUDIO number audio音频类
+    { "AUDIO",       		ROREG_INT(LUAT_USB_CLASS_AUDIO)},
+	//@const CAMERA number 摄像头类
+    { "CAMERA",        		ROREG_INT(LUAT_USB_CLASS_CAMERA)},
+	//@const HID number HID设备类,只支持键盘和自定义
+    { "HID",       			ROREG_INT(LUAT_USB_CLASS_HID)},
+	//@const MSC number 大容量存储类,也就是U盘,TF卡
+    { "MSC",       			ROREG_INT(LUAT_USB_CLASS_MSC)},
+	//@const WINUSB number WINUSB类,透传数据
+    { "WINUSB",       		ROREG_INT(LUAT_USB_CLASS_WINUSB)},
+
+	//@const EV_RX number  有新的数据到来
+    { "EV_RX",       		ROREG_INT(LUAT_USB_EVENT_NEW_RX)},
+	//@const EV_TX number 所有数据都已发送
+    { "EV_TX",        		ROREG_INT(LUAT_USB_EVENT_TX_DONE)},
+	//@const EV_CONNECT number usb从机已经连接上并且枚举成功
+    { "EV_CONNECT",       	ROREG_INT(LUAT_USB_EVENT_CONNECT)},
+	//@const EV_DISCONNECT number usb从机断开
+    { "EV_DISCONNECT",      ROREG_INT(LUAT_USB_EVENT_DISCONNECT)},
+	//@const EV_SUSPEND number usb从机挂起
+    { "EV_SUSPEND",       	ROREG_INT(LUAT_USB_EVENT_SUSPEND)},
+	//@const EV_RESUME number usb从机恢复
+    { "EV_RESUME",       	ROREG_INT(LUAT_USB_EVENT_RESUME)},
+    { NULL,         ROREG_INT(0) }
+};
+
+LUAMOD_API int luaopen_usb( lua_State *L )
+{
+    luat_newlib2(L, reg_usb);
+    return 1;
+}

+ 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°,屏幕方向和分辨率保存一致

+ 65 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/main.lua

@@ -0,0 +1,65 @@
+--[[
+@module  main
+@summary OneWire综合演示项目主文件(单传感器 + 多传感器)
+@version 001.000.000
+@date    2025.11.25
+@author  王棚嶙
+@usage
+本演示项目整合单DS18B20和多DS18B20传感器功能:
+1. 单传感器模式:GPIO2默认OneWire功能、硬件通道0模式、CRC校验、3秒间隔连续监测
+2. 多传感器模式:引脚54/23切换、PWR_KEY按键控制、电源管理、2秒间隔双路监测
+3. 完整的OneWire API接口演示、错误处理、设备检测、温度报警
+]]
+
+-- 项目信息
+PROJECT = "onewire_demo"
+VERSION = "001.000.000"
+
+
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+--添加硬狗防止程序卡死
+if wdt then
+    wdt.init(9000)--初始化watchdog设置为9s
+    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+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)
+
+-- 在加载以下两个功能的时候建议分别打开,避免同时初始化OneWire总线,导致资源冲突
+-- 单设备模式:使用GPIO2默认OneWire功能
+-- 双设备模式:GPIO2默认 + 引脚54复用
+
+-- 加载单传感器应用模块
+-- require("onewire_single_app")
+
+-- 加载多传感器应用模块
+require("onewire_multi_app")
+
+
+-- 启动系统主循环
+sys.run()

+ 0 - 9
module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/onewire_multi_18b20_swich_read/main.lua

@@ -1,9 +0,0 @@
-PROJECT = "DS18B20_GPIO_SWITCH_READ_temperature"
-VERSION = "1.0.0"
-sys = require("sys")
-log.style(1)
-
-
-require"switch_read"
-
-sys.run()

+ 0 - 125
module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/onewire_multi_18b20_swich_read/switch_read.lua

@@ -1,125 +0,0 @@
-
-pm.ioVol(pm.IOVOL_ALL_GPIO, 3300) -- 所有GPIO高电平输出3.3V(方便使用VDD_EXT给18B20供电)
-gpio.setup(2, 1) --GPIO2控制780EPM开发板V1.3版本camera电源打开和关闭
-gpio.setup(31, 1) -- GPIO31控制780EPM开发板V1.4版本camera电源打开和关闭
-
-local onewire_pin = 54 --18B20接的pin 54脚
-
-
-pins.setup(onewire_pin, "ONEWIRE") -- PAD54脚既是GPIO3也是cam_mclk 
-
---读取当前pin脚上的18B20温度
-local function read_ds18b20(id)
-log.info("读取温度",id)
-    local tbuff = zbuff.create(10)
-    local succ, crc8c, range, t
-    local rbuff = zbuff.create(9)
-    tbuff:write(0x55)
-    tbuff:copy(nil, id)
-    tbuff:write(0xb8)
-  
-    tbuff[tbuff:used() - 1] = 0x44
-    succ = onewire.tx(0, tbuff, false, true, true)
-    if not succ then
-        return
-    end
-
-    succ = onewire.reset(0, true)
-    if not succ then
-        return
-    end
-    if onewire.bit(0) > 0 then
-        log.info("温度转换完成")
-    end
-    tbuff[tbuff:used() - 1] = 0xbe
-    succ = onewire.tx(0, tbuff, false, true, true)
-    if not succ then
-        return
-    end
-    succ, rx_data = onewire.rx(0, 9, nil, rbuff, false, false, false)
-    crc8c = crypto.crc8(rbuff:toStr(0, 8), 0x31, 0, true)
-    if crc8c == rbuff[8] then
-        range = (rbuff[4] >> 5) & 0x03
-        -- rbuff[0] = 0xF8
-        -- rbuff[1] = 0xFF
-        t = rbuff:query(0, 2, false, true)
-        t = t * (5000 >> range)
-        t = t / 10000
-        log.info("当前温度", t,"原始值为",mcu.x32(rbuff[8]))
-    else
-        log.info("RAM DATA CRC校验不对", mcu.x32(crc8c), mcu.x32(rbuff[8]))
-        return
-    end
-
-end
-
---初始化当前pin脚上的18B20,并读取18B20的唯一识别ID
-local function test_ds18b20()
-    local succ, rx_data
-    local id = zbuff.create(8)
-    local crc8c
-    onewire.init(0) -- 初始化单总线
-    onewire.timing(0, false, 0, 500, 500, 15, 240, 70, 1, 15, 10, 2)
-    id:set() -- 清空id
-    succ, rx_data = onewire.rx(0, 8, 0x33, id, false, true, true)
-    if succ then
-        if id[0] == 0x28 then
-            crc8c = crypto.crc8(id:query(0, 7), 0x31, 0, true)
-            if crc8c == id[7] then
-                log.info("探测到DS18B20", "18B20对应唯一ID为", id:query(0, 7):toHex())
-                read_ds18b20(id)
-                -- log.info("DS18B20离线,重新探测")
-            else
-                log.info("ROM ID CRC校验不对", mcu.x32(crc8c), mcu.x32(id[7]))
-            end
-        else
-            log.info("ROM ID不正确", mcu.x32(id[0]))
-        end
-    else
-        log.info("没有检测到DS18B20")
-    end
-end
-
-local switchover_pin = gpio.PWR_KEY --选择powerkey作为切换18B20读数的信号脚
-
-gpio.debounce(switchover_pin, 100) --设置防抖
-
-
---设置powerkey按下和抬起的功能(18B20温度的取值再54pin和56pin之间切换)
---如果客户需要其他pin(22/54/56/78)则改动switchover_pin为用户需要的即可
---注意,由于powerkey不能做单边沿触发,所以一次按下和抬起的动作,会触发两次切换pin
---看效果的话,可以先一直按住powerkey几秒,再松开
-gpio.setup(switchover_pin, function()
-    log.info("Touch_pin", switchover_pin, "被触发")
-    log.info("当前单总线pad", onewire_pin)
-    if onewire_pin == 54 then
-        log.info("给PAD" .. onewire_pin .. "配置到GPIO3上去", pins.setup(onewire_pin, "GPIO3"))
-        log.info("给GPIO3设置为高电平输出模式",gpio.setup(3,1))--一定要执行gpio.setup,至于是哪种模式,用户根据自身需求
-        onewire_pin = 56
-    else
-        log.info("给PAD" .. onewire_pin .. "配置到GPIO7上去", pins.setup(onewire_pin, "GPIO7"))
-        log.info("给GPIO7设置为高电平输出模式",gpio.setup(7,1))
-
-        onewire_pin = 54
-    end
-    log.info("设置后单总线pad", onewire_pin)
-
-    onewire.deinit(0) -- 切换的时候一定要先关闭单总线,在读取的时候重新初始化
-
-    log.info("给" .. onewire_pin .. "配置到onewire上去", pins.setup(onewire_pin, "ONEWIRE"))
-    sys.publish("powerkey被按下")
-end, gpio.PULLUP, gpio.RISING)
-
-sys.taskInit(function()
-    while 1 do
-        -- sys.waitUntil("powerkey被按下")
-        log.info("1S后读取"..onewire_pin.."脚上的18B20")
-        sys.wait(1000)
-        log.info("开始读取18B20")
-        test_ds18b20()
-    end
-end)
-
-
-
-

+ 367 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/onewire_multi_app.lua

@@ -0,0 +1,367 @@
+--[[
+@module  onewire_multi_app
+@summary OneWire多DS18B20温度传感器应用演示模块(54和23切换版本)
+@version 1.0.0
+@date    2025.11.25
+@author  王棚嶙
+@usage
+本模块演示多DS18B20温度传感器的完整功能:
+1. 双传感器切换控制(引脚54和23)
+2. 电源管理(GPIO控制)
+3. 按键切换传感器
+4. 双路温度同时监测
+5. 使用引脚复用功能(pins.setup)
+]]
+
+log.info("onewire_multi_app", "多传感器模块版本: 1.0.0")
+
+-- 设置所有GPIO引脚电压为3.3V,确保DS18B20传感器正常供电
+pm.ioVol(pm.IOVOL_ALL_GPIO, 3300)
+
+-- 和GPIO31控制传感器电源使能,确保DS18B20供电正常
+gpio.setup(31, 1)
+
+-- 硬件配置(双设备模式:支持引脚54和23切换)
+local onewire_pin = 54
+local switchover_pin = gpio.PWR_KEY
+
+-- DS18B20命令定义
+local CMD_CONVERT_T = 0x44
+local CMD_READ_SCRATCHPAD = 0xBE
+local CMD_READ_ROM = 0x33
+
+-- 全局状态变量
+local pwr_key_pressed = false
+
+-- PWR_KEY按键中断处理函数
+-- 功能:处理引脚切换按键事件,设置标志位供主循环查询
+local function handle_pwr_key_interrupt()
+    pwr_key_pressed = true
+    log.info("onewire_multi_app", "切换按键被按下")
+end
+
+-- 初始化硬件配置
+local function init_hardware()
+    log.info("onewire_multi_app", "初始化硬件配置...")
+    
+    -- 配置PWR_KEY按键,使用上升沿触发并添加防抖
+    gpio.debounce(switchover_pin, 100)
+    gpio.setup(switchover_pin, handle_pwr_key_interrupt, gpio.PULLUP, gpio.RISING)
+    
+    -- 初始配置当前引脚为ONEWIRE功能
+    pins.setup(onewire_pin, "ONEWIRE")
+    
+    log.info("onewire_multi_app", "硬件初始化完成")
+    log.info("onewire_multi_app", "初始引脚: 引脚" .. onewire_pin .. " (ONEWIRE功能)")
+    log.info("onewire_multi_app", "切换按键: PWR_KEY")
+    log.info("onewire_multi_app", "支持引脚: 54 和 23 循环切换")
+    log.info("onewire_multi_app", "电源控制: GPIO31/GPIO2 (已设置为高电平)")
+    
+    return true
+end
+
+
+-- 时序要求:DS18B20上电后需要稳定时间,100ms延时确保电源稳定
+-- 技术背景:DS18B20在电源切换后需要tREC(恢复时间)完成内部初始化
+-- 实际测试:无延时可能导致设备检测失败或温度读取异常
+-- 建议值:最小50ms,推荐100ms以确保可靠性
+local function power_stabilization_delay()
+    log.info("onewire_multi_app", "电源稳定延时(确保DS18B20内部电路就绪)")
+    sys.wait(100)  -- DS18B20 tREC恢复时间,最小50ms,推荐100ms
+end
+
+-- 单总线分时使用引脚切换(同一条总线,分时复用)
+-- 核心逻辑:使用GPIO54和GPIO23两个引脚连接同一条OneWire总线,实现分时复用
+-- 应用场景:当需要在同一总线上分时访问不同设备时使用
+-- 技术原理:通过切换总线连接引脚,实现同一条物理总线的分时使用
+-- 切换效果:
+-- - GPIO54:当前时间段连接设备A(ROM ID: 28-9F-C4-93-00-00-00-14)
+-- - GPIO23:切换到时间段连接设备B(ROM ID: 28-59-F2-53-00-00-00-14)
+-- 注意:这不是多总线并行,而是单总线的分时复用策略
+local function switch_onewire_pin()
+    log.info("onewire_multi_app", "切换OneWire引脚...")
+    
+    -- 关闭当前OneWire总线
+    onewire.deinit(0)
+    
+    
+    -- 分时复用切换逻辑
+    -- 技术原理:将当前不使用的引脚配置为GPIO功能并输出高电平
+    -- 目的:确保非活动设备处于高阻态,避免干扰当前连接的设备
+    -- 电气特性:GPIO设置为开漏输出模式,高电平由上拉电阻提供
+    if onewire_pin == 54 then
+        -- 从54切换到23
+        -- 将PIN54配置为GPIO3功能,不再作为OneWire使用
+        log.info("onewire_multi_app", "将PIN54配置为GPIO3", pins.setup(54, "GPIO3"))
+        -- 设置GPIO3为高电平输出(开漏模式,高电平由上拉电阻提供)
+        log.info("onewire_multi_app", "将GPIO3设置为高电平输出(OneWire总线空闲状态)", gpio.setup(3, 1))
+        onewire_pin = 23
+        log.info("onewire_multi_app", "切换到引脚23")
+    else
+        -- 从23切换到54
+        -- 将PIN23配置为GPIO2功能,不再作为OneWire使用
+        log.info("onewire_multi_app", "将PIN23配置为GPIO2", pins.setup(23, "GPIO2"))
+        -- 设置GPIO2为高电平输出(开漏模式,高电平由上拉电阻提供)
+        log.info("onewire_multi_app", "将GPIO2设置为高电平输出(OneWire总线空闲状态)", gpio.setup(2, 1))
+        onewire_pin = 54
+        log.info("onewire_multi_app", "切换到引脚54")
+    end
+    
+    log.info("onewire_multi_app", "当前使用引脚:", onewire_pin)
+    
+    -- 配置新引脚为ONEWIRE功能
+    -- 分时复用原理:将选中的引脚配置为OneWire功能,连接到对应设备
+    -- 连接过程:先断开之前的设备连接,再连接新的设备
+    -- 电气特性:确保当前连接的设备具有完整的OneWire通信能力
+    log.info("onewire_multi_app", "将引脚" .. onewire_pin .. "配置为ONEWIRE功能", pins.setup(onewire_pin, "ONEWIRE"))
+    
+    log.info("onewire_multi_app", "引脚切换完成,当前使用: 引脚" .. onewire_pin)
+end
+
+-- 初始化OneWire总线
+local function init_onewire_bus()
+    log.info("onewire_multi_app", "初始化OneWire总线,通道: 0")
+    
+    -- 配置当前引脚
+    pins.setup(onewire_pin, "ONEWIRE")
+    
+    -- 初始化OneWire总线
+    onewire.init(0)
+    
+    -- 配置DS18B20标准时序参数
+    onewire.timing(0, false, 0, 500, 500, 15, 240, 70, 1, 15, 10, 2)
+    
+    log.info("onewire_multi_app", "OneWire总线初始化完成,通道: 0,引脚:" .. onewire_pin)
+    
+    return true
+end
+
+-- 检测DS18B20设备是否存在(分时复用场景)
+-- 分时逻辑:在当前连接的引脚上发送复位脉冲,检测该设备响应
+-- 单总线场景:只有当前连接的引脚上的设备会响应复位脉冲
+-- 返回值:true表示当前引脚连接的设备响应,false表示无设备响应
+local function detect_ds18b20_device()
+    log.info("onewire_multi_app", "检测DS18B20设备,引脚: " .. onewire_pin)
+    
+    -- 发送复位脉冲并检测设备
+    local present = onewire.reset(0, true)
+    
+    if present then
+        log.info("onewire_multi_app", "检测到DS18B20设备响应")
+        return true
+    else
+        log.warn("onewire_multi_app", "未检测到DS18B20设备响应")
+        return false
+    end
+end
+
+-- 读取DS18B20温度(单总线分时复用)
+-- 核心流程:读ROM ID → 选设备 → 温度转换 → 读数据 → CRC校验
+local function read_ds18b20_temperature()
+    log.info("onewire_multi_app", "开始读取DS18B20温度,引脚: " .. onewire_pin)
+    
+    local tbuff = zbuff.create(10)
+    local succ, crc8c, range, t
+    local rbuff = zbuff.create(9)
+    
+    -- 读取设备ROM ID(每个设备唯一)
+    log.info("onewire_multi_app", "读取设备ROM ID(64位唯一标识)")
+    
+    local id = zbuff.create(8)
+    id:set()
+    
+    succ, rx_data = onewire.rx(0, 8, 0x33, id, false, true, true)
+    if not succ then
+        log.warn("onewire_multi_app", "读取ROM ID失败")
+        return nil
+    end
+    
+    -- 检查设备类型码(DS18B20应为0x28)
+    if id[0] ~= 0x28 then
+        log.warn("onewire_multi_app", "非DS18B20设备,类型码:", mcu.x32(id[0]))
+        return nil
+    end
+    
+    -- CRC校验设备ID
+    crc8c = crypto.crc8(id:query(0, 7), 0x31, 0, true)
+    if crc8c ~= id[7] then
+        log.warn("onewire_multi_app", "ROM ID CRC校验不对", 
+                "计算值:", mcu.x32(crc8c), "期望值:", mcu.x32(id[7]))
+        log.info("onewire_multi_app", "完整ROM ID:", id:query(0, 7):toHex())
+        return nil
+    end
+    
+    log.info("onewire_multi_app", "ROM ID校验成功:", id:query(0, 7):toHex())
+    
+    -- 通过MATCH ROM选择设备(确保只选中目标设备)
+    log.info("onewire_multi_app", "开始温度转换(通过ROM匹配选择设备)")
+    
+    -- 构建命令缓冲区:MATCH ROM(0x55) + 目标设备ROM ID + 温度转换命令(0x44)
+    -- 0x55是MATCH ROM命令,后面必须跟64位目标设备的ROM ID
+    tbuff:write(0x55)     -- MATCH ROM命令
+    tbuff:copy(nil, id)  -- 复制64位ROM ID(确保选择正确的设备)
+    tbuff:write(0xb8)
+    tbuff[tbuff:used() - 1] = 0x44  -- CONVERT T温度转换命令
+    
+    succ = onewire.tx(0, tbuff, false, true, true)
+    if not succ then
+        log.warn("onewire_multi_app", "发送温度转换命令失败")
+        return nil
+    end
+
+    -- 第三步:等待转换完成
+    log.info("onewire_multi_app", "等待温度转换完成")
+    
+    -- 等待一段时间让转换完成
+    sys.wait(750)
+    
+    -- 发送复位脉冲检查设备
+    succ = onewire.reset(0, true)
+    if not succ then
+        log.warn("onewire_multi_app", "等待转换完成时设备未响应")
+        return nil
+    end
+    
+    -- 检查转换是否完成
+    if onewire.bit(0) > 0 then
+        log.info("onewire_multi_app", "温度转换完成")
+    end
+    
+    -- 第四步:读取温度数据
+    log.info("onewire_multi_app", "读取温度数据")
+    
+    -- 构建读取命令:匹配ROM(0x55) + ROM ID + 读取暂存器命令(0xBE)
+    tbuff[tbuff:used() - 1] = 0xbe
+    succ = onewire.tx(0, tbuff, false, true, true)
+    if not succ then
+        log.warn("onewire_multi_app", "发送读取命令失败")
+        return nil
+    end
+    
+    -- 接收9字节温度数据
+    succ, rx_data = onewire.rx(0, 9, nil, rbuff, false, false, false)
+    if not succ then
+        log.warn("onewire_multi_app", "温度数据接收失败")
+        return nil
+    end
+    
+    -- 第五步:CRC校验和温度计算
+    log.info("onewire_multi_app", "CRC校验和温度计算")
+    
+    -- CRC校验
+    crc8c = crypto.crc8(rbuff:toStr(0, 8), 0x31, 0, true)
+    if crc8c == rbuff[8] then
+        -- 计算温度值
+        range = (rbuff[4] >> 5) & 0x03
+        t = rbuff:query(0, 2, false, true)
+        t = t * (5000 >> range)
+        t = t / 10000
+        
+        -- 范围检查
+        if t >= -55.0 and t <= 125.0 then
+            log.info("onewire_multi_app", "温度读取成功:", string.format("%.2f°C", t))
+            return t
+        else
+            log.warn("onewire_multi_app", "温度值超出有效范围:", t)
+            return nil
+        end
+    else
+        log.warn("onewire_multi_app", "温度数据CRC校验不对", 
+                "计算值:", mcu.x32(crc8c), "期望值:", mcu.x32(rbuff[8]))
+        return nil
+    end
+end
+
+-- 简化版温度读取(用于快速测试)
+local function quick_read_ds18b20()
+    log.info("onewire_multi_app", "快速读取温度,引脚: " .. onewire_pin)
+    
+    -- 首先检测设备是否存在
+    if not detect_ds18b20_device() then
+        return nil
+    end
+    
+    -- 使用完整读取函数
+    return read_ds18b20_temperature()
+end
+
+-- 单总线分时复用主函数(同一条总线,分时访问不同设备)
+local function multi_sensor_app_main()
+    log.info("onewire_multi_app", "启动双传感器应用(引脚54和23)")
+    
+    -- 初始化硬件
+    if not init_hardware() then
+        log.error("onewire_multi_app", "硬件初始化失败,任务无法启动")
+        return
+    end
+    
+    -- 初始化OneWire总线
+    init_onewire_bus()
+    
+    -- 电源稳定延时:确保DS18B20内部电路就绪
+    power_stabilization_delay()
+    
+    -- 检测设备
+    local device_present = detect_ds18b20_device()
+    
+    if not device_present then
+        log.error("onewire_multi_app", "未检测到设备响应")
+        log.warn("onewire_multi_app", "硬件连接提示:")
+        log.warn("onewire_multi_app", "1. 传感器连接引脚54或23")
+        log.warn("onewire_multi_app", "2. 确保GPIO31/GPIO2已设置为高电平供电")
+        log.warn("onewire_multi_app", "3. 确保4.7kΩ上拉电阻正确安装")
+        log.warn("onewire_multi_app", "4. 检查传感器VDD、GND、DQ连接")
+        -- 关闭OneWire总线
+        onewire.deinit(0)
+        return
+    end
+    
+    log.info("onewire_multi_app", "开始双传感器连续监测...")
+    log.info("onewire_multi_app", "按PWR_KEY按键可切换引脚(54和23)")
+    
+  -- 主循环:按键切换设备,分时读取温度
+    local read_count = 0
+    local success_count = 0
+    
+    while true do
+        read_count = read_count + 1
+        
+        -- 检查按键状态
+        if pwr_key_pressed then
+            pwr_key_pressed = false
+            switch_onewire_pin()
+            
+            -- 重新初始化OneWire总线
+            init_onewire_bus()
+        end
+        
+        log.info("onewire_multi_app", "第" .. read_count .. "次读取,引脚:" .. onewire_pin)
+        
+        -- 尝试读取温度
+        local temperature = read_ds18b20_temperature()
+        
+        if temperature then
+            success_count = success_count + 1
+            log.info("onewire_multi_app", "引脚" .. onewire_pin .. "温度:", 
+                    string.format("%.2f°C", temperature), 
+                    "成功率:", string.format("%.1f%%", success_count/read_count*100))
+            
+            -- 简单的温度报警逻辑
+            if temperature > 30 then
+                log.warn("onewire_multi_app", "温度偏高:", string.format("%.2f°C", temperature))
+            elseif temperature < 10 then
+                log.warn("onewire_multi_app", "温度偏低:", string.format("%.2f°C", temperature))
+            end
+        else
+            log.warn("onewire_multi_app", "本次读取失败")
+            log.info("onewire_multi_app", "成功率:", string.format("%.1f%%", success_count/read_count*100))
+        end
+        
+        -- 等待下一次读取
+        sys.wait(2000)
+    end
+end
+log.info("onewire_multi_app", "双传感器应用模块加载完成(54和23切换)")
+-- 启动多传感器应用任务
+sys.taskInit(multi_sensor_app_main)
+

+ 0 - 108
module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/onewire_single_18b20/main.lua

@@ -1,108 +0,0 @@
-PROJECT = "onewiredemo"
-VERSION = "1.0.0"
-sys = require("sys")
-log.style(1)
-
---[[
-接线说明:
-   DS18B20    Air780EPM
-1. GND    -> GND
-2. VDD    -> 3.3V
-3. DATA    -> GPIO2
-
-注意:
-1. 3.3v在老版本的开发板上没有引脚, 所以需要外接, 一定要确保共地
-2. ONEWIRE功能支持在4个引脚使用, 但硬件通道只有一个, 默认是GPIO2
-3. 如需切换到其他脚, 参考如下切换逻辑, 选其中一种
-
-]]
-
-local function read_ds18b20(id)
-    local tbuff = zbuff.create(10)
-    local succ,crc8c,range,t
-    local rbuff = zbuff.create(9)
-    --如果有多个DS18B20,需要带上ID
-    tbuff:write(0x55)
-    tbuff:copy(nil, id)
-    tbuff:write(0xb8)
-    --如果只有1个DS18B20,就用无ID方式
-    --tbuff:write(0xcc,0xb8)
-    while true do
-        tbuff[tbuff:used() - 1] = 0x44
-        succ = onewire.tx(0, tbuff, false, true, true)
-        if not succ then
-            return
-        end
-        while true do
-            succ = onewire.reset(0, true)
-            if not succ then
-                return
-            end
-            if onewire.bit(0) > 0 then
-                log.info("温度转换完成")
-                break
-            end
-            sys.wait(10)
-        end
-        tbuff[tbuff:used() - 1] = 0xbe
-        succ = onewire.tx(0, tbuff, false, true, true)
-        if not succ then
-            return
-        end
-        succ,rx_data = onewire.rx(0, 9, nil, rbuff, false, false, false)
-        crc8c = crypto.crc8(rbuff:toStr(0,8), 0x31, 0, true)
-        if crc8c == rbuff[8] then
-            range = (rbuff[4] >> 5) & 0x03
-            -- rbuff[0] = 0xF8
-            -- rbuff[1] = 0xFF
-            t = rbuff:query(0,2,false,true)
-            t = t * (5000 >> range)
-            t = t / 10000
-            log.info(t)
-        else
-            log.info("RAM DATA CRC校验不对",  mcu.x32(crc8c), mcu.x32(rbuff[8]))
-            return
-        end
-        sys.wait(500)
-    end
-end
-
-local function test_ds18b20()
-    local succ,rx_data
-    local id = zbuff.create(8)
-
-    local crc8c
-    onewire.init(0)
-    onewire.timing(0, false, 0, 500, 500, 15, 240, 70, 1, 15, 10, 2)
-    while true do
-        id:set() --清空id
-        succ,rx_data = onewire.rx(0, 8, 0x33, id, false, true, true)
-        if succ then
-            if id[0] == 0x28 then
-                crc8c = crypto.crc8(id:query(0,7), 0x31, 0, true)
-                if crc8c == id[7] then
-                    log.info("探测到DS18B20", id:query(0, 7):toHex())
-                    read_ds18b20(id)
-                    log.info("DS18B20离线,重新探测")
-                else
-                    log.info("ROM ID CRC校验不对",  mcu.x32(crc8c), mcu.x32(id[7]))
-                end
-            else
-                log.info("ROM ID不正确", mcu.x32(id[0]))
-            end
-        end
-        log.info("没有检测到DS18B20, 5秒后重试")
-        sys.wait(5000)
-
-    end
-    
-
-end
-
-if onewire then
-    sys.taskInit(test_ds18b20)
-else
-    log.info("no onewire")
-end
-
-sys.run()

+ 203 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/onewire_single_app.lua

@@ -0,0 +1,203 @@
+--[[
+@module  onewire_single_app
+@summary OneWire单DS18B20温度传感器应用演示模块(GPIO2默认模式)
+@version 1.0.0
+@date    2025.11.25
+@author  王棚嶙
+@usage
+本模块演示单DS18B20温度传感器的完整功能:
+1. 使用GPIO2默认OneWire功能
+2. 硬件通道0模式,无需引脚复用
+3. 优化的时序参数和错误处理
+4. 连续温度监测
+5. 完整的OneWire API接口演示
+]]
+
+
+log.info("onewire_single_app", "单传感器模块版本:1.0.0")
+
+-- 设置所有GPIO引脚电压为3.3V,确保DS18B20传感器正常供电
+pm.ioVol(pm.IOVOL_ALL_GPIO, 3300)
+
+-- DS18B20命令定义
+local CMD_CONVERT_T = 0x44
+local CMD_READ_SCRATCHPAD = 0xBE
+local CMD_SKIP_ROM = 0xCC
+local CMD_READ_ROM = 0x33
+
+-- 单传感器应用主函数
+local function single_sensor_app_main()
+    log.info("onewire_single_app", "启动单传感器应用")
+    
+    -- 初始化OneWire总线(使用硬件通道0模式)
+    log.info("onewire_single_app", "初始化OneWire总线...")
+    onewire.init(0)
+    onewire.timing(0, false, 0, 500, 500, 15, 240, 70, 1, 15, 10, 2)
+    log.info("onewire_single_app", "OneWire总线初始化完成,使用GPIO2默认引脚")
+    
+    
+    
+    -- 检测DS18B20设备
+    log.info("onewire_single_app", "检测DS18B20设备...")
+    
+    local succ, rx_data
+    local id = zbuff.create(8)
+    local crc8c
+    
+    -- 清空ID缓冲区
+    id:set()
+    
+    -- 读取设备ROM ID(使用手动配置的引脚)
+    succ, rx_data = onewire.rx(0, 8, 0x33, id, false, true, true)
+    
+    local detected = false
+    local device_id = nil
+    
+    if succ then
+        -- 检查家族码(DS18B20为0x28)
+        if id[0] == 0x28 then
+            -- CRC校验
+            crc8c = crypto.crc8(id:query(0,7), 0x31, 0, true)
+            if crc8c == id[7] then
+                log.info("onewire_single_app", "探测到DS18B20", id:query(0, 7):toHex())
+                detected = true
+                device_id = id
+            else
+                log.warn("onewire_single_app", "ROM ID CRC校验不对", mcu.x32(crc8c), mcu.x32(id[7]))
+            end
+        else
+            log.warn("onewire_single_app", "ROM ID不正确", mcu.x32(id[0]))
+        end
+    else
+        log.warn("onewire_single_app", "未检测到DS18B20设备,请检查硬件连接")
+        log.info("onewire_single_app", "硬件连接提示:")
+        log.info("onewire_single_app", "1. DS18B20 DATA引脚 -> GPIO2 (默认OneWire功能)")
+        log.info("onewire_single_app", "2. 确保上拉电阻4.7kΩ连接DATA到3.3V")
+        log.info("onewire_single_app", "3. 使用硬件通道0模式,无需引脚复用配置")
+    end
+    
+    if not detected then
+        log.warn("onewire_single_app", "设备检测失败,任务无法启动")
+        log.info("onewire_single_app", "单传感器应用启动完成")
+        onewire.deinit(0)
+        return
+    end
+    
+    log.info("onewire_single_app", "开始连续温度监测...")
+    
+    -- 读取DS18B20温度数据(单总线单设备模式)
+    -- 与多传感器模式的对比:
+    -- - 单传感器:使用SKIP ROM(0xCC)直接通信,无需ROM ID
+    -- - 多传感器:使用MATCH ROM(0x55)选择设备,需要目标ROM ID
+    -- 
+    -- 单设备读取流程:
+    -- 1. SKIP ROM:发送0xCC命令,跳过ROM ID识别
+    -- 2. 温度转换:发送CONVERT T(0x44)启动温度转换
+    -- 3. 读取数据:发送READ SCRATCHPAD(0xBE)读取温度数据
+    -- 4. CRC校验:验证数据完整性
+    -- 
+    -- 优势:通信简单高效,无需设备寻址
+    -- 限制:只能用于总线上只有一个设备的场景
+    local function read_temperature(dev_id)
+        local tbuff = zbuff.create(10)
+        local rbuff = zbuff.create(9)
+        local succ, crc8c, range, t
+        
+        -- 发送SKIP ROM命令(0xCC) - 跳过ROM识别,直接与设备通信
+        -- 工作原理:所有设备都会响应SKIP ROM命令,无需发送64位ROM ID
+        -- 适用场景:总线上只有一个设备,无需设备寻址和选择
+        -- 优势:通信效率高,无需传输ROM ID,简化通信流程
+        -- 风险:如果总线上有多个设备,所有设备会同时响应,造成冲突
+        tbuff:write(0xcc)
+        
+        -- 发送温度转换命令
+        tbuff[tbuff:used() - 1] = 0x44
+        succ = onewire.tx(0, tbuff, false, true, true)
+        if not succ then
+            log.warn("onewire_single_app", "发送温度转换命令失败")
+            return nil
+        end
+        
+        -- 等待转换完成(使用位检测)
+        local conversion_complete = false
+        local max_wait = 100
+        local wait_count = 0
+        
+        while wait_count < max_wait do
+            succ = onewire.reset(0, true)
+            if not succ then
+                log.warn("onewire_single_app", "等待转换完成时设备未响应")
+                return nil
+            end
+            if onewire.bit(0) > 0 then
+                log.info("onewire_single_app", "温度转换完成")
+                conversion_complete = true
+                break
+            end
+            sys.wait(10)
+            wait_count = wait_count + 1
+        end
+        
+        if not conversion_complete then
+            log.warn("onewire_single_app", "温度转换超时")
+            return nil
+        end
+        
+        -- 读取温度数据
+        tbuff[tbuff:used() - 1] = 0xBE
+        succ = onewire.tx(0, tbuff, false, true, true)
+        if not succ then
+            log.warn("onewire_single_app", "发送读取命令失败")
+            return nil
+        end
+        
+        succ, rx_data = onewire.rx(0, 9, nil, rbuff, false, false, false)
+        if not succ or rbuff:used() ~= 9 then
+            log.warn("onewire_single_app", "温度数据读取失败")
+            return nil
+        end
+        
+        -- CRC校验
+        crc8c = crypto.crc8(rbuff:toStr(0,8), 0x31, 0, true)
+        if crc8c == rbuff[8] then
+            range = (rbuff[4] >> 5) & 0x03
+            t = rbuff:query(0,2,false,true)
+            t = t * (5000 >> range)
+            t = t / 10000
+            log.info("onewire_single_app", "温度读取成功:", string.format("%.2f°C", t))
+            return t
+        else
+            log.warn("onewire_single_app", "RAM DATA CRC校验不对", mcu.x32(crc8c), mcu.x32(rbuff[8]))
+            return nil
+        end
+    end
+    
+    -- 主循环 - 连续温度监测
+    while true do
+        local temperature = read_temperature(device_id)
+        
+        if temperature then
+            -- 简单的温度报警逻辑(示例)
+            if temperature > 30 then
+                log.warn("onewire_single_app", "温度偏高:", string.format("%.2f°C", temperature))
+            elseif temperature < 10 then
+                log.warn("onewire_single_app", "温度偏低:", string.format("%.2f°C", temperature))
+            else
+                log.info("onewire_single_app", "温度正常:", string.format("%.2f°C", temperature))
+            end
+        else
+            log.warn("onewire_single_app", "本次读取失败,继续下一次")
+        end
+        
+        -- 等待下一次读取
+        sys.wait(3000)
+    end
+    
+    log.info("onewire_single_app", "单传感器连续读取任务结束")
+    log.info("onewire_single_app", "单传感器应用启动完成")
+end
+
+log.info("onewire_single_app", "单传感器应用模块加载完成")
+
+-- 启动单传感器应用任务
+sys.taskInit(single_sensor_app_main)

+ 212 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/onewire/readme.md

@@ -0,0 +1,212 @@
+> 王棚嶙
+
+# OneWire综合演示项目
+
+## 功能模块介绍
+
+本demo演示了完整的DS18B20温度传感器OneWire单总线协议实现。项目采用模块化架构,分别实现单传感器和多传感器应用场景。
+
+1、main.lua:主程序入口 <br> 
+2、onewire_single_app.lua:演示单传感器功能模块(GPIO2默认OneWire功能,硬件通道0模式,3秒间隔连续监测)<br> 
+3、onewire_multi_app.lua:演示多传感器功能模块(引脚54/23切换,PWR_KEY按键控制,2秒间隔双路监测)<br> 
+
+## 演示功能概述
+
+###  主程序入口模块 (main.lua)
+
+- 初始化项目信息和版本号
+- 初始化看门狗,并定时喂狗
+- 启动一个循环定时器,每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况方便分析内存使用是否有异常
+- 加载onewire_single_app模块(通过require "onewire_single_app")
+- 加载onewire_multi_app模块(通过require "onewire_multi_app")
+
+### 单传感器模式 (onewire_single_app.lua)
+- 使用GPIO2默认OneWire功能,硬件通道0模式,无需引脚复用
+- 完整的CRC8数据校验机制,确保数据可靠性
+- 设备自动识别和ROM验证,支持设备类型检测
+- 3秒间隔连续温度监测,实时温度报警功能
+- zbuff缓冲区优化,提高数据传输效率
+
+
+### 多传感器模式 (onewire_multi_app.lua - 单总线多设备演示)
+
+
+**单总线多设备挂载原理**:
+1. **物理连接**:所有DS18B20的VDD、GND、DQ引脚分别并联到同一组单总线
+2. **设备识别**:每个DS18B20出厂时烧录了全球唯一的64位ROM ID
+3. **总线扫描**:主机发送SEARCH ROM(0xF0)命令发现总线上的所有设备
+4. **设备选择**:通过MATCH ROM(0x55)命令+目标设备ROM ID选择特定设备通信
+5. **分时操作**:每次只与一个设备通信,避免总线冲突
+
+**分时复用测试逻辑**(按键切换设备):
+- **引脚54**:连接设备A(ROM ID: 28-9F-C4-93-00-00-00-14)
+- **引脚23**:连接设备B(ROM ID: 28-59-F2-53-00-00-00-14)
+- **PWR_KEY按键**:按一次切换一个设备,实现同一条总线的分时使用
+
+**核心测试流程**:
+1. 初始化当前引脚的OneWire总线
+2. 发送SEARCH ROM命令扫描总线上的设备
+3. 读取并验证设备的64位ROM ID(家族码+序列号+CRC)
+4. 使用MATCH ROM(0x55)命令选择目标设备
+5. 发送温度转换命令(0x44)并等待完成
+6. 读取温度数据并进行CRC校验
+7. 输出设备ROM ID、温度值、读取成功率
+
+
+
+## 演示硬件环境
+1、Air780EPM核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、ds18b20传感器两个
+
+4、Air780EPM核心板和数据线的硬件接线方式为
+
+- Air780EPM核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到on一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+5、Air780EPM核心板和ds18b20传感器接线方式
+
+### 单传感器连接
+
+|   Air780EPM核心板     |    DS18B20传感器    |
+| --------------- | -------------------|
+|    VDD_EXT         |         VCC        |
+|    23/GPIO2     |         DQ         |
+|    GND          |         GND        |
+
+连接图:
+
+![image](https://docs.openluat.com/air780epm/luatos/app/driver/onewire/image/ce9d3b2c9b3a36c22388d710913668a7.jpg)
+
+### 多传感器连接
+
+
+|   Air780EPM核心板     |    DS18B20传感器1    |
+| --------------- | -------------------|
+|    VDD_EXT         |         VCC        |
+|    23/GPIO2     |         DQ         |
+|    任意GND          |         GND        |
+
+|   Air780EPM核心板     |    DS18B20传感器2    |
+| --------------- | -------------------|
+|    32/GPIO31         |         VCC        |
+|    54/CAM_MCLK     |         DQ         |
+|    任意GND          |         GND        |
+
+连接图:
+
+![image](https://docs.openluat.com/air780epm/luatos/app/driver/onewire/image/1957f5eb447654ec31894efb589e809b.jpg)
+
+## 演示软件环境
+
+1、Luatools下载调试工具: https://docs.openluat.com/air780epm/common/Luatools/
+
+2、内核固件版本:
+Air780EPM:https://docs.openluat.com/air780epm/luatos/firmware/version/
+
+## 演示核心步骤
+1、搭建好硬件环境
+
+2、通过Luatools将demo与固件烧录到核心板或开发板中
+
+3、烧录好后,板子开机将会在Luatools上看到如下打印:
+
+```lua
+(1)单传感器演示
+[2025-11-24 23:46:59.904][000000000.251] I/user.main onewire_demo 1.0.0
+[2025-11-24 23:46:59.932][000000000.258] I/user.onewire_single_app 单传感器模块版本: 002.002.000
+[2025-11-24 23:46:59.970][000000000.258] I/user.onewire_single_app 单传感器应用模块加载完成
+[2025-11-24 23:47:00.002][000000000.259] I/user.onewire_single_app 启动单传感器应用
+[2025-11-24 23:47:00.032][000000000.259] I/user.onewire_single_app 初始化OneWire总线...
+[2025-11-24 23:47:00.064][000000000.259] I/user.onewire_single_app OneWire总线初始化完成,使用GPIO2默认引脚
+[2025-11-24 23:47:00.096][000000000.259] I/user.onewire_single_app 检测DS18B20设备...
+[2025-11-24 23:47:00.124][000000000.267] I/user.onewire_single_app 探测到DS18B20 2859F253000000 14
+[2025-11-24 23:47:00.159][000000000.267] I/user.onewire_single_app 开始连续温度监测...
+[2025-11-24 23:47:00.186][000000000.276] I/user.onewire_single_app 温度转换完成
+[2025-11-24 23:47:00.216][000000000.289] I/user.onewire_single_app 温度读取成功: 85.00°C
+[2025-11-24 23:47:00.246][000000000.290] W/user.onewire_single_app 温度偏高: 85.00°C
+[2025-11-24 23:47:00.409][000000003.299] I/user.onewire_single_app 温度转换完成
+[2025-11-24 23:47:00.436][000000003.312] I/user.onewire_single_app 温度读取成功: 28.25°C
+[2025-11-24 23:47:00.465][000000003.313] I/user.onewire_single_app 温度正常: 28.25°C
+[2025-11-24 23:47:03.404][000000006.322] I/user.onewire_single_app 温度转换完成
+[2025-11-24 23:47:03.437][000000006.335] I/user.onewire_single_app 温度读取成功: 28.25°C
+[2025-11-24 23:47:03.469][000000006.335] I/user.onewire_single_app 温度正常: 28.25°C
+
+(2)双传感器演示
+[2025-11-24 23:49:45.699][000000000.260] I/user.onewire_multi_app 双传感器应用模块加载完成(54和23切换)
+[2025-11-24 23:49:45.732][000000000.261] I/user.onewire_multi_app 启动双传感器应用(引脚54和23)
+[2025-11-24 23:49:45.761][000000000.261] I/user.onewire_multi_app 初始化硬件配置...
+[2025-11-24 23:49:45.789][000000000.261] I/user.onewire_multi_app 硬件初始化完成
+[2025-11-24 23:49:45.822][000000000.262] I/user.onewire_multi_app 初始引脚: 引脚54 (ONEWIRE功能)
+[2025-11-24 23:49:45.859][000000000.262] I/user.onewire_multi_app 切换按键: PWR_KEY
+[2025-11-24 23:49:45.898][000000000.262] I/user.onewire_multi_app 支持引脚: 54 和 23 循环切换
+[2025-11-24 23:49:45.932][000000000.262] I/user.onewire_multi_app 电源控制: GPIO31/GPIO2 (已设置为高电平)
+[2025-11-24 23:49:45.963][000000000.262] I/user.onewire_multi_app 电源控制: 开启
+[2025-11-24 23:49:45.997][000000000.363] I/user.onewire_multi_app 初始化OneWire总线,通道: 0
+[2025-11-24 23:49:46.026][000000000.374] I/user.onewire_multi_app OneWire总线初始化完成,通道: 0,引脚:54
+[2025-11-24 23:49:46.059][000000000.573] I/user.onewire_multi_app 检测DS18B20设备,引脚: 54
+[2025-11-24 23:49:46.089][000000000.575] I/user.onewire_multi_app 检测到DS18B20设备响应
+[2025-11-24 23:49:46.117][000000000.575] I/user.onewire_multi_app 开始双传感器连续监测...
+[2025-11-24 23:49:46.148][000000000.575] I/user.onewire_multi_app 按PWR_KEY按键可切换引脚(54和23)
+[2025-11-24 23:49:46.181][000000000.575] I/user.onewire_multi_app 第1次读取,引脚:54
+[2025-11-24 23:49:46.212][000000000.575] I/user.onewire_multi_app 开始读取DS18B20温度,引脚: 54
+[2025-11-24 23:49:46.243][000000000.576] I/user.onewire_multi_app 读取设备ROM ID
+[2025-11-24 23:49:46.272][000000000.583] I/user.onewire_multi_app ROM ID校验成功: 289FC493000000 14
+[2025-11-24 23:49:46.297][000000000.583] I/user.onewire_multi_app 开始温度转换
+[2025-11-24 23:49:46.325][000000000.590] I/user.onewire_multi_app 等待温度转换完成
+[2025-11-24 23:49:46.401][000000001.341] I/user.onewire_multi_app 温度转换完成
+[2025-11-24 23:49:46.433][000000001.342] I/user.onewire_multi_app 读取温度数据
+[2025-11-24 23:49:46.466][000000001.354] I/user.onewire_multi_app CRC校验和温度计算
+[2025-11-24 23:49:46.499][000000001.355] I/user.onewire_multi_app 温度读取成功: 27.44°C
+[2025-11-24 23:49:46.530][000000001.355] I/user.onewire_multi_app 引脚54温度: 27.44°C 成功率: 100.0%
+[2025-11-24 23:49:46.630][000000003.355] I/user.onewire_multi_app 第2次读取,引脚:54
+[2025-11-24 23:49:46.665][000000003.356] I/user.onewire_multi_app 开始读取DS18B20温度,引脚: 54
+[2025-11-24 23:49:46.698][000000003.356] I/user.onewire_multi_app 读取设备ROM ID
+[2025-11-24 23:49:46.727][000000003.363] I/user.onewire_multi_app ROM ID校验成功: 289FC493000000 14
+[2025-11-24 23:49:46.765][000000003.363] I/user.onewire_multi_app 开始温度转换
+[2025-11-24 23:49:46.799][000000003.371] I/user.onewire_multi_app 等待温度转换完成
+[2025-11-24 23:49:46.982][000000004.122] I/user.onewire_multi_app 温度转换完成
+[2025-11-24 23:49:47.010][000000004.123] I/user.onewire_multi_app 读取温度数据
+[2025-11-24 23:49:47.043][000000004.135] I/user.onewire_multi_app CRC校验和温度计算
+[2025-11-24 23:49:47.077][000000004.136] I/user.onewire_multi_app 温度读取成功: 27.50°C
+[2025-11-24 23:49:47.110][000000004.137] I/user.onewire_multi_app 引脚54温度: 27.50°C 成功率: 100.0%
+[2025-11-24 23:49:48.999][000000006.137] I/user.onewire_multi_app 第3次读取,引脚:54
+[2025-11-24 23:49:49.030][000000006.138] I/user.onewire_multi_app 开始读取DS18B20温度,引脚: 54
+[2025-11-24 23:49:49.061][000000006.138] I/user.onewire_multi_app 读取设备ROM ID
+[2025-11-24 23:49:49.094][000000006.145] I/user.onewire_multi_app ROM ID校验成功: 289FC493000000 14
+[2025-11-24 23:49:49.124][000000006.145] I/user.onewire_multi_app 开始温度转换
+[2025-11-24 23:49:49.154][000000006.153] I/user.onewire_multi_app 等待温度转换完成
+[2025-11-24 23:49:49.778][000000006.904] I/user.onewire_multi_app 温度转换完成
+[2025-11-24 23:49:49.806][000000006.905] I/user.onewire_multi_app 读取温度数据
+[2025-11-24 23:49:49.836][000000006.917] I/user.onewire_multi_app CRC校验和温度计算
+[2025-11-24 23:49:49.866][000000006.918] I/user.onewire_multi_app 温度读取成功: 27.50°C
+[2025-11-24 23:49:49.907][000000006.919] I/user.onewire_multi_app 引脚54温度: 27.50°C 成功率: 100.0%
+
+(3)双传感器按键切换演示
+[2025-11-24 23:49:51.147][000000008.278] I/user.onewire_multi_app 切换按键被按下
+[2025-11-24 23:49:51.783][000000008.919] I/user.onewire_multi_app 切换OneWire引脚...
+[2025-11-24 23:49:51.821][000000008.939] I/user.onewire_multi_app 将PAD54配置为GPIO3 true
+[2025-11-24 23:49:51.856][000000008.940] I/user.onewire_multi_app 将GPIO3设置为高电平输出 function: 0C7F4A10
+[2025-11-24 23:49:51.897][000000008.940] I/user.onewire_multi_app 切换到引脚23
+[2025-11-24 23:49:51.933][000000008.940] I/user.onewire_multi_app 当前使用引脚: 23
+[2025-11-24 23:49:51.965][000000008.941] I/user.onewire_multi_app 将引脚23配置为ONEWIRE功能 true
+[2025-11-24 23:49:51.994][000000008.961] I/user.onewire_multi_app 引脚切换完成,当前使用: 引脚23
+[2025-11-24 23:49:52.324][000000009.461] I/user.onewire_multi_app 初始化OneWire总线,通道: 0
+[2025-11-24 23:49:52.356][000000009.471] I/user.onewire_multi_app OneWire总线初始化完成,通道: 0,引脚:23
+[2025-11-24 23:49:52.431][000000009.571] I/user.onewire_multi_app 第4次读取,引脚:23
+[2025-11-24 23:49:52.474][000000009.571] I/user.onewire_multi_app 开始读取DS18B20温度,引脚: 23
+[2025-11-24 23:49:52.519][000000009.572] I/user.onewire_multi_app 读取设备ROM ID
+[2025-11-24 23:49:52.563][000000009.579] I/user.onewire_multi_app ROM ID校验成功: 2859F253000000 14
+[2025-11-24 23:49:52.593][000000009.579] I/user.onewire_multi_app 开始温度转换
+[2025-11-24 23:49:52.622][000000009.587] I/user.onewire_multi_app 等待温度转换完成
+[2025-11-24 23:49:53.203][000000010.338] I/user.onewire_multi_app 温度转换完成
+[2025-11-24 23:49:53.239][000000010.339] I/user.onewire_multi_app 读取温度数据
+[2025-11-24 23:49:53.272][000000010.351] I/user.onewire_multi_app CRC校验和温度计算
+[2025-11-24 23:49:53.302][000000010.352] I/user.onewire_multi_app 温度读取成功: 27.81°C
+[2025-11-24 23:49:53.332][000000010.353] I/user.onewire_multi_app 引脚23温度: 27.81°C 成功率: 100.0%
+
+
+```

+ 66 - 0
module/Air780EHM_Air780EHV_Air780EGH/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()

+ 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测试", "所有测试已启动")

+ 366 - 291
module/Air780EHM_Air780EHV_Air780EGH/demo/tf_card/tfcard_app.lua

@@ -1,341 +1,416 @@
 --[[
-@module  tfcard_app
-@summary TF卡文件操作测试模块
-@version 1.0.0
-@date    2025.08.25
-@author  王棚嶙
+@module  onewire_multi_app
+@summary OneWire多DS18B20温度传感器应用演示模块(带切换功能)
+@version 002.002.000
+@date    2025.11.25
+@author  王棚嶙
 @usage
-本文件为TF卡的文件操作测试流程:
-1. 创建目录
-2. 创建并写入文件
-3. 检查文件是否存在
-4. 获取文件大小
-5. 读取文件内容
-6. 启动计数文件操作
-7. 文件追加测试
-8. 按行读取测试
-9. 读取后关闭文件
-10. 文件重命名
-11. 列举目录内容
-12. 删除文件
-13. 删除目录
-本文件没有对外接口,直接在main.lua中require "tfcard_app"就可以加载运行
-]] 
-
-
-
-
-
-local function tfcard_main_task() -- 开始进行主测试流程。
-    -- ##########  SPI初始化 ##########
-    -- 如果使用核心板演示环境,请打开33——36行代码,同时关闭38——43行的代码。
-    -- 如果使用开发板演示环境,请打开38——43行代码,同时关闭33——36行的代码。
-    -- 在Air780EHM/EHV/EGH核心板上TF卡的的pin_cs为gpio8,spi_id为0.请根据实际硬件修改
-    spi_id, pin_cs = 0, 8
-    spi.setup(spi_id, nil, 0, 0, 400 * 1000)
-    --初始化后拉高pin_cs,准备开始挂载TF卡
-    gpio.setup(pin_cs, 1)
-
-    -- Air780EHM/EHV/EGH开发板上的pin_cs为gpio16,spi_id为0.请根据实际硬件修改
-    -- spi_id, pin_cs = 0, 16
-    -- spi.setup(spi_id, nil, 0, 0, 400 * 1000)
-    --设置片选引脚同一spi总线上的所有从设备在初始化时必须要先拉高CS脚,防止从设备之间互相干扰。
-    -- 在Air780EHM/EHV/EGH开发板上,TF卡和ch390共用SPI0总线。
-    -- gpio.setup(pin_cs, 1)
-
-    -- ########## 开始进行tf卡挂载 ##########
-    -- 挂载失败默认格式化,
-    -- 如无需格式化应改为fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000, nil, 1, false),
-    -- 一般是在测试硬件是否有问题的时候把格式化取消掉
-    mount_ok, mount_err = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
-    if mount_ok then
-        log.info("fatfs.mount", "挂载成功", mount_err)
-    else
-        log.error("fatfs.mount", "挂载失败", mount_err)
-        goto resource_cleanup
-    end
-
-    -- ########## 获取SD卡的可用空间信息并打印。 ########## 
-    data, err = fatfs.getfree("/sd")
-    if data then
-        --打印SD卡的可用空间信息
-        log.info("fatfs", "getfree", json.encode(data))
-    else
-        --打印错误信息
-        log.info("fatfs", "getfree", "err", err)
-        goto resource_cleanup
+本模块演示多DS18B20温度传感器的完整功能:
+1. 多传感器切换控制
+2. 电源管理(GPIO控制)
+3. 按键切换传感器
+4. 多路温度同时监测
+5. 使用引脚复用功能(pins.setup)
+]]
+
+log.info("onewire_multi_app", "多传感器模块版本: 002.002.000")
+
+-- 设置所有GPIO引脚电压为3.3V,确保DS18B20传感器正常供电
+pm.ioVol(pm.IOVOL_ALL_GPIO, 3300)
+
+-- GPIO2和GPIO31控制电源,确保DS18B20供电正常
+gpio.setup(2, 1)  -- GPIO2输出高电平
+gpio.setup(31, 1) -- GPIO31输出高电平
+
+-- 传感器配置
+local sensor_config = {
+    {pin = 2,  channel = 0, name = "GPIO2(默认)"},    -- GPIO2使用通道0
+    {pin = 54, channel = 3, name = "引脚54(复用)"}    -- 引脚54使用通道3
+}
+
+local current_sensor = 1  -- 当前使用的传感器索引
+
+-- DS18B20命令定义
+local CMD_CONVERT_T = 0x44     -- 温度转换命令
+local CMD_READ_SCRATCHPAD = 0xBE  -- 读取暂存器命令
+local CMD_SKIP_ROM = 0xCC      -- 跳过ROM命令
+
+-- 全局状态变量
+local task_running = false
+local switch_pressed = false
+
+-- CRC8校验函数
+local function crc8_check(data)
+    if not data or #data == 0 then
+        return false
     end
-
-    -- 列出所有挂载点,如不需要,可注释掉。
-    data = io.lsmount()
-    log.info("fs", "lsmount", json.encode(data))
-
-    -- ########## 功能: 启用fatfs调试模式 ##########
-    -- fatfs.debug(1) -- 若挂载失败,可以尝试打开调试信息,查找原因.(设置调试模式)
-
-    -- 执行tfcard文件操作演示
-    log.info("文件操作", "===== 开始文件操作 =====")
-
-    dir_path = "/sd/io_test"
-
-    -- 1. 创建目录
-    if io.mkdir(dir_path) then
-        log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
-    else
-        -- 检查是否目录已存在
-        if io.exists(dir_path) then
-            log.warn("io.mkdir", "目录已存在,跳过创建", "路径:" .. dir_path)
-        else
-            log.error("io.mkdir", "目录创建失败且目录不存在", "路径:" .. dir_path)
-            goto resource_cleanup
+    
+    local crc = 0
+    for i = 1, #data - 1 do
+        crc = crc ~ data:byte(i)
+        for j = 1, 8 do
+            if crc & 0x01 then
+                crc = (crc >> 1) ~ 0x8C
+            else
+                crc = crc >> 1
+            end
         end
     end
+    
+    return crc == data:byte(#data)
+end
 
-    -- 2. 创建并写入文件
-    file_path = dir_path .. "/boottime"
-    file = io.open(file_path, "wb")
-    if file then
-        file:write("这是io库API文档示例的测试内容")
-        file:close()
-        --在LuatOS文件操作中,执行file:close()是必须且关键的操作,它用于关闭文件句柄,释放资源,并确保数据被正确写入磁盘。
-        -- 如果不执行file:close(),可能会导致数据丢失、文件损坏或其他不可预测的问题。
-        log.info("文件创建", "文件写入成功", "路径:" .. file_path)
-    else
-        log.error("文件创建", "文件创建失败", "路径:" .. file_path)
-        goto resource_cleanup
+-- 计算温度值
+local function calculate_temperature(data)
+    if not data or #data < 2 then
+        log.warn("onewire_multi_app", "温度数据无效或长度不足")
+        return nil
     end
-
-    -- 3. 检查文件是否存在
-    if io.exists(file_path) then
-        log.info("io.exists", "文件存在", "路径:" .. file_path)
-    else
-        log.error("io.exists", "文件不存在", "路径:" .. file_path)
-        goto resource_cleanup
+    
+    local temp_raw = data:byte(1) + (data:byte(2) * 256)
+    
+    if temp_raw > 32767 then
+        temp_raw = temp_raw - 65536
     end
-
-    -- 4. 获取文件大小
-    file_size = io.fileSize(file_path)
-    if file_size then
-        log.info("io.fileSize", "文件大小:" .. file_size .. "字节", "路径:" .. file_path)
-    else
-        log.error("io.fileSize", "获取文件大小失败", "路径:" .. file_path)
-        goto resource_cleanup
+    
+    local temperature = temp_raw * 0.0625
+    
+    if temperature < -55.0 or temperature > 125.0 then
+        log.warn("onewire_multi_app", "温度值超出有效范围:", temperature)
+        return nil
     end
+    
+    return temperature
+end
 
-    -- 5. 读取文件内容
-    file = io.open(file_path, "rb")
-    if file then
-        content = file:read("*a")
-        log.info("文件读取", "路径:" .. file_path, "内容:" .. content)
-        file:close()
-    else
-        log.error("文件操作", "无法打开文件读取内容", "路径:" .. file_path)
-        goto resource_cleanup
+-- 电源控制函数
+local function power_control(on)
+    log.info("onewire_multi_app", "电源控制:", on and "开启" or "关闭")
+    if on then
+        sys.wait(100)  -- 等待电源稳定
     end
+    return true
+end
 
-    -- 6. 启动计数文件操作
-    count = 0
-    --以只读模式打开文件
-    file = io.open(file_path, "rb")
-    if file then
-        data = file:read("*a")
-        log.info("启动计数", "文件内容:", data, "十六进制:", data:toHex())
-        count = tonumber(data) or 0
-        file:close()
-    else
-        log.warn("启动计数", "文件不存在或无法打开")
-
+-- 初始化指定传感器
+local function init_sensor(sensor_idx)
+    local config = sensor_config[sensor_idx]
+    
+    log.info("onewire_multi_app", "初始化传感器:", config.name, "引脚:", config.pin, "通道:", config.channel)
+    
+    if config.pin == 54 then
+        pins.setup(config.pin, "ONEWIRE")
+        log.info("onewire_multi_app", "配置引脚54为ONEWIRE复用功能")
     end
+    
+    onewire.init(config.channel)
+    onewire.timing(config.channel, false, 0, 480, 70, 70, 410, 70, 10, 10, 15, 70)
+    
+    log.info("onewire_multi_app", "传感器初始化完成:", config.name)
+    return config.channel
+end
 
-    log.info("启动计数", "当前值:", count)
-    count=count + 1
-    log.info("启动计数", "更新值:", count)
-
-    file = io.open(file_path, "wb")
-    if file then
-        file:write(tostring(count))
-        file:close()
-        log.info("文件写入", "路径:" .. file_path, "内容:", count)
+-- 检测DS18B20设备是否存在
+local function detect_ds18b20_device(channel)
+    local present = onewire.reset(channel, true)
+    if present then
+        log.info("onewire_multi_app", "通道", channel, "检测到DS18B20设备")
     else
-        log.error("文件写入", "无法打开文件", "路径:" .. file_path)
-        goto resource_cleanup
+        log.warn("onewire_multi_app", "通道", channel, "未检测到DS18B20设备")
     end
+    return present
+end
 
-    -- 7. 文件追加测试
-    append_file = dir_path .. "/test_a"
-    -- 清理旧文件
-    os.remove(append_file)
+-- 初始化OneWire总线
+local function init_onewire_bus(channel)
+    log.info("onewire_multi_app", "初始化OneWire总线,通道:", channel)
+    onewire.init(channel)
+    onewire.timing(channel, false, 0, 480, 70, 70, 410, 70, 10, 10, 15, 70)
+    log.info("onewire_multi_app", "OneWire总线初始化完成,通道:", channel)
+    return true
+end
 
-    -- 创建并写入初始内容
-    file = io.open(append_file, "wb")
-    if file then
-        file:write("ABC")
-        file:close()
-        log.info("文件创建", "路径:" .. append_file, "初始内容:ABC")
-    else
-        log.error("文件创建", "无法创建文件", "路径:" .. append_file)
-        goto resource_cleanup
+-- 读取DS18B20温度
+local function read_ds18b20_temperature(channel)
+    log.info("onewire_multi_app", "读取DS18B20温度,通道:", channel)
+    
+    local tbuff = zbuff.create(2)
+    local rbuff = zbuff.create(9)
+    
+    local present = onewire.reset(channel, true)
+    if not present then
+        log.warn("onewire_multi_app", "通道", channel, "设备未响应")
+        return nil
     end
-
-    -- 追加内容
-    file = io.open(append_file, "a+")
-    if file then
-        file:write("def")
-        file:close()
-        log.info("文件追加", "路径:" .. append_file, "追加内容:def")
-    else
-        log.error("文件追加", "无法打开文件进行追加", "路径:" .. append_file)
-        goto resource_cleanup
-
+    
+    tbuff:set(0, CMD_SKIP_ROM)
+    tbuff:set(1, CMD_CONVERT_T)
+    local succ = onewire.tx(channel, tbuff, 0, 2, false, true, true)
+    if not succ then
+        log.warn("onewire_multi_app", "通道", channel, "发送温度转换命令失败")
+        return nil
     end
-
-    -- 验证追加结果
-    file = io.open(append_file, "r")
-    if file then
-        data = file:read("*a")
-        log.info("文件验证", "路径:" .. append_file, "内容:" .. data, "结果:",
-            data == "ABCdef" and "成功" or "失败")
-        file:close()
-    else
-        log.error("文件验证", "无法打开文件进行验证", "路径:" .. append_file)
-        goto resource_cleanup
+    
+    log.info("onewire_multi_app", "等待温度转换完成...")
+    sys.wait(750)
+    
+    local succ = onewire.reset(channel, true)
+    if not succ then
+        log.warn("onewire_multi_app", "通道", channel, "读取时设备未响应")
+        return nil
     end
-
-    -- 8. 按行读取测试
-    line_file = dir_path .. "/testline"
-    file = io.open(line_file, "w")
-    if file then
-        file:write("abc\n")
-        file:write("123\n")
-        file:write("wendal\n")
-        file:close()
-        log.info("文件创建", "路径:" .. line_file, "写入3行文本")
-    else
-        log.error("文件创建", "无法创建文件", "路径:" .. line_file)
-        goto resource_cleanup
+    
+    tbuff:set(0, CMD_SKIP_ROM)
+    tbuff:set(1, CMD_READ_SCRATCHPAD)
+    succ = onewire.tx(channel, tbuff, 0, 2, false, true, true)
+    if not succ then
+        log.warn("onewire_multi_app", "通道", channel, "发送读取命令失败")
+        return nil
     end
-
-    -- 按行读取文件
-    file = io.open(line_file, "r")
-    if file then
-        log.info("按行读取", "路径:" .. line_file, "第1行:", file:read("*l"))
-        log.info("按行读取", "路径:" .. line_file, "第2行:", file:read("*l"))
-        log.info("按行读取", "路径:" .. line_file, "第3行:", file:read("*l"))
-        file:close()
-    else
-        log.error("按行读取", "无法打开文件", "路径:" .. line_file)
-        goto resource_cleanup
+    
+    rbuff:resize(9)
+    local succ, rx_len = onewire.rx(channel, 9, nil, rbuff, 0, false, false, false)
+    if not succ or rx_len ~= 9 then
+        log.warn("onewire_multi_app", "通道", channel, "温度数据接收失败,长度:", rx_len or 0)
+        return nil
     end
-
-    -- 9. 文件重命名
-    old_path = append_file
-    new_path = dir_path .. "/renamed_file.txt"
-    success, err = os.rename(old_path, new_path)
-    if success then
-        log.info("os.rename", "文件重命名成功", "原路径:" .. old_path, "新路径:" .. new_path)
-
-        -- 验证重命名结果
-        if io.exists(new_path) and not io.exists(old_path) then
-            log.info("验证结果", "重命名验证成功", "新文件存在", "原文件不存在")
+    
+    local crc8c = crypto.crc8(rbuff:query(0,8), 0x31, 0, true)
+    if crc8c == rbuff[8] then
+        local temp_low = rbuff[0]
+        local temp_high = rbuff[1]
+        local temp_raw = temp_low + (temp_high * 256)
+        
+        if temp_raw > 32767 then
+            temp_raw = temp_raw - 65536
+        end
+        
+        local temperature = temp_raw * 0.0625
+        
+        if temperature >= -55.0 and temperature <= 125.0 then
+            log.info("onewire_multi_app", "温度读取成功,通道", channel, ":", string.format("%.2f°C", temperature))
+            return temperature
         else
-            log.error("验证结果", "重命名验证失败")
+            log.warn("onewire_multi_app", "温度值超出有效范围:", temperature)
+            return nil
         end
     else
-        log.error("os.rename", "重命名失败", "错误:" .. tostring(err), "原路径:" .. old_path)
-        goto resource_cleanup
+        log.warn("onewire_multi_app", "CRC校验失败,期望:", string.format("0x%02X", crc8c), 
+                 "实际:", string.format("0x%02X", rbuff[8]))
+        return nil
     end
+end
 
-    -- 10. 列举目录内容
-    log.info("目录操作", "===== 开始目录列举 =====")
+-- 切换传感器
+local function switch_onewire_sensor()
+    log.info("onewire_multi_app", "切换传感器...")
+    
+    local old_config = sensor_config[current_sensor]
+    onewire.deinit(old_config.channel)
+    
+    current_sensor = (current_sensor % #sensor_config) + 1
+    local new_config = sensor_config[current_sensor]
+    
+    local channel = init_sensor(current_sensor)
+    
+    log.info("onewire_multi_app", "切换到传感器:", new_config.name, "通道:", channel)
+    return channel
+end
 
-    ret, data = io.lsdir(dir_path, 50, 0) -- 50表示最多返回50个文件,0表示从目录开头开始
-    if ret then
-        log.info("fs", "lsdir", json.encode(data))
-    else
-        log.info("fs", "lsdir", "fail", ret, data)
-        goto resource_cleanup
+-- 初始化硬件
+local function init_hardware()
+    log.info("onewire_multi_app", "初始化硬件配置...")
+    
+    gpio.setup(gpio.PWR_KEY, function()
+        switch_pressed = true
+        log.info("onewire_multi_app", "切换按键被按下")
+    end, gpio.PULLUP, gpio.RISING)
+    
+    local channel = init_sensor(current_sensor)
+    local detected = detect_ds18b20_device(channel)
+    
+    if not detected then
+        log.warn("onewire_multi_app", "第一个传感器未检测到,尝试第二个传感器...")
+        current_sensor = 2
+        channel = init_sensor(current_sensor)
+        detected = detect_ds18b20_device(channel)
     end
+    
+    log.info("onewire_multi_app", "硬件初始化完成")
+    log.info("onewire_multi_app", "当前使用传感器:", sensor_config[current_sensor].name)
+    
+    return detected
+end
 
-    -- 11. 删除文件测试
-    -- 测试删除renamed_file.txt文件
-    if os.remove(new_path) then
-        log.info("os.remove", "文件删除成功", "路径:" .. new_path)
-
-        -- 验证renamed_file.txt删除结果
-        if not io.exists(new_path) then
-            log.info("验证结果", "renamed_file.txt文件删除验证成功")
-        else
-            log.error("验证结果", "renamed_file.txt文件删除验证失败")
-        end
-    else
-        log.error("io.remove", "renamed_file.txt文件删除失败", "路径:" .. new_path)
-        goto resource_cleanup
+-- 演示OneWire初始化功能
+local function demonstrate_onewire_init()
+    log.info("demo", "=== 1. 多通道onewire.init()演示 ===")
+    for i, config in ipairs(sensor_config) do
+        log.info("demo", "初始化通道", config.channel, "对应引脚", config.pin, config.name)
+        onewire.init(config.channel)
     end
+    log.info("demo", "OneWire初始化演示完成")
+end
 
-    -- 测试删除testline文件
-    if os.remove(line_file) then
-        log.info("os.remove", "testline文件删除成功", "路径:" .. line_file)
+-- 演示时序配置功能
+local function demonstrate_onewire_timing()
+    log.info("demo", "=== 2. 多通道onewire.timing()演示 ===")
+    for i, config in ipairs(sensor_config) do
+        log.info("demo", "配置通道", config.channel, "的DS18B20标准时序")
+        onewire.timing(config.channel, false, 0, 480, 70, 70, 410, 70, 10, 10, 15, 70)
+    end
+    log.info("demo", "时序配置演示完成")
+end
 
-        -- 验证删除结果
-        if not io.exists(line_file) then
-            log.info("验证结果", "testline文件删除验证成功")
-        else
-            log.error("验证结果", "testline文件删除验证失败")
-        end
-    else
-        log.error("io.remove", "testline文件删除失败", "路径:" .. line_file)
-        goto resource_cleanup
+-- 演示设备检测功能
+local function demonstrate_device_detection()
+    log.info("demo", "=== 3. 多通道设备检测演示 ===")
+    for i, config in ipairs(sensor_config) do
+        local present = onewire.reset(config.channel, true)
+        log.info("demo", "通道", config.channel, config.name, "检测状态:", present and "检测到设备" or "未检测到设备")
     end
+    log.info("demo", "设备检测演示完成")
+end
 
-    if os.remove(file_path) then
-        log.info("os.remove", "文件删除成功", "路径:" .. file_path)
+-- 演示电源控制功能
+local function demonstrate_power_control()
+    log.info("demo", "=== 4. 电源控制演示 ===")
+    power_control(true)
+    power_control(false)
+    log.info("demo", "电源控制演示完成")
+end
 
-        -- 验证删除结果
-        if not io.exists(file_path) then
-            log.info("验证结果", "boottime文件删除验证成功")
-        else
-            log.error("验证结果", "boottime文件删除验证失败")
-        end
-    else
-        log.error("io.remove", "boottime文件删除失败", "路径:" .. file_path)
-        goto resource_cleanup
+-- 演示传感器切换功能
+local function demonstrate_sensor_switching()
+    log.info("demo", "=== 5. 传感器切换功能演示 ===")
+    local original_sensor = current_sensor
+    
+    for i = 1, #sensor_config do
+        local channel = switch_onewire_sensor()
+        local detected = detect_ds18b20_device(channel)
+        log.info("demo", "切换到传感器", current_sensor, "检测状态:", detected and "成功" or "失败")
+        sys.wait(500)
     end
+    
+    current_sensor = original_sensor
+    init_sensor(current_sensor)
+    log.info("demo", "传感器切换演示完成,恢复原始传感器")
+end
 
-    -- 12. 删除目录(不能删除非空目录,所以在删除目录前要确保目录内没有文件或子目录)
-    if io.rmdir(dir_path) then
-        log.info("io.rmdir", "目录删除成功", "路径:" .. dir_path)
-
-        -- 验证删除结果
-        if not io.exists(dir_path) then
-            log.info("验证结果", "目录删除验证成功")
-        else
-            log.error("验证结果", "目录删除验证失败")
-        end
-    else
-        log.error("io.rmdir", "目录删除失败", "路径:" .. dir_path)
-        goto resource_cleanup
+-- 演示位操作功能
+local function demonstrate_bit_operations()
+    log.info("demo", "=== 6. 位操作功能演示 ===")
+    for i, config in ipairs(sensor_config) do
+        onewire.bit(config.channel, 1)
+        log.info("demo", "通道", config.channel, "发送位: 1")
+        
+        local bit_value = onewire.bit(config.channel)
+        log.info("demo", "通道", config.channel, "读取位值:", bit_value)
     end
+    log.info("demo", "位操作演示完成")
+end
+
+-- 演示调试模式功能
+local function demonstrate_debug_mode()
+    log.info("demo", "=== 7. 调试模式演示 ===")
+    log.info("demo", "调试模式当前状态: 关闭(可通过取消注释开启)")
+    -- for i, config in ipairs(sensor_config) do
+    --     onewire.debug(config.channel, true)
+    --     log.info("demo", "开启通道", config.channel, "的调试模式")
+    -- end
+    log.info("demo", "调试模式演示完成")
+end
 
-    log.info("文件操作", "===== 文件操作完成 =====")
+-- 演示资源释放功能
+local function demonstrate_resource_cleanup()
+    log.info("demo", "=== 8. 资源释放演示 ===")
+    for i, config in ipairs(sensor_config) do
+        onewire.deinit(config.channel)
+        log.info("demo", "释放通道", config.channel, "的资源")
+    end
+    log.info("demo", "资源释放演示完成")
+    
+    -- 重新初始化所有通道
+    for i, config in ipairs(sensor_config) do
+        init_sensor(i)
+    end
+end
 
-    -- ########## 功能: 收尾功能演示##########
-    -- 卸载文件系统和关闭SPI
-    ::resource_cleanup::
+-- 演示多传感器OneWire API接口
+local function demonstrate_multi_onewire_apis()
+    log.info("onewire_multi_app", "开始演示多传感器OneWire API接口...")
+    
+    demonstrate_onewire_init()
+    demonstrate_onewire_timing()
+    demonstrate_device_detection()
+    demonstrate_power_control()
+    demonstrate_sensor_switching()
+    demonstrate_bit_operations()
+    demonstrate_debug_mode()
+    demonstrate_resource_cleanup()
+    
+    log.info("onewire_multi_app", "多传感器API演示完成")
+end
 
-    log.info("结束", "开始执行关闭操作...")  
-    -- 如已挂载需先卸载文件系统,未挂载直接关闭SPI
-    if mount_ok then
-        if fatfs.unmount("/sd") then
-            log.info("文件系统", "卸载成功")
+-- 多传感器应用主函数
+local function multi_sensor_app_main()
+    log.info("onewire_multi_app", "启动多传感器应用")
+    
+    if not init_hardware() then
+        log.error("onewire_multi_app", "所有传感器都未检测到,请检查硬件连接")
+        log.warn("onewire_multi_app", "硬件连接提示:")
+        log.warn("onewire_multi_app", "1. 传感器1连接GPIO2 (默认OneWire功能)")
+        log.warn("onewire_multi_app", "2. 传感器2连接引脚54 (复用为GPIO3/ONEWIRE)")
+        log.warn("onewire_multi_app", "3. 确保GPIO31/GPIO2已设置为高电平供电")
+        return
+    end
+    
+    task_running = true
+    log.info("onewire_multi_app", "开始多传感器连续监测...")
+    log.info("onewire_multi_app", "按PWR_KEY按键可切换传感器")
+    
+    while task_running do
+        if switch_pressed then
+            switch_pressed = false
+            switch_onewire_sensor()
+        end
+        
+        local config = sensor_config[current_sensor]
+        local temperature = read_ds18b20_temperature(config.channel)
+        
+        if temperature then
+            log.info("onewire_multi_app", 
+                "传感器" .. current_sensor .. "(" .. config.name .. "):", 
+                string.format("%.2f°C", temperature))
         else
-            log.error("文件系统", "卸载失败")
+            log.warn("onewire_multi_app", "传感器" .. current_sensor .. "读取失败")
         end
+        
+        sys.wait(1000)
+    end
+    
+    for i, config in ipairs(sensor_config) do
+        onewire.deinit(config.channel)
     end
+    
+    log.info("onewire_multi_app", "多传感器应用结束")
+end
 
-    -- 2. 关闭SPI接口
-    spi.close(spi_id)
-    log.info("SPI接口", "已关闭")
+-- 启动多传感器应用任务
+local function start_multi_sensor_app()
+    sys.taskInit(function()
+        demonstrate_multi_onewire_apis()
+        multi_sensor_app_main()
+    end)
+end
 
+-- 模块初始化函数
+local function init_module()
+    log.info("onewire_multi_app", "多传感器应用模块初始化")
+    start_multi_sensor_app()
 end
 
-sys.taskInit(tfcard_main_task)
+-- 启动模块
+init_module()
+
+log.info("onewire_multi_app", "多传感器应用模块加载完成")

+ 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


+ 65 - 0
module/Air780EPM/demo/onewire/main.lua

@@ -0,0 +1,65 @@
+--[[
+@module  main
+@summary OneWire综合演示项目主文件(单传感器 + 多传感器)
+@version 001.000.000
+@date    2025.11.25
+@author  王棚嶙
+@usage
+本演示项目整合单DS18B20和多DS18B20传感器功能:
+1. 单传感器模式:GPIO2默认OneWire功能、硬件通道0模式、CRC校验、3秒间隔连续监测
+2. 多传感器模式:引脚54/23切换、PWR_KEY按键控制、电源管理、2秒间隔双路监测
+3. 完整的OneWire API接口演示、错误处理、设备检测、温度报警
+]]
+
+-- 项目信息
+PROJECT = "onewire_demo"
+VERSION = "001.000.000"
+
+
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+--添加硬狗防止程序卡死
+if wdt then
+    wdt.init(9000)--初始化watchdog设置为9s
+    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+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)
+
+-- 在加载以下两个功能的时候建议分别打开,避免同时初始化OneWire总线,导致资源冲突
+-- 单设备模式:使用GPIO2默认OneWire功能
+-- 双设备模式:GPIO2默认 + 引脚54复用
+
+-- 加载单传感器应用模块
+-- require("onewire_single_app")
+
+-- 加载多传感器应用模块
+require("onewire_multi_app")
+
+
+-- 启动系统主循环
+sys.run()

+ 0 - 9
module/Air780EPM/demo/onewire/onewire_multi_18b20_swich_read/main.lua

@@ -1,9 +0,0 @@
-PROJECT = "DS18B20_GPIO_SWITCH_READ_temperature"
-VERSION = "1.0.0"
-sys = require("sys")
-log.style(1)
-
-
-require"switch_read"
-
-sys.run()

+ 0 - 125
module/Air780EPM/demo/onewire/onewire_multi_18b20_swich_read/switch_read.lua

@@ -1,125 +0,0 @@
-
-pm.ioVol(pm.IOVOL_ALL_GPIO, 3300) -- 所有GPIO高电平输出3.3V(方便使用VDD_EXT给18B20供电)
-gpio.setup(2, 1) --GPIO2控制780EPM开发板V1.3版本camera电源打开和关闭
-gpio.setup(31, 1) -- GPIO31控制780EPM开发板V1.4版本camera电源打开和关闭
-
-local onewire_pin = 54 --18B20接的pin 54脚
-
-
-pins.setup(onewire_pin, "ONEWIRE") -- PAD54脚既是GPIO3也是cam_mclk 
-
---读取当前pin脚上的18B20温度
-local function read_ds18b20(id)
-log.info("读取温度",id)
-    local tbuff = zbuff.create(10)
-    local succ, crc8c, range, t
-    local rbuff = zbuff.create(9)
-    tbuff:write(0x55)
-    tbuff:copy(nil, id)
-    tbuff:write(0xb8)
-  
-    tbuff[tbuff:used() - 1] = 0x44
-    succ = onewire.tx(0, tbuff, false, true, true)
-    if not succ then
-        return
-    end
-
-    succ = onewire.reset(0, true)
-    if not succ then
-        return
-    end
-    if onewire.bit(0) > 0 then
-        log.info("温度转换完成")
-    end
-    tbuff[tbuff:used() - 1] = 0xbe
-    succ = onewire.tx(0, tbuff, false, true, true)
-    if not succ then
-        return
-    end
-    succ, rx_data = onewire.rx(0, 9, nil, rbuff, false, false, false)
-    crc8c = crypto.crc8(rbuff:toStr(0, 8), 0x31, 0, true)
-    if crc8c == rbuff[8] then
-        range = (rbuff[4] >> 5) & 0x03
-        -- rbuff[0] = 0xF8
-        -- rbuff[1] = 0xFF
-        t = rbuff:query(0, 2, false, true)
-        t = t * (5000 >> range)
-        t = t / 10000
-        log.info("当前温度", t,"原始值为",mcu.x32(rbuff[8]))
-    else
-        log.info("RAM DATA CRC校验不对", mcu.x32(crc8c), mcu.x32(rbuff[8]))
-        return
-    end
-
-end
-
---初始化当前pin脚上的18B20,并读取18B20的唯一识别ID
-local function test_ds18b20()
-    local succ, rx_data
-    local id = zbuff.create(8)
-    local crc8c
-    onewire.init(0) -- 初始化单总线
-    onewire.timing(0, false, 0, 500, 500, 15, 240, 70, 1, 15, 10, 2)
-    id:set() -- 清空id
-    succ, rx_data = onewire.rx(0, 8, 0x33, id, false, true, true)
-    if succ then
-        if id[0] == 0x28 then
-            crc8c = crypto.crc8(id:query(0, 7), 0x31, 0, true)
-            if crc8c == id[7] then
-                log.info("探测到DS18B20", "18B20对应唯一ID为", id:query(0, 7):toHex())
-                read_ds18b20(id)
-                -- log.info("DS18B20离线,重新探测")
-            else
-                log.info("ROM ID CRC校验不对", mcu.x32(crc8c), mcu.x32(id[7]))
-            end
-        else
-            log.info("ROM ID不正确", mcu.x32(id[0]))
-        end
-    else
-        log.info("没有检测到DS18B20")
-    end
-end
-
-local switchover_pin = gpio.PWR_KEY --选择powerkey作为切换18B20读数的信号脚
-
-gpio.debounce(switchover_pin, 100) --设置防抖
-
-
---设置powerkey按下和抬起的功能(18B20温度的取值再54pin和56pin之间切换)
---如果客户需要其他pin(22/54/56/78)则改动switchover_pin为用户需要的即可
---注意,由于powerkey不能做单边沿触发,所以一次按下和抬起的动作,会触发两次切换pin
---看效果的话,可以先一直按住powerkey几秒,再松开
-gpio.setup(switchover_pin, function()
-    log.info("Touch_pin", switchover_pin, "被触发")
-    log.info("当前单总线pad", onewire_pin)
-    if onewire_pin == 54 then
-        log.info("给PAD" .. onewire_pin .. "配置到GPIO3上去", pins.setup(onewire_pin, "GPIO3"))
-        log.info("给GPIO3设置为高电平输出模式",gpio.setup(3,1))--一定要执行gpio.setup,至于是哪种模式,用户根据自身需求
-        onewire_pin = 56
-    else
-        log.info("给PAD" .. onewire_pin .. "配置到GPIO7上去", pins.setup(onewire_pin, "GPIO7"))
-        log.info("给GPIO7设置为高电平输出模式",gpio.setup(7,1))
-
-        onewire_pin = 54
-    end
-    log.info("设置后单总线pad", onewire_pin)
-
-    onewire.deinit(0) -- 切换的时候一定要先关闭单总线,在读取的时候重新初始化
-
-    log.info("给" .. onewire_pin .. "配置到onewire上去", pins.setup(onewire_pin, "ONEWIRE"))
-    sys.publish("powerkey被按下")
-end, gpio.PULLUP, gpio.RISING)
-
-sys.taskInit(function()
-    while 1 do
-        -- sys.waitUntil("powerkey被按下")
-        log.info("1S后读取"..onewire_pin.."脚上的18B20")
-        sys.wait(1000)
-        log.info("开始读取18B20")
-        test_ds18b20()
-    end
-end)
-
-
-
-

+ 367 - 0
module/Air780EPM/demo/onewire/onewire_multi_app.lua

@@ -0,0 +1,367 @@
+--[[
+@module  onewire_multi_app
+@summary OneWire多DS18B20温度传感器应用演示模块(54和23切换版本)
+@version 1.0.0
+@date    2025.11.25
+@author  王棚嶙
+@usage
+本模块演示多DS18B20温度传感器的完整功能:
+1. 双传感器切换控制(引脚54和23)
+2. 电源管理(GPIO控制)
+3. 按键切换传感器
+4. 双路温度同时监测
+5. 使用引脚复用功能(pins.setup)
+]]
+
+log.info("onewire_multi_app", "多传感器模块版本: 1.0.0")
+
+-- 设置所有GPIO引脚电压为3.3V,确保DS18B20传感器正常供电
+pm.ioVol(pm.IOVOL_ALL_GPIO, 3300)
+
+-- 和GPIO31控制传感器电源使能,确保DS18B20供电正常
+gpio.setup(31, 1)
+
+-- 硬件配置(双设备模式:支持引脚54和23切换)
+local onewire_pin = 54
+local switchover_pin = gpio.PWR_KEY
+
+-- DS18B20命令定义
+local CMD_CONVERT_T = 0x44
+local CMD_READ_SCRATCHPAD = 0xBE
+local CMD_READ_ROM = 0x33
+
+-- 全局状态变量
+local pwr_key_pressed = false
+
+-- PWR_KEY按键中断处理函数
+-- 功能:处理引脚切换按键事件,设置标志位供主循环查询
+local function handle_pwr_key_interrupt()
+    pwr_key_pressed = true
+    log.info("onewire_multi_app", "切换按键被按下")
+end
+
+-- 初始化硬件配置
+local function init_hardware()
+    log.info("onewire_multi_app", "初始化硬件配置...")
+    
+    -- 配置PWR_KEY按键,使用上升沿触发并添加防抖
+    gpio.debounce(switchover_pin, 100)
+    gpio.setup(switchover_pin, handle_pwr_key_interrupt, gpio.PULLUP, gpio.RISING)
+    
+    -- 初始配置当前引脚为ONEWIRE功能
+    pins.setup(onewire_pin, "ONEWIRE")
+    
+    log.info("onewire_multi_app", "硬件初始化完成")
+    log.info("onewire_multi_app", "初始引脚: 引脚" .. onewire_pin .. " (ONEWIRE功能)")
+    log.info("onewire_multi_app", "切换按键: PWR_KEY")
+    log.info("onewire_multi_app", "支持引脚: 54 和 23 循环切换")
+    log.info("onewire_multi_app", "电源控制: GPIO31/GPIO2 (已设置为高电平)")
+    
+    return true
+end
+
+
+-- 时序要求:DS18B20上电后需要稳定时间,100ms延时确保电源稳定
+-- 技术背景:DS18B20在电源切换后需要tREC(恢复时间)完成内部初始化
+-- 实际测试:无延时可能导致设备检测失败或温度读取异常
+-- 建议值:最小50ms,推荐100ms以确保可靠性
+local function power_stabilization_delay()
+    log.info("onewire_multi_app", "电源稳定延时(确保DS18B20内部电路就绪)")
+    sys.wait(100)  -- DS18B20 tREC恢复时间,最小50ms,推荐100ms
+end
+
+-- 单总线分时使用引脚切换(同一条总线,分时复用)
+-- 核心逻辑:使用GPIO54和GPIO23两个引脚连接同一条OneWire总线,实现分时复用
+-- 应用场景:当需要在同一总线上分时访问不同设备时使用
+-- 技术原理:通过切换总线连接引脚,实现同一条物理总线的分时使用
+-- 切换效果:
+-- - GPIO54:当前时间段连接设备A(ROM ID: 28-9F-C4-93-00-00-00-14)
+-- - GPIO23:切换到时间段连接设备B(ROM ID: 28-59-F2-53-00-00-00-14)
+-- 注意:这不是多总线并行,而是单总线的分时复用策略
+local function switch_onewire_pin()
+    log.info("onewire_multi_app", "切换OneWire引脚...")
+    
+    -- 关闭当前OneWire总线
+    onewire.deinit(0)
+    
+    
+    -- 分时复用切换逻辑
+    -- 技术原理:将当前不使用的引脚配置为GPIO功能并输出高电平
+    -- 目的:确保非活动设备处于高阻态,避免干扰当前连接的设备
+    -- 电气特性:GPIO设置为开漏输出模式,高电平由上拉电阻提供
+    if onewire_pin == 54 then
+        -- 从54切换到23
+        -- 将PIN54配置为GPIO3功能,不再作为OneWire使用
+        log.info("onewire_multi_app", "将PIN54配置为GPIO3", pins.setup(54, "GPIO3"))
+        -- 设置GPIO3为高电平输出(开漏模式,高电平由上拉电阻提供)
+        log.info("onewire_multi_app", "将GPIO3设置为高电平输出(OneWire总线空闲状态)", gpio.setup(3, 1))
+        onewire_pin = 23
+        log.info("onewire_multi_app", "切换到引脚23")
+    else
+        -- 从23切换到54
+        -- 将PIN23配置为GPIO2功能,不再作为OneWire使用
+        log.info("onewire_multi_app", "将PIN23配置为GPIO2", pins.setup(23, "GPIO2"))
+        -- 设置GPIO2为高电平输出(开漏模式,高电平由上拉电阻提供)
+        log.info("onewire_multi_app", "将GPIO2设置为高电平输出(OneWire总线空闲状态)", gpio.setup(2, 1))
+        onewire_pin = 54
+        log.info("onewire_multi_app", "切换到引脚54")
+    end
+    
+    log.info("onewire_multi_app", "当前使用引脚:", onewire_pin)
+    
+    -- 配置新引脚为ONEWIRE功能
+    -- 分时复用原理:将选中的引脚配置为OneWire功能,连接到对应设备
+    -- 连接过程:先断开之前的设备连接,再连接新的设备
+    -- 电气特性:确保当前连接的设备具有完整的OneWire通信能力
+    log.info("onewire_multi_app", "将引脚" .. onewire_pin .. "配置为ONEWIRE功能", pins.setup(onewire_pin, "ONEWIRE"))
+    
+    log.info("onewire_multi_app", "引脚切换完成,当前使用: 引脚" .. onewire_pin)
+end
+
+-- 初始化OneWire总线
+local function init_onewire_bus()
+    log.info("onewire_multi_app", "初始化OneWire总线,通道: 0")
+    
+    -- 配置当前引脚
+    pins.setup(onewire_pin, "ONEWIRE")
+    
+    -- 初始化OneWire总线
+    onewire.init(0)
+    
+    -- 配置DS18B20标准时序参数
+    onewire.timing(0, false, 0, 500, 500, 15, 240, 70, 1, 15, 10, 2)
+    
+    log.info("onewire_multi_app", "OneWire总线初始化完成,通道: 0,引脚:" .. onewire_pin)
+    
+    return true
+end
+
+-- 检测DS18B20设备是否存在(分时复用场景)
+-- 分时逻辑:在当前连接的引脚上发送复位脉冲,检测该设备响应
+-- 单总线场景:只有当前连接的引脚上的设备会响应复位脉冲
+-- 返回值:true表示当前引脚连接的设备响应,false表示无设备响应
+local function detect_ds18b20_device()
+    log.info("onewire_multi_app", "检测DS18B20设备,引脚: " .. onewire_pin)
+    
+    -- 发送复位脉冲并检测设备
+    local present = onewire.reset(0, true)
+    
+    if present then
+        log.info("onewire_multi_app", "检测到DS18B20设备响应")
+        return true
+    else
+        log.warn("onewire_multi_app", "未检测到DS18B20设备响应")
+        return false
+    end
+end
+
+-- 读取DS18B20温度(单总线分时复用)
+-- 核心流程:读ROM ID → 选设备 → 温度转换 → 读数据 → CRC校验
+local function read_ds18b20_temperature()
+    log.info("onewire_multi_app", "开始读取DS18B20温度,引脚: " .. onewire_pin)
+    
+    local tbuff = zbuff.create(10)
+    local succ, crc8c, range, t
+    local rbuff = zbuff.create(9)
+    
+    -- 读取设备ROM ID(每个设备唯一)
+    log.info("onewire_multi_app", "读取设备ROM ID(64位唯一标识)")
+    
+    local id = zbuff.create(8)
+    id:set()
+    
+    succ, rx_data = onewire.rx(0, 8, 0x33, id, false, true, true)
+    if not succ then
+        log.warn("onewire_multi_app", "读取ROM ID失败")
+        return nil
+    end
+    
+    -- 检查设备类型码(DS18B20应为0x28)
+    if id[0] ~= 0x28 then
+        log.warn("onewire_multi_app", "非DS18B20设备,类型码:", mcu.x32(id[0]))
+        return nil
+    end
+    
+    -- CRC校验设备ID
+    crc8c = crypto.crc8(id:query(0, 7), 0x31, 0, true)
+    if crc8c ~= id[7] then
+        log.warn("onewire_multi_app", "ROM ID CRC校验不对", 
+                "计算值:", mcu.x32(crc8c), "期望值:", mcu.x32(id[7]))
+        log.info("onewire_multi_app", "完整ROM ID:", id:query(0, 7):toHex())
+        return nil
+    end
+    
+    log.info("onewire_multi_app", "ROM ID校验成功:", id:query(0, 7):toHex())
+    
+    -- 通过MATCH ROM选择设备(确保只选中目标设备)
+    log.info("onewire_multi_app", "开始温度转换(通过ROM匹配选择设备)")
+    
+    -- 构建命令缓冲区:MATCH ROM(0x55) + 目标设备ROM ID + 温度转换命令(0x44)
+    -- 0x55是MATCH ROM命令,后面必须跟64位目标设备的ROM ID
+    tbuff:write(0x55)     -- MATCH ROM命令
+    tbuff:copy(nil, id)  -- 复制64位ROM ID(确保选择正确的设备)
+    tbuff:write(0xb8)
+    tbuff[tbuff:used() - 1] = 0x44  -- CONVERT T温度转换命令
+    
+    succ = onewire.tx(0, tbuff, false, true, true)
+    if not succ then
+        log.warn("onewire_multi_app", "发送温度转换命令失败")
+        return nil
+    end
+
+    -- 第三步:等待转换完成
+    log.info("onewire_multi_app", "等待温度转换完成")
+    
+    -- 等待一段时间让转换完成
+    sys.wait(750)
+    
+    -- 发送复位脉冲检查设备
+    succ = onewire.reset(0, true)
+    if not succ then
+        log.warn("onewire_multi_app", "等待转换完成时设备未响应")
+        return nil
+    end
+    
+    -- 检查转换是否完成
+    if onewire.bit(0) > 0 then
+        log.info("onewire_multi_app", "温度转换完成")
+    end
+    
+    -- 第四步:读取温度数据
+    log.info("onewire_multi_app", "读取温度数据")
+    
+    -- 构建读取命令:匹配ROM(0x55) + ROM ID + 读取暂存器命令(0xBE)
+    tbuff[tbuff:used() - 1] = 0xbe
+    succ = onewire.tx(0, tbuff, false, true, true)
+    if not succ then
+        log.warn("onewire_multi_app", "发送读取命令失败")
+        return nil
+    end
+    
+    -- 接收9字节温度数据
+    succ, rx_data = onewire.rx(0, 9, nil, rbuff, false, false, false)
+    if not succ then
+        log.warn("onewire_multi_app", "温度数据接收失败")
+        return nil
+    end
+    
+    -- 第五步:CRC校验和温度计算
+    log.info("onewire_multi_app", "CRC校验和温度计算")
+    
+    -- CRC校验
+    crc8c = crypto.crc8(rbuff:toStr(0, 8), 0x31, 0, true)
+    if crc8c == rbuff[8] then
+        -- 计算温度值
+        range = (rbuff[4] >> 5) & 0x03
+        t = rbuff:query(0, 2, false, true)
+        t = t * (5000 >> range)
+        t = t / 10000
+        
+        -- 范围检查
+        if t >= -55.0 and t <= 125.0 then
+            log.info("onewire_multi_app", "温度读取成功:", string.format("%.2f°C", t))
+            return t
+        else
+            log.warn("onewire_multi_app", "温度值超出有效范围:", t)
+            return nil
+        end
+    else
+        log.warn("onewire_multi_app", "温度数据CRC校验不对", 
+                "计算值:", mcu.x32(crc8c), "期望值:", mcu.x32(rbuff[8]))
+        return nil
+    end
+end
+
+-- 简化版温度读取(用于快速测试)
+local function quick_read_ds18b20()
+    log.info("onewire_multi_app", "快速读取温度,引脚: " .. onewire_pin)
+    
+    -- 首先检测设备是否存在
+    if not detect_ds18b20_device() then
+        return nil
+    end
+    
+    -- 使用完整读取函数
+    return read_ds18b20_temperature()
+end
+
+-- 单总线分时复用主函数(同一条总线,分时访问不同设备)
+local function multi_sensor_app_main()
+    log.info("onewire_multi_app", "启动双传感器应用(引脚54和23)")
+    
+    -- 初始化硬件
+    if not init_hardware() then
+        log.error("onewire_multi_app", "硬件初始化失败,任务无法启动")
+        return
+    end
+    
+    -- 初始化OneWire总线
+    init_onewire_bus()
+    
+    -- 电源稳定延时:确保DS18B20内部电路就绪
+    power_stabilization_delay()
+    
+    -- 检测设备
+    local device_present = detect_ds18b20_device()
+    
+    if not device_present then
+        log.error("onewire_multi_app", "未检测到设备响应")
+        log.warn("onewire_multi_app", "硬件连接提示:")
+        log.warn("onewire_multi_app", "1. 传感器连接引脚54或23")
+        log.warn("onewire_multi_app", "2. 确保GPIO31/GPIO2已设置为高电平供电")
+        log.warn("onewire_multi_app", "3. 确保4.7kΩ上拉电阻正确安装")
+        log.warn("onewire_multi_app", "4. 检查传感器VDD、GND、DQ连接")
+        -- 关闭OneWire总线
+        onewire.deinit(0)
+        return
+    end
+    
+    log.info("onewire_multi_app", "开始双传感器连续监测...")
+    log.info("onewire_multi_app", "按PWR_KEY按键可切换引脚(54和23)")
+    
+  -- 主循环:按键切换设备,分时读取温度
+    local read_count = 0
+    local success_count = 0
+    
+    while true do
+        read_count = read_count + 1
+        
+        -- 检查按键状态
+        if pwr_key_pressed then
+            pwr_key_pressed = false
+            switch_onewire_pin()
+            
+            -- 重新初始化OneWire总线
+            init_onewire_bus()
+        end
+        
+        log.info("onewire_multi_app", "第" .. read_count .. "次读取,引脚:" .. onewire_pin)
+        
+        -- 尝试读取温度
+        local temperature = read_ds18b20_temperature()
+        
+        if temperature then
+            success_count = success_count + 1
+            log.info("onewire_multi_app", "引脚" .. onewire_pin .. "温度:", 
+                    string.format("%.2f°C", temperature), 
+                    "成功率:", string.format("%.1f%%", success_count/read_count*100))
+            
+            -- 简单的温度报警逻辑
+            if temperature > 30 then
+                log.warn("onewire_multi_app", "温度偏高:", string.format("%.2f°C", temperature))
+            elseif temperature < 10 then
+                log.warn("onewire_multi_app", "温度偏低:", string.format("%.2f°C", temperature))
+            end
+        else
+            log.warn("onewire_multi_app", "本次读取失败")
+            log.info("onewire_multi_app", "成功率:", string.format("%.1f%%", success_count/read_count*100))
+        end
+        
+        -- 等待下一次读取
+        sys.wait(2000)
+    end
+end
+log.info("onewire_multi_app", "双传感器应用模块加载完成(54和23切换)")
+-- 启动多传感器应用任务
+sys.taskInit(multi_sensor_app_main)
+

+ 0 - 107
module/Air780EPM/demo/onewire/onewire_single_18b20/main.lua

@@ -1,107 +0,0 @@
-PROJECT = "onewiredemo"
-VERSION = "1.0.0"
-sys = require("sys")
-log.style(1)
-
---[[
-接线说明:
-   DS18B20    Air780EPM
-1. GND    -> GND
-2. VDD    -> 3.3V
-3. DATA    -> GPIO2
-
-注意:
-1. 3.3v在老版本的开发板上没有引脚, 所以需要外接, 一定要确保共地
-2. ONEWIRE功能支持在4个引脚使用, 但硬件通道只有一个, 默认是GPIO2
-3. 如需切换到其他脚, 参考上一级目录下的onewire_multi_18b20_swich_read
-]]
-
-local function read_ds18b20(id)
-    local tbuff = zbuff.create(10)
-    local succ,crc8c,range,t
-    local rbuff = zbuff.create(9)
-    --如果有多个DS18B20,需要带上ID
-    tbuff:write(0x55)
-    tbuff:copy(nil, id)
-    tbuff:write(0xb8)
-    --如果只有1个DS18B20,就用无ID方式
-    --tbuff:write(0xcc,0xb8)
-    while true do
-        tbuff[tbuff:used() - 1] = 0x44
-        succ = onewire.tx(0, tbuff, false, true, true)
-        if not succ then
-            return
-        end
-        while true do
-            succ = onewire.reset(0, true)
-            if not succ then
-                return
-            end
-            if onewire.bit(0) > 0 then
-                log.info("温度转换完成")
-                break
-            end
-            sys.wait(10)
-        end
-        tbuff[tbuff:used() - 1] = 0xbe
-        succ = onewire.tx(0, tbuff, false, true, true)
-        if not succ then
-            return
-        end
-        succ,rx_data = onewire.rx(0, 9, nil, rbuff, false, false, false)
-        crc8c = crypto.crc8(rbuff:toStr(0,8), 0x31, 0, true)
-        if crc8c == rbuff[8] then
-            range = (rbuff[4] >> 5) & 0x03
-            -- rbuff[0] = 0xF8
-            -- rbuff[1] = 0xFF
-            t = rbuff:query(0,2,false,true)
-            t = t * (5000 >> range)
-            t = t / 10000
-            log.info(t)
-        else
-            log.info("RAM DATA CRC校验不对",  mcu.x32(crc8c), mcu.x32(rbuff[8]))
-            return
-        end
-        sys.wait(500)
-    end
-end
-
-local function test_ds18b20()
-    local succ,rx_data
-    local id = zbuff.create(8)
-
-    local crc8c
-    onewire.init(0)
-    onewire.timing(0, false, 0, 500, 500, 15, 240, 70, 1, 15, 10, 2)
-    while true do
-        id:set() --清空id
-        succ,rx_data = onewire.rx(0, 8, 0x33, id, false, true, true)
-        if succ then
-            if id[0] == 0x28 then
-                crc8c = crypto.crc8(id:query(0,7), 0x31, 0, true)
-                if crc8c == id[7] then
-                    log.info("探测到DS18B20", id:query(0, 7):toHex())
-                    read_ds18b20(id)
-                    log.info("DS18B20离线,重新探测")
-                else
-                    log.info("ROM ID CRC校验不对",  mcu.x32(crc8c), mcu.x32(id[7]))
-                end
-            else
-                log.info("ROM ID不正确", mcu.x32(id[0]))
-            end
-        end
-        log.info("没有检测到DS18B20, 5秒后重试")
-        sys.wait(5000)
-
-    end
-    
-
-end
-
-if onewire then
-    sys.taskInit(test_ds18b20)
-else
-    log.info("no onewire")
-end
-
-sys.run()

+ 203 - 0
module/Air780EPM/demo/onewire/onewire_single_app.lua

@@ -0,0 +1,203 @@
+--[[
+@module  onewire_single_app
+@summary OneWire单DS18B20温度传感器应用演示模块(GPIO2默认模式)
+@version 1.0.0
+@date    2025.11.25
+@author  王棚嶙
+@usage
+本模块演示单DS18B20温度传感器的完整功能:
+1. 使用GPIO2默认OneWire功能
+2. 硬件通道0模式,无需引脚复用
+3. 优化的时序参数和错误处理
+4. 连续温度监测
+5. 完整的OneWire API接口演示
+]]
+
+
+log.info("onewire_single_app", "单传感器模块版本:1.0.0")
+
+-- 设置所有GPIO引脚电压为3.3V,确保DS18B20传感器正常供电
+pm.ioVol(pm.IOVOL_ALL_GPIO, 3300)
+
+-- DS18B20命令定义
+local CMD_CONVERT_T = 0x44
+local CMD_READ_SCRATCHPAD = 0xBE
+local CMD_SKIP_ROM = 0xCC
+local CMD_READ_ROM = 0x33
+
+-- 单传感器应用主函数
+local function single_sensor_app_main()
+    log.info("onewire_single_app", "启动单传感器应用")
+    
+    -- 初始化OneWire总线(使用硬件通道0模式)
+    log.info("onewire_single_app", "初始化OneWire总线...")
+    onewire.init(0)
+    onewire.timing(0, false, 0, 500, 500, 15, 240, 70, 1, 15, 10, 2)
+    log.info("onewire_single_app", "OneWire总线初始化完成,使用GPIO2默认引脚")
+    
+    
+    
+    -- 检测DS18B20设备
+    log.info("onewire_single_app", "检测DS18B20设备...")
+    
+    local succ, rx_data
+    local id = zbuff.create(8)
+    local crc8c
+    
+    -- 清空ID缓冲区
+    id:set()
+    
+    -- 读取设备ROM ID(使用手动配置的引脚)
+    succ, rx_data = onewire.rx(0, 8, 0x33, id, false, true, true)
+    
+    local detected = false
+    local device_id = nil
+    
+    if succ then
+        -- 检查家族码(DS18B20为0x28)
+        if id[0] == 0x28 then
+            -- CRC校验
+            crc8c = crypto.crc8(id:query(0,7), 0x31, 0, true)
+            if crc8c == id[7] then
+                log.info("onewire_single_app", "探测到DS18B20", id:query(0, 7):toHex())
+                detected = true
+                device_id = id
+            else
+                log.warn("onewire_single_app", "ROM ID CRC校验不对", mcu.x32(crc8c), mcu.x32(id[7]))
+            end
+        else
+            log.warn("onewire_single_app", "ROM ID不正确", mcu.x32(id[0]))
+        end
+    else
+        log.warn("onewire_single_app", "未检测到DS18B20设备,请检查硬件连接")
+        log.info("onewire_single_app", "硬件连接提示:")
+        log.info("onewire_single_app", "1. DS18B20 DATA引脚 -> GPIO2 (默认OneWire功能)")
+        log.info("onewire_single_app", "2. 确保上拉电阻4.7kΩ连接DATA到3.3V")
+        log.info("onewire_single_app", "3. 使用硬件通道0模式,无需引脚复用配置")
+    end
+    
+    if not detected then
+        log.warn("onewire_single_app", "设备检测失败,任务无法启动")
+        log.info("onewire_single_app", "单传感器应用启动完成")
+        onewire.deinit(0)
+        return
+    end
+    
+    log.info("onewire_single_app", "开始连续温度监测...")
+    
+    -- 读取DS18B20温度数据(单总线单设备模式)
+    -- 与多传感器模式的对比:
+    -- - 单传感器:使用SKIP ROM(0xCC)直接通信,无需ROM ID
+    -- - 多传感器:使用MATCH ROM(0x55)选择设备,需要目标ROM ID
+    -- 
+    -- 单设备读取流程:
+    -- 1. SKIP ROM:发送0xCC命令,跳过ROM ID识别
+    -- 2. 温度转换:发送CONVERT T(0x44)启动温度转换
+    -- 3. 读取数据:发送READ SCRATCHPAD(0xBE)读取温度数据
+    -- 4. CRC校验:验证数据完整性
+    -- 
+    -- 优势:通信简单高效,无需设备寻址
+    -- 限制:只能用于总线上只有一个设备的场景
+    local function read_temperature(dev_id)
+        local tbuff = zbuff.create(10)
+        local rbuff = zbuff.create(9)
+        local succ, crc8c, range, t
+        
+        -- 发送SKIP ROM命令(0xCC) - 跳过ROM识别,直接与设备通信
+        -- 工作原理:所有设备都会响应SKIP ROM命令,无需发送64位ROM ID
+        -- 适用场景:总线上只有一个设备,无需设备寻址和选择
+        -- 优势:通信效率高,无需传输ROM ID,简化通信流程
+        -- 风险:如果总线上有多个设备,所有设备会同时响应,造成冲突
+        tbuff:write(0xcc)
+        
+        -- 发送温度转换命令
+        tbuff[tbuff:used() - 1] = 0x44
+        succ = onewire.tx(0, tbuff, false, true, true)
+        if not succ then
+            log.warn("onewire_single_app", "发送温度转换命令失败")
+            return nil
+        end
+        
+        -- 等待转换完成(使用位检测)
+        local conversion_complete = false
+        local max_wait = 100
+        local wait_count = 0
+        
+        while wait_count < max_wait do
+            succ = onewire.reset(0, true)
+            if not succ then
+                log.warn("onewire_single_app", "等待转换完成时设备未响应")
+                return nil
+            end
+            if onewire.bit(0) > 0 then
+                log.info("onewire_single_app", "温度转换完成")
+                conversion_complete = true
+                break
+            end
+            sys.wait(10)
+            wait_count = wait_count + 1
+        end
+        
+        if not conversion_complete then
+            log.warn("onewire_single_app", "温度转换超时")
+            return nil
+        end
+        
+        -- 读取温度数据
+        tbuff[tbuff:used() - 1] = 0xBE
+        succ = onewire.tx(0, tbuff, false, true, true)
+        if not succ then
+            log.warn("onewire_single_app", "发送读取命令失败")
+            return nil
+        end
+        
+        succ, rx_data = onewire.rx(0, 9, nil, rbuff, false, false, false)
+        if not succ or rbuff:used() ~= 9 then
+            log.warn("onewire_single_app", "温度数据读取失败")
+            return nil
+        end
+        
+        -- CRC校验
+        crc8c = crypto.crc8(rbuff:toStr(0,8), 0x31, 0, true)
+        if crc8c == rbuff[8] then
+            range = (rbuff[4] >> 5) & 0x03
+            t = rbuff:query(0,2,false,true)
+            t = t * (5000 >> range)
+            t = t / 10000
+            log.info("onewire_single_app", "温度读取成功:", string.format("%.2f°C", t))
+            return t
+        else
+            log.warn("onewire_single_app", "RAM DATA CRC校验不对", mcu.x32(crc8c), mcu.x32(rbuff[8]))
+            return nil
+        end
+    end
+    
+    -- 主循环 - 连续温度监测
+    while true do
+        local temperature = read_temperature(device_id)
+        
+        if temperature then
+            -- 简单的温度报警逻辑(示例)
+            if temperature > 30 then
+                log.warn("onewire_single_app", "温度偏高:", string.format("%.2f°C", temperature))
+            elseif temperature < 10 then
+                log.warn("onewire_single_app", "温度偏低:", string.format("%.2f°C", temperature))
+            else
+                log.info("onewire_single_app", "温度正常:", string.format("%.2f°C", temperature))
+            end
+        else
+            log.warn("onewire_single_app", "本次读取失败,继续下一次")
+        end
+        
+        -- 等待下一次读取
+        sys.wait(3000)
+    end
+    
+    log.info("onewire_single_app", "单传感器连续读取任务结束")
+    log.info("onewire_single_app", "单传感器应用启动完成")
+end
+
+log.info("onewire_single_app", "单传感器应用模块加载完成")
+
+-- 启动单传感器应用任务
+sys.taskInit(single_sensor_app_main)

+ 212 - 0
module/Air780EPM/demo/onewire/readme.md

@@ -0,0 +1,212 @@
+> 王棚嶙
+
+# OneWire综合演示项目
+
+## 功能模块介绍
+
+本demo演示了完整的DS18B20温度传感器OneWire单总线协议实现。项目采用模块化架构,分别实现单传感器和多传感器应用场景。
+
+1、main.lua:主程序入口 <br> 
+2、onewire_single_app.lua:演示单传感器功能模块(GPIO2默认OneWire功能,硬件通道0模式,3秒间隔连续监测)<br> 
+3、onewire_multi_app.lua:演示多传感器功能模块(引脚54/23切换,PWR_KEY按键控制,2秒间隔双路监测)<br> 
+
+## 演示功能概述
+
+###  主程序入口模块 (main.lua)
+
+- 初始化项目信息和版本号
+- 初始化看门狗,并定时喂狗
+- 启动一个循环定时器,每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况方便分析内存使用是否有异常
+- 加载onewire_single_app模块(通过require "onewire_single_app")
+- 加载onewire_multi_app模块(通过require "onewire_multi_app")
+
+### 单传感器模式 (onewire_single_app.lua)
+- 使用GPIO2默认OneWire功能,硬件通道0模式,无需引脚复用
+- 完整的CRC8数据校验机制,确保数据可靠性
+- 设备自动识别和ROM验证,支持设备类型检测
+- 3秒间隔连续温度监测,实时温度报警功能
+- zbuff缓冲区优化,提高数据传输效率
+
+
+### 多传感器模式 (onewire_multi_app.lua - 单总线多设备演示)
+
+
+**单总线多设备挂载原理**:
+1. **物理连接**:所有DS18B20的VDD、GND、DQ引脚分别并联到同一组单总线
+2. **设备识别**:每个DS18B20出厂时烧录了全球唯一的64位ROM ID
+3. **总线扫描**:主机发送SEARCH ROM(0xF0)命令发现总线上的所有设备
+4. **设备选择**:通过MATCH ROM(0x55)命令+目标设备ROM ID选择特定设备通信
+5. **分时操作**:每次只与一个设备通信,避免总线冲突
+
+**分时复用测试逻辑**(按键切换设备):
+- **引脚54**:连接设备A(ROM ID: 28-9F-C4-93-00-00-00-14)
+- **引脚23**:连接设备B(ROM ID: 28-59-F2-53-00-00-00-14)
+- **PWR_KEY按键**:按一次切换一个设备,实现同一条总线的分时使用
+
+**核心测试流程**:
+1. 初始化当前引脚的OneWire总线
+2. 发送SEARCH ROM命令扫描总线上的设备
+3. 读取并验证设备的64位ROM ID(家族码+序列号+CRC)
+4. 使用MATCH ROM(0x55)命令选择目标设备
+5. 发送温度转换命令(0x44)并等待完成
+6. 读取温度数据并进行CRC校验
+7. 输出设备ROM ID、温度值、读取成功率
+
+
+
+## 演示硬件环境
+1、Air780EPM核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、ds18b20传感器两个
+
+4、Air780EPM核心板和数据线的硬件接线方式为
+
+- Air780EPM核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到on一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+5、Air780EPM核心板和ds18b20传感器接线方式
+
+### 单传感器连接
+
+|   Air780EPM核心板     |    DS18B20传感器    |
+| --------------- | -------------------|
+|    VDD_EXT         |         VCC        |
+|    23/GPIO2     |         DQ         |
+|    GND          |         GND        |
+
+连接图:
+
+![image](https://docs.openluat.com/air780epm/luatos/app/driver/onewire/image/ce9d3b2c9b3a36c22388d710913668a7.jpg)
+
+### 多传感器连接
+
+
+|   Air780EPM核心板     |    DS18B20传感器1    |
+| --------------- | -------------------|
+|    VDD_EXT         |         VCC        |
+|    23/GPIO2     |         DQ         |
+|    任意GND          |         GND        |
+
+|   Air780EPM核心板     |    DS18B20传感器2    |
+| --------------- | -------------------|
+|    32/GPIO31         |         VCC        |
+|    54/CAM_MCLK     |         DQ         |
+|    任意GND          |         GND        |
+
+连接图:
+
+![image](https://docs.openluat.com/air780epm/luatos/app/driver/onewire/image/1957f5eb447654ec31894efb589e809b.jpg)
+
+## 演示软件环境
+
+1、Luatools下载调试工具: https://docs.openluat.com/air780epm/common/Luatools/
+
+2、内核固件版本:
+Air780EPM:https://docs.openluat.com/air780epm/luatos/firmware/version/
+
+## 演示核心步骤
+1、搭建好硬件环境
+
+2、通过Luatools将demo与固件烧录到核心板或开发板中
+
+3、烧录好后,板子开机将会在Luatools上看到如下打印:
+
+```lua
+(1)单传感器演示
+[2025-11-24 23:46:59.904][000000000.251] I/user.main onewire_demo 1.0.0
+[2025-11-24 23:46:59.932][000000000.258] I/user.onewire_single_app 单传感器模块版本: 002.002.000
+[2025-11-24 23:46:59.970][000000000.258] I/user.onewire_single_app 单传感器应用模块加载完成
+[2025-11-24 23:47:00.002][000000000.259] I/user.onewire_single_app 启动单传感器应用
+[2025-11-24 23:47:00.032][000000000.259] I/user.onewire_single_app 初始化OneWire总线...
+[2025-11-24 23:47:00.064][000000000.259] I/user.onewire_single_app OneWire总线初始化完成,使用GPIO2默认引脚
+[2025-11-24 23:47:00.096][000000000.259] I/user.onewire_single_app 检测DS18B20设备...
+[2025-11-24 23:47:00.124][000000000.267] I/user.onewire_single_app 探测到DS18B20 2859F253000000 14
+[2025-11-24 23:47:00.159][000000000.267] I/user.onewire_single_app 开始连续温度监测...
+[2025-11-24 23:47:00.186][000000000.276] I/user.onewire_single_app 温度转换完成
+[2025-11-24 23:47:00.216][000000000.289] I/user.onewire_single_app 温度读取成功: 85.00°C
+[2025-11-24 23:47:00.246][000000000.290] W/user.onewire_single_app 温度偏高: 85.00°C
+[2025-11-24 23:47:00.409][000000003.299] I/user.onewire_single_app 温度转换完成
+[2025-11-24 23:47:00.436][000000003.312] I/user.onewire_single_app 温度读取成功: 28.25°C
+[2025-11-24 23:47:00.465][000000003.313] I/user.onewire_single_app 温度正常: 28.25°C
+[2025-11-24 23:47:03.404][000000006.322] I/user.onewire_single_app 温度转换完成
+[2025-11-24 23:47:03.437][000000006.335] I/user.onewire_single_app 温度读取成功: 28.25°C
+[2025-11-24 23:47:03.469][000000006.335] I/user.onewire_single_app 温度正常: 28.25°C
+
+(2)双传感器演示
+[2025-11-24 23:49:45.699][000000000.260] I/user.onewire_multi_app 双传感器应用模块加载完成(54和23切换)
+[2025-11-24 23:49:45.732][000000000.261] I/user.onewire_multi_app 启动双传感器应用(引脚54和23)
+[2025-11-24 23:49:45.761][000000000.261] I/user.onewire_multi_app 初始化硬件配置...
+[2025-11-24 23:49:45.789][000000000.261] I/user.onewire_multi_app 硬件初始化完成
+[2025-11-24 23:49:45.822][000000000.262] I/user.onewire_multi_app 初始引脚: 引脚54 (ONEWIRE功能)
+[2025-11-24 23:49:45.859][000000000.262] I/user.onewire_multi_app 切换按键: PWR_KEY
+[2025-11-24 23:49:45.898][000000000.262] I/user.onewire_multi_app 支持引脚: 54 和 23 循环切换
+[2025-11-24 23:49:45.932][000000000.262] I/user.onewire_multi_app 电源控制: GPIO31/GPIO2 (已设置为高电平)
+[2025-11-24 23:49:45.963][000000000.262] I/user.onewire_multi_app 电源控制: 开启
+[2025-11-24 23:49:45.997][000000000.363] I/user.onewire_multi_app 初始化OneWire总线,通道: 0
+[2025-11-24 23:49:46.026][000000000.374] I/user.onewire_multi_app OneWire总线初始化完成,通道: 0,引脚:54
+[2025-11-24 23:49:46.059][000000000.573] I/user.onewire_multi_app 检测DS18B20设备,引脚: 54
+[2025-11-24 23:49:46.089][000000000.575] I/user.onewire_multi_app 检测到DS18B20设备响应
+[2025-11-24 23:49:46.117][000000000.575] I/user.onewire_multi_app 开始双传感器连续监测...
+[2025-11-24 23:49:46.148][000000000.575] I/user.onewire_multi_app 按PWR_KEY按键可切换引脚(54和23)
+[2025-11-24 23:49:46.181][000000000.575] I/user.onewire_multi_app 第1次读取,引脚:54
+[2025-11-24 23:49:46.212][000000000.575] I/user.onewire_multi_app 开始读取DS18B20温度,引脚: 54
+[2025-11-24 23:49:46.243][000000000.576] I/user.onewire_multi_app 读取设备ROM ID
+[2025-11-24 23:49:46.272][000000000.583] I/user.onewire_multi_app ROM ID校验成功: 289FC493000000 14
+[2025-11-24 23:49:46.297][000000000.583] I/user.onewire_multi_app 开始温度转换
+[2025-11-24 23:49:46.325][000000000.590] I/user.onewire_multi_app 等待温度转换完成
+[2025-11-24 23:49:46.401][000000001.341] I/user.onewire_multi_app 温度转换完成
+[2025-11-24 23:49:46.433][000000001.342] I/user.onewire_multi_app 读取温度数据
+[2025-11-24 23:49:46.466][000000001.354] I/user.onewire_multi_app CRC校验和温度计算
+[2025-11-24 23:49:46.499][000000001.355] I/user.onewire_multi_app 温度读取成功: 27.44°C
+[2025-11-24 23:49:46.530][000000001.355] I/user.onewire_multi_app 引脚54温度: 27.44°C 成功率: 100.0%
+[2025-11-24 23:49:46.630][000000003.355] I/user.onewire_multi_app 第2次读取,引脚:54
+[2025-11-24 23:49:46.665][000000003.356] I/user.onewire_multi_app 开始读取DS18B20温度,引脚: 54
+[2025-11-24 23:49:46.698][000000003.356] I/user.onewire_multi_app 读取设备ROM ID
+[2025-11-24 23:49:46.727][000000003.363] I/user.onewire_multi_app ROM ID校验成功: 289FC493000000 14
+[2025-11-24 23:49:46.765][000000003.363] I/user.onewire_multi_app 开始温度转换
+[2025-11-24 23:49:46.799][000000003.371] I/user.onewire_multi_app 等待温度转换完成
+[2025-11-24 23:49:46.982][000000004.122] I/user.onewire_multi_app 温度转换完成
+[2025-11-24 23:49:47.010][000000004.123] I/user.onewire_multi_app 读取温度数据
+[2025-11-24 23:49:47.043][000000004.135] I/user.onewire_multi_app CRC校验和温度计算
+[2025-11-24 23:49:47.077][000000004.136] I/user.onewire_multi_app 温度读取成功: 27.50°C
+[2025-11-24 23:49:47.110][000000004.137] I/user.onewire_multi_app 引脚54温度: 27.50°C 成功率: 100.0%
+[2025-11-24 23:49:48.999][000000006.137] I/user.onewire_multi_app 第3次读取,引脚:54
+[2025-11-24 23:49:49.030][000000006.138] I/user.onewire_multi_app 开始读取DS18B20温度,引脚: 54
+[2025-11-24 23:49:49.061][000000006.138] I/user.onewire_multi_app 读取设备ROM ID
+[2025-11-24 23:49:49.094][000000006.145] I/user.onewire_multi_app ROM ID校验成功: 289FC493000000 14
+[2025-11-24 23:49:49.124][000000006.145] I/user.onewire_multi_app 开始温度转换
+[2025-11-24 23:49:49.154][000000006.153] I/user.onewire_multi_app 等待温度转换完成
+[2025-11-24 23:49:49.778][000000006.904] I/user.onewire_multi_app 温度转换完成
+[2025-11-24 23:49:49.806][000000006.905] I/user.onewire_multi_app 读取温度数据
+[2025-11-24 23:49:49.836][000000006.917] I/user.onewire_multi_app CRC校验和温度计算
+[2025-11-24 23:49:49.866][000000006.918] I/user.onewire_multi_app 温度读取成功: 27.50°C
+[2025-11-24 23:49:49.907][000000006.919] I/user.onewire_multi_app 引脚54温度: 27.50°C 成功率: 100.0%
+
+(3)双传感器按键切换演示
+[2025-11-24 23:49:51.147][000000008.278] I/user.onewire_multi_app 切换按键被按下
+[2025-11-24 23:49:51.783][000000008.919] I/user.onewire_multi_app 切换OneWire引脚...
+[2025-11-24 23:49:51.821][000000008.939] I/user.onewire_multi_app 将PAD54配置为GPIO3 true
+[2025-11-24 23:49:51.856][000000008.940] I/user.onewire_multi_app 将GPIO3设置为高电平输出 function: 0C7F4A10
+[2025-11-24 23:49:51.897][000000008.940] I/user.onewire_multi_app 切换到引脚23
+[2025-11-24 23:49:51.933][000000008.940] I/user.onewire_multi_app 当前使用引脚: 23
+[2025-11-24 23:49:51.965][000000008.941] I/user.onewire_multi_app 将引脚23配置为ONEWIRE功能 true
+[2025-11-24 23:49:51.994][000000008.961] I/user.onewire_multi_app 引脚切换完成,当前使用: 引脚23
+[2025-11-24 23:49:52.324][000000009.461] I/user.onewire_multi_app 初始化OneWire总线,通道: 0
+[2025-11-24 23:49:52.356][000000009.471] I/user.onewire_multi_app OneWire总线初始化完成,通道: 0,引脚:23
+[2025-11-24 23:49:52.431][000000009.571] I/user.onewire_multi_app 第4次读取,引脚:23
+[2025-11-24 23:49:52.474][000000009.571] I/user.onewire_multi_app 开始读取DS18B20温度,引脚: 23
+[2025-11-24 23:49:52.519][000000009.572] I/user.onewire_multi_app 读取设备ROM ID
+[2025-11-24 23:49:52.563][000000009.579] I/user.onewire_multi_app ROM ID校验成功: 2859F253000000 14
+[2025-11-24 23:49:52.593][000000009.579] I/user.onewire_multi_app 开始温度转换
+[2025-11-24 23:49:52.622][000000009.587] I/user.onewire_multi_app 等待温度转换完成
+[2025-11-24 23:49:53.203][000000010.338] I/user.onewire_multi_app 温度转换完成
+[2025-11-24 23:49:53.239][000000010.339] I/user.onewire_multi_app 读取温度数据
+[2025-11-24 23:49:53.272][000000010.351] I/user.onewire_multi_app CRC校验和温度计算
+[2025-11-24 23:49:53.302][000000010.352] I/user.onewire_multi_app 温度读取成功: 27.81°C
+[2025-11-24 23:49:53.332][000000010.353] I/user.onewire_multi_app 引脚23温度: 27.81°C 成功率: 100.0%
+
+
+```

+ 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

Неке датотеке нису приказане због велике количине промена