소스 검색

merge:合并 branch 'origin/master' into feat/freetypefont

zengeshuai 4 달 전
부모
커밋
c90705d900
100개의 변경된 파일9304개의 추가작업 그리고 9198개의 파일을 삭제
  1. 1 1
      components/airlink/include/luat_airlink.h
  2. 7 0
      components/airlink/include/luat_airlink_fota.h
  3. 41 15
      components/airlink/src/driver/luat_airlink_drv_uart.c
  4. 56 1
      components/airlink/src/luat_airlink_fota.c
  5. 84 30
      components/airlink/src/task/luat_airlink_spi_master_task.c
  6. 3 3
      components/fatfs/diskio.h
  7. 241 183
      components/fatfs/ff.c
  8. 91 73
      components/fatfs/ff.h
  9. 27 10
      components/fatfs/ffconf.h
  10. 3600 6381
      components/fatfs/ffunicode.c
  11. 2 2
      components/io_queue/luat_lib_io_queue.c
  12. 37 17
      components/lcd/luat_lcd.c
  13. 3 0
      components/lcd/luat_lcd_jd9261t.c
  14. 2 0
      components/lcd/luat_lcd_sh8601z.c
  15. 0 1
      components/lfs/lfs_sfd.c
  16. 4 0
      components/mobile/luat_lib_mobile.c
  17. 6 0
      components/mobile/luat_mobile.h
  18. 99 0
      components/mobile/luat_mobile_common.c
  19. 3 0
      components/mreport/src/luat_mreport.c
  20. 15 15
      components/network/adapter/luat_network_adapter.c
  21. 13 4
      components/network/errdump/luat_lib_errdump.c
  22. 39 18
      components/network/httpsrv/src/luat_httpsrv_lwip.c
  23. 0 327
      components/network/posix/luat_network_posix.c
  24. 0 20
      components/network/posix/luat_network_posix.h
  25. 17 9
      components/pins/binding/luat_lib_pins.c
  26. 6 0
      components/pins/src/luat_pins.c
  27. 48 56
      components/sms/binding/luat_lib_sms.c
  28. 622 636
      components/tp/cst92xx_fw.h
  29. 2 2
      components/tp/luat_lib_tp.c
  30. 3 4
      components/tp/luat_tp.c
  31. 236 15
      components/tp/luat_tp_cst9220.c
  32. 10 1
      components/wlan/luat_lib_wlan.c
  33. 6 1
      lua/src/loslib.c
  34. 4 0
      luat/include/luat_sfd.h
  35. 1 1
      luat/modules/luat_lib_mcu.c
  36. 9 0
      luat/modules/luat_lib_rtos.c
  37. 2 2
      module/Air780E/demo/fota2/main.lua
  38. 0 161
      module/Air780E/demo/iotcloud/oneNET/main.lua
  39. 58 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/audio_drv.lua
  40. 246 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/cc_app.lua
  41. 78 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/main.lua
  42. 240 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/pins_Air780EHM.json
  43. 210 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/readme.md
  44. 58 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/audio_drv.lua
  45. 246 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/cc_app.lua
  46. 79 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/main.lua
  47. 198 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/readme.md
  48. 0 126
      module/Air780EHM_Air780EHV_Air780EGH/demo/CC/main.lua
  49. 16 7
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/AirGPIO_1000.lua
  50. 20 5
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/gpio_app.lua
  51. 14 6
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/main.lua
  52. 20 8
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/readme.md
  53. 15 11
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/AirKEY_1000.lua
  54. 21 13
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/key_app.lua
  55. 14 5
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/main.lua
  56. 38 8
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/readme.md
  57. 16 6
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/AirSHT30_1000.lua
  58. 14 5
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/main.lua
  59. 19 8
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/readme.md
  60. 14 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/sht30_app.lua
  61. 4 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSPINORFLASH_1000/lf_fs.lua
  62. 8 3
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSPINORFLASH_1000/main.lua
  63. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSPINORFLASH_1000/ram_spi.lua
  64. 55 3
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSPINORFLASH_1000/readme.md
  65. 219 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSPINORFLASH_1000/sfud_test.lua
  66. 16 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/AirVOC_1000.lua
  67. 15 6
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/main.lua
  68. 20 7
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/readme.md
  69. 14 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/voc_app.lua
  70. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/10.amr
  71. 92 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/main.lua
  72. 240 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/pins_air780ehm.json
  73. 101 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_file.lua
  74. 108 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_stream.lua
  75. 104 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_tts.lua
  76. 212 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/readme.md
  77. 84 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/record_file.lua
  78. 55 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/record_stream.lua
  79. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/sample-6s.mp3
  80. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/test.pcm
  81. BIN
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/10.amr
  82. 20 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/main.lua
  83. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_file.lua
  84. 1 1
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_stream.lua
  85. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_tts.lua
  86. 204 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/readme.md
  87. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/record_file.lua
  88. 0 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/record_stream.lua
  89. BIN
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/sample-6s.mp3
  90. BIN
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/test.pcm
  91. 0 541
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/exaudio.lua
  92. 0 106
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/readme.md
  93. BIN
      module/Air780EHM_Air780EHV_Air780EGH/demo/audio/音频硬件框架.png
  94. 124 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/bit64_app.lua
  95. 55 98
      module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/main.lua
  96. 85 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/readme.md
  97. 282 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/crypto_app.lua
  98. 55 226
      module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/main.lua
  99. 152 0
      module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/readme.md
  100. 2 2
      module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/iot_server/air_srv_fota.lua

+ 1 - 1
components/airlink/include/luat_airlink.h

@@ -79,7 +79,7 @@ void luat_airlink_task_start(void);
 void luat_airlink_print_buff(const char* tag, uint8_t* buff, size_t len);
 void luat_airlink_on_data_recv(uint8_t *data, size_t len);
 
-void airlink_wait_for_slave_ready(size_t timeout_ms);
+void airlink_wait_for_slave_reply(size_t timeout_ms);
 void airlink_transfer_and_exec(uint8_t *txbuff, uint8_t *rxbuff);
 void airlink_wait_and_prepare_data(uint8_t *txbuff);
 

+ 7 - 0
components/airlink/include/luat_airlink_fota.h

@@ -3,6 +3,13 @@
 #include "luat_base.h"
 #include "luat_fs.h"
 
+#define AIRLINK_FOTA_SUCCESS            (0)
+#define AIRLINK_FOTA_NO_MEM             (1)
+#define AIRLINK_FOTA_OPEN_FILE_FAIL     (2)
+
+
+
+
 typedef struct luat_airlink_fota {
     uint32_t state;
     size_t total_size;

+ 41 - 15
components/airlink/src/driver/luat_airlink_drv_uart.c

@@ -37,22 +37,48 @@ int luat_airlink_drv_uart_setup(luat_uart_t* conf) {
 }
 
 int luat_airlink_drv_uart_write(int uart_id, void* data, size_t length) {
-    // LLOGD("执行uart write %d %p %d", uart_id, data, length);
-    uint64_t luat_airlink_next_cmd_id = luat_airlink_get_next_cmd_id();
-    size_t total_len = length + sizeof(luat_airlink_cmd_t) + 8 + 1;
-    airlink_queue_item_t item = {.len = total_len};
-    luat_airlink_cmd_t* cmd = luat_airlink_cmd_new(0x401, length + 1 + 8);
-    if (cmd == NULL) {
-        LLOGE("内存分配失败!!");
-        return 0;
+    if(length <= 1536) {
+        // LLOGD("执行uart write %d %p %d", uart_id, data, length);
+        uint64_t luat_airlink_next_cmd_id = luat_airlink_get_next_cmd_id();
+        size_t total_len = length + sizeof(luat_airlink_cmd_t) + 8 + 1;
+        airlink_queue_item_t item = {.len = total_len};
+        luat_airlink_cmd_t* cmd = luat_airlink_cmd_new(0x401, length + 1 + 8);
+        if (cmd == NULL) {
+            LLOGE("内存分配失败!!");
+            return 0;
+        }
+        memcpy(cmd->data, &luat_airlink_next_cmd_id, 8);
+        uint8_t tmp = (uint8_t)uart_id;
+        memcpy(cmd->data + 8, &tmp, 1);
+        memcpy(cmd->data + 9, data, length);
+        item.cmd = cmd;
+        int ret = luat_airlink_queue_send(LUAT_AIRLINK_QUEUE_CMD, &item);
+        return ret == 0 ? length : 0;
+    } else {
+        // 如果数据长度大于1536,分段发送
+        size_t segment_size = 1535;  // 使用1535避免无限递归
+        const char* segment_data = (const char*)data;
+        size_t remaining = length;
+        size_t sent_total = 0;
+
+        while (remaining > 0) {
+            size_t current_segment = (remaining > segment_size) ? segment_size : remaining;
+
+            int ret = luat_airlink_drv_uart_write(uart_id, (void*)segment_data, current_segment);
+            if (ret <= 0) {
+                return sent_total;
+            }
+            sent_total += ret;
+            segment_data += ret;
+            remaining -= ret;
+
+            if (ret < current_segment) {
+                break;
+            }
+        }
+
+        return sent_total;
     }
-    memcpy(cmd->data, &luat_airlink_next_cmd_id, 8);
-    uint8_t tmp = (uint8_t)uart_id;
-    memcpy(cmd->data + 8, &tmp, 1);
-    memcpy(cmd->data + 9, data, length);
-    item.cmd = cmd;
-    int ret = luat_airlink_queue_send(LUAT_AIRLINK_QUEUE_CMD, &item);
-    return ret == 0 ? length : 0;
 }
 
 int luat_airlink_drv_uart_close(int uart_id) {

+ 56 - 1
components/airlink/src/luat_airlink_fota.c

@@ -8,6 +8,46 @@
 #define LUAT_LOG_TAG "airlink.fota"
 #include "luat_log.h"
 
+#ifdef __LUATOS__
+#include "luat_msgbus.h"
+/*
+@sys_pub airlink
+AIRLINK升级结束消息 2025/10/24启用
+AIRLINK_SFOTA_DONE
+@bool result, 升级成功为true,否则为false
+@string reason, 失败原因,当前取值有"no_memory" 内存不足, "file_error" 文件打开异常
+@usage
+-- 订阅式
+sys.subscribe("AIRLINK_SFOTA_DONE", function(result, reason)
+    log.info("airlink fota", result, reason)
+end)
+*/
+static int airlink_sfota_lua_cb(lua_State *L, void *ptr)
+{
+    rtos_msg_t *msg = (rtos_msg_t *)lua_topointer(L, -1);
+    lua_getglobal(L, "sys_pub");
+    lua_pushstring(L, "AIRLINK_SFOTA_DONE");
+    lua_pushboolean(L, msg->arg1 == AIRLINK_FOTA_SUCCESS ? 1 : 0);
+    switch (msg->arg1)
+    {
+    case AIRLINK_FOTA_SUCCESS:
+        lua_call(L, 2, 0);
+        break;
+    case AIRLINK_FOTA_NO_MEM:
+        lua_pushstring(L, "no_memory");
+        lua_call(L, 3, 0);
+        break;
+    case AIRLINK_FOTA_OPEN_FILE_FAIL:
+        lua_pushstring(L, "file_error");
+        lua_call(L, 3, 0);
+        break;
+    default:
+        break;
+    }
+    return 0;
+}
+#endif
+
 luat_airlink_fota_t *g_airlink_fota;
 
 int luat_airlink_fota_init(luat_airlink_fota_t *ctx)
@@ -60,7 +100,7 @@ static void pack_and_send(uint16_t cmd_id, uint8_t *data, size_t len)
     luat_airlink_data_pack(cmd, sizeof(luat_airlink_cmd_t) + len, s_airlink_fota_txbuff);
     // LLOGD("发送sFOTA %d %d", cmd->cmd, cmd->len);
     airlink_transfer_and_exec(s_airlink_fota_txbuff, s_airlink_fota_rxbuff);
-    airlink_wait_for_slave_ready(5000); // 等待从机回复完成接收
+    airlink_wait_for_slave_reply(5000); // 等待从机回复完成接收
 
     memset(s_airlink_fota_txbuff, 0, AIRLINK_SFOTA_BUFF_SIZE);
 }
@@ -69,6 +109,7 @@ void airlink_sfota_exec(void)
 {
     size_t wait_timeout = 0;
     int ret = 0;
+    uint8_t fota_ret = AIRLINK_FOTA_SUCCESS;
     g_airlink_fota->total_size = luat_fs_fsize(g_airlink_fota->path);
     LLOGI("开始执行sFOTA file size %ld", g_airlink_fota->total_size);
     uint32_t tmpv = 0;
@@ -77,6 +118,7 @@ void airlink_sfota_exec(void)
     if (fd == NULL)
     {
         LLOGE("打开sFOTA文件失败 %s", g_airlink_fota->path);
+        fota_ret = AIRLINK_FOTA_OPEN_FILE_FAIL;
         goto clean;
     }
     if (s_airlink_fota_txbuff == NULL)
@@ -87,6 +129,7 @@ void airlink_sfota_exec(void)
         if (s_airlink_fota_txbuff == NULL || s_airlink_fota_rxbuff == NULL || s_airlink_fota_cmdbuff == NULL)
         {
             LLOGE("申请sFOTA内存失败");
+            fota_ret = AIRLINK_FOTA_NO_MEM;
             goto clean;
         }
         memset(s_airlink_fota_txbuff, 0, AIRLINK_SFOTA_BUFF_SIZE);
@@ -187,6 +230,8 @@ void airlink_sfota_exec(void)
     luat_rtos_task_sleep(wait_timeout);
     LLOGI("FOTA执行完毕");
 
+
+
 clean:
     g_airlink_fota->state = 0;
     if (fd)
@@ -208,5 +253,15 @@ clean:
         luat_heap_opt_free(AIRLINK_MEM_TYPE, s_airlink_fota_cmdbuff);
         s_airlink_fota_cmdbuff = NULL;
     }
+
+#ifdef __LUATOS__
+    rtos_msg_t msg = {
+        .handler = airlink_sfota_lua_cb,
+        .arg1 = fota_ret,
+        .arg2 = 0,
+        .ptr = NULL
+    };
+    luat_msgbus_put(&msg, 0);
+#endif
     return;
 }

+ 84 - 30
components/airlink/src/task/luat_airlink_spi_master_task.c

@@ -41,11 +41,15 @@ static uint8_t thread_rdy;
 static uint8_t spi_rdy;
 static luat_rtos_task_handle spi_task_handle;
 
+#if defined(LUAT_USE_AIRLINK_EXEC_MOBILE)
+extern luat_airlink_dev_info_t g_airlink_self_dev_info;
+#endif
 static uint8_t basic_info[256];
 
 // static uint32_t is_waiting_queue = 0;
 
 static luat_rtos_queue_t evt_queue;
+static luat_rtos_queue_t rdy_evt_queue;  // 专门用于RDY事件(id=6)的队列
 
 extern luat_airlink_irq_ctx_t g_airlink_irq_ctx;
 extern luat_airlink_irq_ctx_t g_airlink_wakeup_irq_ctx;
@@ -53,6 +57,25 @@ extern luat_airlink_irq_ctx_t g_airlink_wakeup_irq_ctx;
 luat_airlink_irq_ctx_t g_airlink_irq_ctx;
 luat_airlink_irq_ctx_t g_airlink_wakeup_irq_ctx;
 
+// RDY引脚中断等待相关变量
+static volatile uint8_t rdy_ready_flag = 0;           // RDY就绪标志
+
+// RDY引脚下降沿中断处理函数,设置就绪标志并发送事件
+__USER_FUNC_IN_RAM__ static int rdy_pin_irq_handler(void* param)
+{
+    // 设置RDY就绪标志
+    if (rdy_evt_queue == NULL) {
+        return 0;
+    }
+    rdy_ready_flag = 1;
+    // 发送通知事件,告知任务RDY已就绪
+    luat_event_t evt = {.id = 6};
+    luat_rtos_queue_send(rdy_evt_queue, &evt, sizeof(evt), 0);
+    
+    // LLOGD("RDY中断触发,设置就绪标志");
+    return 0;
+}
+
 __USER_FUNC_IN_RAM__ static int slave_irq_cb(void *data, void *args)
 {
     uint32_t len = 0;
@@ -116,22 +139,31 @@ void luat_airlink_spi_master_pin_setup(void)
         .CPHA = 1,
         .CPOL = 1,
         .dataw = 8,
-        .bit_dict = 0,
+        .bit_dict = 1, // MSB, 大部分平台也只支持MSB
         .master = 1,
         .mode = 1, // mode设置为1,全双工
         .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);
 
     luat_spi_setup(&spi_conf);
     luat_gpio_cfg_t gpio_cfg = {0};
 
     // 从机准备好脚
+    // luat_gpio_set_default_cfg(&gpio_cfg);
+    // gpio_cfg.pin = AIRLINK_SPI_RDY_PIN;
+    // gpio_cfg.mode = LUAT_GPIO_INPUT;
+    // gpio_cfg.irq_type = LUAT_GPIO_FALLING_IRQ;
+    // gpio_cfg.pull = 0;
+    // luat_gpio_open(&gpio_cfg);
+
+    // 设置RDY引脚中断,
     luat_gpio_set_default_cfg(&gpio_cfg);
     gpio_cfg.pin = AIRLINK_SPI_RDY_PIN;
-    gpio_cfg.mode = LUAT_GPIO_INPUT;
-    gpio_cfg.irq_type = LUAT_GPIO_FALLING_IRQ;
-    gpio_cfg.pull = 0;
+    gpio_cfg.mode = LUAT_GPIO_IRQ;
+    gpio_cfg.irq_type = LUAT_GPIO_FALLING_IRQ;  // 下降沿中断
+    gpio_cfg.pull = LUAT_GPIO_PULLUP;
+    gpio_cfg.irq_cb = rdy_pin_irq_handler;
     luat_gpio_open(&gpio_cfg);
 
     // CS片选脚
@@ -187,7 +219,21 @@ __USER_FUNC_IN_RAM__ void airlink_transfer_and_exec(uint8_t *txbuff, uint8_t *rx
     airlink_link_data_t *link = NULL;
 
     g_airlink_statistic.tx_pkg.total++;
+    // 拉低片选, 准备发送数据
+    luat_gpio_set(AIRLINK_SPI_CS_PIN, 0);
+    // 发送数据
     luat_spi_transfer(MASTER_SPI_ID, (const char *)txbuff, TEST_BUFF_SIZE, (char *)rxbuff, TEST_BUFF_SIZE);
+    // 拉高片选之前,先检查一下是否有RDY事件未处理,如果有,则全部清除
+    size_t qlen = 0;
+    luat_rtos_queue_get_cnt(rdy_evt_queue, &qlen);
+    if (qlen > 0) {
+        // LLOGW("发送数据后发现有%d个RDY事件未处理", (int)qlen);
+        // 清除掉这些冗余的RDY事件
+        luat_event_t evt = {0};
+        luat_rtos_queue_recv(rdy_evt_queue, &evt, sizeof(evt), 0);
+    }
+    rdy_ready_flag = 0;
+    // 拉高片选, 数据发送完毕
     luat_gpio_set(AIRLINK_SPI_CS_PIN, 1);
     // luat_airlink_print_buff("RX", rxbuff, 32);
     // 对接收到的数据进行解析
@@ -209,30 +255,16 @@ __USER_FUNC_IN_RAM__ void airlink_transfer_and_exec(uint8_t *txbuff, uint8_t *rx
     }
 }
 
-__USER_FUNC_IN_RAM__ void airlink_wait_for_slave_ready(size_t timeout_ms)
+__USER_FUNC_IN_RAM__ void airlink_wait_for_slave_reply(size_t timeout_ms)
 {
-    luat_gpio_set(AIRLINK_SPI_CS_PIN, 0); // 拉低片选, 等待从机就绪
-    int tmpval = 0;
-    for (size_t i = 0; i < timeout_ms; i++)
+    luat_event_t event = {0};
+    while (1)
     {
-        tmpval = luat_gpio_get(AIRLINK_SPI_RDY_PIN);
-        if (tmpval == 1)
-        {
-            g_airlink_statistic.wait_rdy.total++;
-            if (g_airlink_debug)
-            {
-                tnow = luat_mcu_tick64_ms();
-                if (tnow - warn_slave_no_ready > 1000)
-                {
-                    warn_slave_no_ready = tnow;
-                    LLOGD("从机未就绪,等1ms");
-                }
-            }
-            luat_rtos_task_sleep(1);
-            continue;
+        event.id = 0;
+        luat_rtos_queue_recv(rdy_evt_queue, &event, sizeof(luat_event_t), 10);
+        if (event.id != 0) {
+            break;
         }
-        // LLOGD("从机已就绪!! %s %s", __DATE__, __TIME__);
-        break;
     }
 }
 
@@ -321,6 +353,9 @@ __USER_FUNC_IN_RAM__ void airlink_wait_and_prepare_data(uint8_t *txbuff)
     else
     {
         // LLOGD("填充PING数据");
+        #if defined(LUAT_USE_AIRLINK_EXEC_MOBILE)
+        memcpy(basic_info + sizeof(luat_airlink_cmd_t), &g_airlink_self_dev_info, sizeof(g_airlink_self_dev_info));
+        #endif
         luat_airlink_data_pack(basic_info, sizeof(basic_info), txbuff);
         queue_emtry_counter ++;
     }
@@ -334,11 +369,27 @@ __USER_FUNC_IN_RAM__ static void on_link_data_notify(airlink_link_data_t* link)
     }
 }
 
+#if defined(LUAT_USE_AIRLINK_EXEC_MOBILE)
+static void send_devinfo_update_evt(void) {
+    airlink_queue_item_t item = {0};
+    // 发送空消息, 会自动转为devinfo消息
+    luat_airlink_queue_send(LUAT_AIRLINK_QUEUE_CMD, &item); 
+}
+#endif
 
 __USER_FUNC_IN_RAM__ static void spi_master_task(void *param)
 {
     // int i;
     // luat_event_t event = {0};
+    #if defined(LUAT_USE_AIRLINK_EXEC_MOBILE)
+    luat_airlink_cmd_t *cmd = (luat_airlink_cmd_t *)basic_info;
+    cmd->cmd = 0x10;
+    cmd->len = 128;
+
+    extern void luat_airlink_devinfo_init();
+    luat_airlink_devinfo_init(send_devinfo_update_evt);
+    #endif
+
     luat_rtos_task_sleep(5); // 等5ms
     luat_airlink_spi_master_pin_setup();
     g_airlink_newdata_notify_cb = on_newdata_notify;
@@ -355,13 +406,13 @@ __USER_FUNC_IN_RAM__ static void spi_master_task(void *param)
 
         memset(s_txbuff, 0, TEST_BUFF_SIZE);
         airlink_wait_and_prepare_data(s_txbuff);
-        // slave_rdy = 0;
-        airlink_wait_for_slave_ready(1000); // 最多等1秒
 
+        // 立即发送数据给从机
         airlink_transfer_and_exec(s_txbuff, s_rxbuff);
-
         memset(s_rxbuff, 0, TEST_BUFF_SIZE);
-        // start = 0;
+
+        // 发送完成后,等待从机的响应/确认
+        airlink_wait_for_slave_reply(5000);
     }
 }
 
@@ -376,7 +427,10 @@ void luat_airlink_start_master(void)
     s_txbuff = luat_heap_opt_malloc(AIRLINK_MEM_TYPE, TEST_BUFF_SIZE);
     s_rxbuff = luat_heap_opt_malloc(AIRLINK_MEM_TYPE, TEST_BUFF_SIZE);
 
+    // 创建通用事件队列 (id=2,3等)
     luat_rtos_queue_create(&evt_queue, 4 * 1024, sizeof(luat_event_t));
+    // 创建专门的RDY事件队列 (id=6)
+    luat_rtos_queue_create(&rdy_evt_queue, 1, sizeof(luat_event_t));
     luat_rtos_task_create(&spi_task_handle, 8 * 1024, 50, "spi", spi_master_task, NULL, 0);
 }
 

+ 3 - 3
components/fatfs/diskio.h

@@ -1,5 +1,5 @@
 /*-----------------------------------------------------------------------/
-/  Low level disk interface modlue include file   (C)ChaN, 2019          /
+/  Low level disk interface modlue include file   (C)ChaN, 2025          /
 /-----------------------------------------------------------------------*/
 
 #ifndef _DISKIO_DEFINED
@@ -77,7 +77,7 @@ DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
 #define CTRL_EJECT			7	/* Eject media */
 #define CTRL_FORMAT			8	/* Create physical format on the media */
 
-/* MMC/SDC specific ioctl command */
+/* MMC/SDC specific ioctl command (Not used by FatFs) */
 #define MMC_GET_TYPE		10	/* Get card type */
 #define MMC_GET_CSD			11	/* Get CSD */
 #define MMC_GET_CID			12	/* Get CID */
@@ -87,7 +87,7 @@ DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
 #define ISDIO_WRITE			56	/* Write data to SD iSDIO register */
 #define ISDIO_MRITE			57	/* Masked write data to SD iSDIO register */
 
-/* ATA/CF specific ioctl command */
+/* ATA/CF specific ioctl command (Not used by FatFs) */
 #define ATA_GET_REV			20	/* Get F/W revision */
 #define ATA_GET_MODEL		21	/* Get model name */
 #define ATA_GET_SN			22	/* Get serial number */

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 241 - 183
components/fatfs/ff.c


+ 91 - 73
components/fatfs/ff.h

@@ -1,8 +1,8 @@
 /*----------------------------------------------------------------------------/
-/  FatFs - Generic FAT Filesystem module  R0.15a                              /
+/  FatFs - Generic FAT Filesystem module  R0.16                               /
 /-----------------------------------------------------------------------------/
 /
-/ Copyright (C) 2024, ChaN, all right reserved.
+/ Copyright (C) 2025, ChaN, all right reserved.
 /
 / FatFs module is an open source software. Redistribution and use of FatFs in
 / source and binary forms, with or without modification, are permitted provided
@@ -20,7 +20,7 @@
 
 
 #ifndef FF_DEFINED
-#define FF_DEFINED	5380	/* Revision ID */
+#define FF_DEFINED	80386	/* Revision ID */
 
 #ifdef __cplusplus
 extern "C" {
@@ -127,51 +127,65 @@ extern const char* VolumeStr[FF_VOLUMES];	/* User defined volume ID table */
 #endif
 
 
+/* Current working directory structure (FFXCWDS) */
+
+#if FF_FS_EXFAT && FF_FS_RPATH
+#if FF_PATH_DEPTH < 1
+#error FF_PATH_DEPTH must not be zero
+#endif
+typedef struct {
+	DWORD	d_scl;		/* Directory start cluster (0:root dir) */
+	DWORD	d_size;		/* Size of directory (b7-b0: cluster chain status) (invalid if d_scl == 0) */
+	DWORD	nxt_ofs;	/* Offset of entry of next dir in this directory (invalid if last link) */
+} FFXCWDL;
+typedef struct {
+	UINT	depth;		/* Current directory depth (0:root dir) */
+	FFXCWDL	tbl[FF_PATH_DEPTH + 1];	/* Directory chain of current working directory path */
+} FFXCWDS;
+#endif
+
 
 /* Filesystem object structure (FATFS) */
 
 typedef struct {
-	BYTE	fs_type;		/* Filesystem type (0:blank filesystem object) */
-	BYTE	pdrv;			/* Volume hosting physical drive */
-	BYTE	ldrv;			/* Logical drive number (used only when FF_FS_REENTRANT) */
-	BYTE	n_fats;			/* Number of FATs (1 or 2) */
-	BYTE	wflag;			/* win[] status (1:dirty) */
-	BYTE	fsi_flag;		/* Allocation information control (b7:disabled, b0:dirty) */
-	WORD	id;				/* Volume mount ID */
-	WORD	n_rootdir;		/* Number of root directory entries (FAT12/16) */
-	WORD	csize;			/* Cluster size [sectors] */
+	BYTE	fs_type;	/* Filesystem type (0:not mounted) */
+	BYTE	pdrv;		/* Physical drive that holds this volume */
+	BYTE	ldrv;		/* Logical drive number (used only when FF_FS_REENTRANT) */
+	BYTE	n_fats;		/* Number of FATs (1 or 2) */
+	BYTE	wflag;		/* win[] status (b0:dirty) */
+	BYTE	fsi_flag;	/* Allocation information control (b7:disabled, b0:dirty) */
+	WORD	id;			/* Volume mount ID */
+	WORD	n_rootdir;	/* Number of root directory entries (FAT12/16) */
+	WORD	csize;		/* Cluster size [sectors] */
 #if FF_MAX_SS != FF_MIN_SS
-	WORD	ssize;			/* Sector size (512, 1024, 2048 or 4096) */
+	WORD	ssize;		/* Sector size (512, 1024, 2048 or 4096) */
 #endif
 #if FF_USE_LFN
-	WCHAR*	lfnbuf;			/* LFN working buffer */
-#endif
-#if FF_FS_EXFAT
-	BYTE*	dirbuf;			/* Directory entry block scratch pad buffer for exFAT */
+	WCHAR*	lfnbuf;		/* Pointer to LFN working buffer */
 #endif
 #if !FF_FS_READONLY
-	DWORD	last_clst;		/* Last allocated cluster (Unknown if >= n_fatent) */
-	DWORD	free_clst;		/* Number of free clusters (Unknown if >= n_fatent-2) */
+	DWORD	last_clst;	/* Last allocated cluster (invalid if >=n_fatent) */
+	DWORD	free_clst;	/* Number of free clusters (invalid if >=fs->n_fatent-2) */
 #endif
 #if FF_FS_RPATH
-	DWORD	cdir;			/* Current directory start cluster (0:root) */
+	DWORD	cdir;		/* Current directory start cluster (0:root) */
+#endif
+	DWORD	n_fatent;	/* Number of FAT entries (number of clusters + 2) */
+	DWORD	fsize;		/* Number of sectors per FAT */
+	LBA_t	winsect;	/* Current sector appearing in the win[] */
+	LBA_t	volbase;	/* Volume base sector */
+	LBA_t	fatbase;	/* FAT base sector */
+	LBA_t	dirbase;	/* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */
+	LBA_t	database;	/* Data base sector */
 #if FF_FS_EXFAT
-	DWORD	cdc_scl;		/* Containing directory start cluster (invalid when cdir is 0) */
-	DWORD	cdc_size;		/* b31-b8:Size of containing directory, b7-b0: Chain status */
-	DWORD	cdc_ofs;		/* Offset in the containing directory (invalid when cdir is 0) */
-#endif
+	LBA_t	bitbase;	/* Allocation bitmap base sector */
+	BYTE*	dirbuf;		/* Pointer to directory entry block buffer */
+#if FF_FS_RPATH
+	FFXCWDS	xcwds;		/* Crrent working directory structure */
+	FFXCWDS	xcwds2;		/* Working buffer to follow the path */
 #endif
-	DWORD	n_fatent;		/* Number of FAT entries (number of clusters + 2) */
-	DWORD	fsize;			/* Number of sectors per FAT */
-	LBA_t	volbase;		/* Volume base sector */
-	LBA_t	fatbase;		/* FAT base sector */
-	LBA_t	dirbase;		/* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */
-	LBA_t	database;		/* Data base sector */
-#if FF_FS_EXFAT
-	LBA_t	bitbase;		/* Allocation bitmap base sector */
 #endif
-	LBA_t	winsect;		/* Current sector appearing in the win[] */
-	BYTE	win[FF_MAX_SS];	/* Disk access window for Directory, FAT (and file data at tiny cfg) */
+	BYTE	win[FF_MAX_SS];	/* Disk access window for directory, FAT (and file data in tiny cfg) */
 } FATFS;
 
 
@@ -179,21 +193,21 @@ typedef struct {
 /* Object ID and allocation information (FFOBJID) */
 
 typedef struct {
-	FATFS*	fs;				/* Pointer to the hosting volume of this object */
-	WORD	id;				/* Hosting volume's mount ID */
-	BYTE	attr;			/* Object attribute */
-	BYTE	stat;			/* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
-	DWORD	sclust;			/* Object data start cluster (0:no cluster or root directory) */
-	FSIZE_t	objsize;		/* Object size (valid when sclust != 0) */
+	FATFS*	fs;			/* Pointer to the volume holding this object */
+	WORD	id;			/* Volume mount ID when this object was opened */
+	BYTE	attr;		/* Object attribute */
+	BYTE	stat;		/* Object chain status (exFAT: b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
+	DWORD	sclust;		/* Object data cluster (0:no data or root directory) */
+	FSIZE_t	objsize;	/* Object size (valid when sclust != 0) */
 #if FF_FS_EXFAT
-	DWORD	n_cont;			/* Size of first fragment - 1 (valid when stat == 3) */
-	DWORD	n_frag;			/* Size of last fragment needs to be written to FAT (valid when not zero) */
-	DWORD	c_scl;			/* Containing directory start cluster (valid when sclust != 0) */
-	DWORD	c_size;			/* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
-	DWORD	c_ofs;			/* Offset in the containing directory (valid when file object and sclust != 0) */
+	DWORD	n_cont;		/* Size of first fragment - 1 (valid when stat == 3) */
+	DWORD	n_frag;		/* Size of last fragment needs to be written to FAT (valid when not zero) */
+	DWORD	c_scl;		/* Cluster of directory holding this object (valid when sclust != 0) */
+	DWORD	c_size;		/* Size of directory holding this object (b7-b0: allocation status, valid when c_scl != 0) */
+	DWORD	c_ofs;		/* Offset of entry in the holding directory */
 #endif
 #if FF_FS_LOCK
-	UINT	lockid;			/* File lock ID origin from 1 (index of file semaphore table Files[]) */
+	UINT	lockid;		/* File lock ID origin from 1 (index of file semaphore table Files[]) */
 #endif
 } FFOBJID;
 
@@ -202,18 +216,18 @@ typedef struct {
 /* File object structure (FIL) */
 
 typedef struct {
-	FFOBJID	obj;			/* Object identifier (must be the 1st member to detect invalid object pointer) */
-	BYTE	flag;			/* File status flags */
-	BYTE	err;			/* Abort flag (error code) */
-	FSIZE_t	fptr;			/* File read/write pointer (Zeroed on file open) */
-	DWORD	clust;			/* Current cluster of fpter (invalid when fptr is 0) */
-	LBA_t	sect;			/* Sector number appearing in buf[] (0:invalid) */
+	FFOBJID	obj;		/* Object identifier (must be the 1st member to detect invalid object pointer) */
+	BYTE	flag;		/* File status flags */
+	BYTE	err;		/* Abort flag (error code) */
+	FSIZE_t	fptr;		/* File read/write pointer (0 on open) */
+	DWORD	clust;		/* Current cluster of fptr (invalid when fptr is 0) */
+	LBA_t	sect;		/* Sector number appearing in buf[] (0:invalid) */
 #if !FF_FS_READONLY
-	LBA_t	dir_sect;		/* Sector number containing the directory entry (not used at exFAT) */
-	BYTE*	dir_ptr;		/* Pointer to the directory entry in the win[] (not used at exFAT) */
+	LBA_t	dir_sect;	/* Sector number containing the directory entry (not used in exFAT) */
+	BYTE*	dir_ptr;	/* Pointer to the directory entry in the win[] (not used in exFAT) */
 #endif
 #if FF_USE_FASTSEEK
-	DWORD*	cltbl;			/* Pointer to the cluster link map table (nulled on open, set by application) */
+	DWORD*	cltbl;		/* Pointer to the cluster link map table (nulled on open; set by application) */
 #endif
 #if !FF_FS_TINY
 	BYTE	buf[FF_MAX_SS];	/* File private data read/write window */
@@ -225,40 +239,44 @@ typedef struct {
 /* Directory object structure (DIR) */
 
 typedef struct {
-	FFOBJID	obj;			/* Object identifier */
-	DWORD	dptr;			/* Current read/write offset */
-	DWORD	clust;			/* Current cluster */
-	LBA_t	sect;			/* Current sector (0:Read operation has terminated) */
-	BYTE*	dir;			/* Pointer to the directory item in the win[] */
-	BYTE	fn[12];			/* SFN (in/out) {body[8],ext[3],status[1]} */
+	FFOBJID	obj;		/* Object identifier (must be the 1st member to detect invalid object pointer) */
+	DWORD	dptr;		/* Current read/write offset */
+	DWORD	clust;		/* Current cluster */
+	LBA_t	sect;		/* Current sector (0:no more item to read) */
+	BYTE*	dir;		/* Pointer to the directory item in the win[] in filesystem object */
+	BYTE	fn[12];		/* SFN (in/out) {body[0-7],ext[8-10],status[11]} */
 #if FF_USE_LFN
-	DWORD	blk_ofs;		/* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
+	DWORD	blk_ofs;	/* Offset of current entry block being processed (0xFFFFFFFF:invalid) */
 #endif
 #if FF_USE_FIND
-	const TCHAR* pat;		/* Pointer to the name matching pattern */
+	const TCHAR *pat;	/* Pointer to the name matching pattern */
 #endif
 } DIR;
 
 
 
-/* File information structure (FILINFO) */
+/* File/directory information structure (FILINFO) */
 
 typedef struct {
-	FSIZE_t	fsize;			/* File size */
-	WORD	fdate;			/* Modified date */
-	WORD	ftime;			/* Modified time */
-	BYTE	fattrib;		/* File attribute */
+	FSIZE_t	fsize;			/* File size (invalid for directory) */
+	WORD	fdate;			/* Date of file modification or directory creation */
+	WORD	ftime;			/* Time of file modification or directory creation */
+#if FF_FS_CRTIME
+	WORD	crdate;			/* Date of object createion */
+	WORD	crtime;			/* Time of object createion */
+#endif
+	BYTE	fattrib;		/* Object attribute */
 #if FF_USE_LFN
-	TCHAR	altname[FF_SFN_BUF + 1];/* Alternative file name */
-	TCHAR	fname[FF_LFN_BUF + 1];	/* Primary file name */
+	TCHAR	altname[FF_SFN_BUF + 1];/* Alternative object name */
+	TCHAR	fname[FF_LFN_BUF + 1];	/* Primary object name */
 #else
-	TCHAR	fname[12 + 1];	/* File name */
+	TCHAR	fname[12 + 1];	/* Object name */
 #endif
 } FILINFO;
 
 
 
-/* Format parameter structure (MKFS_PARM) */
+/* Format parameter structure (MKFS_PARM) used for f_mkfs() */
 
 typedef struct {
 	BYTE fmt;			/* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */
@@ -290,7 +308,7 @@ typedef enum {
 	FR_MKFS_ABORTED,		/* (14) The f_mkfs function aborted due to some problem */
 	FR_TIMEOUT,				/* (15) Could not take control of the volume within defined period */
 	FR_LOCKED,				/* (16) The operation is rejected according to the file sharing policy */
-	FR_NOT_ENOUGH_CORE,		/* (17) LFN working buffer could not be allocated or given buffer is insufficient in size */
+	FR_NOT_ENOUGH_CORE,		/* (17) LFN working buffer could not be allocated, given buffer size is insufficient or too deep path */
 	FR_TOO_MANY_OPEN_FILES,	/* (18) Number of open files > FF_FS_LOCK */
 	FR_INVALID_PARAMETER	/* (19) Given parameter is invalid */
 } FRESULT;

+ 27 - 10
components/fatfs/ffconf.h

@@ -5,7 +5,7 @@
 /  Configurations of FatFs Module
 /---------------------------------------------------------------------------*/
 
-#define FFCONF_DEF	5380	/* Revision ID */
+#define FFCONF_DEF	80386	/* Revision ID */
 
 /*---------------------------------------------------------------------------/
 / Function Configurations
@@ -63,12 +63,12 @@
 #define FF_PRINT_LLI	0
 #define FF_PRINT_FLOAT	0
 #define FF_STRF_ENCODE	0
-/* FF_USE_STRFUNC switches the string API functions, f_gets(), f_putc(), f_puts()
-/  and f_printf().
+/* FF_USE_STRFUNC switches string API functions, f_gets(), f_putc(), f_puts() and
+/  f_printf().
 /
 /   0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
-/   1: Enable without LF - CRLF conversion.
-/   2: Enable with LF - CRLF conversion.
+/   1: Enable without LF-CRLF conversion.
+/   2: Enable with LF-CRLF conversion.
 /
 /  FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
 /  makes f_printf() support floating point argument. These features want C99 or later.
@@ -165,14 +165,26 @@
 
 
 #define FF_FS_RPATH		0
-/* This option configures support for relative path.
+/* This option configures support for relative path feature.
 /
 /   0: Disable relative path and remove related API functions.
-/   1: Enable relative path. f_chdir() and f_chdrive() are available.
+/   1: Enable relative path and dot names. f_chdir() and f_chdrive() are available.
 /   2: f_getcwd() is available in addition to 1.
 */
 
 
+#define FF_PATH_DEPTH	10
+/*  This option defines maximum depth of directory in the exFAT volume. It is NOT
+/   relevant to FAT/FAT32 volume.
+/   For example, FF_PATH_DEPTH = 3 will able to follow a path "/dir1/dir2/dir3/file"
+/   but a sub-directory in the dir3 will not able to be followed and set current
+/   directory.
+/   The size of filesystem object (FATFS) increases FF_PATH_DEPTH * 24 bytes.
+/   When FF_FS_EXFAT == 0 or FF_FS_RPATH == 0, this option has no effect.
+*/
+
+
+
 /*---------------------------------------------------------------------------/
 / Drive/Volume Configurations
 /---------------------------------------------------------------------------*/
@@ -237,7 +249,7 @@
 
 #define FF_FS_TINY		0
 /* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
-/  At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
+/  At the tiny configuration, size of file object (FIL) is reduced FF_MAX_SS bytes.
 /  Instead of private sector buffer eliminated from the file object, common sector
 /  buffer in the filesystem object (FATFS) is used for the file data transfer. */
 
@@ -262,9 +274,14 @@
 /  These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
 
 
+#define FF_FS_CRTIME	0
+/* This option enables(1)/disables(0) the timestamp of the file created. When
+/  set 1, the file created time is available in FILINFO structure. */
+
+
 #define FF_FS_NOFSINFO	0
-/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
-/  option, and f_getfree() at the first time after volume mount will force
+/* If you need to know the correct free space on the FAT32 volume, set bit 0 of
+/  this option, and f_getfree() on the first time after volume mount will force
 /  a full FAT scan. Bit 1 controls the use of last allocated cluster number.
 /
 /  bit0=0: Use free cluster count in the FSINFO if available.

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 3600 - 6381
components/fatfs/ffunicode.c


+ 2 - 2
components/io_queue/luat_lib_io_queue.c

@@ -211,13 +211,13 @@ static int l_io_queue_capture_pin(lua_State *L) {
 
 /*
 对io操作队列增加结束捕获某个IO命令
-@api  ioqueue.capend(hwtimer_id,pin)
+@api  ioqueue.cap_done(hwtimer_id,pin)
 @int  硬件定时器id
 @int  pin
 @return nil 无返回值
 @usage
 -- 结束捕获
-ioqueue.capend(0, 17)
+ioqueue.cap_done(0, 17)
 */
 static int l_io_queue_capture_end(lua_State *L) {
 	uint8_t timer_id = luaL_optinteger(L, 1, 0);

+ 37 - 17
components/lcd/luat_lcd.c

@@ -205,27 +205,35 @@ INIT_NOT_DONE:
 int luat_lcd_setup_buff_default(luat_lcd_conf_t* conf){
     if (conf->buff) {
         LLOGE("lcd buff已经分配过了");
-        return 0;
-    }
-    conf->buff = luat_heap_opt_malloc(LUAT_HEAP_PSRAM, sizeof(luat_color_t) * conf->w * conf->h);
-    if (conf->buff == NULL) {
-        LLOGW("psram 分配 lcd buff失败, 尝试在sram分配");
-        conf->buff = luat_heap_opt_malloc(LUAT_HEAP_SRAM, sizeof(luat_color_t) * conf->w * conf->h);
     }
-    if (conf->buff == NULL) {
-        LLOGE("分配 lcd buff失败");
-        return -1;
+    else
+    {
+        conf->buff = luat_heap_opt_malloc(LUAT_HEAP_PSRAM, sizeof(luat_color_t) * conf->w * conf->h);
+        if (conf->buff == NULL) {
+            LLOGW("psram 分配 lcd buff失败, 尝试在sram分配");
+            conf->buff = luat_heap_opt_malloc(LUAT_HEAP_SRAM, sizeof(luat_color_t) * conf->w * conf->h);
+        }
+        if (conf->buff == NULL) {
+            LLOGE("分配 lcd buff失败");
+            return -1;
+        }
     }
-    conf->buff_ex = luat_heap_opt_malloc(LUAT_HEAP_PSRAM, sizeof(luat_color_t) * conf->w * conf->h);
-    if (conf->buff_ex == NULL) {
-        LLOGW("psram 分配 lcd buff_ex失败, 尝试在sram分配");
-        conf->buff_ex = luat_heap_opt_malloc(LUAT_HEAP_SRAM, sizeof(luat_color_t) * conf->w * conf->h);
+
+    if (conf->buff_ex) {
+        LLOGE("lcd buff_ex已经分配过了");
     }
-    if (conf->buff_ex == NULL) {
-        LLOGE("分配 lcd buff_ex失败");
-        return -1;
+    else
+    {
+        conf->buff_ex = luat_heap_opt_malloc(LUAT_HEAP_PSRAM, sizeof(luat_color_t) * conf->w * conf->h);
+        if (conf->buff_ex == NULL) {
+            LLOGW("psram 分配 lcd buff_ex失败, 尝试在sram分配");
+            conf->buff_ex = luat_heap_opt_malloc(LUAT_HEAP_SRAM, sizeof(luat_color_t) * conf->w * conf->h);
+        }
+        if (conf->buff_ex == NULL) {
+            LLOGE("分配 lcd buff_ex失败");
+            return -1;
+        }
     }
-
     return 0;
 }
 
@@ -558,6 +566,18 @@ int luat_lcd_draw_line(luat_lcd_conf_t* conf,int16_t x1, int16_t y1, int16_t x2,
     int incx, incy, row, col;
     if (x1 == x2 || y1 == y2) // 直线
     {
+        if (x1 > x2)
+        {
+            int16_t tmp = x1;
+            x1 = x2;
+            x2 = tmp;
+        }
+        if (y1 > y2)
+        {
+            int16_t tmp = y1;
+            y1 = y2;
+            y2 = tmp;
+        }
         size_t dots = (x2 - x1 + 1) * (y2 - y1 + 1);//点数量
         luat_color_t* line_buf = (luat_color_t*) luat_heap_malloc(dots * sizeof(luat_color_t));
         if (conf->endianness_swap)

+ 3 - 0
components/lcd/luat_lcd_jd9261t.c

@@ -15,6 +15,9 @@ static int jd9261t_inited_init(luat_lcd_conf_t* conf)
     	conf->flush_y_min = conf->h;
     	conf->flush_y_max = 0;
     }
+	if (!conf->buff_ex)
+		conf->buff_ex = luat_heap_opt_zalloc(LUAT_HEAP_AUTO, conf->w * conf->h * ((conf->bpp <= 16)?2:4));
+
 	luat_lcd_qspi_conf_t auto_flush =
 	{
 			.write_4line_cmd = 0xde,

+ 2 - 0
components/lcd/luat_lcd_sh8601z.c

@@ -29,6 +29,8 @@ static int sh8601z_inited_init(luat_lcd_conf_t* conf)
     lcd_write_cmd_data(conf,0x53, &temp, 1);
     temp = 0xff;
     lcd_write_cmd_data(conf,0x51, &temp, 1);
+//    temp = 0x00;
+//    lcd_write_cmd_data(conf,0x36, &temp, 1);
 //    luat_lcd_clear(conf,LCD_BLACK);
     luat_lcd_display_on(conf);
     return 0;

+ 0 - 1
components/lfs/lfs_sfd.c

@@ -1,5 +1,4 @@
 
-#
 #include "luat_base.h"
 #include "luat_sfd.h"
 #include "luat_mem.h"

+ 4 - 0
components/mobile/luat_lib_mobile.c

@@ -601,6 +601,10 @@ static int l_mobile_scell_extern_info(lua_State* L) {
     lua_pushinteger(L, info.pci);
     lua_setfield(L, -2, "pci");
 
+    ret = luat_mobile_get_band_from_earfcn(info.earfcn);
+    lua_pushinteger(L, ret);
+    lua_setfield(L, -2, "band");
+
     // 基站相关
     uint32_t eci = 0;
     uint16_t tac = 0;

+ 6 - 0
components/mobile/luat_mobile.h

@@ -894,6 +894,12 @@ int luat_mobile_get_isp_from_plmn(uint16_t mcc, uint8_t mnc);
  */
 int luat_mobile_get_plmn_from_imsi(char *imsi, uint16_t *mcc, uint8_t *mnc);
 
+/**
+ * @brief 通过DL EARFCN查找对应频段
+ * @param earfcn 下行频点
+ * @return >0为对应频段,=0未找到对应频段
+ */
+int luat_mobile_get_band_from_earfcn(uint32_t earfcn);
 
 /**
  * @brief 获取最近一次来电号码

+ 99 - 0
components/mobile/luat_mobile_common.c

@@ -199,3 +199,102 @@ int luat_mobile_get_plmn_from_imsi(char *imsi, uint16_t *mcc, uint8_t *mnc)
 	*mnc = temp;
 	return 0;
 }
+typedef struct{
+	uint8_t band;
+	uint32_t min_earfcn;
+	uint32_t max_earfcn;
+}earfcn_to_band_t;
+
+static const earfcn_to_band_t g_band_list[] = {
+	{1, 0, 599},
+	{2, 600, 1199},
+	{3, 1200, 1949},
+	{4, 1950, 2399},
+	{5, 2400, 2649},
+	{7, 2750, 3449},
+	{8, 3450, 3799},
+	{9, 3800, 4149},
+	{10, 4150, 4749},
+	{11, 4750, 4949},
+	{12, 5010, 5179},
+	{13, 5180, 5279},
+	{14, 5280, 5379},
+	{17, 5730, 5849},
+	{18, 5850, 5999},
+	{19, 6000, 6149},
+	{20, 6150, 6449},
+	{21, 6450, 6599},
+	{22, 6600, 7399},
+	{24, 7700, 8039},
+	{25, 8040, 8689},
+	{26, 8690, 9039},
+	{27, 9040, 9209},
+	{28, 9210, 9659},
+	{29, 9660, 9769},
+	{30, 9770, 9869},
+	{31, 9870, 9919},
+	{32, 9920, 10359},
+	{33, 36000, 36199},
+	{34, 36200, 36349},
+	{35, 36350, 36949},
+	{36, 36950, 37549},
+	{37, 37550, 37749},
+	{38, 37750, 38249},
+	{39, 38250, 38649},
+	{40, 38650, 39649},
+	{41, 39650, 41589},
+	{42, 41590, 43589},
+	{43, 43590, 45589},
+	{44, 45590, 46589},
+	{45, 46590, 46789},
+	{46, 46790, 54539},
+	{47, 54540, 55239},
+	{48, 55240, 56739},
+	{49, 56740, 58239},
+	{50, 58240, 59089},
+	{51, 59090, 59139},
+	{52, 59140, 60139},
+	{53, 60140, 60254},
+	{65, 65536, 66435},
+	{66, 66436, 67335},
+	{67, 67336, 67535},
+	{68, 67536, 67835},
+	{69, 67836, 68335},
+	{70, 68336, 68585},
+	{71, 68586, 68935},
+	{72, 68936, 68985},
+	{73, 68986, 69035},
+	{74, 69036, 69465},
+	{75, 69466, 70315},
+	{76, 70316, 70365},
+	{85, 70366, 70545},
+	{87, 70546, 70595},
+	{88, 70596, 70645},
+};
+
+
+int luat_mobile_get_band_from_earfcn(uint32_t earfcn)
+{
+	uint32_t l = 0;
+	uint32_t h = (sizeof(g_band_list) / sizeof(g_band_list[0]) - 1);
+	while (l <= h)
+	{
+		uint16_t index = l + ((h - l) >> 1);
+		uint32_t min =  g_band_list[index].min_earfcn;
+		uint32_t max = g_band_list[index].max_earfcn;
+		if (earfcn >= min && earfcn <= max)
+		{
+			return g_band_list[index].band;
+		}
+		else if(earfcn > max)
+		{
+			l = index + 1;
+		}
+		else if(earfcn < min)
+		{
+			h = index - 1;
+		}
+	}
+	return 0;
+}
+

+ 3 - 0
components/mreport/src/luat_mreport.c

@@ -285,16 +285,19 @@ void luat_mreport_send(void) {
     int adapter_index = network_register_get_default();
 	if (adapter_index < 0 || adapter_index >= NW_ADAPTER_QTY){
 		LLOGE("尚无已注册的网络适配器");
+        cJSON_Delete(mreport_data);
 		return;
 	}
     luat_netdrv_t* netdrv = luat_netdrv_get(adapter_index);
     if (netdrv == NULL || netdrv->netif == NULL) {
+        cJSON_Delete(mreport_data);
         return;
     }
 
     struct netif *netif = netdrv->netif;
     if (ip_addr_isany(&netif->ip_addr)) {
         LLOGD("还没联网");
+        cJSON_Delete(mreport_data);
         return;
     }
 

+ 15 - 15
components/network/adapter/luat_network_adapter.c

@@ -26,8 +26,8 @@ typedef struct
 {
 #ifdef LUAT_USE_LWIP
 	network_ctrl_t lwip_ctrl_table[LWIP_NUM_SOCKETS];
-//	HANDLE network_mutex;
 #endif
+	HANDLE network_mutex;
 	int last_adapter_index;
 	int default_adapter_index;
 	llist_head dns_cache_head;
@@ -154,8 +154,8 @@ network_adapter_info* network_adapter_fetch(int id, void** userdata) {
 #endif
 extern void DBG_Printf(const char* format, ...);
 extern void DBG_HexPrintf(void *Data, unsigned int len);
-//#define DBG(x,y...)		DBG_Printf("%s %d:"x"\r\n", __FUNCTION__,__LINE__,##y)
-//#define DBG_ERR(x,y...)		DBG_Printf("%s %d:"x"\r\n", __FUNCTION__,__LINE__,##y)
+#define G_LOCK		platform_lock_mutex(prv_network.network_mutex)
+#define G_UNLOCK	platform_unlock_mutex(prv_network.network_mutex)
 static int tls_random( void *p_rng,
         unsigned char *output, size_t output_len );
 
@@ -1356,7 +1356,7 @@ int network_register_adapter(uint8_t adapter_index, network_adapter_info *info,
 	prv_adapter_table[adapter_index].port = 0;
 	if (!prv_network.is_init)
 	{
-		//prv_network.network_mutex = platform_create_mutex();
+		prv_network.network_mutex = platform_create_mutex();
 		INIT_LLIST_HEAD(&prv_network.dns_cache_head);
 		prv_network.default_adapter_index = -1;
 		prv_network.last_adapter_index = -1;
@@ -1385,7 +1385,7 @@ network_ctrl_t *network_alloc_ctrl(uint8_t adapter_index)
 		return NULL;
 	}
 	network_adapter_t *adapter = &prv_adapter_table[adapter_index];
-	OS_LOCK;
+	G_LOCK;
 	for (i = 0; i < adapter->opt->max_socket_num; i++)
 	{
 		if (!adapter->ctrl_busy[i])
@@ -1399,7 +1399,7 @@ network_ctrl_t *network_alloc_ctrl(uint8_t adapter_index)
 			break;
 		}
 	}
-	OS_UNLOCK;
+	G_UNLOCK;
 	if (i >= adapter->opt->max_socket_num) {DBG_ERR("adapter no more ctrl!");}
 	#ifdef LUAT_USE_NETDRV
 	if (ctrl) {
@@ -1417,7 +1417,7 @@ void network_release_ctrl(network_ctrl_t *ctrl)
 	int i;
 	network_adapter_t *adapter = &prv_adapter_table[ctrl->adapter_index];
 	NW_UNLOCK;
-	OS_LOCK;
+	G_LOCK;
 	for (i = 0; i < adapter->opt->max_socket_num; i++)
 	{
 		if (&adapter->ctrl_table[i] == ctrl)
@@ -1453,7 +1453,7 @@ void network_release_ctrl(network_ctrl_t *ctrl)
 			break;
 		}
 	}
-	OS_UNLOCK;
+	G_UNLOCK;
 	if (i >= adapter->opt->max_socket_num) {DBG_ERR("adapter index maybe error!, %d, %x", ctrl->adapter_index, ctrl);}
 
 }
@@ -1524,7 +1524,7 @@ int network_set_local_port(network_ctrl_t *ctrl, uint16_t local_port)
 	network_adapter_t *adapter = &prv_adapter_table[ctrl->adapter_index];
 	if (local_port)
 	{
-		OS_LOCK;
+		// G_LOCK;
 		#if 0
 		for (i = 0; i < adapter->opt->max_socket_num; i++)
 		{
@@ -1532,7 +1532,7 @@ int network_set_local_port(network_ctrl_t *ctrl, uint16_t local_port)
 			{
 				if (adapter->ctrl_table[i].local_port == local_port)
 				{
-					OS_UNLOCK;
+					G_UNLOCK;
 					return -1;
 				}
 			}
@@ -1540,7 +1540,7 @@ int network_set_local_port(network_ctrl_t *ctrl, uint16_t local_port)
 		}
 		#endif
 		ctrl->local_port = local_port;
-		OS_UNLOCK;
+		// G_UNLOCK;
 		return 0;
 	}
 	else
@@ -1552,14 +1552,14 @@ int network_set_local_port(network_ctrl_t *ctrl, uint16_t local_port)
 			ctrl->local_port = 0;
 			return 0;
 		}
-		OS_LOCK;
+		G_LOCK;
 		adapter->port++;
 		if (adapter->port < 60000)
 		{
 			adapter->port = 60000;
 		}
 		ctrl->local_port = adapter->port;
-		OS_UNLOCK;
+		G_UNLOCK;
 #endif
 		return 0;
 	}
@@ -1801,7 +1801,7 @@ void network_clean_invaild_socket(uint8_t adapter_index)
 	network_adapter_t *adapter = &prv_adapter_table[adapter_index];
 	network_ctrl_t *ctrl;
 	list = malloc(adapter->opt->max_socket_num * sizeof(int));
-	OS_LOCK;
+	G_LOCK;
 	for (i = 0; i < adapter->opt->max_socket_num; i++)
 	{
 		ctrl = &adapter->ctrl_table[i];
@@ -1816,7 +1816,7 @@ void network_clean_invaild_socket(uint8_t adapter_index)
 		}
 		DBG("%d,%d", i, list[i]);
 	}
-	OS_UNLOCK;
+	G_UNLOCK;
 	adapter->opt->socket_clean(list, adapter->opt->max_socket_num, adapter->user_data);
 	free(list);
 }

+ 13 - 4
components/network/errdump/luat_lib_errdump.c

@@ -75,6 +75,7 @@ typedef struct luat_errdump_conf
 	char custom_id[49];
 	char custom_domain_host[49];
 	uint16_t custom_domain_port;
+	int adapter_id;
 }luat_errdump_conf_t;
 
 static luat_errdump_conf_t econf;
@@ -236,13 +237,17 @@ static int32_t l_errdump_callback(lua_State *L, void* ptr)
 	uint32_t dummy_len;
     rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
     const char *ok_result = "{\"r\": 1}";
+	int adapter_id = econf.adapter_id;
+	if (adapter_id <= 0) {
+		adapter_id = network_register_get_default();
+	}
     switch(msg->arg1)
     {
     case LUAT_ERRDUMP_CONNECT:
-    	econf.netc = network_alloc_ctrl(network_register_get_default());
+    	econf.netc = network_alloc_ctrl(adapter_id);
     	if (!econf.netc)
     	{
-    		LLOGE("no socket, errdump fail, after %d second retry", econf.upload_period);
+    		LLOGE("no socket, errdump fail, after %d second retry, adapter %d", econf.upload_period, adapter_id);
     		econf.is_uploading = 0;
     		luat_rtos_timer_start(econf.upload_timer, econf.upload_period * 1000, 0, luat_errdump_timer_callback, NULL);
     		OS_DeInitBuffer(&econf.tx_buf);
@@ -635,13 +640,14 @@ static int l_errdump_record(lua_State *L) {
 
 /*
 配置关键日志上传IOT平台,这里的日志包括引起luavm异常退出的日志和用户通过record写入的日志,类似于air的errDump
-@api    errDump.config(enable, period, user_flag, custom_id, host, port)
+@api    errDump.config(enable, period, user_flag, custom_id, host, port, adapter)
 @boolean  是否启用记录功能,false的话将不会记录任何日志
 @int     定时上传周期,单位秒,默认600秒,这个是自动上传时候后的重试时间时间,在开机后或者有record操作后会很快尝试上传到合宙IOT平台一次,如果为0,则不会上传,由用户dump后自己上传自己的平台
 @string 用户的特殊标识,可以为空
 @string 设备识别号, 4G设备默认是imei,wifi设备默认STA的MAC,其他设备默认是mcu.unique_id
 @string 服务器域名,默认dev_msg1.openluat.com
-@int    服务器端口,默认
+@int    服务器端口,默认12425
+@int    网络适配器,默认-1,一般不需要设置. 2025.10.22 新增
 @return nil 无返回值
 @usage
 errDump.config(true, 3600, "12345678")	--一个小时尝试上次一次,上传时会在imei后附加上12345678
@@ -691,6 +697,9 @@ static int l_errdump_upload_config(lua_State *L) {
 			}
 			LLOGD("自定义服务器 %s %d", econf.custom_domain_host, econf.custom_domain_port);
 		}
+		if (LUA_TNUMBER == lua_type(L, 7)) {
+			econf.adapter_id = lua_tointeger(L, 7);
+		}
 	}
 	return 0;
 }

+ 39 - 18
components/network/httpsrv/src/luat_httpsrv_lwip.c

@@ -32,6 +32,8 @@ typedef struct client_socket_ctx
     char *body;
 
     size_t body_size;
+    size_t expect_body_size;
+    int next_header_value_is_content_length;
     uint32_t recv_done;
     char *buff;
     size_t buff_offset;
@@ -58,8 +60,8 @@ static int my_on_message_complete(http_parser* parser);
 // static int my_on_chunk_complete(http_parser* parser);
 static int my_on_url(http_parser* parser, const char *at, size_t length);
 // static int my_on_status(http_parser* parser, const char *at, size_t length);
-// static int my_on_header_field(http_parser* parser, const char *at, size_t length);
-// static int my_on_header_value(http_parser* parser, const char *at, size_t length);
+static int my_on_header_field(http_parser* parser, const char *at, size_t length);
+static int my_on_header_value(http_parser* parser, const char *at, size_t length);
 static int my_on_body(http_parser* parser, const char *at, size_t length);
 
 //================================
@@ -69,8 +71,8 @@ static const struct http_parser_settings hp_settings = {
     // .on_message_begin = my_on_message_begin,
     .on_url = my_on_url,
     // .on_status = my_on_status,
-    // .on_header_field = my_on_header_field,
-    // .on_header_value = my_on_header_value,
+    .on_header_field = my_on_header_field,
+    .on_header_value = my_on_header_value,
     // .on_headers_complete = my_on_headers_complete,
     .on_body = my_on_body,
     .on_message_complete = my_on_message_complete,
@@ -94,7 +96,6 @@ static const ct_reg_t ct_regs[] = {
     {"css",     "text/css"},
     {"wav",     "audio/wave"},
     {"ogg",     "audio/ogg"},
-    {"wav",     "audio/wave"},
     {"webm",    "video/webm"},
     {"mp4",     "video/mpeg4"},
     {"bin",     "application/octet-stream"},
@@ -170,10 +171,9 @@ static void client_resp(void* arg) {
         LLOGD("send body %d", body_size);
         client_write(client, client->body, body_size);
     }
-    // client_cleanup(client);
-    // tcp_output(pcb);
-    // tcp_close(pcb);
     client->write_done = 1;
+    LLOGD("resp done, total send %d", client->send_size);
+    tcp_output(client->pcb);
 }
 
 //================================
@@ -405,11 +405,12 @@ static err_t client_sent_cb(void *arg, struct tcp_pcb *tpcb, u16_t len) {
     return ERR_OK;
 }
 static void client_err_cb(void *arg, err_t err) {
-    LLOGD("client cb %d", err);
+    LLOGD("client_err_cb %d", err);
     client_socket_ctx_t* client = (client_socket_ctx_t*)arg;
     if(ERR_RST == err)
     {
         tcp_err(client->pcb, NULL);
+        tcp_sent(client->pcb, NULL);
     }
     client_cleanup(client);
 }
@@ -632,6 +633,10 @@ static int handle_static_file(client_socket_ctx_t *client) {
 static int my_on_message_complete(http_parser* parser) {
     LLOGD("on_message_complete");
     client_socket_ctx_t* client = (client_socket_ctx_t*)parser->data;
+    if (client->expect_body_size > 0 && client->body_size < client->expect_body_size) {
+        LLOGD("body size not match expect %d/%d", client->body_size, client->expect_body_size);
+        return HPE_INVALID_CONTENT_LENGTH;
+    }
     client->recv_done = 1;
     return 0;
 }
@@ -671,23 +676,39 @@ static int my_on_url(http_parser* parser, const char *at, size_t length) {
 //     return 0;
 // }
 
-// static int my_on_header_field(http_parser* parser, const char *at, size_t length) {
-//     // LLOGD("on_header_field %p %d", at, length);
-//     return 0;
-// }
+static int my_on_header_field(http_parser* parser, const char *at, size_t length) {
+    // LLOGI("on_header_field %.*s", (int)length, at);
+    if (length == 14 && !memcmp(at, "Content-Length", 14)) {
+        client_socket_ctx_t* client = (client_socket_ctx_t*)parser->data;
+        client->next_header_value_is_content_length = 1;
+    }
+    else {
+        client_socket_ctx_t* client = (client_socket_ctx_t*)parser->data;
+        client->next_header_value_is_content_length = 0;
+    }
+    return 0;
+}
 
-// static int my_on_header_value(http_parser* parser, const char *at, size_t length) {
-//     // LLOGD("on_header_value %p %d", at, length);
-//     return 0;
-// }
+static int my_on_header_value(http_parser* parser, const char *at, size_t length) {
+    // LLOGI("on_header_value %.*s", (int)length, at);
+    client_socket_ctx_t* client = (client_socket_ctx_t*)parser->data;
+    if (client->next_header_value_is_content_length) {
+        client->expect_body_size = atoi(at);
+        client->next_header_value_is_content_length = 0;
+    }
+    return 0;
+}
 
 static int my_on_body(http_parser* parser, const char *at, size_t length) {
     LLOGD("on_body %p %d", at, length);
     if (length == 0)
         return 0;
     client_socket_ctx_t* client = (client_socket_ctx_t*)parser->data;
+    if (client->expect_body_size > 0 && length < client->expect_body_size) {
+        return 0; // 等数据
+    }
     if (client->body == NULL) {
-        client->body = luat_heap_malloc(length);
+        client->body = luat_heap_malloc(client->expect_body_size > 0 ? client->expect_body_size : length);
         //client->body_size = length;
         if (client->body == NULL) {
             LLOGE("malloc body FAIL!!!");

+ 0 - 327
components/network/posix/luat_network_posix.c

@@ -1,327 +0,0 @@
-#include "luat_base.h"
-#include "luat_network_adapter.h"
-#include "luat_mem.h"
-#include "luat_msgbus.h"
-#include "luat_crypto.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
-#include <netdb.h>
-#include <signal.h>
-#include <fcntl.h>
-#include <sys/select.h>
-
-#define LUAT_LOG_TAG "network"
-#include "luat_log.h"
-
-#include "luat_network_posix.h"
-
-CBFuncEx_t posix_network_cb;
-void * posix_network_param;
-uint8_t posix_network_ready;
-
-static luat_rtos_mutex_t* master_lock;
-
-static void posix_send_event(int id, int p1, int p2, int p3) {
-    luat_network_cb_param_t params = {0};
-    params.tag = 0;
-    params.param = posix_network_param;
-    // 触发一下回调
-    // if (ready) {
-    OS_EVENT event = {
-        .ID = id,
-        .Param1 = p1,
-        .Param2 = p2,
-        .Param3 = p3
-    };
-    LLOGD("posix event %d %d %d %d", id, p1, p2, p3);
-    posix_network_cb(&event, &params);
-}
-
-void posix_network_client_thread_entry(posix_socket_t *ps) {
-
-    luat_network_cb_param_t params = {0};
-    params.tag = 0;
-    params.param = posix_network_param;
-    // 触发一下回调
-    // if (ready) {
-    OS_EVENT event = {0};
-
-    struct sockaddr_in sockaddr = {0};
-    sockaddr.sin_family = AF_INET;
-    sockaddr.sin_port = htons(ps->remote_port);
-    sockaddr.sin_addr.s_addr = ps->remote_ip.ipv4;
-    luat_rtos_task_sleep(50);
-
-    LLOGD("ready to connect %d", ps->socket_id);
-    int ret = connect(ps->socket_id, (struct sockaddr*)&sockaddr, sizeof(sockaddr));
-
-    LLOGD("connect ret %d", ret);
-    if (ret) {
-        // 失败了
-        LLOGD("connect FAIL ret %d", ret);
-        posix_send_event(EV_NW_SOCKET_ERROR, ps->socket_id, 0, 0);
-        luat_heap_free(ps);
-        return;
-    }
-    // 发送系统消息, 通知连接成功
-    posix_send_event(EV_NW_SOCKET_CONNECT_OK, ps->socket_id, 0, 0);
-    LLOGD("wait data now");
-
-    fd_set readfds;
-    fd_set writefds;
-    fd_set errorfds;
-    int maxsock;
-    struct timeval tv;
-    maxsock = ps->socket_id;
-    // timeout setting
-    tv.tv_sec = 0;
-    tv.tv_usec = 3000; //暂时3ms吧
-    while (1) {
-        // initialize file descriptor set
-        FD_ZERO(&readfds);
-        // FD_ZERO(&writefds);
-        FD_ZERO(&errorfds);
-        FD_SET(ps->socket_id, &readfds);
-        // FD_SET(ps->socket_id, &writefds);
-        FD_SET(ps->socket_id, &errorfds);
-
-        if (master_lock)
-            if (luat_rtos_mutex_lock(master_lock, 100))
-                continue;
-        ret = select(maxsock + 1, &readfds, NULL, &errorfds, &tv);
-        if (master_lock)
-            luat_rtos_mutex_unlock(master_lock);
-
-        if (ret < 0) {
-            LLOGE("select ret %d", ret);
-            break;
-        } else if (ret == 0) {
-            //printf("select timeout\n");
-            continue;
-        }
-
-        if (FD_ISSET(maxsock, &readfds)) {
-            // 发消息,可读了
-        }
-        // if (FD_ISSET(maxsock, &writefds)) {
-        //     // 发消息,发送完成了??
-        // }
-        if (FD_ISSET(maxsock, &errorfds)) {
-            // 发消息,出错了
-            break;
-        }
-    }
-
-    luat_heap_free(ps);
-    LLOGI("socket thread exit");
-}
-
-void posix_network_set_ready(uint8_t ready) {
-    LLOGD("CALL posix_network_set_ready");
-    posix_network_ready = ready;
-    luat_network_cb_param_t params = {0};
-    params.tag = 0;
-    params.param = posix_network_param;
-    // 触发一下回调
-    // if (ready) {
-        OS_EVENT event = {
-            .ID = EV_NW_STATE,
-            .Param1 = 0,
-            .Param2 = ready,
-            .Param3 = 0
-        };
-        posix_network_cb(&event, &params);
-    // }
-}
-
-//检查网络是否准备好,返回非0准备好,user_data是注册时的user_data,传入给底层api
-uint8_t (posix_check_ready)(void *user_data) {
-    LLOGD("CALL posix_check_ready %d", posix_network_ready);
-    return posix_network_ready;
-};
-
-//创建一个socket,并设置成非阻塞模式,user_data传入对应适配器, tag作为socket的合法依据,给check_socket_vaild比对用
-//成功返回socketid,失败 < 0
-int (posix_create_socket)(uint8_t is_tcp, uint64_t *tag, void *param, uint8_t is_ipv6, void *user_data) {
-    // TODO 支持IPV6
-    int s = socket(AF_INET, is_tcp ? SOCK_STREAM : SOCK_DGRAM, is_tcp ? IPPROTO_TCP : IPPROTO_UDP);
-    LLOGD("CALL posix_create_socket %d %d", s, is_tcp);
-    return s;
-}
-
-//作为client绑定一个port,并连接remote_ip和remote_port对应的server
-//成功返回0,失败 < 0
-int (posix_socket_connect)(int socket_id, uint64_t tag, uint16_t local_port, luat_ip_addr_t *remote_ip, uint16_t remote_port, void *user_data) {
-    LLOGD("CALL posix_socket_connect %d", socket_id);
-    posix_socket_t *ps = luat_heap_malloc(sizeof(posix_socket_t));
-    if (ps == NULL) {
-        LLOGE("out of memory when malloc posix_socket_t");
-        return -1;
-    }
-    ps->socket_id = socket_id;
-    ps->tag = tag;
-    ps->local_port = local_port;
-    memcpy(&ps->remote_ip, remote_ip, sizeof(luat_ip_addr_t));
-    ps->remote_port = remote_port;
-    ps->user_data = user_data;
-
-    int ret = network_posix_client_thread_start(ps);
-    LLOGD("socket thread start %d", ret);
-
-    if (ret) {
-        luat_heap_free(ps);
-    }
-    return ret;
-}
-//作为server绑定一个port,开始监听
-//成功返回0,失败 < 0
-int (posix_socket_listen)(int socket_id, uint64_t tag, uint16_t local_port, void *user_data) {
-    // 尚未支持
-    return -1;
-}
-//作为server接受一个client
-//成功返回0,失败 < 0
-int (posix_socket_accept)(int socket_id, uint64_t tag, luat_ip_addr_t *remote_ip, uint16_t *remote_port, void *user_data) {
-    // 尚未支持
-    return -1;
-}
-
-//主动断开一个tcp连接,需要走完整个tcp流程,用户需要接收到close ok回调才能确认彻底断开
-//成功返回0,失败 < 0
-int (posix_socket_disconnect)(int socket_id, uint64_t tag, void *user_data) {
-    return close(socket_id);
-}
-
-//释放掉socket的控制权,除了tag异常外,必须立刻生效
-//成功返回0,失败 < 0
-int (posix_socket_close)(int socket_id, uint64_t tag, void *user_data) {
-    return close(socket_id);
-}
-
-//强行释放掉socket的控制权,必须立刻生效
-//成功返回0,失败 < 0
-int (posix_socket_force_close)(int socket_id, void *user_data) {
-    return close(socket_id);
-}
-
-//tcp时,不需要remote_ip和remote_port,如果buf为NULL,则返回当前缓存区的数据量,当返回值小于len时说明已经读完了
-//udp时,只返回1个block数据,需要多次读直到没有数据为止
-//成功返回实际读取的值,失败 < 0
-int (posix_socket_receive)(int socket_id, uint64_t tag, uint8_t *buf, uint32_t len, int flags, luat_ip_addr_t *remote_ip, uint16_t *remote_port, void *user_data) {
-    
-    struct timeval tv;
-    tv.tv_sec = 0;
-    tv.tv_usec = 1000; //暂时1ms吧
-    setsockopt(socket_id, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
-
-    if (master_lock)
-        if (luat_rtos_mutex_lock(master_lock, 100))
-            return -1;
-    int ret = recv(socket_id, buf, len, flags);
-    if (master_lock)
-        luat_rtos_mutex_unlock(master_lock);
-    return ret;
-}
-
-//tcp时,不需要remote_ip和remote_port
-//成功返回>0的len,缓冲区满了=0,失败 < 0,如果发送了len=0的空包,也是返回0,注意判断
-int (posix_socket_send)(int socket_id, uint64_t tag, const uint8_t *buf, uint32_t len, int flags, luat_ip_addr_t *remote_ip, uint16_t remote_port, void *user_data) {
-    
-    struct timeval tv;
-    tv.tv_sec = 0;
-    tv.tv_usec = 1000; //暂时1ms吧
-    setsockopt(socket_id, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
-
-    if (master_lock)
-        if (luat_rtos_mutex_lock(master_lock, 100))
-            return -1;
-    int ret = send(socket_id, buf, len, flags);
-    if (master_lock)
-        luat_rtos_mutex_unlock(master_lock);
-    return ret;
-}
-
-//检查socket合法性,成功返回0,失败 < 0
-int (posix_socket_check)(int socket_id, uint64_t tag, void *user_data) {
-    // TODO 通过select errorfds?
-    LLOGD("CALL posix_socket_check %d %lld", socket_id, tag);
-    return 0;
-}
-
-//保留有效的socket,将无效的socket关闭
-void (posix_socket_clean)(int *vaild_socket_list, uint32_t num, void *user_data) {
-    
-}
-
-int (posix_getsockopt)(int socket_id, uint64_t tag, int level, int optname, void *optval, uint32_t *optlen, void *user_data) {
-    return getsockopt(socket_id, level, optname, optval, optlen);
-}
-
-int (posix_setsockopt)(int socket_id, uint64_t tag, int level, int optname, const void *optval, uint32_t optlen, void *user_data) {
-    return setsockopt(socket_id, level, optname, optval, optlen);
-}
-
-//非posix的socket,用这个根据实际硬件设置参数
-int (posix_user_cmd)(int socket_id, uint64_t tag, uint32_t cmd, uint32_t value, void *user_data) {
-    return 0; // 没有这些东西
-}
-
-
-int (posix_dns)(const char *domain_name, uint32_t len, void *param,  void *user_data) {
-    LLOGD("CALL posix_dns %.*s", len, domain_name);
-    return -1; // 暂不支持DNS
-}
-
-int (posix_set_dns_server)(uint8_t server_index, luat_ip_addr_t *ip, void *user_data) {
-    return 0; // 暂不支持设置DNS
-}
-
-#ifdef LUAT_USE_LWIP
-int (posix_set_mac)(uint8_t *mac, void *user_data);
-int (posix_set_static_ip)(luat_ip_addr_t *ip, luat_ip_addr_t *submask, luat_ip_addr_t *gateway, luat_ip_addr_t *ipv6, void *user_data);
-#endif
-int (posix_get_local_ip_info)(luat_ip_addr_t *ip, luat_ip_addr_t *submask, luat_ip_addr_t *gateway, void *user_data) {
-    ip->ipv4 = 0;
-    submask->ipv4 = 0;
-    gateway->ipv4 = 0;
-    return 0;
-}
-
-//所有网络消息都是通过cb_fun回调
-//cb_fun回调时第一个参数为OS_EVENT,包含了socket的必要信息,第二个是luat_network_cb_param_t,其中的param是这里传入的param(就是适配器序号)
-//OS_EVENT ID为EV_NW_XXX,param1是socket id param2是各自参数 param3是create_soceket传入的socket_param(就是network_ctrl *)
-//dns结果是特别的,ID为EV_NW_SOCKET_DNS_RESULT,param1是获取到的IP数据量,0就是失败了,param2是ip组,动态分配的, param3是dns传入的param(就是network_ctrl *)
-void (posix_socket_set_callback)(CBFuncEx_t cb_fun, void *param, void *user_data) {
-    LLOGD("call posix_socket_set_callback %p %p", cb_fun, param);
-    if (master_lock == NULL)
-        luat_rtos_mutex_create(master_lock);
-    posix_network_cb = cb_fun;
-    posix_network_param = param;
-}
-
-
-network_adapter_info network_posix = {
-    .check_ready = posix_check_ready,
-    .create_soceket = posix_create_socket,
-    .socket_connect  = posix_socket_connect,
-    .socket_accept = posix_socket_accept,
-    .socket_disconnect  = posix_socket_disconnect,
-    .socket_close = posix_socket_close,
-    .socket_force_close = posix_socket_force_close,
-    .socket_receive = posix_socket_receive,
-    .socket_send = posix_socket_send,
-    .socket_clean = posix_socket_clean,
-    .getsockopt = posix_getsockopt,
-    .setsockopt = posix_setsockopt,
-    .user_cmd  = posix_user_cmd,
-    .dns = posix_dns,
-    .set_dns_server = posix_set_dns_server,
-    .get_local_ip_info = posix_get_local_ip_info,
-    .socket_set_callback = posix_socket_set_callback,
-    .name = "posix",
-    .max_socket_num = 4,
-    .no_accept = 1, // 暂时不支持接收
-    .is_posix = 1,
-};

+ 0 - 20
components/network/posix/luat_network_posix.h

@@ -1,20 +0,0 @@
-#ifndef LUAT_NETWORK_POSIX_H
-#define LUAT_NETWORK_POSIX_H
-
-#include "luat_base.h"
-#include "luat_network_adapter.h"
-
-typedef struct posix_socket
-{
-    int socket_id;
-    uint64_t tag;
-    uint16_t local_port;
-    luat_ip_addr_t remote_ip;
-    uint16_t remote_port;
-    void *user_data;
-}posix_socket_t;
-
-int network_posix_client_thread_start(posix_socket_t* ps);
-void posix_network_client_thread_entry(posix_socket_t* args);
-
-#endif

+ 17 - 9
components/pins/binding/luat_lib_pins.c

@@ -179,17 +179,25 @@ LUAMOD_API int luaopen_pins( lua_State *L ) {
 	if (luat_fs_fexist(buff) == 0) {
 		int size = strlen(name);
     	int j;
-    
-    	for( int i = 0; i < size - 1 ; i++){
-        	for (int j = 97; j < 123; j ++){
-            	if (name[i] == j){
-                	j = j - 32;
-                	name[i] = j;
-                	break;
-            	}
-        	}
+
+		// 为了兼容性,先找大写的
+		for( int i = 0; i < size - 1 ; i++){
+        	// 转大写
+			if (name[i] >= 'a' && name[i] <= 'z'){
+				name[i] = name[i] - 32;
+			}
     	}
 		snprintf(buff, sizeof(buff), "/luadb/pins_%s.json", name);
+    
+		if (luat_fs_fexist(buff) == 0) {
+    		for( int i = 0; i < size ; i++){
+        		// 转小写
+				if (name[i] >= 'A' && name[i] <= 'Z'){
+					name[i] = name[i] + 32;
+				}
+    		}
+			snprintf(buff, sizeof(buff), "/luadb/pins_%s.json", name);
+		}
 	}
 	
 	int ret = luat_pins_load_from_file(buff);

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

@@ -322,6 +322,12 @@ int luat_pins_setup(uint16_t pin, const char* func_name, size_t name_len, int al
 		return 1;
 	}
 	#endif
+	#ifdef __BK72XX__
+	if (pin == 26 || pin == 27){	// air8101的26/27不支持配置,只能作为uart0调试串口
+		LLOGE("pin%d不支持修改", pin);
+		goto LUAT_PIN_SETUP_DONE;
+	}
+	#endif
 	if (func_name != NULL)
 	{
 		if (name_len < 2)

+ 48 - 56
components/sms/binding/luat_lib_sms.c

@@ -54,20 +54,31 @@ static uint8_t ref_idx = 254;
 static uint64_t long_sms_send_idp = 0;
 
 
-
 static int l_long_sms_send_callback(lua_State *L, void* ptr){
     rtos_msg_t* msg = (rtos_msg_t*)lua_topointer(L, -1);
-    if (msg->arg1)
-    {
-        lua_pushboolean(L, 0);
-    }
-    else
+
+    if (long_sms_send_idp)
     {
-        lua_pushboolean(L, 1);
+        lua_pushboolean(L, msg->arg1 == 0 ? 0 : 1);
+        luat_cbcwait(L, long_sms_send_idp, 1);
+        long_sms_send_idp = 0;
     }
-    luat_cbcwait(L, long_sms_send_idp, 1);
-    long_sms_send_idp = 0;
-    g_s_sms_pdu_packet.maxNum = 0;             // 通过sms.sendLong发送的短信,需要在回调里确定发送结束
+
+/*
+@sys_pub sms
+短信发送结果
+SMS_SENT
+@result boolean 发送结果,成功为true, 失败为false
+@usage
+sys.subscribe("SMS_SENT", function(result)
+    log.info("sms send result", result)
+end)
+*/
+    lua_getglobal(L, "sys_pub");
+    lua_pushliteral(L, "SMS_SENT");
+    lua_pushboolean(L, msg->arg1 == 0 ? 0 : 1);
+    lua_call(L, 2, 0);
+    g_s_sms_pdu_packet.maxNum = 0;
     return 0;
 }
 
@@ -259,77 +270,59 @@ void luat_sms_recv_cb(uint32_t event, void *param)
     luat_msgbus_put(&msg, 0);
 }
 
+static void luat_sms_send_done(uint8_t is_success)
+{
+    rtos_msg_t msg = {
+        .handler = l_long_sms_send_callback,
+        .arg1 = is_success,
+        .arg2 = 0
+    };
+    luat_msgbus_put(&msg, 0);
+    if (g_s_sms_send.payload != NULL) {
+        luat_heap_free(g_s_sms_send.payload);
+        g_s_sms_send.payload = NULL;
+    }
+}
+
+
 void luat_sms_send_cb(int ret)
 {
     // 当前没有短信在发送,应该不会产生这个回调吧?
     if (!g_s_sms_pdu_packet.maxNum) {
         return;
     }
-    rtos_msg_t msg = {
-        .handler = l_long_sms_send_callback,
-        .arg1 = 0,
-        .arg2 = 0
-    };
+
     // 发送失败
     if (ret) {
-        // 长短信发送失败
-        if (long_sms_send_idp) {
-            msg.arg1 = 1;
-            luat_msgbus_put(&msg, 0);
-        } else {
-            // 通过sms.send发送的短信,这里可以直接判断发送结束
-            g_s_sms_pdu_packet.maxNum = 0;
-        }
-        if (g_s_sms_send.payload != NULL) {
-            luat_heap_free(g_s_sms_send.payload);
-            g_s_sms_send.payload = NULL;
-        }
+        luat_sms_send_done(0);
         return;
     }
-    LLOGI("long sms callback seqNum = %d", g_s_sms_pdu_packet.seqNum);
+    LLOGD("long sms callback seqNum = %d", g_s_sms_pdu_packet.seqNum);
     // 全部短信发送完成
     if (g_s_sms_pdu_packet.seqNum == g_s_sms_pdu_packet.maxNum) {
-        if (long_sms_send_idp) {
-            msg.arg1 = 0;
-            luat_msgbus_put(&msg, 0);
-        } else {
-            g_s_sms_pdu_packet.maxNum = 0;
-        }
-
-        if (g_s_sms_send.payload != NULL) {
-            luat_heap_free(g_s_sms_send.payload);
-            g_s_sms_send.payload = NULL;
-        }
+        luat_sms_send_done(1);
         return;
     }
 
     // 长短信继续发送
     g_s_sms_pdu_packet.seqNum++;
+    uint8_t packet_len = g_s_sms_send.payload_len - (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE;
+    uint32_t addr = g_s_sms_send.payload + (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE;
     // 最后一包
-    if (g_s_sms_send.payload_len - (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE <= LUAT_SMS_LONG_MSG_PDU_SIZE) {
-        memcpy(g_s_sms_pdu_packet.payload_buf, g_s_sms_send.payload + (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE, g_s_sms_send.payload_len - (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE);
-        g_s_sms_pdu_packet.payload_len = g_s_sms_send.payload_len - (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE ;
+    if (packet_len <= LUAT_SMS_LONG_MSG_PDU_SIZE) {
+        memcpy(g_s_sms_pdu_packet.payload_buf, (void *)addr, packet_len);
+        g_s_sms_pdu_packet.payload_len = packet_len;
     } else {
         // 继续发送
-        memcpy(g_s_sms_pdu_packet.payload_buf, g_s_sms_send.payload + (g_s_sms_pdu_packet.seqNum - 1) * LUAT_SMS_LONG_MSG_PDU_SIZE, LUAT_SMS_LONG_MSG_PDU_SIZE);
+        memcpy(g_s_sms_pdu_packet.payload_buf, (void *)addr, LUAT_SMS_LONG_MSG_PDU_SIZE);
         g_s_sms_pdu_packet.payload_len = LUAT_SMS_LONG_MSG_PDU_SIZE;
     }
     
     int len = luat_sms_pdu_packet(&g_s_sms_pdu_packet);
     ret = luat_sms_send_msg_v2(g_s_sms_pdu_packet.pdu_buf, len);
-    // 发送失败
+    // 发送失败
     if (ret) {
-        // 长短信接口
-        if(long_sms_send_idp) {
-            msg.arg1 = 0;
-            luat_msgbus_put(&msg, 0);
-        } else {
-            g_s_sms_pdu_packet.maxNum = 0;
-        }
-        if (g_s_sms_send.payload != NULL) {
-            luat_heap_free(g_s_sms_send.payload);
-            g_s_sms_send.payload = NULL;
-        }
+        luat_sms_send_done(0);
     }
     return;
 }
@@ -547,7 +540,6 @@ static int l_long_sms_send(lua_State *L) {
     if (!ret) {
         return 1;
     }
-    LLOGE("sms send task create failed");
 SMS_FAIL:
     long_sms_send_idp = 0;
     g_s_sms_pdu_packet.maxNum = 0;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 622 - 636
components/tp/cst92xx_fw.h


+ 2 - 2
components/tp/luat_lib_tp.c

@@ -36,7 +36,7 @@ static const tp_reg_t tp_regs[] = {
 	{"jd9261t_inited",  &tp_config_jd9261t_inited},
 	{"ft3x68", &tp_config_ft3x68},
     {"cst820", &tp_config_cst820},
-    {"cst9220", &tp_config_cst92xx},
+    // {"cst9220", &tp_config_cst92xx},
     #endif
 #endif
 #ifdef LUAT_USE_TP_PC
@@ -85,7 +85,7 @@ static int l_tp_handler(lua_State* L, void* ptr) {
             lua_call(L, 2, 0);
         }
     }
-    luat_tp_config->opts->read_done(luat_tp_config);
+    // luat_tp_config->opts->read_done(luat_tp_config);
     return 0;
 }
 

+ 3 - 4
components/tp/luat_tp.c

@@ -47,12 +47,11 @@ void luat_tp_task_entry(void* param){
         //     last_x = tp_data->x_coordinate;
         //     last_y = tp_data->y_coordinate;
         // }
-        
-        if (luat_tp_config->callback == NULL){
-            luat_tp_config->opts->read_done(luat_tp_config);
-        }else{
+
+        if (luat_tp_config->callback){
             luat_tp_config->callback(luat_tp_config,tp_data);
         }
+        luat_tp_config->opts->read_done(luat_tp_config);
     }
 }
 

+ 236 - 15
components/tp/luat_tp_cst9220.c

@@ -11,7 +11,9 @@
 #define LUAT_LOG_TAG "cst92xx"
 #include "luat_log.h"
 
-#define HYN_POWER_ON_UPDATA           (0) //touch fw updata
+#define HYN_POWER_ON_UPDATA           (0) //touch fw updata  //! !!!目前这里的升级还有点问题,不要随便开,小心变砖!!!
+#define HYNITRON_PROGRAM_PAGE_SIZE    (128)
+#define RW_REG_LEN   (2)
 
 #define CST92XX_ADDRESS               (0x5A)
 
@@ -72,8 +74,11 @@ typedef struct hyn_ts_data {
     struct tp_info hw_info;
     int boot_is_pass;
     int need_updata_fw;
+    int fw_updata_process;
 }hyn_ts_data_t;
 
+static int tp_cst92xx_enter_boot(luat_tp_config_t* luat_tp_config);
+static uint32_t cst92xx_read_checksum(luat_tp_config_t* luat_tp_config);
 static hyn_ts_data_t hyn_92xxdata = {0};
 
 typedef struct luat_touch_info{
@@ -107,19 +112,20 @@ static int hyn_wr_reg(luat_tp_config_t* luat_tp_config, uint32_t reg_addr, uint8
         reg_addr >>= 8;
     }
 
-    if (luat_tp_config->soft_i2c != NULL){
-        ret = i2c_soft_send(luat_tp_config->soft_i2c, luat_tp_config->address, (char *)wbuf, reg_len, 1);
-    }else{
-        ret = luat_i2c_send(luat_tp_config->i2c_id, luat_tp_config->address, wbuf, reg_len, 1);
-    }
-    if(rlen){
+    if (rlen){
         if (luat_tp_config->soft_i2c != NULL){
+            ret = i2c_soft_send(luat_tp_config->soft_i2c, luat_tp_config->address, (char *)wbuf, reg_len, 1);
             ret |= i2c_soft_recv(luat_tp_config->soft_i2c, luat_tp_config->address, (char *)rbuf, rlen);
         }else{
-            ret |= luat_i2c_recv(luat_tp_config->i2c_id, luat_tp_config->address, rbuf, rlen);
+            ret = luat_i2c_transfer(luat_tp_config->i2c_id, luat_tp_config->address, wbuf, reg_len, rbuf, rlen);
+        }
+    }else{
+        if (luat_tp_config->soft_i2c != NULL){
+            ret = i2c_soft_send(luat_tp_config->soft_i2c, luat_tp_config->address, (char *)wbuf, reg_len, 1);
+        }else{
+            ret = luat_i2c_send(luat_tp_config->i2c_id, luat_tp_config->address, wbuf, reg_len, 1);
         }
     }
-
     return ret;
 }
 
@@ -251,12 +257,215 @@ static int tp_cst92xx_updata_tpinfo(luat_tp_config_t* luat_tp_config){
 static int tp_cst92xx_hw_reset(luat_tp_config_t* luat_tp_config){
     if (luat_tp_config->pin_rst != LUAT_GPIO_NONE){
         luat_gpio_set(luat_tp_config->pin_rst, Luat_GPIO_LOW);
-        luat_rtos_task_sleep(8);
+        luat_rtos_task_sleep(1);
         luat_gpio_set(luat_tp_config->pin_rst, Luat_GPIO_HIGH);
     }
     return 0;
 }
 
+static int erase_all_mem(luat_tp_config_t* luat_tp_config)
+{
+    int ok = -1,t;
+    uint8_t i2c_buf[8];
+
+	//erase_all_mem
+    ok = hyn_wr_reg(luat_tp_config, 0xA0140000, 4, NULL, 0);
+    if (ok){
+        return -1;
+    }
+    ok = hyn_wr_reg(luat_tp_config, 0xA00C807F, 4, NULL, 0);
+    if (ok){
+        return -1;
+    }
+    ok = hyn_wr_reg(luat_tp_config, 0xA004EC, 3, NULL, 0);
+    if (ok){
+        return -1;
+    }
+        
+    luat_rtos_task_sleep(300);
+    for (t = 0;; t += 10) {
+        if (t >= 1000) {
+           return -1;
+        }
+
+        luat_rtos_task_sleep(10);
+
+        ok = hyn_wr_reg(luat_tp_config, 0xA005, 2, i2c_buf, 1);
+        if (ok) {
+            continue;
+        }
+
+        if (i2c_buf[0] == 0x88) {
+            break;
+        }
+    }
+
+    return 0;
+}
+
+static int write_mem_page(luat_tp_config_t* luat_tp_config, uint16_t addr, uint8_t *buf, uint16_t len)
+{
+    int ok = -1,t;
+    uint8_t i2c_buf[1024+2] = {0};
+    uint32_t write_data;
+
+    i2c_buf[0] = 0xA0;
+    i2c_buf[1] = 0x0C;
+    i2c_buf[2] = len;
+    i2c_buf[3] = len >> 8;
+    write_data = i2c_buf[0] << 24 + i2c_buf[1] << 16 + i2c_buf[2] << 8 + i2c_buf[3];    
+    //ok = hyn_i2c_write_r16(HYN_BOOT_I2C_ADDR, 0xA00C, i2c_buf, 2);
+    ok = tp_i2c_write(luat_tp_config, i2c_buf, 4, NULL, 0);
+    // ok = hyn_write_data(luat_tp_config, i2c_buf,RW_REG_LEN, 4);
+    if(ok){
+         return -1;
+    }
+
+
+    i2c_buf[0] = 0xA0;
+    i2c_buf[1] = 0x14;
+    i2c_buf[2] = addr;
+    i2c_buf[3] = addr >> 8;
+    ok = tp_i2c_write(luat_tp_config, i2c_buf, 4, NULL, 0);
+    // ok = hyn_write_data(luat_tp_config, i2c_buf,RW_REG_LEN, 4);
+    if(ok) {
+        return -1;
+    }
+
+
+    i2c_buf[0] = 0xA0;
+    i2c_buf[1] = 0x18;
+    memcpy(i2c_buf + 2, buf, len);         
+    ok = tp_i2c_write(luat_tp_config, i2c_buf, len+2, NULL, 0);   
+    // ok = hyn_write_data(luat_tp_config, i2c_buf,RW_REG_LEN, len+2);
+    if(ok){
+        return -1;
+    }
+
+
+    ok =  hyn_wr_reg(luat_tp_config,0xA004EE,3,NULL,0);
+    if(ok){
+        return -1;
+    }
+
+    for (t = 0;; t += 10) {
+        if (t >= 1000) {
+            return -1;
+        }
+
+        luat_rtos_task_sleep(5);
+
+        ok =  hyn_wr_reg(luat_tp_config,0xA005,2,i2c_buf,1);
+        if(ok){
+            continue;
+        }        
+
+        if (i2c_buf[0] == 0x55) {
+            break;
+        }
+    }
+
+    return 0;
+}
+
+static int write_code(luat_tp_config_t* luat_tp_config, uint8_t *bin_addr,uint8_t retry)
+{
+    uint8_t data[HYNITRON_PROGRAM_PAGE_SIZE+4];//= (uint8_t *)bin_addr;
+    uint16_t addr = 0;
+    uint16_t remain_len = CST92XX_BIN_SIZE;
+    int ret;
+   
+    while (remain_len > 0) {
+        uint16_t cur_len = remain_len;
+        if (cur_len > HYNITRON_PROGRAM_PAGE_SIZE) {
+            cur_len = HYNITRON_PROGRAM_PAGE_SIZE;
+        }
+        
+        memcpy(data, bin_addr + addr, HYNITRON_PROGRAM_PAGE_SIZE);
+        //HYN_INFO("write_code addr 0x%x 0x%x",addr,*data);
+        if (write_mem_page(luat_tp_config, addr, data, cur_len) ==  -1) {
+             return -1;
+        }
+        //data += cur_len;
+        addr += cur_len;
+        remain_len -= cur_len;
+    }
+    return 0;
+}
+
+static int cst92xx_updata_fw(luat_tp_config_t* luat_tp_config, uint8_t *bin_addr, uint16_t len)
+{ 
+    #define CHECKSUM_OFFECT  (0x7F6C)
+    int retry = 0;
+    int ok_copy = 0;
+    int ok = -1;
+    uint8_t i2c_buf[4];
+
+    uint32_t fw_checksum=0;
+
+    if(len < CST92XX_BIN_SIZE){
+        LLOGE("len = %d",len);
+        goto UPDATA_END;
+    }
+    if(len > CST92XX_BIN_SIZE) len = CST92XX_BIN_SIZE;
+
+    fw_checksum = U8TO32(bin_addr[CHECKSUM_OFFECT+3],bin_addr[CHECKSUM_OFFECT+2],bin_addr[CHECKSUM_OFFECT+1],bin_addr[CHECKSUM_OFFECT+0]);
+    LLOGD("updating fw checksum:0x%04x",fw_checksum);
+
+    luat_tp_irq_enable(luat_tp_config, 0);
+    
+    LLOGD("updata_fw start");
+    for(retry = 1; retry<5; retry++){
+        hyn_92xxdata.fw_updata_process = 0;
+        ok = tp_cst92xx_enter_boot(luat_tp_config);
+        if (ok){
+            continue;
+        }
+        hyn_92xxdata.fw_updata_process = 10;
+        ok = erase_all_mem(luat_tp_config);
+        if (ok){
+            continue;
+        }
+        hyn_92xxdata.fw_updata_process = 20;
+        ok = write_code(luat_tp_config,bin_addr,retry);
+        if (ok){
+            continue;
+        }
+        hyn_92xxdata.fw_updata_process = 30;
+        hyn_92xxdata.hw_info.ic_fw_checksum = cst92xx_read_checksum(luat_tp_config);
+        if(fw_checksum != hyn_92xxdata.hw_info.ic_fw_checksum){
+            LLOGD("out data fw checksum err:0x%04x",hyn_92xxdata.hw_info.ic_fw_checksum);
+            hyn_92xxdata.fw_updata_process |= 0x80;
+            continue;
+        }
+        hyn_92xxdata.fw_updata_process = 100;   
+        if(retry>=5){
+            ok_copy = -1;
+            break;
+        }
+        break;
+    }
+
+    hyn_wr_reg(luat_tp_config,0xA006EE,3,NULL,0); //exit boot
+    luat_rtos_task_sleep(2);
+
+UPDATA_END:   
+    tp_cst92xx_hw_reset(luat_tp_config);
+    luat_rtos_task_sleep(50);
+
+    if(ok_copy == 0){
+        tp_cst92xx_updata_tpinfo(luat_tp_config);
+        LLOGD("updata_fw success");
+    }
+    else{
+        LLOGD("updata_fw failed");
+    }
+
+    luat_tp_irq_enable(luat_tp_config, 1);
+
+    return ok_copy;
+}
+
 static int16_t read_word_from_mem(luat_tp_config_t* luat_tp_config, uint8_t type, uint16_t addr, uint32_t *value){
     uint8_t i2c_buf[4] = {0};
 
@@ -486,7 +695,10 @@ static int tp_cst92xx_init(luat_tp_config_t* luat_tp_config){
         LLOGE("cst92xx_updata_tpinfo failed");
         return ret;
     }
-    cst92xx_updata_judge(luat_tp_config,(uint8_t*)fw_bin,CST92XX_BIN_SIZE);
+    ret = cst92xx_updata_judge(luat_tp_config,(uint8_t*)fw_bin,CST92XX_BIN_SIZE);
+    //! !!!目前这里的升级还有点问题,不要开!!!
+    // if(ret == -1)
+    //     cst92xx_updata_fw(luat_tp_config, (uint8_t*)fw_bin, CST92XX_BIN_SIZE);
     tp_cst92xx_hw_reset(luat_tp_config);
     luat_rtos_task_sleep(40);
 #endif
@@ -659,10 +871,19 @@ static int tp_cst92xx_read(luat_tp_config_t* luat_tp_config, luat_tp_data_t *lua
     if (hyn_wr_reg(luat_tp_config, 0xD000,2,read_buff,sizeof(read_buff))){
         goto exit_;
     }   
-        
-    if (hyn_wr_reg(luat_tp_config, 0xD000AB,3,read_buff,0)){
-        goto exit_;
-    }   
+    
+    if (read_buff[0] & 0x0F)
+    {
+        if (hyn_wr_reg(luat_tp_config, 0xD000AB,3,read_buff,0)){
+            goto exit_;
+        }
+    }
+    else
+    {
+        if (hyn_wr_reg(luat_tp_config, 0xD000CC,3,read_buff,0)){
+            goto exit_;
+        }
+    }
 
     // luat_rtos_task_sleep(8);
     // for (size_t i = 0; i < sizeof(read_buff); i++){

+ 10 - 1
components/wlan/luat_lib_wlan.c

@@ -237,7 +237,16 @@ static int l_wlan_scan_result(lua_State* L) {
     }
     memset(results, 0, sizeof(luat_wlan_scan_result_t) * ap_limit);
     #ifdef LUAT_USE_DRV_WLAN
-    int len = luat_drv_wlan_scan_get_result(results, ap_limit);
+    int len = 0;
+    #ifdef LUAT_USE_AIRLINK
+    if (luat_airlink_has_wifi()) {
+        len = luat_drv_wlan_scan_get_result(results, ap_limit);
+    }
+    else
+    {
+        len = luat_wlan_scan_get_result(results, ap_limit);
+    }
+    #endif
     #else
     int len = luat_wlan_scan_get_result(results, ap_limit);
     #endif

+ 6 - 1
lua/src/loslib.c

@@ -215,7 +215,12 @@ static int os_getenv (lua_State *L) {
 #endif
 
 static int os_clock (lua_State *L) {
-  lua_pushinteger(L, ((lua_Integer)clock())/(lua_Integer)CLOCKS_PER_SEC);
+  #ifdef LUAT_USE_MCU
+  extern uint64_t luat_mcu_tick64(void);
+  lua_pushinteger(L, (lua_Integer)luat_mcu_tick64());
+  #else
+  lua_pushinteger(L, 0);
+  #endif
   return 1;
 }
 

+ 4 - 0
luat/include/luat_sfd.h

@@ -5,7 +5,9 @@
 #include "luat_base.h"
 
 #include "luat_spi.h"
+#ifdef __LUATOS__
 #include "luat_zbuff.h"
+#endif
 
 typedef struct sdf_opts {
     int (*initialize) (void* userdata);
@@ -25,7 +27,9 @@ typedef struct sfd_drv {
             int id;
             int cs;
         } spi;
+#ifdef __LUATOS__
         luat_zbuff_t* zbuff;
+#endif
     } cfg;
     size_t sector_size;
     size_t sector_count;

+ 1 - 1
luat/modules/luat_lib_mcu.c

@@ -402,7 +402,7 @@ static int l_mcu_xtal_ref_output(lua_State* L) {
 
 LUAT_WEAK int luat_mcu_muid(char* buf) {return -1;}
 static int l_mcu_muid(lua_State* L) {
-	char muid[20];
+	char muid[33] = {0};
 	luat_mcu_muid(muid);
 	// LLOGD("mcu muid %s", muid);
 	lua_pushstring(L, muid);

+ 9 - 0
luat/modules/luat_lib_rtos.c

@@ -248,6 +248,15 @@ local luatos_version = rtos.version()
 */
 static int l_rtos_version(lua_State *L) {
     lua_pushstring(L, luat_version_str());
+    // 当多传入一个参数时,且值为true,则返回数字版本号
+    if (lua_isboolean(L, 1) && lua_toboolean(L, 1)) {
+        #ifdef LUAT_CONF_FIRMWARE_TYPE_NUM
+        lua_pushinteger(L, LUAT_CONF_FIRMWARE_TYPE_NUM);
+        #else
+        lua_pushinteger(L, 0);
+        #endif
+        return 2;
+    }
     return 1;
 }
 

+ 2 - 2
module/Air780E/demo/fota2/main.lua

@@ -37,7 +37,7 @@ end)
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
+--   5缺少必要的PROJECT_KEY参数
 local function fota_cb(ret)
     log.info("fota", ret)
     if ret == 0 then
@@ -52,7 +52,7 @@ local function fota_cb(ret)
     elseif ret == 4 then
         log.info("接收报文错误", "检查模块固件或升级包内文件是否正常")
     elseif ret == 5 then
-        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
+        log.info("缺少必要的PROJECT_KEY参数")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

+ 0 - 161
module/Air780E/demo/iotcloud/oneNET/main.lua

@@ -1,161 +0,0 @@
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "oneNET_demo"
-VERSION = "1.0.0"
-
--- sys库是标配
-_G.sys = require("sys")
---[[特别注意, 使用mqtt库需要下列语句]]
-_G.sysplus = require("sysplus")
-lbsLoc2 = require("lbsLoc2")
-local iotcloud = require("iotcloud")
-mobile.simid(2, true)
--- Air780E的AT固件默认会为开机键防抖, 导致部分用户刷机很麻烦
-if rtos.bsp() == "EC618" and pm and pm.PWK_MODE then
-    pm.power(pm.PWK_MODE, false)
-end
-
-local produt_id = "4qM5N1Sa4T"
-local userid = "226691"
-local userkey = "pk1M3FKXBvvmjF8If/xDfSFFmr96NZCEg00sxlLBMjjh9vOD5hpIs42rmAYnMh5b3m9B1+0rmYdqzUyoQVrxow=="
-local device_name = mobile.imei()
-local send_data_time = 5 * 60 * 1000 -- 定时发送数据的时间,单位ms
-
--- 统一联网函数
-sys.taskInit(function()
-    local device_id = mcu.unique_id():toHex()
-
-    -- 默认都等到联网成功
-    sys.waitUntil("IP_READY")
-    sys.publish("net_ready", device_id)
-end)
-
-sys.taskInit(function()
-    -- 等待联网
-    local ret, device_id = sys.waitUntil("net_ready")
-
-    --------    以下接入方式根据自己需要修改,相关参数修改为自己的    ---------
-
-    -- ONENET云
-    -- 动态注册
-    iotcloudc = iotcloud.new(iotcloud.ONENET, {
-        device_name = device_name,
-        produt_id = produt_id,
-        userid = userid,
-        userkey = userkey
-    })
-    -- 一型一密
-    -- iotcloudc = iotcloud.new(iotcloud.ONENET,{produt_id = "xxx",product_secret = "xxx"})
-    -- 一机一密
-    -- iotcloudc = iotcloud.new(iotcloud.ONENET,{produt_id = "xxx",device_name = "xxx",device_secret = "xxx"})
-
-    if iotcloudc then
-        iotcloudc:connect()
-    end
-
-end)
-
--- 发布和订阅的主题
-
-local oneNET_sub = "$sys/" .. produt_id .. "/" .. device_name .. "/thing/property/post/reply"
-
-local oneNET_pub = "$sys/" .. produt_id .. "/" .. device_name .. "/thing/property/post"
-
-local function oneNET_send_data()
-    log.info("oneNET 链接成功,准备开始发送数据")
-    while 1 do
-        -- 没有mobile库就没有基站定位
-        mobile.reqCellInfo(15)
-        -- 由于基站定位需要等待扫描周围基站,推荐扫描时间为15S
-        sys.waitUntil("CELL_INFO_UPDATE", 15000)
-        local lat, lng, t = lbsLoc2.request(5000)
-        log.info("lbsLoc2", lat, lng, (json.encode(t or {})))
-        -- 如果没扫描到基站则给lat和lng赋值为0
-        if lat and lng then
-            log.info("扫描到了,有位置信息")
-        else
-            lat = "0"
-            lng = "0"
-        end
-        -- 读取CPU温度, 单位为0.001摄氏度, 是内部温度, 非环境温度
-        adc.open(adc.CH_CPU)
-        local cpu_temp = adc.get(adc.CH_CPU)
-        adc.close(adc.CH_CPU)
-        local gpio_pin = 6 -- GPIO编号
-        local gpio_state = gpio.get(gpio_pin)
-        local send_data = {
-            id = "123",
-            verson = VERSION,
-            params = {
-                gpio_state = {
-                    value = gpio_state
-                },
-                cpu_temp = {
-                    value = cpu_temp / 1000
-                },
-                lbs_lat = {
-                    value = tonumber(lat)
-                },
-                lbs_lng = {
-                    value = tonumber(lng)
-                    -- value = lng
-                }
-            }
-        }
-        local send_data = json.encode(send_data)
-        log.info("发送的数据为", send_data)
-        -- 正式发布数据
-        iotcloudc:publish(oneNET_pub, send_data)
-        -- 循环发送数据的定时时间
-        sys.wait(send_data_time)
-    end
-
-end
-
-local con = 0
---oneNET断开后的处理函数,
-local function oneNET_DISCONNECT()
-    log.info("云平台断开了,隔一分钟重连一次,如果10次都没有连上则重启设备")
-    while con < 10 do
-        sys.wait(60*1000)
-        log.info("oneNET reconnection",con)
-        iotcloudc:connect()
-    end
-    pm.reboot()
-end
-sys.subscribe("iotcloud", function(cloudc, event, data, payload)
-    -- 注意,此处不是协程内,复杂操作发消息给协程内进行处理
-    if event == iotcloud.CONNECT then -- 云平台联上了
-        log.info("iotcloud", "CONNECT", "oneNET平台连接成功")
-        iotcloudc:subscribe({
-            [oneNET_sub] = 1
-        }) -- 订阅服务器下发数据的主题
-        -- 链接成功,启动一个task专门用来定时发消息
-        sys.taskInit(oneNET_send_data)
-
-    elseif event == iotcloud.RECEIVE then
-        log.info("收到服务器下发的数据")
-        log.info("iotcloud", "topic", data, "payload", payload)
-
-        -- 用户处理代码
-        if payload then
-            payload = json.decode(payload)
-            if payload["code"] == 200 then
-                log.info("服务器收到了刚刚上传的数据", payload["msg"])
-            else
-                log.info("服务器接收数据有误", "错误码为", payload["code"], "错误信息为",
-                    payload["msg"])
-            end
-        end
-
-    elseif event == iotcloud.SEND then
-        log.info("发送数据成功")
-
-    elseif event == iotcloud.DISCONNECT then -- 云平台断开了
-       sys.taskInit(oneNET_DISCONNECT)
-    end
-end)
-
--- 用户代码已结束---------------------------------------------
--- 结尾总是这一句
-sys.run()
--- sys.run()之后后面不要加任何语句!!!!!

+ 58 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/audio_drv.lua

@@ -0,0 +1,58 @@
+--[[
+@module  audio_drv
+@summary 音频设备管理模块,负责音频设备的初始化和控制(仅使用exaudio扩展库)
+@version 2.0
+@date    2025.10.23
+@author  陈媛媛
+@usage
+本模块提供以下功能:
+1、定义所有硬件引脚常量
+2、使用exaudio扩展库初始化音频设备
+]]
+
+-- 引入exaudio库
+local exaudio = require("exaudio")
+
+-- exaudio配置参数
+local audio_configs = {
+    model = "es8311",         -- dac类型: "es8311"
+    i2c_id = 1,               -- i2c_id: 可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = 26,            -- 音频放大器电源控制管脚
+    dac_ctrl = 2,           -- 音频编解码芯片电源控制管脚
+    dac_delay = 3,            -- DAC启动前冗余时间(单位100ms)
+    pa_delay = 100,           -- DAC启动后延迟打开PA的时间(单位1ms)
+    dac_time_delay = 100,     -- 播放完毕后PA与DAC关闭间隔(单位1ms)
+    bits_per_sample = 16,     -- 采样位深
+    pa_on_level = 1           -- PA打开电平 1:高 0:低
+}
+
+
+exaudio.vol(70)            -- 喇叭音量
+exaudio.mic_vol(65)        -- 麦克风音量
+
+-- 初始化音频设备
+local function initAudioDevice()
+
+    -- 使用exaudio.setup统一配置音频设备
+    log.info("audio_drv", "使用exaudio.setup初始化音频设备")
+    if exaudio.setup(audio_configs) then
+        log.info("audio_drv", "exaudio.setup初始化成功")
+    else
+        log.error("audio_drv", "exaudio.setup初始化失败")
+        return false
+    end
+    
+    -- log.info("audio_drv", "Audio device initialized using exaudio only")
+    return true
+end
+
+-- 获取音频通道ID(保留用于兼容性)
+local function getMultimediaId()
+    return 0  -- 返回默认值0
+end
+
+-- 导出接口
+return {
+    initAudioDevice = initAudioDevice,
+    getMultimediaId = getMultimediaId
+}

+ 246 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/cc_app.lua

@@ -0,0 +1,246 @@
+--[[
+@module  cc_app
+@summary 通话业务逻辑模块,实现4种通话场景的处理和通话录音功能
+@version 1.0
+@date    2025.10.21
+@author  陈媛媛
+@usage
+本模块提供以下功能:
+1. 设置 ACTIVE_SCENARIO 变量选择需要的场景(1-4)
+2. 其他场景自动禁用,不会影响程序运行
+3. 每个场景都有独立的状态处理逻辑
+4. 实现通话录音功能
+
+支持的场景:
+[场景1] 呼入立即挂断(响铃3次后自动拒接)
+[场景2] 呼入自动接听(响2声)+10秒后主动挂断
+[场景3] 呼入自动接听(响2声)+等待对方挂断
+[场景4] 主动呼出电话+等待对方挂断
+
+
+注意事项:
+1. 设置ACTIVE_SCENARIO选择要启用的场景(1-4)
+2. 场景4会主动拨打电话(修改为自己测试时要拨打的电话号码)
+3. 所有通话都会被录音(需确保存储空间足够)
+]]
+
+-- 引入音频设备模块
+local audio_drv = require "audio_drv"
+
+-- ====================== 配置区域 ======================
+-- 设置当前激活的场景(1-4),注释掉不需要的场景
+-- local ACTIVE_SCENARIO = 1  -- 场景1:呼入立即挂断
+-- local ACTIVE_SCENARIO = 2  -- 场景2:呼入自动接听,10秒后主动挂断
+local ACTIVE_SCENARIO = 3  -- 场景3:呼入自动接听,等待对方挂断
+-- local ACTIVE_SCENARIO = 4  -- 场景4:主动呼出,等待对方挂断
+
+-- 全局状态变量
+local call_counter = 0                 -- 响铃计数器(用于场景1-3)
+local caller_number = ""               -- 来电号码(用于场景1-3)
+local is_connected = false             -- 通话连接状态标志(用于场景2)
+local outgoing_number = "10000"  -- 呼出号码(用于场景4),修改为自己测试时要拨打的电话号码
+
+-- ====================== 录音功能 ======================
+-- 创建音频数据缓冲区
+local up1 = zbuff.create(6400,0)      -- 上行数据保存区1
+local up2 = zbuff.create(6400,0)      -- 上行数据保存区2
+local down1 = zbuff.create(6400,0)    -- 下行数据保存区1
+local down2 = zbuff.create(6400,0)    -- 下行数据保存区2
+
+-- 音频数据回调函数
+local function recordCallback(is_dl, point)
+    if is_dl then
+        log.info("录音", "下行数据,位于缓存", point+1, "缓存1数据量", down1:used(), "缓存2数据量", down2:used())
+    else
+        log.info("录音", "上行数据,位于缓存", point+1, "缓存1数据量", up1:used(), "缓存2数据量", up2:used())
+    end
+    log.info("通话质量", cc.quality())
+    -- 可以在初始化串口后,通过uart.tx来发送走对应的zbuff即可
+end
+
+-- 启用通话录音
+local function enableRecording()
+    cc.record(true, up1, up2, down1, down2)
+    cc.on("record", recordCallback)
+    log.info("cc_app", "通话录音已启用")
+end
+
+-- 获取所有缓冲区
+local function getRecordingBuffers()
+    return {
+        up1 = up1,
+        up2 = up2,
+        down1 = down1,
+        down2 = down2
+    }
+end
+
+-- ====================== 场景处理函数 ======================
+
+-- 场景1:呼入立即挂断(响铃3次后)
+local function handle_scenario1(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景1", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃3声后拒接
+        if call_counter >= 3 then
+            log.info("场景1", "拒接来电")
+            cc.hangUp(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "HANGUP_CALL_DONE" then
+        log.info("场景1", "挂断完成")
+        call_counter = 0
+    end
+end
+
+-- 场景2挂断回调函数
+local function scenario2_hangup_callback()
+    log.info("场景2", "10秒通话结束,主动挂断")
+    cc.hangUp(0)
+    is_connected = false
+end
+
+-- 场景2:呼入自动接听,10秒后主动挂断
+local function handle_scenario2(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景2", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃2声后自动接听
+        if call_counter >= 2 then
+            log.info("场景2", "自动接听来电")
+            cc.accept(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "ANSWER_CALL_DONE" then
+        log.info("场景2", "接听完成,等待通话建立")
+    elseif status == "SPEECH_START" then
+        -- 语音通话真正开始
+        if not is_connected then
+            log.info("场景2", "通话已建立,开始计时")
+            is_connected = true
+            
+            -- 创建10秒后挂断的定时器
+            sys.timerStart(scenario2_hangup_callback, 10000)  -- 10秒后执行挂断
+            log.info("场景2", "10秒挂断定时器创建成功")
+        end
+    elseif status == "HANGUP_CALL_DONE" or status == "DISCONNECTED" then
+        log.info("场景2", "通话结束")
+        is_connected = false
+        
+        -- 取消挂断定时器
+        sys.timerStop(scenario2_hangup_callback)
+        log.info("场景2", "已取消挂断定时器")
+        
+        call_counter = 0  -- 重置计数器
+    end
+end
+
+-- 场景3:呼入自动接听,等待对方挂断
+local function handle_scenario3(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景3", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃2声后自动接听
+        if call_counter >= 2 then
+            log.info("场景3", "自动接听来电")
+            cc.accept(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "SPEECH_START" then
+        -- 语音通话真正开始
+        log.info("场景3", "电话已接通,电话号码:", caller_number)
+    elseif status == "DISCONNECTED" then
+        -- 对方挂断通话
+        log.info("场景3", "通话结束对方挂断")
+        call_counter = 0  -- 重置计数器
+    end
+end
+
+-- 场景4:主动呼出,等待对方挂断
+local function handle_scenario4(status)
+    if status == "CONNECTED" then
+        log.info("场景4", "呼叫接通")
+    elseif status == "DISCONNECTED" then
+        log.info("场景4", "通话结束(对方挂断)")
+    elseif status == "MAKE_CALL_FAILED" then
+        log.info("场景4", "呼叫失败")
+    end
+end
+
+-- 场景4拨号函数
+local function dial_for_scenario4()
+    log.info("场景4", "开始拨打", outgoing_number)
+    cc.dial(0, outgoing_number)
+end
+
+-- ====================== 主事件处理器 ======================
+sys.subscribe("CC_IND", function(status)
+    log.info("CC状态", status)
+    
+    -- 根据激活的场景调用对应的处理函数
+    if ACTIVE_SCENARIO == 1 then
+        handle_scenario1(status)
+    elseif ACTIVE_SCENARIO == 2 then
+        handle_scenario2(status)
+    elseif ACTIVE_SCENARIO == 3 then
+        handle_scenario3(status)
+    elseif ACTIVE_SCENARIO == 4 then
+        handle_scenario4(status)
+    end
+    
+    -- 所有场景都需要处理的通用状态
+    if status == "READY" then
+        sys.publish("CC_READY")  -- 发布系统就绪事件
+        
+        -- 场景4:电话系统就绪后自动拨号
+        if ACTIVE_SCENARIO == 4 then
+            sys.timerStart(dial_for_scenario4, 1000)  -- 延迟1秒拨号
+        end
+    elseif status == "HANGUP_CALL_DONE" or status == "MAKE_CALL_FAILED" or status == "DISCONNECTED" then
+        
+        audio.pm(0,audio.STANDBY)
+        -- audio.pm(0,audio.SHUTDOWN)   --低功耗可以选择SHUTDOWN或者POWEROFF,如果codec无法断电用SHUTDOWN
+    end
+end)
+
+-- ====================== 电话系统初始化 ======================
+local function init_cc()
+    -- 初始化音频设备(使用exaudio)
+    audio_drv.initAudioDevice()
+    
+    -- 等待电话系统就绪
+    sys.waitUntil("CC_READY")
+    
+    -- 初始化电话功能
+    cc.init(audio_drv.getMultimediaId())
+    
+    -- 启用通话录音(录音功能在cc_app中)
+    enableRecording()
+    
+    log.info("cc_app", "电话系统初始化完成")
+end
+
+-- 启动初始化任务
+sys.taskInit(init_cc)
+
+log.info("cc_app", "通话业务逻辑模块加载完成,当前场景:", ACTIVE_SCENARIO)
+
+-- 导出录音相关功能(如果需要被其他模块使用)
+-- return {
+--     enableRecording = enableRecording,
+--     getRecordingBuffers = getRecordingBuffers,
+--     recordCallback = recordCallback
+-- }

+ 78 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/main.lua

@@ -0,0 +1,78 @@
+--[[
+@module  main
+@summary LuatOS语音通话应用主入口,负责加载功能模块
+@version 1.0
+@date    2025.07.16
+@author  陈媛媛
+@usage
+本demo演示的核心功能为:
+1、音频设备初始化与控制
+2、完整通话业务逻辑处理(4种通话场景),详情如下:
+-呼入,挂断,挂断消息识别打印;
+-呼入,接听,接听消息识别打印,录音,主动挂断,挂断消息识别打印;
+-呼入,接听,接听消息识别打印,录音,对方挂断,挂断消息识别打印;
+-呼出,对方接通,接听消息识别打印,建立通话后一段时间,对方主动挂断,挂断消息识别打印;
+3、通话状态监控与日志记录
+4、支持Air8000系列和Air780EHV等硬件平台
+
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "VOICE_CALL_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)
+
+
+
+-- 仅加载必要的功能模块
+require "audio_drv"  -- 音频设备管理模块
+require "cc_app"        -- 通话业务逻辑模块
+
+-- 用户代码已结束---------------------------------------------
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!

+ 240 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/pins_Air780EHM.json

@@ -0,0 +1,240 @@
+{
+  "model": "Air780EHM",
+  "pins": [
+    [
+      7,
+      "PWR_KEY",
+      ""
+    ],
+    [
+      16,
+      "GPIO27",
+      ""
+    ],
+    [
+      17,
+      "UART1_RXD",
+      ""
+    ],
+    [
+      18,
+      "UART1_TXD",
+      ""
+    ],
+    [
+      19,
+      "GPIO22",
+      ""
+    ],
+    [
+      20,
+      "PWM1",
+      ""
+    ],
+    [
+      22,
+      "PWM0",
+      ""
+    ],
+    [
+      23,
+      "ONEWIRE",
+      ""
+    ],
+    [
+      25,
+      "CAN_TXD",
+      ""
+    ],
+    [
+      26,
+      "PWM4",
+      ""
+    ],
+    [
+      28,
+      "UART2_RXD",
+      ""
+    ],
+    [
+      29,
+      "UART2_TXD",
+      ""
+    ],
+    [
+      30,
+      "GPIO29",
+      ""
+    ],
+    [
+      31,
+      "GPIO30",
+      ""
+    ],
+    [
+      32,
+      "GPIO31",
+      ""
+    ],
+    [
+      33,
+      "GPIO32",
+      ""
+    ],
+    [
+      38,
+      "DBG_RXD",
+      ""
+    ],
+    [
+      39,
+      "DBG_TXD",
+      ""
+    ],
+    [
+      49,
+      "LCD_RST",
+      ""
+    ],
+    [
+      50,
+      "LCD_SDA",
+      ""
+    ],
+    [
+      51,
+      "LCD_RS",
+      ""
+    ],
+    [
+      52,
+      "LCD_CS",
+      ""
+    ],
+    [
+      53,
+      "LCD_CLK",
+      ""
+    ],
+    [
+      54,
+      "CAM_MCLK",
+      ""
+    ],
+    [
+      55,
+      "CAM_RX0",
+      ""
+    ],
+    [
+      56,
+      "CAM_RX1",
+      ""
+    ],
+    [
+      57,
+      "UART3_TXD",
+      ""
+    ],
+    [
+      58,
+      "UART3_RXD",
+      ""
+    ],
+    [
+      61,
+      "VBUS",
+      ""
+    ],
+    [
+      66,
+      "I2C1_SDA",
+      ""
+    ],
+    [
+      67,
+      "I2C1_SCL",
+      ""
+    ],
+    [
+      78,
+      "GPIO28",
+      ""
+    ],
+    [
+      79,
+      "USIM_DET",
+      ""
+    ],
+    [
+      80,
+      "CAM_BCLK",
+      ""
+    ],
+    [
+      81,
+      "CAM_CS",
+      ""
+    ],
+    [
+      82,
+      "USB_BOOT",
+      ""
+    ],
+    [
+      83,
+      "SPI0_CS",
+      ""
+    ],
+    [
+      84,
+      "SPI0_MISO",
+      ""
+    ],
+    [
+      85,
+      "SPI0_MOSI",
+      ""
+    ],
+    [
+      86,
+      "SPI0_CLK",
+      ""
+    ],
+    [
+      97,
+      "GPIO16",
+      ""
+    ],
+    [
+      99,
+      "GPIO23",
+      ""
+    ],
+    [
+      100,
+      "GPIO17",
+      ""
+    ],
+    [
+      101,
+      "WAKEUP0",
+      ""
+    ],
+    [
+      102,
+      "GPIO20",
+      ""
+    ],
+    [
+      106,
+      "CAN_RXD",
+      ""
+    ],
+    [
+      107,
+      "GPIO21",
+      ""
+    ]
+  ]
+}

+ 210 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHM_cc/readme.md

@@ -0,0 +1,210 @@
+# CC_DEMO 项目说明
+
+## 项目概述
+本项目是基于 Air780EHM/Air780EGH 的语音通信演示demo,实现了基本的语音通话功能,包括音频设备初始化、通话建立、通话管理等核心功能。
+
+## 文件结构
+- main.lua: 主程序入口,仅加载初始化模块
+- audio_device.lua: 管理音频设备初始化与控制
+- cc_app.lua: 实现完整通话业务逻辑
+- pins_Air780EHM.json/pins_Air780EGH.json: IO复用描述文件,由LuatIO工具自动生成
+
+## 功能说明
+1. **音频设备初始化与控制**:配置并管理ES8311音频编解码芯片和扬声器功放,包括I2C、I2S接口设置及音量控制。
+
+2. **完整通话业务逻辑处理**:实现4种通话场景,包括呼入和呼出的各种情况处理。
+
+按照自己的通话需求启用对应的Lua文件,其余注释掉;
+
+- (1)呼入,主动挂断(响铃3次后自动拒接);
+
+- (2)呼入,自动接听,接听消息识别打印,主动挂断,挂断消息识别打印;
+
+- (3)呼入,自动接听,接听消息识别打印,等待对方挂断,挂断消息识别打印;
+
+- (4)呼出,对方接通,接听消息识别打印,建立通话后一段时间,等待对方挂断,挂断消息识别打印;
+
+3. **通话状态监控与日志记录**:实时记录通话状态变化及相关信息。
+
+## 演示硬件环境
+ 
+ Air780EHM核心板/Air780EGH核心板+AirAUDIO_1010 音频扩展板+喇叭
+
+![alt text]( https://docs.openLuat.com/cdn/image/Air780EHM+Airaudio1010.jpg)
+
+
+![alt text]( https://docs.openLuat.com/cdn/image/Air780EGH-AIRAUDIO_1010.jpg)
+
+- 具备volte功能的电话卡插入开发板/核心板的sim卡槽
+
+2、TYPE-C USB数据线一根
+- Air780EHM核心板通过 TYPE-C USB 口供电;
+- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+3、可Air780EHM核心板/Air780EGH核心板和AirAudio_1010 扩展板的硬件接线方式为:
+|  Air780EHM核心板/Air780EGH核心板 | AirAUDIO_1010扩展板 |
+| ---------------                 | -----------------   |
+| 33/I2S_MCLK                     | I2S_MCLK            |
+| 30/I2S_BCK                      | I2S_BCK             |
+| 31/I2S_LRCK                     | I2S_LRCK            |
+| 32/I2S_DIN                      | I2S_DIN             |
+| 33/I2S_DOUT                     | I2S_DOUT            |
+| 67/I2C1_SCL                     | I2C_SCL             |
+| 66/I2C1_SDA                     | I2C_SDA             |
+| 25/GPIO26                       | PA_EN               |
+| 23/GPIO2                        | 8311_EN             |
+| VDD_EXT                         | VCC                 |
+| GND                             | GND                 |
+
+## 演示软件环境
+1、[Luatools下载调试工具](https://docs.openluat.com/air780epm/common/Luatools/)
+
+2、[Air780EHM V2016版本固件](https://cdn6.vue2.cn/Luat_tool_src/v2tools/LuatOS_Air780EHM/LuatOS-SoC_V2016_Air780EHM.zip),选择支持Volte功能的1、2、13或101、102、113号固件。不同版本区别请见[Air780EHM LuatOS固件版本](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+
+ [Air780EGH V2016版本固件](https://cdn6.vue2.cn/Luat_tool_src/v2tools/LuatOS_Air780EGH/LuatOS-SoC_V2016_Air780EGH.zip)选择支持Volte功能的1、2、13或101、102、113号固件。不同版本区别参考[Air780EGH LuatOS固件版本](https://docs.openluat.com/air780egh/luatos/firmware/version/)。
+
+3、[合宙 LuatIO 工具(GPIO 复用初始化配置)使用说明] (https://docs.openluat.com/air780epm/common/luatio/)
+
+4、 lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
+
+## 相关软件资料
+1、cc库   https://docs.openluat.com/osapi/core/cc/
+
+2、exaudio - 音频扩展库  https://docs.openluat.com/osapi/ext/exaudio/
+
+3、CC_IND -- 通话状态变化
+
+  "READY":通话准备完成,可以拨打电话或者呼入电话了
+
+  "INCOMINGCALL":有电话呼入
+  
+  "CONNECTED":电话已经接通
+
+  "DISCONNECTED":电话被对方挂断
+
+  "SPEECH_START":通话开始
+
+  "MAKE_CALL_OK":拨打电话请求成功
+
+  "MAKE_CALL_FAILED":拨打电话请求失败
+
+  "ANSWER_CALL_DONE":接听电话请求完成
+
+  "HANGUP_CALL_DONE":挂断电话请求完成
+
+  "PLAY":开始有音频输出
+
+## 演示核心步骤
+1、搭建好硬件环境
+
+2、demo脚本代码cc_app.lua中的local TEST_PHONE_NUMBER = "139xxxxxxxx"  -- 场景4拨打的测试号码,修改为自己测试时要拨打的电话号码
+
+3、Luatools烧录内核固件和修改后的demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+5、运行程序,观察日志输出了解通话状态
+
+- 场景1 呼入立即挂断
+
+    当设备启动并初始化完成后,打印READY和电话系统初始化完成。
+
+    当有来电时,会打印INCOMINGCALL,并开始计数响铃次数。
+
+    响铃3次后,自动拒接来电,打印拒接来电和挂断完成。
+
+    类似以下日志:
+
+    ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 1
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 1
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 2
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 3
+    I/user.场景1 拒接来电
+    I/user.CC状态 HANGUP_CALL_DONE
+    I/user.场景1 挂断完成
+    ``` 
+- 场景2 呼入自动接听+10秒后主动挂断
+
+    来电响铃2次后自动接听,通话建立10秒后设备会自动挂断。通话期间持续进行双向录音。
+
+    类似以下日志:
+
+    ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 2
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 PLAY
+    I/user.CC状态 INCOMINGCALL   
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 1  
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 2  
+    I/user.场景2 自动接听来电   
+    I/user.场景2 接听完成,等待通话建立             
+    I/user.场景2 通话已建立,开始计时  
+    I/user.场景2 10秒挂断定时器创建成功,ID: 2097153
+    I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+    I/user.通话质量 2
+    I/user.CC状态 DISCONNECTED
+    I/user.场景2 10秒通话结束,主动挂断  
+    I/user.场景2 已取消挂断定时器 
+    
+           
+- 场景3 呼入自动接听+等待对方挂断
+
+    呼入主动接听并等待对方主动挂断。通话期间持续进行双向录音。
+    
+    类似以下日志:
+
+  ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 3
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 PLAY
+    I/user.CC状态 INCOMINGCALL   
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 1  
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 2  
+    I/user.场景2 自动接听来电  
+    I/user.场景3 电话已接通,电话号码: 139xxxxxxxx
+    I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+    I/user.通话质量 2
+    I/user.CC状态 DISCONNECTED
+    I/user.场景3 通话结束对方挂断
+
+- 场景4 主动呼出预设号码并等待对方挂断。
+  
+  类似以下效果:
+
+  ``` lua
+   I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 4
+   I/user.exaudio_device 使用exaudio.setup初始化音频设备
+   I/user.exaudio_device exaudio.setup初始化成功
+   I/user.CC状态 READY
+   I/user.exaudio_device 通话录音已启用
+   I/user.cc_app 电话系统初始化完成
+   I/user.场景4 开始拨打 139xxxxxxxx
+   I/user.CC状态 MAKE_CALL_OK
+   I/user.CC状态 PLAY
+   I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+   I/user.通话质量 2
+   I/user.CC状态 DISCONNECTED
+   I/user.场景4 通话结束(对方挂断)
+
+
+
+
+    

+ 58 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/audio_drv.lua

@@ -0,0 +1,58 @@
+--[[
+@module  audio_drv
+@summary 音频设备管理模块,负责音频设备的初始化和控制(仅使用exaudio扩展库)
+@version 2.0
+@date    2025.10.23
+@author  陈媛媛
+@usage
+本模块提供以下功能:
+1、定义所有硬件引脚常量
+2、使用exaudio扩展库初始化音频设备
+]]
+
+-- 引入exaudio库
+local exaudio = require("exaudio")
+
+-- exaudio配置参数
+local audio_configs = {
+    model = "es8311",         -- dac类型: "es8311"
+    i2c_id = 0,               -- i2c_id: 可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = gpio.AUDIOPA_EN,            -- 音频放大器电源控制管脚
+    dac_ctrl = 20,           -- 音频编解码芯片电源控制管脚
+    dac_delay = 3,            -- DAC启动前冗余时间(单位100ms)
+    pa_delay = 100,           -- DAC启动后延迟打开PA的时间(单位1ms)
+    dac_time_delay = 100,     -- 播放完毕后PA与DAC关闭间隔(单位1ms)
+    bits_per_sample = 16,     -- 采样位深
+    pa_on_level = 1           -- PA打开电平 1:高 0:低
+}
+
+
+exaudio.vol(70)            -- 喇叭音量
+exaudio.mic_vol(65)        -- 麦克风音量
+
+-- 初始化音频设备
+local function initAudioDevice()
+
+    -- 使用exaudio.setup统一配置音频设备
+    log.info("audio_drv", "使用exaudio.setup初始化音频设备")
+    if exaudio.setup(audio_configs) then
+        log.info("audio_drv", "exaudio.setup初始化成功")
+    else
+        log.error("audio_drv", "exaudio.setup初始化失败")
+        return false
+    end
+    
+    -- log.info("audio_drv", "Audio device initialized using exaudio only")
+    return true
+end
+
+-- 获取音频通道ID(保留用于兼容性)
+local function getMultimediaId()
+    return 0  -- 返回默认值0
+end
+
+-- 导出接口
+return {
+    initAudioDevice = initAudioDevice,
+    getMultimediaId = getMultimediaId
+}

+ 246 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/cc_app.lua

@@ -0,0 +1,246 @@
+--[[
+@module  cc_app
+@summary 通话业务逻辑模块,实现4种通话场景的处理和通话录音功能
+@version 1.0
+@date    2025.10.21
+@author  陈媛媛
+@usage
+本模块提供以下功能:
+1. 设置 ACTIVE_SCENARIO 变量选择需要的场景(1-4)
+2. 其他场景自动禁用,不会影响程序运行
+3. 每个场景都有独立的状态处理逻辑
+4. 实现通话录音功能
+
+支持的场景:
+[场景1] 呼入立即挂断(响铃3次后自动拒接)
+[场景2] 呼入自动接听(响2声)+10秒后主动挂断
+[场景3] 呼入自动接听(响2声)+等待对方挂断
+[场景4] 主动呼出电话+等待对方挂断
+
+
+注意事项:
+1. 设置ACTIVE_SCENARIO选择要启用的场景(1-4)
+2. 场景4会主动拨打电话(修改为自己测试时要拨打的电话号码)
+3. 所有通话都会被录音(需确保存储空间足够)
+]]
+
+-- 引入音频设备模块
+local audio_drv = require "audio_drv"
+
+-- ====================== 配置区域 ======================
+-- 设置当前激活的场景(1-4),注释掉不需要的场景
+-- local ACTIVE_SCENARIO = 1  -- 场景1:呼入立即挂断
+-- local ACTIVE_SCENARIO = 2  -- 场景2:呼入自动接听,10秒后主动挂断
+--local ACTIVE_SCENARIO = 3  -- 场景3:呼入自动接听,等待对方挂断
+-- local ACTIVE_SCENARIO = 4  -- 场景4:主动呼出,等待对方挂断
+
+-- 全局状态变量
+local call_counter = 0                 -- 响铃计数器(用于场景1-3)
+local caller_number = ""               -- 来电号码(用于场景1-3)
+local is_connected = false             -- 通话连接状态标志(用于场景2)
+local outgoing_number = "10000"  -- 呼出号码(用于场景4),修改为自己测试时要拨打的电话号码
+
+-- ====================== 录音功能 ======================
+-- 创建音频数据缓冲区
+local up1 = zbuff.create(6400,0)      -- 上行数据保存区1
+local up2 = zbuff.create(6400,0)      -- 上行数据保存区2
+local down1 = zbuff.create(6400,0)    -- 下行数据保存区1
+local down2 = zbuff.create(6400,0)    -- 下行数据保存区2
+
+-- 音频数据回调函数
+local function recordCallback(is_dl, point)
+    if is_dl then
+        log.info("录音", "下行数据,位于缓存", point+1, "缓存1数据量", down1:used(), "缓存2数据量", down2:used())
+    else
+        log.info("录音", "上行数据,位于缓存", point+1, "缓存1数据量", up1:used(), "缓存2数据量", up2:used())
+    end
+    log.info("通话质量", cc.quality())
+    -- 可以在初始化串口后,通过uart.tx来发送走对应的zbuff即可
+end
+
+-- 启用通话录音
+local function enableRecording()
+    cc.record(true, up1, up2, down1, down2)
+    cc.on("record", recordCallback)
+    log.info("cc_app", "通话录音已启用")
+end
+
+-- 获取所有缓冲区
+local function getRecordingBuffers()
+    return {
+        up1 = up1,
+        up2 = up2,
+        down1 = down1,
+        down2 = down2
+    }
+end
+
+-- ====================== 场景处理函数 ======================
+
+-- 场景1:呼入立即挂断(响铃3次后)
+local function handle_scenario1(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景1", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃3声后拒接
+        if call_counter >= 3 then
+            log.info("场景1", "拒接来电")
+            cc.hangUp(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "HANGUP_CALL_DONE" then
+        log.info("场景1", "挂断完成")
+        call_counter = 0
+    end
+end
+
+-- 场景2挂断回调函数
+local function scenario2_hangup_callback()
+    log.info("场景2", "10秒通话结束,主动挂断")
+    cc.hangUp(0)
+    is_connected = false
+end
+
+-- 场景2:呼入自动接听,10秒后主动挂断
+local function handle_scenario2(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景2", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃2声后自动接听
+        if call_counter >= 2 then
+            log.info("场景2", "自动接听来电")
+            cc.accept(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "ANSWER_CALL_DONE" then
+        log.info("场景2", "接听完成,等待通话建立")
+    elseif status == "SPEECH_START" then
+        -- 语音通话真正开始
+        if not is_connected then
+            log.info("场景2", "通话已建立,开始计时")
+            is_connected = true
+            
+            -- 创建10秒后挂断的定时器
+            sys.timerStart(scenario2_hangup_callback, 10000)  -- 10秒后执行挂断
+            log.info("场景2", "10秒挂断定时器创建成功")
+        end
+    elseif status == "HANGUP_CALL_DONE" or status == "DISCONNECTED" then
+        log.info("场景2", "通话结束")
+        is_connected = false
+        
+        -- 取消挂断定时器
+        sys.timerStop(scenario2_hangup_callback)
+        log.info("场景2", "已取消挂断定时器")
+        
+        call_counter = 0  -- 重置计数器
+    end
+end
+
+-- 场景3:呼入自动接听,等待对方挂断
+local function handle_scenario3(status)
+    if status == "INCOMINGCALL" then
+        -- 获取来电号码
+        caller_number = cc.lastNum() or "未知号码"
+        call_counter = call_counter + 1
+        
+        log.info("场景3", "收到来电,号码:", caller_number, "响铃次数:", call_counter)
+        
+        -- 响铃2声后自动接听
+        if call_counter >= 2 then
+            log.info("场景3", "自动接听来电")
+            cc.accept(0)
+            call_counter = 0  -- 重置计数器
+        end
+    elseif status == "SPEECH_START" then
+        -- 语音通话真正开始
+        log.info("场景3", "电话已接通,电话号码:", caller_number)
+    elseif status == "DISCONNECTED" then
+        -- 对方挂断通话
+        log.info("场景3", "通话结束对方挂断")
+        call_counter = 0  -- 重置计数器
+    end
+end
+
+-- 场景4:主动呼出,等待对方挂断
+local function handle_scenario4(status)
+    if status == "CONNECTED" then
+        log.info("场景4", "呼叫接通")
+    elseif status == "DISCONNECTED" then
+        log.info("场景4", "通话结束(对方挂断)")
+    elseif status == "MAKE_CALL_FAILED" then
+        log.info("场景4", "呼叫失败")
+    end
+end
+
+-- 场景4拨号函数
+local function dial_for_scenario4()
+    log.info("场景4", "开始拨打", outgoing_number)
+    cc.dial(0, outgoing_number)
+end
+
+-- ====================== 主事件处理器 ======================
+sys.subscribe("CC_IND", function(status)
+    log.info("CC状态", status)
+    
+    -- 根据激活的场景调用对应的处理函数
+    if ACTIVE_SCENARIO == 1 then
+        handle_scenario1(status)
+    elseif ACTIVE_SCENARIO == 2 then
+        handle_scenario2(status)
+    elseif ACTIVE_SCENARIO == 3 then
+        handle_scenario3(status)
+    elseif ACTIVE_SCENARIO == 4 then
+        handle_scenario4(status)
+    end
+    
+    -- 所有场景都需要处理的通用状态
+    if status == "READY" then
+        sys.publish("CC_READY")  -- 发布系统就绪事件
+        
+        -- 场景4:电话系统就绪后自动拨号
+        if ACTIVE_SCENARIO == 4 then
+            sys.timerStart(dial_for_scenario4, 1000)  -- 延迟1秒拨号
+        end
+    elseif status == "HANGUP_CALL_DONE" or status == "MAKE_CALL_FAILED" or status == "DISCONNECTED" then
+        
+        audio.pm(0,audio.STANDBY)
+        -- audio.pm(0,audio.SHUTDOWN)   --低功耗可以选择SHUTDOWN或者POWEROFF,如果codec无法断电用SHUTDOWN
+    end
+end)
+
+-- ====================== 电话系统初始化 ======================
+local function init_cc()
+    -- 初始化音频设备(使用exaudio)
+    audio_drv.initAudioDevice()
+    
+    -- 等待电话系统就绪
+    sys.waitUntil("CC_READY")
+    
+    -- 初始化电话功能
+    cc.init(audio_drv.getMultimediaId())
+    
+    -- 启用通话录音(录音功能在cc_app中)
+    enableRecording()
+    
+    log.info("cc_app", "电话系统初始化完成")
+end
+
+-- 启动初始化任务
+sys.taskInit(init_cc)
+
+log.info("cc_app", "通话业务逻辑模块加载完成,当前场景:", ACTIVE_SCENARIO)
+
+-- 导出录音相关功能(如果需要被其他模块使用)
+-- return {
+--     enableRecording = enableRecording,
+--     getRecordingBuffers = getRecordingBuffers,
+--     recordCallback = recordCallback
+-- }

+ 79 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/main.lua

@@ -0,0 +1,79 @@
+--[[
+@module  main
+@summary LuatOS语音通话应用主入口,负责加载功能模块
+@version 1.0
+@date    2025.07.16
+@author  陈媛媛
+@usage
+本demo演示的核心功能为:
+1、音频设备初始化与控制
+2、完整通话业务逻辑处理(5种通话场景),详情如下:
+-呼入,挂断,挂断消息识别打印;
+-呼入,接听,接听消息识别打印,录音,主动挂断,挂断消息识别打印;
+-呼入,接听,接听消息识别打印,录音,对方挂断,挂断消息识别打印;
+-呼出,对方接通,接听消息识别打印,建立通话后一段时间,对方主动挂断,挂断消息识别打印;
+-通话中给对方播放音频功能(暂不支持,待补充)
+3、通话状态监控与日志记录
+4、支持Air8000系列和Air780EHV等硬件平台
+
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "VOICE_CALL_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)
+
+
+
+-- 仅加载必要的功能模块
+require "audio_drv"  -- 音频设备管理模块
+require "cc_app"        -- 通话业务逻辑模块
+
+-- 用户代码已结束---------------------------------------------
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!

+ 198 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/Air780EHV_cc/readme.md

@@ -0,0 +1,198 @@
+# CC_DEMO 项目说明
+
+## 项目概述
+本项目是基于 Air780EHV 的语音通信演示demo,实现了基本的语音通话功能,包括音频设备初始化、通话建立、通话管理等核心功能。
+
+## 文件结构
+- main.lua: 主程序入口,仅加载初始化模块
+- audio_device.lua: 管理音频设备初始化与控制
+- cc_app.lua: 实现完整通话业务逻辑
+
+## 功能说明
+1. **音频设备初始化与控制**:使用exaudio.setup统一配置ES8311音频编解码芯片和扬声器功放,包括I2C、I2S接口设置及音量控制。
+
+2. **完整通话业务逻辑处理**:实现4种通话场景,包括呼入和呼出的各种情况处理。
+
+按照自己的通话需求启用对应的Lua文件,其余注释掉;
+
+- (1)呼入,主动挂断(响铃3次后自动拒接);
+
+- (2)呼入,自动接听,接听消息识别打印,主动挂断,挂断消息识别打印;
+
+- (3)呼入,自动接听,接听消息识别打印,等待对方挂断,挂断消息识别打印;
+
+- (4)呼出,对方接通,接听消息识别打印,建立通话后一段时间,等待对方挂断,挂断消息识别打印;
+
+3. **通话状态监控与日志记录**:实时记录通话状态变化及相关信息,所有通话场景均支持双向录音(上行+下行),实时记录通话数据。
+
+## 演示硬件环境
+1、Air780EHV核心板+AirAUDIO_1000配件板+喇叭
+
+![alt text](https://docs.openLuat.com/cdn/image/Air780EHV+Airaudio1000.jpg)
+ 
+- 具备volte功能的电话卡插入开发板/核心板的sim卡槽
+
+2、TYPE-C USB数据线一根
+- Air780EHV核心板通过 TYPE-C USB 口供电;
+- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+3、可选AirAudio_1000 配件板一块,Air780EHV核心板和AirAudio_1000 配件板的硬件接线方式为:
+| Air780EHV核心板 | AirAUDIO_1000配件板 |
+| ---------------| -----------------   |
+| 3/MIC+         |     MIC+            |
+| 4/MIC-         |     MIC-            |
+| 5/SPK+         |     SPK+            |
+| 6/SPK-         |     SPK-            |
+| 19/GPIO22      |     PA_EN           |
+| 3V3            |     VCC             |
+| GND            |     GND             |
+
+## 演示软件环境
+1、Luatools下载调试工具 [https://docs.openluat.com/air780epm/common/Luatools/]
+
+2、Air780EHV V2014版本固件(理论上,2025年7月26日之后发布的固件都可以),选择支持Volte功能的固件。不同版本区别请见https://docs.openluat.com/air780ehv/luatos/firmware/version/
+
+3、合宙 LuatIO 工具(GPIO 复用初始化配置)使用说明  https://docs.openluat.com/air780epm/common/luatio/
+
+4、 lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
+
+## 相关软件资料
+1、cc库   https://docs.openluat.com/osapi/core/cc/
+
+2、exaudio - 音频扩展库  https://docs.openluat.com/osapi/ext/exaudio/
+
+3、CC_IND -- 通话状态变化
+
+  "READY":通话准备完成,可以拨打电话或者呼入电话了
+
+  "INCOMINGCALL":有电话呼入
+
+  "CONNECTED":电话已经接通
+
+  "DISCONNECTED":电话被对方挂断
+
+  "SPEECH_START":通话开始
+
+  "MAKE_CALL_OK":拨打电话请求成功
+
+  "MAKE_CALL_FAILED":拨打电话请求失败
+
+  "ANSWER_CALL_DONE":接听电话请求完成
+
+  "HANGUP_CALL_DONE":挂断电话请求完成
+  
+  "PLAY":开始有音频输出
+
+## 演示核心步骤
+1、搭建好硬件环境
+
+2、demo脚本代码cc_app.lua中的local TEST_PHONE_NUMBER = "139XXXXXXXX"  -- 场景4拨打的测试号码,修改为自己测试时要拨打的电话号码
+
+3、Luatools烧录内核固件和修改后的demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+5、运行程序,观察日志输出了解通话状态
+
+- 场景1 呼入立即挂断
+
+    当设备启动并初始化完成后,打印READY和电话系统初始化完成。
+
+    当有来电时,会打印INCOMINGCALL,并开始计数响铃次数。
+
+    响铃3次后,自动拒接来电,打印拒接来电和挂断完成。
+
+    类似以下日志:
+
+    ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 1
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 1
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 2
+    I/user.CC状态 INCOMINGCALL
+    I/user.场景1 收到来电,号码: 139XXXXXXXX 响铃次数: 3
+    I/user.场景1 拒接来电
+    I/user.CC状态 HANGUP_CALL_DONE
+    I/user.场景1 挂断完成
+    ``` 
+- 场景2 呼入自动接听+10秒后主动挂断
+
+    来电响铃2次后自动接听,通话建立10秒后设备会自动挂断。通话期间持续进行双向录音。
+
+    类似以下日志:
+
+    ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 2
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 PLAY
+    I/user.CC状态 INCOMINGCALL   
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 1  
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 2  
+    I/user.场景2 自动接听来电   
+    I/user.场景2 接听完成,等待通话建立             
+    I/user.场景2 通话已建立,开始计时  
+    I/user.场景2 10秒挂断定时器创建成功,ID: 2097153
+    I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+    I/user.通话质量 2
+    I/user.CC状态 DISCONNECTED
+    I/user.场景2 10秒通话结束,主动挂断  
+    I/user.场景2 已取消挂断定时器 
+    
+           
+- 场景3 呼入自动接听+等待对方挂断
+
+    呼入主动接听并等待对方主动挂断。通话期间持续进行双向录音。
+    
+    类似以下日志:
+
+  ``` lua
+    I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 3
+    I/user.exaudio_device 使用exaudio.setup初始化音频设备
+    I/user.exaudio_device exaudio.setup初始化成功
+    I/user.CC状态 READY
+    I/user.exaudio_device 通话录音已启用
+    I/user.cc_app 电话系统初始化完成
+    I/user.CC状态 PLAY
+    I/user.CC状态 INCOMINGCALL   
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 1  
+    I/user.场景2 收到来电,号码: 139xxxxxxxx 响铃次数: 2  
+    I/user.场景2 自动接听来电  
+    I/user.场景3 电话已接通,电话号码: 139xxxxxxxx
+    I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+    I/user.通话质量 2
+    I/user.CC状态 DISCONNECTED
+    I/user.场景3 通话结束对方挂断
+
+- 场景4 主动呼出预设号码并等待对方挂断。
+  
+  类似以下效果:
+
+  ``` lua
+   I/user.cc_app 通话业务逻辑模块加载完成,当前场景: 4
+   I/user.exaudio_device 使用exaudio.setup初始化音频设备
+   I/user.exaudio_device exaudio.setup初始化成功
+   I/user.CC状态 READY
+   I/user.exaudio_device 通话录音已启用
+   I/user.cc_app 电话系统初始化完成
+   I/user.场景4 开始拨打 139xxxxxxxx
+   I/user.CC状态 MAKE_CALL_OK
+   I/user.CC状态 PLAY
+   I/user.录音 上行数据,位于缓存 1 缓存1数据量 6400 缓存2数据量 0
+   I/user.通话质量 2
+   I/user.CC状态 DISCONNECTED
+   I/user.场景4 通话结束(对方挂断)
+
+
+
+
+    

+ 0 - 126
module/Air780EHM_Air780EHV_Air780EGH/demo/CC/main.lua

@@ -1,126 +0,0 @@
-
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "ccdemo"
-VERSION = "1.0.0"
-log.style(1)
---[[]
-运行环境:Air780EHV核心板+AirAUDIO_1000配件板
-最后修改时间:2025-6-17
-使用了如下IO口:
-[3, "MIC+", " PIN3脚, 用于麦克风正极"],
-[4, "MIC-", " PIN4脚, 用于麦克风负极"],
-[5, "spk+", " PIN5脚, 用于喇叭正极"],
-[6, "spk-", " PIN6脚, 用于喇叭负极"],
-[20, "AudioPA_EN", " PIN20脚, 用于PA使能脚"],
-3.3V
-GND
-执行逻辑为:
-设置i2s和音频参数,读取文件qianzw.txt里面的内容,然后播放出来
-]]
-
--- sys库是标配
-sys = require("sys")
-
-local up1 = zbuff.create(6400,0)
-local up2 = zbuff.create(6400,0)
-local down1 = zbuff.create(6400,0)
-local down2 = zbuff.create(6400,0)
-local cnt = 0
-
-local function record(is_dl, point)
-    if is_dl then
-        log.info("下行数据,位于缓存", point+1, "缓存1数据量", down1:used(), "缓存2数据量", down2:used())
-    else
-        log.info("上行数据,位于缓存", point+1, "缓存1数据量", up1:used(), "缓存2数据量", up2:used())
-    end
-	log.info("通话质量", cc.quality())
-    -- 可以在初始化串口后,通过uart.tx来发送走对应的zbuff即可
-end
-
-sys.subscribe("CC_IND", function(state)
-    log.info("cc状态", state)
-    if state == "READY" then
-        sys.publish("CC_READY")
-    elseif state == "INCOMINGCALL" then
-		cnt = cnt + 1
-		if cnt > 1 then
-			cc.accept(0)
-		end
-    elseif state == "HANGUP_CALL_DONE" or state == "MAKE_CALL_FAILED" or state == "DISCONNECTED" then
-		audio.pm(0,audio.STANDBY)
-		-- audio.pm(0,audio.SHUTDOWN)	--低功耗可以选择SHUTDOWN或者POWEROFF,如果codec无法断电用SHUTDOWN
-	end
-end)
-
-
-
-sys.taskInit(function()
-    cc.on("record", record)
-    cc.record(true, up1, up2, down1, down2)
-    local multimedia_id = 0
- local i2c_id = 0 -- i2c_id 0
-
-    local pa_pin = gpio.AUDIOPA_EN -- 喇叭pa功放脚
-    local power_pin = 20 -- es8311电源脚
-
-    local i2s_id = 0 -- i2s_id 0
-    local i2s_mode = 0 -- i2s模式 0 主机 1 从机
-    local i2s_sample_rate = 16000 -- 采样率
-    local i2s_bits_per_sample = 16 -- 数据位数
-    local i2s_channel_format = i2s.MONO_R -- 声道, 0 左声道, 1 右声道, 2 立体声
-    local i2s_communication_format = i2s.MODE_LSB -- 格式, 可选MODE_I2S, MODE_LSB, MODE_MSB
-    local i2s_channel_bits = 16 -- 声道的BCLK数量
-
-    local multimedia_id = 0 -- 音频通道 0
-    local pa_on_level = 1 -- PA打开电平 1 高电平 0 低电平
-    local power_delay = 3 -- 在DAC启动前插入的冗余时间,单位100ms
-    local pa_delay = 100 -- 在DAC启动后,延迟多长时间打开PA,单位1ms
-    local power_on_level = 1 -- 电源控制IO的电平,默认拉高
-    local power_time_delay = 100 -- 音频播放完毕时,PA与DAC关闭的时间间隔,单位1ms
-
-    local voice_vol = 70 -- 喇叭音量
-    local mic_vol = 80 -- 麦克风音量
-    gpio.setup(power_pin, 1, gpio.PULLUP)
-    gpio.setup(pa_pin, 1, gpio.PULLUP)
-
-    sys.wait(200)
-
-
-    i2c.setup(i2c_id, i2c.FAST) -- 设置i2c
-    i2s.setup(i2s_id, i2s_mode, i2s_sample_rate, i2s_bits_per_sample, i2s_channel_format, i2s_communication_format,
-        i2s_channel_bits) -- 设置i2s
-
-    audio.config(multimedia_id, pa_pin, pa_on_level, power_delay, pa_delay, power_pin, power_on_level, power_time_delay)
-    audio.setBus(multimedia_id, audio.BUS_I2S, {
-        chip = "es8311",
-        i2cid = i2c_id,
-        i2sid = i2s_id,
-    }) -- 通道0的硬件输出通道设置为I2S
-
-    audio.vol(multimedia_id, voice_vol)
-    audio.micVol(multimedia_id, mic_vol)
-
-    cc.init(multimedia_id)
-	audio.pm(0,audio.STANDBY)
-    sys.waitUntil("CC_READY")
-    log.info("一切就绪,5S后准备打电话")
-    sys.wait(5000)   
-    -- cc.dial(0,"12345678910) --拨打电话
-
-
-
-end)
-
--- sys.taskInit(function()
---     while 1 do
---         -- 打印内存状态, 调试用
---         sys.wait(1000)
---         log.info("lua", rtos.meminfo())
---         log.info("sys", rtos.meminfo("sys"))
---     end
--- end)
-
--- 用户代码已结束---------------------------------------------
--- 结尾总是这一句
-sys.run()
--- sys.run()之后后面不要加任何语句!!!!!

+ 16 - 7
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/AirGPIO_1000.lua

@@ -1,4 +1,19 @@
---本文件中的主机是指I2C主机,具体指Air780EHV
+--[[
+@module  AirGPIO_1000
+@summary AirGPIO_1000应用功能模块 
+@version 1.0
+@date    2025.10.21
+@author  沈园园
+@usage
+本文件为AirGPIO_1000驱动配置文件,核心业务逻辑为:
+1、配置主机和AirGPIO_1000之间的通信参数;
+2、配置AirGPIO_1000上的扩展GPIO管脚功能;支持配置为输出,输入和中断三种模式;
+
+本文件没有对外接口,直接require "AirGPIO_1000"就可以加载运行;
+]]
+
+
+--本文件中的主机是指I2C主机,具体指Air780EHM/Air780EHV/Air780EGH
 --本文件中的从机是指I2C从机,具体指AirGPIO_1000配件板上的IO扩展芯片
 
 local AirGPIO_1000 = 
@@ -138,7 +153,6 @@ end
 --        AirGPIO_1000可以扩展出来16个GPIO,这些GPIO支持配置为输入;
 --        AirGPIO_1000上的任意一个输入GPIO的状态发生上升沿或者下降沿变化时,会通过INT引脚通知到主机的int_id中断引脚;
 --        此时主机可以通过I2C接口立即读取AirGPIO_1000上配置为输入模式的扩展GPIO的电平状态,从而判断是哪些扩展GPIO的输入电平发生了变化;
---        取值范围:nil或者空,或者0到9,或者12到55,注意不要使用已经复用为其他功能的引脚;nil或者空时,表示不使用中断通知功能;
 --        如果没有传入此参数,则默认为空,表示不使用中断通知功能;
 
 --返回值:成功返回true,失败返回false
@@ -149,11 +163,6 @@ function AirGPIO_1000.init(i2c_id, gpio_int_id)
         return false
     end
 
-    if not (gpio_int_id==nil or gpio_int_id>=0 and gpio_int_id<=9 or gpio_int_id>=12 and gpio_int_id<=55) then
-        log.error("AirGPIO_1000.init", "invalid gpio_int_id", gpio_int_id)
-        return false
-    end
-
     AirGPIO_1000.i2c_id = i2c_id
     AirGPIO_1000.gpio_int_id = gpio_int_id
 

+ 20 - 5
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/gpio_app.lua

@@ -1,3 +1,18 @@
+--[[
+@module  gpio_app
+@summary gpio_app应用功能模块 
+@version 1.0
+@date    2025.10.21
+@author  沈园园
+@usage
+本文件为gpio_app应用功能模块,核心业务逻辑为:
+1、初始化Air780EHM/Air780EHV/Air780EGH和AirGPIO_1000之间的通信参数
+2、GPIO输出测试,输入测试,GPIO中断测试
+
+本文件没有对外接口,直接在main.lua中require "gpio_app"就可以加载运行;
+]]
+
+
 --加载AirGPIO_1000驱动文件
 local air_gpio = require "AirGPIO_1000"
 
@@ -73,11 +88,11 @@ local function gpio_int_task_func()
 end
 
 
---初始化Air780EHV和AirGPIO_1000之间的通信参数
---使用Air780EHV的I2C1
---使用Air780EHV的GPIO2做为中断引脚
---Air780EHV核心板和AirGPIO_1000配件板的接线方式如下
---Air780EHV核心板             AirGPIO_1000配件板
+--初始化Air780EHM/Air780EHV/Air780EGH和AirGPIO_1000之间的通信参数
+--使用Air780EHM/Air780EHV/Air780EGH的I2C1
+--使用Air780EHM/Air780EHV/Air780EGH的GPIO2做为中断引脚
+--Air780EHM/Air780EHV/Air780EGH核心板和AirGPIO_1000配件板的接线方式如下
+--Air780EHM/Air780EHV/Air780EGH核心板             AirGPIO_1000配件板
 --      3V3-----------------3V3
 --       GND-----------------GND
 --     66/I2C1SDA-----------------SDA

+ 14 - 6
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/main.lua

@@ -1,3 +1,17 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.24
+@author  沈园园
+@usage
+AirGPIO_1000是合宙设计生产的一款I2C转16路扩展GPIO的配件板;
+本demo演示的核心功能为:
+Air780EHM/Air780EHV/Air780EGH核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
+分输出、输入和中断三种应用场景来演示;
+更多说明参考本目录下的readme.md文件
+]]
+
 --[[
 必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
 PROJECT:项目名,ascii string类型
@@ -7,12 +21,6 @@ VERSION:项目版本号,ascii string类型
             X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
-
-AirGPIO_1000是合宙设计生产的一款I2C转16路扩展GPIO的配件板;
-本demo演示的核心功能为:
-Air8101核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
-分输出、输入和中断三种应用场景来演示;
-更多说明参考本目录下的readme.md文件
 ]]
 PROJECT = "AirGPIO_1000"
 VERSION = "001.000.000"

+ 20 - 8
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirGPIO_1000/readme.md

@@ -1,3 +1,10 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、gpio_app.lua:AirGPIO_1000扩展GPIO输出测试,输入测试,GPIO中断测试;
+
+3、AirGPIO_1000.lua:AirGPIO_1000驱动配置文件;
 
 ## 演示功能概述
 
@@ -5,28 +12,29 @@ AirGPIO_1000是合宙设计生产的一款I2C转16路扩展GPIO的配件板;
 
 本demo演示的核心功能为:
 
-Air780EHV核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
+Air780EHM/Air780EHV/Air780EGH核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
 
 分输出、输入和中断三种应用场景来演示;
 
-
 ## 核心板+配件板资料
 
-[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+[Air780EHM/Air780EHV/Air780EGH](https://docs.openluat.com/air780ehv/product/shouce/)
+
+[AirGPIO_1000配件板相关资料](https://docs.openluat.com/accessory/AirGPIO_1000/)
 
 ## 演示硬件环境
 
 ![](https://docs.openluat.com/accessory/AirGPIO_1000/image/connect_Air780EHV.jpg)
 
-1、Air780EHV核心板
+1、Air780EHM/Air780EHV/Air780EGH核心板
 
 2、AirGPIO_1000配件板
 
 3、母对母的杜邦线8根
 
-4、Air780EHV核心板和AirGPIO_1000配件板的硬件接线方式为
+4、Air780EHM/Air780EHV/Air780EGH核心板和AirGPIO_1000配件板的硬件接线方式为
 
-| Air780EHV核心板 | AirGPIO_1000配件板 |
+| Air780EHM/Air780EHV/Air780EGH核心板 | AirGPIO_1000配件板 |
 | ------------ | ------------------ |
 |     3V3     |         3V3        |
 |     GND   |         GND        |
@@ -43,9 +51,13 @@ Air780EHV核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
 
 ## 演示软件环境
 
-1、Luatools下载调试工具
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
+
+2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
 
-2、[Air780EHV最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+4、[Air780EGH 最新版本的内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
 
 
 ## 演示操作步骤

+ 15 - 11
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/AirKEY_1000.lua

@@ -1,4 +1,18 @@
---本文件中的主机是指Air780EHV核心板
+--[[
+@module  AirKEY_1000
+@summary AirKEY_1000应用功能模块 
+@version 1.0
+@date    2025.10.22
+@author  沈园园
+@usage
+本文件为AirKEY_1000驱动配置文件,核心业务逻辑为:
+1、配置主机和AirKEY_1000之间的控制参数;
+
+本文件没有对外接口,直接require "AirKEY_1000"就可以加载运行;
+]]
+
+
+--本文件中的主机是指Air780EHM/Air780EHV/Air780EGH核心板
 --AirKEY_1000是合宙设计生产的一款8路独立按键的配件板
 
 local AirKEY_1000 = {}
@@ -31,16 +45,6 @@ local AirKEY_1000 = {}
 
 --返回值:成功返回true,失败返回false
 function AirKEY_1000.setup(key_id, gpio_id, int_mode, int_cbfunc)
-    if not (key_id>=1 and key_id<=8) then
-        log.error("AirKEY_1000.setup error", "invalid key_id", key_id)
-        return false
-    end
-
-    if not (gpio_id>=0 and gpio_id<=9 or gpio_id>=12 and gpio_id<=55) then
-        log.error("AirKEY_1000.setup error", "invalid gpio_id", gpio_id)
-        return false
-    end
-
     if not (int_mode==gpio.RISING or int_mode==gpio.FALLING) then
         log.error("AirKEY_1000.setup error", "invalid int_mode", int_mode)
         return false

+ 21 - 13
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/key_app.lua

@@ -1,14 +1,22 @@
 --[[
-本功能模块演示的内容为:
-使用Air780EHV核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态
-AirKEY_1000是合宙设计生产的一款8路独立按键的配件板
+@module  key_app
+@summary key_app应用功能模块 
+@version 1.0
+@date    2025.10.22
+@author  沈园园
+@usage
+本文件为key_app应用功能模块,核心业务逻辑为:
+1、使用Air780EHM/Air780EHV/Air780EGH核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态
+2、AirKEY_1000是合宙设计生产的一款8路独立按键的配件板
+
+本文件没有对外接口,直接在main.lua中require "key_app"就可以加载运行;
 ]]
 
 
 --加载AirKEY_1000驱动文件
 local air_key = require "AirKEY_1000"
 
---AirKEY_1000上8个按键对应的Air780EHV的GPIO ID
+--AirKEY_1000上8个按键对应的Air780EHM/Air780EHV/Air780EGH的GPIO ID
 local KEY1_GPIO_ID = 26
 local KEY2_GPIO_ID = 21
 local KEY3_GPIO_ID = 24
@@ -21,7 +29,7 @@ local KEY8_GPIO_ID = 27
 
 --按键1的中断处理函数
 --int_level:number类型,表示触发中断后,某一时刻引脚的电平,1为高电平,0为低电平,并不一定是触发中断时的电平
---gpio_id:number类型,air_key.setup函数配置按键1时,对应的Air780EHV上的GPIO ID
+--gpio_id:number类型,air_key.setup函数配置按键1时,对应的Air780EHM/Air780EHV/Air780EGH上的GPIO ID
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
 --可以publish消息给其他协程或者给订阅消息的处理函数去执行耗时动作
 local function key1_int_cbfunc(int_level, gpio_id)
@@ -34,7 +42,7 @@ end
 
 --按键2的中断处理函数
 --int_level:number类型,表示触发中断后,某一时刻引脚的电平,1为高电平,0为低电平,并不一定是触发中断时的电平
---gpio_id:number类型,air_key.setup函数配置按键2时,对应的Air780EHV上的GPIO ID
+--gpio_id:number类型,air_key.setup函数配置按键2时,对应的Air780EHM/Air780EHV/Air780EGH上的GPIO ID
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
 --可以publish消息给其他协程或者给订阅消息的处理函数去执行耗时动作
 local function key2_int_cbfunc(int_level, gpio_id)
@@ -47,7 +55,7 @@ end
 
 --按键3的中断处理函数
 --int_level:number类型,表示触发中断后,某一时刻引脚的电平,1为高电平,0为低电平,并不一定是触发中断时的电平
---gpio_id:number类型,air_key.setup函数配置按键3时,对应的Air780EHV上的GPIO ID
+--gpio_id:number类型,air_key.setup函数配置按键3时,对应的Air780EHM/Air780EHV/Air780EGH上的GPIO ID
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
 --可以publish消息给其他协程或者给订阅消息的处理函数去执行耗时动作
 local function key3_int_cbfunc(int_level, gpio_id)
@@ -60,7 +68,7 @@ end
 
 --按键4的中断处理函数
 --int_level:number类型,表示触发中断后,某一时刻引脚的电平,1为高电平,0为低电平,并不一定是触发中断时的电平
---gpio_id:number类型,air_key.setup函数配置按键4时,对应的Air780EHV上的GPIO ID
+--gpio_id:number类型,air_key.setup函数配置按键4时,对应的Air780EHM/Air780EHV/Air780EGH上的GPIO ID
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
 --可以publish消息给其他协程或者给订阅消息的处理函数去执行耗时动作
 local function key4_int_cbfunc(int_level, gpio_id)
@@ -74,7 +82,7 @@ end
 
 --按键5、6、7、8的中断处理函数
 --int_level:number类型,表示触发中断后,某一时刻引脚的电平,1为高电平,0为低电平,并不一定是触发中断时的电平
---gpio_id:number类型,air_key.setup函数配置按键5、6、7、8时,对应的Air780EHV上的GPIO ID
+--gpio_id:number类型,air_key.setup函数配置按键5、6、7、8时,对应的Air780EHM/Air780EHV/Air780EGH上的GPIO ID
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
 --可以publish消息给其他协程或者给订阅消息的处理函数去执行耗时动作
 local function key5678_int_cbfunc(int_level, gpio_id)
@@ -99,8 +107,8 @@ end
 
 
 
---本demo中,Air780EHV核心板和AirKEY_1000配件板的接线方式如下
---Air780EHV核心板             AirKEY_1000配件板
+--本demo中,Air780EHM/Air780EHV/Air780EGH核心板和AirKEY_1000配件板的接线方式如下
+--Air780EHM/Air780EHV/Air780EGH核心板             AirKEY_1000配件板
 --     25/GPIO26-----------------K1
 --     107/GPIO21-----------------K2
 --     20/GPIO24-----------------K3
@@ -114,7 +122,7 @@ end
 
 
 --AirKEY_1000上的1、2、3、4,四个按键的引脚
---分别和Air780EHV的KEY1_GPIO_ID、KEY2_GPIO_ID、KEY3_GPIO_ID、KEY4_GPIO_ID四个引脚相连
+--分别和Air780EHM/Air780EHV/Air780EGH的KEY1_GPIO_ID、KEY2_GPIO_ID、KEY3_GPIO_ID、KEY4_GPIO_ID四个引脚相连
 --GPIO配置为上升沿触发中断,可以实时检测到按键弹起的动作
 --按键弹起时,会执行对应的中断处理函数key1_int_cbfunc、key2_int_cbfunc、key3_int_cbfunc、key4_int_cbfunc
 --在中断处理函数中,不要直接执行耗时较长的动作,例如写fskv,写文件,延时等
@@ -125,7 +133,7 @@ air_key.setup(3, KEY3_GPIO_ID, gpio.RISING, key3_int_cbfunc)
 air_key.setup(4, KEY4_GPIO_ID, gpio.RISING, key4_int_cbfunc)
 
 --AirKEY_1000上的5、6、7、8,四个按键的引脚
---分别和Air780EHV的KEY5_GPIO_ID、KEY6_GPIO_ID、KEY7_GPIO_ID、KEY8_GPIO_ID四个引脚相连
+--分别和Air780EHM/Air780EHV/Air780EGH的KEY5_GPIO_ID、KEY6_GPIO_ID、KEY7_GPIO_ID、KEY8_GPIO_ID四个引脚相连
 --GPIO配置为下降沿触发中断,可以实时检测到按键按下的动作
 --按键按下时,会执行对应的中断处理函数key5678_int_cbfunc
 --这四个按键共用了同一个中断处理函数,可以通过函数传入的GPIO ID来区分是哪一个按键被按下

+ 14 - 5
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/main.lua

@@ -1,3 +1,17 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.24
+@author  沈园园
+@usage
+AirKEY_1000是合宙设计生产的一款支持8个独立按键的配件板;
+本demo演示的核心功能为:
+Air780EHM/Air780EHV/Air780EGH核心板+AirKEY_1000配件板,使用Air780EHM/Air780EHV/Air780EGH核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态;
+更多说明参考本目录下的readme.md文件
+]]
+
+
 --[[
 必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
 PROJECT:项目名,ascii string类型
@@ -7,11 +21,6 @@ VERSION:项目版本号,ascii string类型
             X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
-
-AirKEY_1000是合宙设计生产的一款支持8个独立按键的配件板;
-本demo演示的核心功能为:
-Air780EHV核心板+AirKEY_1000配件板,使用Air780EHV核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态;
-更多说明参考本目录下的readme.md文件
 ]]
 PROJECT = "AirKEY_1000"
 VERSION = "001.000.000"

+ 38 - 8
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirKEY_1000/readme.md

@@ -1,3 +1,30 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、key_app.lua:使用核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态;
+
+3、AirKEY_1000.lua:配置主机和AirKEY_1000之间的控制参数;
+
+## 用户消息介绍
+
+## 用户消息介绍
+
+1、"KEY1_PRESSUP_IND":按键消息,publish该消息给其他协程或者给订阅消息的处理函数去执行耗时动作;
+
+2、"KEY2_PRESSUP_IND":按键消息,publish该消息给其他协程或者给订阅消息的处理函数去执行耗时动作;
+
+3、"KEY3_PRESSUP_IND":按键消息,publish该消息给其他协程或者给订阅消息的处理函数去执行耗时动作;
+
+4、"KEY4_PRESSUP_IND":按键消息,publish该消息给其他协程或者给订阅消息的处理函数去执行耗时动作;
+
+5、"KEY5_PRESSUP_IND":按键消息,publish该消息给其他协程或者给订阅消息的处理函数去执行耗时动作;
+
+6、"KEY5_PRESSUP_IND":按键消息,publish该消息给其他协程或者给订阅消息的处理函数去执行耗时动作;
+
+7、"KEY7_PRESSUP_IND":按键消息,publish该消息给其他协程或者给订阅消息的处理函数去执行耗时动作;
+
+8、"KEY8_PRESSUP_IND":按键消息,publish该消息给其他协程或者给订阅消息的处理函数去执行耗时动作;
 
 ## 演示功能概述
 
@@ -5,29 +32,32 @@ AirKEY_1000是合宙设计生产的一款支持8个独立按键的配件板;
 
 本demo演示的核心功能为:
 
-Air780EHV核心板+AirKEY_1000配件板,使用Air780EHV核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态;
+Air780EHM/Air780EHV/Air780EGH核心板+AirKEY_1000配件板,使用Air780EHM/Air780EHV/Air780EGH核心板的GPIO中断检测AirKEY_1000配件板上8个独立按键的按下或者弹起状态;
 
 
 ## 核心板+配件板资料
 
-[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+[Air780EHM/Air780EHV/Air780EGH核心板](https://docs.openluat.com/air780ehv/product/shouce/)
+
+[AirKEY_1000配件板相关资料](https://docs.openluat.com/accessory/AirKEY_1000/)
 
 
 ## 演示硬件环境
 
 ![](https://docs.openluat.com/accessory/AirKEY_1000/image/Air780EHV_connection.jpg)
 
-1、Air780EHV核心板
+1、Air780EHM/Air780EHV/Air780EGH核心板
 
 2、AirKEY_1000配件板
 
 3、母对母的杜邦线9根
 
-4、Air780EHV核心板和AirKEY_1000配件板的硬件接线方式为
+4、Air780EHM/Air780EHV/Air780EGH核心板和AirKEY_1000配件板的硬件接线方式为
 
-- Air780EHV核心板通过TYPE-C USB口供电(核心板背面的功耗测试开关拨到OFF一端)
+- Air780EHM/Air780EHV/Air780EGH核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
+- 核心板正面的 ON/OFF 拨动开关 拨到ON一端;
 
-| Air780EHV核心板 |  AirKEY_1000配件板 |
+| Air780EHM/Air780EHV/Air780EGH核心板 |  AirKEY_1000配件板 |
 | ------------ | ------------------ |
 |    25/GPIO26    |         K1         |
 |    107/GPIO21    |         K2         |
@@ -42,9 +72,9 @@ Air780EHV核心板+AirKEY_1000配件板,使用Air780EHV核心板的GPIO中断
 
 ## 演示软件环境
 
-1、[最新版本的内核固件](https://docs.openluat.com/air8101/luatos/firmware/)
+1、[Luatools下载调试工具](https://docs.openluat.com/air780epm/common/Luatools/)
 
-2、Luatools下载调试工具
+2、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
 
 
 ## 演示操作步骤

+ 16 - 6
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/AirSHT30_1000.lua

@@ -1,4 +1,19 @@
---本文件中的主机是指I2C主机,具体指Air780EHV
+--[[
+@module  AirSHT30_1000
+@summary AirSHT30_1000应用功能模块 
+@version 1.0
+@date    2025.10.22
+@author  沈园园
+@usage
+本文件为AirSHT30_1000驱动配置文件,核心业务逻辑为:
+1、打开AirSHT30_1000;
+2、读取温湿度数据;
+
+本文件没有对外接口,直接require "AirSHT30_1000"就可以加载运行;
+]]
+
+
+--本文件中的主机是指I2C主机,具体指Air780EHM/Air780EHV/Air780EGH
 --本文件中的从机是指I2C从机,具体指AirSHT30_1000配件板上的sht30温湿度传感器芯片
 
 local AirSHT30_1000 = 
@@ -9,11 +24,6 @@ local AirSHT30_1000 =
 -- 从机地址为0x44
 local slave_addr = 0x44
 
---电平设为3.3v
-pm.ioVol(pm.IOVOL_ALL_GPIO, 3300)
---设置gpio2输出,给camera_sda、camera_scl引脚提供上拉
-gpio.setup(2, 1)
-
 -- 计算数据表data中所有数据元素的crc8校验值
 local function crc8(data)
     local crc = 0xFF

+ 14 - 5
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/main.lua

@@ -1,3 +1,17 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.24
+@author  沈园园
+@usage
+AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器配件板;
+本demo演示的核心功能为:
+Air780EHM/Air780EHV/Air780EGH核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
+更多说明参考本目录下的readme.md文件
+]]
+
+
 --[[
 必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
 PROJECT:项目名,ascii string类型
@@ -7,11 +21,6 @@ VERSION:项目版本号,ascii string类型
             X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
-
-AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器配件板;
-本demo演示的核心功能为:
-Air780EHV核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
-更多说明参考本目录下的readme.md文件
 ]]
 PROJECT = "AirSHT30_1000"
 VERSION = "001.000.000"

+ 19 - 8
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/readme.md

@@ -1,3 +1,10 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、sht30_app.lua:每隔1秒读取一次温湿度数据;
+
+3、AirSHT30_1000.lua:AirSHT30_1000驱动文件;
 
 ## 演示功能概述
 
@@ -5,25 +12,27 @@ AirSHT30_1000是合宙设计生产的一款I2C接口的SHT30温湿度传感器
 
 本demo演示的核心功能为:
 
-Air780EHV核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
+Air780EHM/Air780EHV/Air780EGH核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据;
 
 
 ## 核心板+配件板资料
 
-[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+[Air780EHM/Air780EHV/Air780EGH核心板](https://docs.openluat.com/air780ehv/product/shouce/)
+
+[AirSHT30_1000配件板相关资料](https://docs.openluat.com/accessory/AirSHT30_1000/)
 
 
 ## 演示硬件环境
 
 ![](https://docs.openluat.com/accessory/AirSHT30_1000/image/connect_780ehv.png)
 
-1、Air780EHV核心板
+1、Air780EHM/Air780EHV/Air780EGH核心板
 
 2、AirSHT30_1000配件板
 
 3、母对母的杜邦线4根
 
-| Air780EHV核心板 | AirSHT30_1000配件板|
+| Air780EHM/Air780EHV/Air780EGH核心板 | AirSHT30_1000配件板|
 | ------------ | ------------------ |
 |     3V3     |         3V3        |
 |     GND   |         GND        |
@@ -33,9 +42,13 @@ Air780EHV核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据
 
 ## 演示软件环境
 
-1、Luatools下载调试工具
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
 
-2、[Air780EHV最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+4、[Air780EGH 最新版本的内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
 
 
 ## 演示操作步骤
@@ -53,5 +66,3 @@ Air780EHV核心板+AirSHT30_1000配件板,每隔1秒读取1次温湿度数据
 ``` lua
 [2025-09-22 11:32:35.955][000000203.154] I/user.read_sht30_task_func temprature 27.61 ℃
 [2025-09-22 11:32:35.957][000000203.154] I/user.read_sht30_task_func humidity 60.17 %RH
-
-

+ 14 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSHT30_1000/sht30_app.lua

@@ -1,3 +1,17 @@
+--[[
+@module  sht30_app
+@summary sht30_app应用功能模块 
+@version 1.0
+@date    2025.10.22
+@author  沈园园
+@usage
+本文件为sht30_app应用功能模块,核心业务逻辑为:
+1、每隔1秒读取一次温湿度数据;
+
+本文件没有对外接口,直接在main.lua中require "sht30_app"就可以加载运行;
+]]
+
+
 --加载AirSHT30_1000驱动文件
 local air_sht30 = require "AirSHT30_1000"
 

+ 4 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSPINORFLASH_1000/lf_fs.lua

@@ -8,7 +8,7 @@
 本demo演示的功能为:使用Air780EHV核心板通过SPI库实现对 NOR Flash的操作,演示读数据写数据、删除数据等操作。
 以Air780EHV核心板为例, 接线如下:
 
-Air780EHV核心板    AirSPINAND_1000配件版
+Air780EHV核心板    AirSPINORFLASH_1000配件版
 GND(任意)          GND
 VDD_EXT            VCC
 GPIO8/SPI0_CS     CS,片选
@@ -48,7 +48,7 @@ local function spiDev_init_func()
         log.error("SPI初始化", "失败")
         return nil
     end
-    log.info("SPI初始化", "成功,波特率:20MHz")
+    log.info("SPI初始化", "成功,波特率:",bandrate)
     return spi_device
 end
 
@@ -176,6 +176,7 @@ local function spinor_test_func()
     local flash_device = init_flash_device(spi_device)
     if not flash_device then
         log.error("主流程", "Flash初始化失败,终止")
+        spi_close_func()
         return
     end
 
@@ -183,6 +184,7 @@ local function spinor_test_func()
     local mount_point = "/little_flash"
     if not mount_filesystem(flash_device, mount_point) then
         log.error("主流程", "文件系统挂载失败,终止")
+        spi_close_func()
         return
     end
 

+ 8 - 3
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSPINORFLASH_1000/main.lua

@@ -6,15 +6,18 @@
 @date    2025.9.05
 @author  马亚丹
 @usage
-本demo是演示合宙AirSPINORFLASH_1000配件版的功能使用,可通过以下两种方式挂载flash模块,任选一种即可。
+本demo是演示合宙AirSPINORFLASH_1000配件版的功能使用,可通过以下三种方式驱动flash模块,任选一种即可。
 1. ram_spi
 通过原始spi接口对flash模块进行读写数据操作,详细逻辑请看ram_spi.lua 文件
 2. lf_fs
 通过littleFS文件系统,对flash模块以文件系统的方式进行读写数据操作,详细逻辑请看lf_fs.lua 文件
-
+3. sfud_test
+通过sfud核心库和io文件系统,对flash模块以文件系统的方式进行读写数据操作,详细逻辑请看sfud.lua 文件
 ]]
 
 
+
+
 --[[
 必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
 PROJECT:项目名,ascii string类型
@@ -70,11 +73,13 @@ end
 
 
 -- 加载ram_spi功能模块
---require "ram_spi"
+-- require "ram_spi"
 
 -- 加载lf_fs功能模块
 require"lf_fs"
 
+-- 加载sfud_test功能模块
+--require"sfud_test"
 
 -- 用户代码已结束---------------------------------------------
 -- 结尾总是这一句

+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSPINORFLASH_1000/ram_spi.lua

@@ -8,7 +8,7 @@
 本demo演示的功能为:使用Air780EHV核心板通过SPI库实现对Flash的操作,演示读数据写数据、删除数据等操作。
 以 Air780EHV核心板为例, 接线如下:
 
-Air780EHV核心板    AirSPINAND_1000配件版
+Air780EHV核心板    AirSPINORFLASH_1000配件版
 GND(任意)          GND
 VDD_EXT            VCC
 GPIO8/SPI0_CS     CS,片选

+ 55 - 3
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSPINORFLASH_1000/readme.md

@@ -6,6 +6,8 @@
 
 3. lf_fs:通过littleFS文件系统,对flash模块以文件系统的方式进行读写数据操作,详细逻辑请看lf_fs.lua 文件
 
+4. sfud_test:通过sfud核心库和io文件系统,对flash模块以文件系统的方式进行读写数据操作,详细逻辑请看sfud.lua 文件
+
 ## 演示功能概述:
 
 ### ram_spi:
@@ -30,10 +32,22 @@
 
 3.用 lf 库挂载 flash 设备对象为文件系统
 
-4.读取文件系统的信息,以确认内存足够用于文件操作
+4.读取文件系统的信息
 
 5.操作文件读写,并验证写入一致性,追加文件等。
 
+### sfud_test.lua:
+
+1.以对象的方式配置参数,初始化启用SPI,返回SPI对象
+
+2.用SPI对象初始化sfud,
+
+3.用sfud库挂载flash设备为文件系统
+
+4.读取文件系统的信息
+
+5.操作文件读写,擦除,并验证写入一致性,追加文件等。
+
 ## 演示硬件环境:
 
 ![](https://docs.openluat.com/accessory/AirSPINORFLASH_1000/image/780EHV.jpg)
@@ -80,7 +94,7 @@
 ## 演示核心步骤:
 
 1. 搭建好硬件环境
-2. main.lua 中加载需要用的功能模块,两个功能模块同时只能选择一个使用,另一个注释。
+2. main.lua 中加载需要用的功能模块,三个功能模块同时只能选择一个使用,另两个注释。
 3. Luatools 烧录内核固件和修改后的 demo 脚本代码
 4. 烧录成功后,代码会自动运行,查看打印日志,如果正常运行,会打印相关信息,spi 初始化,数据读写,文件操作等。
 5. ram_spi.lua 如下 log 显示:
@@ -137,4 +151,42 @@
 
 ```
 
-# 
+7. sfud_test.lua 如下 log 显示:
+
+```bash
+[2025-10-17 17:01:21.762][000000000.250] I/user.main AirSPINORFLASH_1000 001.000.000
+[2025-10-17 17:01:21.769][000000000.257] I/user.sfud SPI_ID 0 CS_PIN 8
+[2025-10-17 17:01:21.778][000000000.258] SPI_HWInit 552:spi0 speed 200000,200000,64
+[2025-10-17 17:01:21.788][000000000.258] I/user.硬件spi 初始化,波特率: SPI*: 0C7F5C38 200000
+[2025-10-17 17:01:21.797][000000000.259] I/user.SPI初始化 成功,波特率: 200000
+[2025-10-17 17:01:21.804][000000000.259] I/user.sfud初始化 开始
+[2025-10-17 17:01:21.811][000000000.263] I/sfud Found a Winbond flash chip. Size is 16777216 bytes.
+[2025-10-17 17:01:21.823][000000000.283] I/sfud LuatOS-sfud flash device initialized successfully.
+[2025-10-17 17:01:21.833][000000000.284] I/user.获取flash设备信息表: userdata: 0C0E0340
+[2025-10-17 17:01:21.845][000000000.284] I/user.获取 Flash 容量和page大小: 16777216 4096
+[2025-10-17 17:01:21.853][000000000.346] I/user.擦除一个块的数据: 0
+[2025-10-17 17:01:21.865][000000000.349] I/user.写入数据: 0
+[2025-10-17 17:01:21.875][000000000.546] I/user.读取数据: 
+[2025-10-17 17:01:21.886][000000000.546] testdata
+[2025-10-17 17:01:21.942][000000000.603] I/user.先擦除再写入数据: 0
+[2025-10-17 17:01:21.954][000000000.603] I/user.文件系统 开始挂载: /sfud_flash
+[2025-10-17 17:01:21.962][000000000.643] D/sfud lfs_mount -84
+[2025-10-17 17:01:22.020][000000000.954] D/sfud lfs_mount 0
+[2025-10-17 17:01:22.037][000000000.954] D/sfud vfs mount /sfud_flash ret 0
+[2025-10-17 17:01:22.045][000000000.955] I/user.文件系统 挂载成功: /sfud_flash
+[2025-10-17 17:01:22.054][000000000.955] I/user.文件系统信息 开始查询: /sfud_flash
+[2025-10-17 17:01:22.061][000000001.008] I/user.  总block数: 4096
+[2025-10-17 17:01:22.071][000000001.008] I/user.  已用block数: 2
+[2025-10-17 17:01:22.079][000000001.009] I/user.  block大小: 4096 字节
+[2025-10-17 17:01:22.085][000000001.009] I/user.  文件系统类型: lfs
+[2025-10-17 17:01:22.089][000000001.009] I/user.文件操作测试 开始
+[2025-10-17 17:01:22.099][000000001.130] I/user.  写入成功 /sfud_flash/test.txt 内容: 当前时间: Sun Jan  0 08:00:00 1900
+[2025-10-17 17:01:22.153][000000001.222] I/user.  读取成功 /sfud_flash/test.txt 内容: 当前时间: Sun Jan  0 08:00:00 1900
+[2025-10-17 17:01:22.681][000000001.747] I/user.  追加后内容: LuatOS 测试 - 追加时间: Sun Jan  0 08:00:01 1900
+[2025-10-17 17:01:22.688][000000001.747] I/user.文件操作测试 完成
+[2025-10-17 17:01:22.693][000000001.748] I/user.关闭spi true
+
+
+
+```
+

+ 219 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirSPINORFLASH_1000/sfud_test.lua

@@ -0,0 +1,219 @@
+--[[
+@module  sfud_test
+@summary sfud_test测试功能模块
+@version 1.0
+@date    2025.10.11
+@author  马亚丹
+@usage
+本demo演示的功能为:使用Air780EHV核心板通过SPI库实现对 NOR Flash的操作,演示读数据写数据、删除数据等操作。
+以Air780EHV核心板为例, 接线如下:
+
+Air780EHV核心板    AirSPINORFLASH_1000配件版
+GND(任意)          GND
+VDD_EXT            VCC
+GPIO8/SPI0_CS     CS,片选
+SPI0_SLK           CLK,时钟
+SPI0_MOSI          DI,主机输出,从机输入
+SPI0_MISO          DO,主机输入,从机输出
+
+--使用SPI0,硬件SPI CS接在gpio8上
+
+运行核心逻辑:
+1.以对象的方式配置参数,初始化启用SPI,返回SPI对象
+2.用SPI对象初始化sfud,
+3.用sfud库挂载flash设备为文件系统
+4.读取文件系统的信息
+5.操作文件读写,擦除,并验证写入一致性,追加文件等。
+
+]]
+
+-- SPI配置参数
+local SPI_ID = 0        -- SPI总线ID,根据实际情况修改
+local CS_PIN = 8       -- CS引脚,根据实际情况修改
+local CPHA = 0          -- 时钟相位
+local CPOL = 0          -- 时钟极性
+local data_Width = 8    -- 数据宽度(位)
+local bandrate = 20*100*100 -- 波特率(Hz),初始化为2MHz
+-- flash操作起始地址(示例值,需根据需求调整)
+local erase_addr = 4096 
+-- 擦除数据的大小(示例值,需匹配 Flash block 大小)
+local erase_size = 4096   
+--需要操作的数据(示例值,需根据需求调整)
+local data = "testdata"
+
+-- 1. 以对象方式设置并启用 SPI,返回设备对象
+local function spiDev_init_func()
+    log.info("sfud", "SPI_ID", SPI_ID, "CS_PIN", CS_PIN)
+
+    --以对象的方式初始化spi,高位在前,主模式,全双工模式
+    spi_device = spi.deviceSetup(SPI_ID, CS_PIN, CPHA, CPOL, data_Width, bandrate, spi.MSB, 1, 0)    
+    log.info("硬件spi", "初始化,波特率:", spi_device, bandrate)
+    if not spi_device then
+        log.error("SPI初始化", "失败")
+        return nil
+    end
+    log.info("SPI初始化", "成功,波特率:",bandrate)
+    return spi_device
+end
+
+
+-- 2. 初始化Flash设备,返回设备对象
+local function init_sfud_device(spi_device)
+    log.info("sfud初始化", "开始")
+    local sfud_flash_device = sfud.init(spi_device)
+    if not sfud_flash_device then
+        log.error("Flash初始化", "失败")        
+    else 
+        return true
+    end
+    
+end
+
+-- 3. 挂载文件系统
+local function mount_filesystem(sfud_device, mount_point)
+    log.info("文件系统", "开始挂载:", mount_point)
+
+    -- 检查是否支持挂载功能
+    if not sfud.mount then
+        log.error("文件系统", "不支持挂载功能")
+        return false
+    end
+
+    -- 尝试挂载
+    local mount_ok = sfud.mount(sfud_device, mount_point)
+    if not mount_ok then
+        log.warn("文件系统", "挂载失败,尝试重新挂载...")
+        mount_ok = sfud.mount(sfud_device, mount_point)
+        if not mount_ok then
+            log.error("文件系统", "仍挂载失败")
+            return false
+        end
+    end
+
+    log.info("文件系统", "挂载成功:", mount_point)
+    return true
+end
+
+-- 4. 打印文件系统信息
+local function print_filesystem_info(mount_point)
+    log.info("文件系统信息", "开始查询:", mount_point)
+
+    -- 获取文件系统详细信息,总块数/已用块数等
+    local ok, total_blocks, used_blocks, block_size, fs_type = io.fsstat(mount_point)
+    if ok then
+        log.info("  总block数:", total_blocks)
+        log.info("  已用block数:", used_blocks)
+        log.info("  block大小:", block_size, "字节")
+        log.info("  文件系统类型:", fs_type)
+    else
+        log.warn("  无法获取详细信息")
+    end
+end
+
+-- 5. 执行文件操作测试
+local function test_file_operations(mount_point)
+    log.info("文件操作测试", "开始")
+
+    -- 测试写入文件
+    local test_file = mount_point .. "/test.txt"
+    local f, err = io.open(test_file, "w")
+    if not f then
+        log.error("  写入失败", test_file, "错误:", err)
+        return false
+    end
+    local write_data = "当前时间: " .. os.date()
+    f:write(write_data)
+    f:close()
+    log.info("  写入成功", test_file, "内容:", write_data)
+
+    -- 测试读取文件
+    local read_data, read_err = io.readFile(test_file)
+    if not read_data then
+        log.error("  读取失败", test_file, "错误:", read_err)
+        return false
+    end
+    log.info("  读取成功", test_file, "内容:", read_data)
+
+    -- 验证内容一致性
+    if read_data ~= write_data then
+        log.warn("  内容不一致", "写入:", write_data, "读取:", read_data)
+    end
+
+    -- 测试文件追加
+    local append_file = mount_point .. "/append.txt"
+    os.remove(append_file) -- 清除旧文件
+    io.writeFile(append_file, "LuatOS 测试") -- 初始写入
+
+    local f_append, append_err = io.open(append_file, "a+")
+    if not f_append then
+        log.error("  追加失败", append_file, "错误:", append_err)
+        return false
+    end
+    local append_data = " - 追加时间: " .. os.date()
+    f_append:write(append_data)
+    -- 执行完操作后,一定要关掉文件
+    f_append:close()
+
+    local final_data = io.readFile(append_file)
+    log.info("  追加后内容:", final_data)
+
+    log.info("文件操作测试", "完成")
+
+    return true
+end
+
+-- 7. 关闭SPI设备,成功返回0
+local function spi_close_func()    
+    log.info("关闭spi", spi_device:close())
+end
+
+-- 主任务函数:按流程调用各功能函数
+local function spinor_test_func()
+    --1.判断SPI初始化  
+    spi_device = spiDev_init_func()
+    if not spi_device then
+        log.error("主流程", "SPI初始化失败,终止")
+        spi_close_func()
+        return
+    end
+
+    -- 流程2:初始化sfud设备
+    local sfud_init = init_sfud_device(spi_device)
+    if not sfud_init then
+        log.error("主流程", "sfud 初始化失败,终止")
+        spi_close_func()
+        return
+    end
+
+    -- 流程3:获取Flash设备,并进行数据擦除、读写操作
+    local sfud_device = sfud.getDeviceTable()    
+    log.info("获取flash设备信息表:", sfud_device)
+    log.info("获取 Flash 容量和page大小:", sfud.getInfo(sfud_device))
+    log.info("擦除一个块的数据:", sfud.erase(sfud_device, erase_addr, erase_size))   
+    log.info("写入数据:", sfud.write(sfud_device, erase_addr, data))    
+    log.info("读取数据:", sfud.read(sfud_device, erase_addr, erase_size ))    
+    log.info("先擦除再写入数据:", sfud.eraseWrite(sfud_device, erase_addr, data))
+    --sys.wait (1000)
+    
+
+    -- 流程4:挂载flash为文件系统
+    local mount_point = "/sfud_flash"
+    if not mount_filesystem(sfud_device, mount_point) then
+        log.error("主流程", "文件系统挂载失败,终止")
+        spi_close_func()
+        return
+    end
+
+    -- 流程5:打印文件系统信息
+    print_filesystem_info(mount_point)
+
+    -- 流程6:执行文件操作测试
+    if not test_file_operations(mount_point) then
+        log.warn("主流程", "文件操作测试部分失败")
+    end  
+ 
+     -- 流程7:关闭SPI设备
+    spi_close_func()
+end
+
+sys.taskInit(spinor_test_func)

+ 16 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/AirVOC_1000.lua

@@ -1,4 +1,19 @@
---本文件中的主机是指I2C主机,具体指Air780EHV
+--[[
+@module  AirVOC_1000
+@summary AirVOC_1000应用功能模块 
+@version 1.0
+@date    2025.10.21
+@author  沈园园
+@usage
+本文件为AirVOC_1000驱动配置文件,核心业务逻辑为:
+1、打开AirVOC_1000;
+2、读取TVOC的空气质量等级;
+
+本文件没有对外接口,直接require "AirVOC_1000"就可以加载运行;
+]]
+
+
+--本文件中的主机是指I2C主机,具体指Air780EHM/Air780EHV/Air780EGH
 --本文件中的从机是指I2C从机,具体指AirVOC_1000配件板上的ags02ma VOC(挥发性有机化合物)气体传感器芯片
 
 local AirVOC_1000 = 

+ 15 - 6
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/main.lua

@@ -1,3 +1,18 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.24
+@author  沈园园
+@usage
+AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合物)气体传感器配件板;
+主要用于检测甲醛、一氧化碳、可燃气体、酒精、氨气、硫化物、苯系蒸汽、烟雾、其它有害气体的监测;
+本demo演示的核心功能为:
+Air780EHM/Air780EHV/Air780EGH核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
+更多说明参考本目录下的readme.md文件
+]]
+
+
 --[[
 必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
 PROJECT:项目名,ascii string类型
@@ -7,12 +22,6 @@ VERSION:项目版本号,ascii string类型
             X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
             因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
         如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
-
-AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合物)气体传感器配件板;
-主要用于检测甲醛、一氧化碳、可燃气体、酒精、氨气、硫化物、苯系蒸汽、烟雾、其它有害气体的监测;
-本demo演示的核心功能为:
-Air8101核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
-更多说明参考本目录下的readme.md文件
 ]]
 PROJECT = "AirVOC_1000"
 VERSION = "001.000.000"

+ 20 - 7
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/readme.md

@@ -1,3 +1,10 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、voc_app.lua:每隔1秒读取一次TVOC数据;
+
+3、AirVOC_1000.lua:AirVOC_1000驱动文件;
 
 ## 演示功能概述
 
@@ -7,27 +14,29 @@ AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合
 
 本demo演示的核心功能为:
 
-Air780EHV核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
+Air780EHM/Air780EHV/Air780EGH核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
 
 
 ## 核心板+配件板资料
 
-[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+[Air780EHM/Air780EHV/Air780EGH核心板](https://docs.openluat.com/air780ehv/product/shouce/)
+
+[AirVOC_1000配件板相关资料](https://docs.openluat.com/accessory/AirVOC_1000/)
 
 
 ## 演示硬件环境
 
 ![](https://docs.openluat.com/accessory/AirVOC_1000/image/connect_Air780ehv.jpg)
 
-1、Air780EHV核心板
+1、Air780EHM/Air780EHV/Air780EGH核心板
 
 2、AirVOC_1000配件板
 
 3、母对母的杜邦线4根
 
-4、Air780EHV核心板和AirVOC_1000配件板的硬件接线方式为
+4、Air780EHM/Air780EHV/Air780EGH核心板和AirVOC_1000配件板的硬件接线方式为
 
-| Air780EHV核心板 | AirVOC_1000配件板  |
+| Air780EHM/Air780EHV/Air780EGH核心板 | AirVOC_1000配件板  |
 | ------------ | ------------------ |
 |     3V3     |         3V3        |
 |     GND   |         GND        |
@@ -37,9 +46,13 @@ Air780EHV核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量
 
 ## 演示软件环境
 
-1、Luatools下载调试工具
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
+
+2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
 
-2、[Air780EHV最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+4、[Air780EGH 最新版本的内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
 
 
 ## 演示操作步骤

+ 14 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/voc_app.lua

@@ -1,3 +1,17 @@
+--[[
+@module  voc_app
+@summary voc_app应用功能模块 
+@version 1.0
+@date    2025.10.21
+@author  沈园园
+@usage
+本文件为voc_app应用功能模块,核心业务逻辑为:
+1、每隔1秒读取一次TVOC数据空气质量数据;
+
+本文件没有对外接口,直接在main.lua中require "voc_app"就可以加载运行;
+]]
+
+
 --加载AirVOC_1000驱动文件
 local air_voc = require "AirVOC_1000"
 

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/10.amr → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/10.amr


+ 92 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/main.lua

@@ -0,0 +1,92 @@
+
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+本demo演示的核心功能为:
+1、play_file.lua: 播放音频文件,可支持wav,amr,mp3 格式音频
+
+2、play_tts: 支持文字转普通话输出需要固件支持
+
+3、play_stream: 流式播放音频,仅支持PCM 格式,可以将音频推流到云端,用来对接大模型或者流式录音的应用。
+
+4、record_file: 录音到文件,仅支持PCM 格式
+
+5、record_stream:  流式录音,仅支持PCM,可以将音频流不断的拉取,可用来对接大模型
+
+6、sample-6s: 用于测试本地mp3文件播放
+
+7、test.pcm: 用于测试pcm 流式播放(实际可以云端下载)
+
+
+更多说明参考本目录下的readme.md文件
+]]
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+
+--[[
+本demo可使用Air780EHM核心板/Air780EGH核心板+AirAUDIO_1010 音频扩展板+喇叭两种硬件环境演示
+]]
+
+PROJECT = "audio"
+VERSION = "1.0.0"
+-- 在日志中打印项目名和项目版本号
+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
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+
+
+require "play_file"     --   播放音频文件,可支持wav,amr,mp3 格式音频
+-- require "play_tts"      -- 支持文字转普通话输出需要固件支持
+-- require "play_stream"        -- 流式播放音频,仅支持PCM 格式,可以将音频推流到云端,用来对接大模型或者流式录音的应用。
+-- require "record_file"        -- 录音到文件
+-- require "record_stream"        -- 流式录音   
+
+-- 音频对内存影响较大,不断的打印内存,用于判断是否异常
+sys.timerLoopStart(function()
+    log.info("mem.lua", rtos.meminfo())
+    log.info("mem.sys", rtos.meminfo("sys"))
+ end, 3000)
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 240 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/pins_air780ehm.json

@@ -0,0 +1,240 @@
+{
+  "model": "Air780EHM",
+  "pins": [
+    [
+      7,
+      "PWR_KEY",
+      ""
+    ],
+    [
+      16,
+      "GPIO27",
+      ""
+    ],
+    [
+      17,
+      "UART1_RXD",
+      ""
+    ],
+    [
+      18,
+      "UART1_TXD",
+      ""
+    ],
+    [
+      19,
+      "GPIO22",
+      ""
+    ],
+    [
+      20,
+      "PWM1",
+      ""
+    ],
+    [
+      22,
+      "PWM0",
+      ""
+    ],
+    [
+      23,
+      "ONEWIRE",
+      ""
+    ],
+    [
+      25,
+      "CAN_TXD",
+      ""
+    ],
+    [
+      26,
+      "PWM4",
+      ""
+    ],
+    [
+      28,
+      "UART2_RXD",
+      ""
+    ],
+    [
+      29,
+      "UART2_TXD",
+      ""
+    ],
+    [
+      30,
+      "GPIO29",
+      ""
+    ],
+    [
+      31,
+      "GPIO30",
+      ""
+    ],
+    [
+      32,
+      "GPIO31",
+      ""
+    ],
+    [
+      33,
+      "GPIO32",
+      ""
+    ],
+    [
+      38,
+      "DBG_RXD",
+      ""
+    ],
+    [
+      39,
+      "DBG_TXD",
+      ""
+    ],
+    [
+      49,
+      "LCD_RST",
+      ""
+    ],
+    [
+      50,
+      "LCD_SDA",
+      ""
+    ],
+    [
+      51,
+      "LCD_RS",
+      ""
+    ],
+    [
+      52,
+      "LCD_CS",
+      ""
+    ],
+    [
+      53,
+      "LCD_CLK",
+      ""
+    ],
+    [
+      54,
+      "CAM_MCLK",
+      ""
+    ],
+    [
+      55,
+      "CAM_RX0",
+      ""
+    ],
+    [
+      56,
+      "CAM_RX1",
+      ""
+    ],
+    [
+      57,
+      "UART3_TXD",
+      ""
+    ],
+    [
+      58,
+      "UART3_RXD",
+      ""
+    ],
+    [
+      61,
+      "VBUS",
+      ""
+    ],
+    [
+      66,
+      "I2C1_SDA",
+      ""
+    ],
+    [
+      67,
+      "I2C1_SCL",
+      ""
+    ],
+    [
+      78,
+      "GPIO28",
+      ""
+    ],
+    [
+      79,
+      "USIM_DET",
+      ""
+    ],
+    [
+      80,
+      "CAM_BCLK",
+      ""
+    ],
+    [
+      81,
+      "CAM_CS",
+      ""
+    ],
+    [
+      82,
+      "USB_BOOT",
+      ""
+    ],
+    [
+      83,
+      "SPI0_CS",
+      ""
+    ],
+    [
+      84,
+      "SPI0_MISO",
+      ""
+    ],
+    [
+      85,
+      "SPI0_MOSI",
+      ""
+    ],
+    [
+      86,
+      "SPI0_CLK",
+      ""
+    ],
+    [
+      97,
+      "GPIO16",
+      ""
+    ],
+    [
+      99,
+      "GPIO23",
+      ""
+    ],
+    [
+      100,
+      "GPIO17",
+      ""
+    ],
+    [
+      101,
+      "WAKEUP0",
+      ""
+    ],
+    [
+      102,
+      "GPIO20",
+      ""
+    ],
+    [
+      106,
+      "CAN_RXD",
+      ""
+    ],
+    [
+      107,
+      "GPIO21",
+      ""
+    ]
+  ]
+}

+ 101 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_file.lua

@@ -0,0 +1,101 @@
+--[[
+@module  play_file
+@summary 播放文件
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+
+本文件为播放文件的应用功能模块,核心业务逻辑为:
+1、自动播放一个1.mp3音乐,
+2、点powerkey 按键进行音频切换,点击boot 按键停止音频播放
+3、点击boot 按键停止音频播放
+本文件没有对外接口,直接在main.lua中require "play_file"就可以加载运行;
+]]
+
+exaudio = require("exaudio")
+local taskName = "task_audio"
+
+
+-- 音频初始化设置参数,exaudio.setup 传入参数
+local audio_setup_param ={
+    model= "es8311",          -- 音频编解码类型,可填入"es8311","es8211"
+    i2c_id = 1,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = 26,         -- 音频放大器电源控制管脚
+    dac_ctrl = 2,        --  音频编解码芯片电源控制管脚
+}
+
+--  播放结束回调
+local function play_end(event)
+    if event == exaudio.PLAY_DONE then
+        log.info("播放完成",exaudio.is_end())
+    end
+end
+
+--  音频播放的配置
+local audio_play_param ={
+    type= 0,                -- 播放类型,有0,播放文件,1.播放tts 2. 流式播放
+                            -- 如果是播放文件,支持mp3,amr,wav格式
+                            -- 如果是tts,内容格式见:https://wiki.luatos.com/chips/air780e/tts.html?highlight=tts
+                            -- 流式播放,仅支持PCM 格式音频,如果是流式播放,则sampling_rate, sampling_depth,signed_or_unsigned 必填写
+    content = "/luadb/sample-6s.mp3",          -- 如果播放类型为0时,则填入string 是播放单个音频文件,如果是表则是播放多段音频文件。
+    cbfnc = play_end,            -- 播放完毕回调函数
+}
+
+
+---------------------------------
+---通过BOOT 按键进行播放停止操作---
+---------------------------------
+local function stop_audio()
+    log.info("停止播放")
+    sys.sendMsg(taskName, MSG_KEY_PRESS, "STOP_AUDIO")
+end
+--按下boot 停止播放
+gpio.setup(0, stop_audio, gpio.PULLDOWN, gpio.RISING)
+gpio.debounce(0, 200, 1) -- 防抖,防止频繁触发
+
+---------------------------------
+---通过POWERKEY按键进行音频切换---
+---------------------------------
+
+local function next_audio()
+    log.info("切换播放")
+    sys.sendMsg(taskName, MSG_KEY_PRESS, "NEXT_AUDIO")
+end
+
+--按下powerkey 打断播放,播放优先级更高的音频
+gpio.setup(gpio.PWR_KEY, next_audio, gpio.PULLUP, gpio.FALLING)
+gpio.debounce(gpio.PWR_KEY, 200, 1) -- 防抖,防止频繁触发
+
+
+---------------------------------
+-----主task,处理播放音频---------
+---------------------------------
+
+
+local index_number = 1
+local audio_path = nil
+local function audio_task()
+    log.info("开始播放音频文件")
+    if exaudio.setup(audio_setup_param) then
+        exaudio.play_start(audio_play_param) -- 仅仅支持task 中运行
+        while true do
+            local msg = sys.waitMsg(taskName, MSG_KEY_PRESS)   -- 等待按键触发
+            if msg[2] ==  "NEXT_AUDIO" then  
+                
+                if index_number %2 == 0 then     --  切换音频路径
+                    audio_path = "/luadb/sample-6s.mp3"
+                else
+                    audio_path = "/luadb/10.amr"
+                end
+
+                exaudio.play_start({type= 0, content = audio_path,cbfnc = play_end,priority = index_number})
+                index_number= index_number +1 
+            elseif msg[2] ==  "STOP_AUDIO" then
+                exaudio.play_stop()
+            end 
+        end
+    end
+    
+end
+sys.taskInitEx(audio_task, taskName)

+ 108 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_stream.lua

@@ -0,0 +1,108 @@
+--[[
+@module  play_stream
+@summary 流式播放
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+
+本文件为流式播放应用功能模块,核心业务逻辑为:
+1、创建一个播放流式音频task(task_audio)
+2、创建一个模拟获取流式音频的task(audio_get_data)
+3、此task通过流式传输不断向exaudio.play_stream_write填入播放的音频
+4、播放task 不断播放传入流式音频
+5、使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
+本文件没有对外接口,直接在main.lua中require "play_stream"就可以加载运行;
+]]
+
+exaudio = require("exaudio")
+
+
+-- 音频初始化设置参数,exaudio.setup 传入参数
+local audio_setup_param ={
+    model= "es8311",          -- dac类型,可填入"es8311","es8211"
+    i2c_id = 1,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = 26,         -- 音频放大器电源控制管脚
+    dac_ctrl = 2,        --  音频编解码芯片电源控制管脚
+}
+
+-- 播放完成回调
+local function play_end(event)
+    if event == exaudio.PLAY_DONE then
+        log.info("播放完成",exaudio.is_end())
+
+    end
+end 
+
+-- 流式播放音频播放的配置
+local audio_play_param ={
+    type= 2,                -- 播放类型,有0,播放文件,1.播放tts 2. 流式播放
+                            -- 如果是播放文件,支持mp3,amr,wav格式
+                            -- 如果是tts,内容格式见:https://wiki.luatos.com/chips/air780e/tts.html?highlight=tts
+                            -- 流式播放,仅支持PCM 格式音频,如果是流式播放,则sampling_rate, sampling_depth,signed_or_unsigned 必填写
+    cbfnc = play_end,            -- 播放完毕回调函数
+    sampling_rate = 16000,  -- 采样率,仅为流式播放起作用
+    sampling_depth =  16,   -- 采样位位深,仅流式播放的时候才有作用
+    signed_or_unsigned = true  -- PCM 的数据是否有符号,仅为流式播放起作用
+}
+
+---------------------------------
+---通过BOOT 按键增大音量---
+---------------------------------
+local volume_number = 50 
+local function add_volume()
+    volume_number = volume_number + 20
+    log.info("增大音量",volume_number)
+    exaudio.vol(volume_number)
+end
+
+gpio.setup(0, add_volume, gpio.PULLDOWN, gpio.RISING)
+gpio.debounce(0, 200, 1)
+
+---------------------------------
+---通过POWERKEY按键减小音量-------
+---------------------------------
+
+local function down_volume()
+    volume_number = volume_number - 15
+    log.info("减小音量",volume_number)
+    exaudio.vol(volume_number)
+end
+
+gpio.setup(gpio.PWR_KEY, down_volume, gpio.PULLUP, gpio.FALLING)
+gpio.debounce(gpio.PWR_KEY, 200, 1)   -- 防抖,防止频繁触发
+
+
+---------------------------------
+---------模拟获取音频task---------
+---------------------------------
+local function audio_get_data()
+    log.info("开始流式获取音频数据")
+    local file = io.open("/luadb/test.pcm", "rb")   -- 模拟流式播放音源,实际的音频数据来源也可以来自网络或者本地存储
+    while true do
+        local read_data = file:read(4096)  --  读取文件,模拟流式音频源,需要1024 的倍数
+        if read_data  == nil then
+            file:close()                -- 模拟音频获取完毕,关闭音频文件
+            break
+        end
+        exaudio.play_stream_write(read_data)  -- 流式写入音频数据
+        sys.wait(100)                   -- 写数据需要留出事件给其他task 运行代码
+    end
+end
+
+sys.taskInitEx(audio_get_data, "audio_get_data")
+
+
+---------------------------------
+------------通过主task------------
+---------------------------------
+local taskName = "task_audio"
+local function audio_task()
+    log.info("开始流式播报")
+    if exaudio.setup(audio_setup_param) then
+        exaudio.play_start(audio_play_param)
+        log.info("播放状态",exaudio.is_end())
+    end
+end
+
+sys.taskInitEx(audio_task, taskName)

+ 104 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/play_tts.lua

@@ -0,0 +1,104 @@
+--[[
+@module  play_tts
+@summary 文字转语音
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+
+本文件为流式播放应用功能模块,核心业务逻辑为:
+1、播放一个TTS
+2、点powerkey 按键进行tts 的音色切换
+3、点击boot 按键停止音频播放
+本文件没有对外接口,直接在main.lua中require "play_tts"就可以加载运行;
+]]
+exaudio = require("exaudio")
+local taskName = "task_audio"
+
+-- 音频初始化设置参数,exaudio.setup 传入参数
+local audio_setup_param ={
+    model= "es8311",          -- 音频编解码类型,可填入"es8311","es8211"
+    i2c_id = 1,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = 26,         -- 音频放大器电源控制管脚
+    dac_ctrl = 2,        --  音频编解码芯片电源控制管脚
+}
+
+local function play_end(event)
+    if event == exaudio.PLAY_DONE then
+        log.info("播放完成",exaudio.is_end())
+        exaudio.play_stop()
+    end
+end 
+
+local audio_play_param ={
+    type= 1,                -- 播放类型,有0,播放文件,1.播放tts 2. 流式播放
+                            -- 如果是播放文件,支持mp3,amr,wav格式
+                            -- 如果是tts,内容格式见:https://docs.openluat.com/air780epm/common/tts/
+                            -- 流式播放,仅支持PCM 格式音频,如果是流式播放,则sampling_rate, sampling_depth,signed_or_unsigned 必填写
+    content = "支付宝到账,1千万元",          -- 如果播放类型为0时,则填入string 是播放单个音频文件,如果是表则是播放多段音频文件。
+    cbfnc = play_end,            -- 播放完毕回调函数
+}
+
+
+---------------------------------
+---通过BOOT 按键进行播放停止操作---
+---------------------------------
+local function stop_audio()
+    log.info("停止播放")
+    sys.sendMsg(taskName, MSG_KEY_PRESS, "STOP_AUDIO")
+end
+--按下boot 停止播放
+gpio.setup(0, stop_audio, gpio.PULLDOWN, gpio.RISING)
+gpio.debounce(0, 200, 1)
+
+---------------------------------
+---通过POWERKEY按键进行音频切换---
+---------------------------------
+
+local function next_audio()
+    log.info("切换播放")
+    sys.sendMsg(taskName, MSG_KEY_PRESS, "NEXT_AUDIO")
+end
+
+--按下powerkey 打断播放,播放优先级更高的音频
+gpio.setup(gpio.PWR_KEY, next_audio, gpio.PULLUP, gpio.FALLING)
+gpio.debounce(gpio.PWR_KEY, 200, 1)  -- 防抖,防止频繁触发
+
+
+---------------------------------------------------------------------------------------------------
+---------------主task------------------------------------------------------------------------------
+--- 关于TTS 音色设置请见: https://docs.openluat.com/air780epm/common/tts/
+---------------------------------------------------------------------------------------------------
+
+
+local index_number = 1
+local audio_path = nil
+local function audio_task()
+    log.info("开始播放TTS")
+    if exaudio.setup(audio_setup_param) then
+        exaudio.play_start(audio_play_param) -- 仅仅支持task 中运行
+        while true do
+            local msg = sys.waitMsg(taskName, MSG_KEY_PRESS)   -- 等待按键触发
+            if msg[2] ==  "NEXT_AUDIO" then      
+                if index_number %5 == 0 then     --  切换播报音色
+                    audio_path = "[m51]支付宝到账,1千万元"   -- 许久
+                elseif index_number %5 == 1 then
+                    audio_path = "[m52]支付宝到账,1千万元"   -- 许多
+                elseif index_number %5 == 2 then
+                    audio_path = "[m53]支付宝到账,1千万元"   -- 晓萍
+                elseif index_number %5 == 3 then                    
+                    audio_path = "[m54]支付宝到账,1千万元"   -- 唐老鸭
+                elseif index_number %5 == 4 then                    
+                    audio_path = "[m55]支付宝到账,1千万元"   -- 许宝宝 
+                end
+
+                exaudio.play_start({type= 1, content = audio_path,cbfnc = play_end,priority = index_number})
+                index_number= index_number +1 
+            elseif msg[2] ==  "STOP_AUDIO" then
+                exaudio.play_stop()
+            end 
+        end
+    end
+    
+end
+sys.taskInitEx(audio_task, taskName)

+ 212 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/readme.md

@@ -0,0 +1,212 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、play_file.lua: 播放音频文件,可支持wav,amr,mp3 格式音频
+
+3、play_tts: 支持文字转普通话输出需要固件支持
+
+4、play_stream: 流式播放音频,仅支持PCM 格式
+
+5、record_file: 录音到文件,仅支持PCM 格式
+
+6、record_stream:  流式录音,仅支持PCM。
+
+7、sample-6s.mp3/10.amr: 用于测试本地mp3和amr文件播放
+
+8、test.pcm: 用于测试pcm 流式播放(实际可以云端下载)
+
+
+**注意:目前不支持录音和放音同时进行**
+
+
+## 常量的介绍
+
+1、exaudio.PLAY_DONE : 当播放音频结束时,会在回调函数返回播放完成的时间
+
+2、exaudio.RECORD_DONE : 当录音结束时,会在回调函数返回播放完成的时间
+
+3、exaudio.AMR_NB : 仅录音时有用,表示使用AMR_NB 方式录音
+
+4、exaudio.AMR_WB : 仅录音时有用,表示使用AMR_WB 方式录音
+
+5、exaudio.PCM_8000/exaudio.PCM_16000/exaudio.PCM_24000/exaudio.PCM_32000 :  仅录音时有用,表示使用8000/16000/24000/32000/秒 的速度对音频进行采样
+
+
+## 演示功能概述
+
+1、play_flie.lua 自动播放一个sample-6s.mp3音乐,点powerkey 按键进行音频切换,播放10.amr文件,点击boot 按键停止音频播放
+
+2、play_tts.lua 播放一个TTS,点powerkey 按键进行tts 的音色切换,点击boot 按键停止音频播放
+
+3、play_stream.lua 流式播放PCM,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
+
+4、record_file.lua 录音到文件,演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+5、record_stream.lua 流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用
+
+
+## 演示硬件环境
+1、Air780EHM核心板/Air780EGH核心板+AirAUDIO_1010 音频扩展板+喇叭
+
+![alt text]( https://docs.openLuat.com/cdn/image/Air780EHM+Airaudio1010.jpg)
+
+
+![alt text]( https://docs.openLuat.com/cdn/image/Air780EGH-AIRAUDIO_1010.jpg)
+
+Air780EHM核心板/Air780EGH核心板和AirAudio_1010 扩展板的硬件接线方式为:
+|  Air780EHM核心板/Air780EGH核心板 | AirAUDIO_1010扩展板 |
+| ---------------                 | -----------------   |
+| 33/I2S_MCLK                     | I2S_MCLK            |
+| 30/I2S_BCK                      | I2S_BCK             |
+| 31/I2S_LRCK                     | I2S_LRCK            |
+| 32/I2S_DIN                      | I2S_DIN             |
+| 33/I2S_DOUT                     | I2S_DOUT            |
+| 67/I2C1_SCL                     | I2C_SCL             |
+| 66/I2C1_SDA                     | I2C_SDA             |
+| 25/GPIO26                       | PA_EN               |
+| 23/GPIO2                        | 8311_EN             |
+| VDD_EXT                         | VCC                 |
+| GND                             | GND                 |
+
+2、TYPE-C USB数据线一根
+- Air780EHM核心板通过/Air780EGH核心板 TYPE-C USB 口供电;
+- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EHM V2016版本固件](https://cdn6.vue2.cn/Luat_tool_src/v2tools/LuatOS_Air780EHM/LuatOS-SoC_V2016_Air780EHM.zip),选择支持TTS功能的1、3、5、7、13或101、103、105、107、113号固件。不同版本区别参考[Air780EHM LuatOS固件版本](https://docs.openluat.com/air780epm/luatos/firmware/version/)。
+
+ [Air780EGH V2016版本固件](https://cdn6.vue2.cn/Luat_tool_src/v2tools/LuatOS_Air780EGH/LuatOS-SoC_V2016_Air780EGH.zip),选择支持TTS功能的1、3、5、7、13或101、103、105、107、113号固件。不同版本区别参考[Air780EGH LuatOS固件版本](https://docs.openluat.com/air780egh/luatos/firmware/version/)。
+
+3、 luatos需要的脚本和资源文件
+- 脚本和资源文件[点我浏览所有文件](https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EHM_Air780EHV_Air780EGH/demo/audio)
+
+- 准备好软件环境之后,接下来查看[如何烧录项目文件到Air780EHM/Air780EGH核心板](https://docs.openluat.com/air8000/luatos/common/download/),将本篇文章中演示使用的项目文件烧录到Air780EHM/Air780EGH核心板中。
+
+4、[合宙 LuatIO 工具(GPIO 复用初始化配置)使用说明](https://docs.openluat.com/air780epm/common/luatio/)
+
+5、 lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、demo脚本代码main.lua中,按照自己的需求选择对应的功能
+
+- 如果需要测试播放音频文件,则选择play_file 文件
+
+- 如果需要测试播放tts,则选择play_tts 文件
+
+- 如果需要测试流式播放音频,则选择play_stream 文件
+
+- 如果需要测试录音频到文件,则选择record_file 文件
+
+- 如果需要测试流式录音,则选择record_stream 文件
+
+
+3、Luatools烧录内核固件和修改后的demo脚本代码
+
+4、 在测试播放音频文件的时候,点powerkey 按键进行音频切换,切换内容是MP3,AMR格式,切换是通过播放优先级进行区分的,注意音频格式仅仅支持:MP3,WAV,AMR,点击boot 按键停止音频播放
+
+5、 在测试播放TTS的时候,点powerkey 按键进行TTS 音色切换,点击boot 按键停止音频播放,注意:仅支持中文TTS。
+
+
+6、在进行流式播放测试的时候,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加,注意流式播放目前仅支持PCM 格式音频,可选择不同的采样率,以及位深
+
+7、在测试录音到文件(仅支持PCM),演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+8、在测试流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用。
+
+9、运行结果展示
+
+- 播放文件 (play_file.lua)
+
+自动播放一个sample-6s.mp3 音乐;
+
+点击powerkey 按键进行音频切换播放10.amr文件;
+
+点击boot 按键停止音频播放
+
+``` lua
+ I/user.开始播放音频文件
+ I/user.播放完成 true
+
+ I/user.切换播放
+ E/user.是否完成播放 true
+ I/user.播放完成 true
+
+ I/user.停止播放
+ ```
+
+ - 文字转语音 (play_tts.lua)
+
+ 播放一个TTS;
+
+点击powerkey 按键进行tts 的音色切换
+
+点击boot 按键停止音频播放
+
+``` lua
+ I/user.开始播放TTS
+ I/user.切换播放
+
+ E/user.是否完成播放 true
+ I/user.播放完成 true
+
+ I/user.停止播放
+
+```
+
+- 流式播放 (play_stream.lua)
+
+创建一个播放流式音频task(task_audio)和一个模拟获取流式音频的task(audio_get_data),此task通过流式传输不断向exaudio.play_stream_write填入播放的音频,播放task 不断播放传入流式音频。
+
+使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
+
+``` lua
+ I/user.开始流式获取音频数据
+ I/user.开始流式播报
+
+ I/user.减小音量55
+ I/user.增大音量75
+
+ I/user.播放状态 true
+ I/user.播放完成 true
+
+```
+
+- 录音到文件  (record_file.lua)
+
+主程序录音到/record.amr 文件
+
+使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+``` lua
+ I/user.开始录制音频到文件
+
+ E/user.减小音量55
+ I/user.增大音量75
+
+ I/user.录音后文件大小 320000
+```
+
+- 流式录音(record_stream.lua),仅支持PCM格式
+
+主程序录音进行流式录音
+
+录音过程中不断的进行recode_data_callback回调,回调内容为音频流的地址和长度。
+
+``` lua
+ I/user.开始流式录制音频到文件
+ I/user.收到音频流,地址为: ZBUFF*:0C7F70A8 有效数据长度为: 9600
+ I/user.录音完成
+
+ E/user.减小音量55
+ I/user.增大音量75
+
+ I/user.录音后
+
+

+ 84 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/record_file.lua

@@ -0,0 +1,84 @@
+--[[
+@module  record_file
+@summary 录音到文件
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+
+录音到文件,核心业务逻辑为:
+1、主程序录音到/record.amr 文件
+2、使用powerkey 按键进行录音音量减小
+3、点击boot 按键进行录音音量增加
+本文件没有对外接口,直接在main.lua中require "record_file"就可以加载运行;
+]]
+exaudio = require("exaudio")
+
+-- 音频初始化设置参数,exaudio.setup 传入参数
+local audio_setup_param ={
+    model= "es8311",          -- dac类型,可填入"es8311","es8211"
+    i2c_id = 1,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl =26,         -- 音频放大器电源控制管脚
+    dac_ctrl = 2,        --  音频编解码芯片电源控制管脚
+    bits_per_sample = 16  -- 录音的位深,可选8,16,24 位,但是当选择音频格式为AMR_NB,自动调节为8位,当音频格式为AMR_WB,自动调节为16位
+}
+local recordPath = "/record.amr"
+
+-- 录音完成回调
+local function record_end(event)
+    if event == exaudio.RECORD_DONE then
+        log.info("录音后文件大小",io.fileSize(recordPath))
+    end
+end 
+
+-- 录音配置参数,exaudio.record_start 的入参
+local audio_record_param ={
+    format= exaudio.PCM_32000,    -- 录制格式,有exaudio.AMR_NB,exaudio.AMR_WB,exaudio.PCM_8000,exaudio.PCM_16000,exaudio.PCM_24000,exaudio.PCM_32000
+                                  -- 如果选择exaudio.AMR_WB,则需要固件支持volte 功能
+    time = 5,               -- 录制时间,单位(秒)
+    path = recordPath,             -- 如果填入的是字符串,则表示是文件路径,录音会传输到这个路径里
+                                   -- 如果填入的是函数,则表示是流式录音,录音的数据会传输到此函数内,返回的是zbuf地址,以及数据长度
+                                   -- 如果是流式录音,则仅支持PCM 格式 
+    cbfnc = record_end,            -- 录音完毕回调函数
+}
+
+
+---------------------------------
+---通过BOOT 按键增大录音---
+---------------------------------
+local volume_number = 50 
+local function add_volume()
+    volume_number = volume_number + 20
+    log.info("增大录音音量",volume_number)
+    exaudio.mic_vol(volume_number)
+end
+--按下boot 停止播放
+gpio.setup(0, add_volume, gpio.PULLDOWN, gpio.RISING)
+gpio.debounce(0, 200, 1)
+
+---------------------------------
+---通过POWERKEY按键减小录音-------
+---------------------------------
+
+local function down_volume()
+    volume_number = volume_number - 15
+    log.info("减小录音音量",volume_number)
+    exaudio.mic_vol(volume_number)
+end
+
+gpio.setup(gpio.PWR_KEY, down_volume, gpio.PULLUP, gpio.FALLING)
+gpio.debounce(gpio.PWR_KEY, 200, 1)   -- 防抖,防止频繁触发
+
+---------------------------------
+---音频 task 初始化函数-----------
+---------------------------------
+local taskName = "task_audio"
+local function audio_task()
+    sys.wait(100)
+    log.info("开始录制音频到文件")
+    if exaudio.setup(audio_setup_param) then
+        exaudio.record_start(audio_record_param)
+    end
+end
+
+sys.taskInitEx(audio_task, taskName)

+ 55 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/record_stream.lua

@@ -0,0 +1,55 @@
+--[[
+@module  record_stream
+@summary 流式录音
+@version 1.0
+@date    2025.09.08
+@author  梁健
+@usage
+
+流式录音(仅支持PCM),核心业务逻辑为:
+1、主程序录音进行流式录音
+2、录音过程中不断的进行recode_data_callback回调,回调内容为音频流的地址和长度
+本文件没有对外接口,直接在main.lua中require "record_stream"就可以加载运行;
+]]
+exaudio = require("exaudio")
+
+-- 音频初始化设置参数,exaudio.setup 传入参数
+local audio_setup_param ={
+    model= "es8311",          -- dac类型,可填入"es8311","es8211"
+    i2c_id = 1,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
+    pa_ctrl = 26,         -- 音频放大器电源控制管脚
+    dac_ctrl = 2,        --  音频编解码芯片电源控制管脚
+    bits_per_sample = 16  -- 录音的位深,可选8,16,24 位,但是当选择音频格式为AMR_NB,自动调节为8位,当音频格式为AMR_WB,自动调节为16位
+}
+
+-- 录音的数据流回调函数,注意不可以在回调函数中加入耗时和延迟的操作
+local function  recode_data_callback(addr,data_len)
+    log.info("收到音频流,地址为:",addr,"有效数据长度为:",data_len)
+end
+local function record_end(event)
+    if event == exaudio.RECORD_DONE then
+        log.info("录音完成")
+    end
+end 
+
+-- 录音配置参数,exaudio.record_start 的入参
+local audio_record_param ={
+    format= exaudio.PCM_16000,    -- 录制格式,有exaudio.AMR_NB,exaudio.AMR_WB,exaudio.PCM_8000,exaudio.PCM_16000,exaudio.PCM_24000,exaudio.PCM_32000
+    time = 5,               -- 录制时间,单位(秒)
+    path = recode_data_callback,             -- 如果填入的是字符串,则表示是文件路径,录音会传输到这个路径里
+                                        -- 如果填入的是函数,则表示是流式录音,录音的数据会传输到此函数内,返回的是zbuf地址,以及数据长度
+                                        -- 如果是流式录音,则仅支持PCM 格式 
+    cbfnc = record_end,            -- 录音完毕回调函数
+}
+
+
+
+local taskName = "task_audio"
+local function audio_task()
+    log.info("开始流式录制音频到文件")
+    if exaudio.setup(audio_setup_param) then
+        exaudio.record_start(audio_record_param)
+    end
+end
+
+sys.taskInitEx(audio_task, taskName)

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/sample-6s.mp3 → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/sample-6s.mp3


+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/test.pcm → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHM_Air780EGH/test.pcm


BIN
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/10.amr


+ 20 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/main.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/main.lua

@@ -17,7 +17,7 @@
 
 5、record_stream:  流式录音,仅支持PCM,可以将音频流不断的拉取,可用来对接大模型
 
-6、1.mp3: 用于测试本地mp3文件播放
+6、sample-6s: 用于测试本地mp3文件播放
 
 7、test.pcm: 用于测试pcm 流式播放(实际可以云端下载)
 
@@ -37,7 +37,7 @@ VERSION:项目版本号,ascii string类型
 ]]
 
 --[[
-本demo可直接在Air8000整机开发板上运行
+本demo可使用Air780EHV核心板+AirAUDIO_1000 音频配件板+喇叭演示
 ]]
 
 PROJECT = "audio"
@@ -55,6 +55,24 @@ if wdt then
     sys.timerLoopStart(wdt.feed, 3000)
 end
 
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+
 
 require "play_file"     --   播放音频文件,可支持wav,amr,mp3 格式音频
 -- require "play_tts"      -- 支持文字转普通话输出需要固件支持

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/play_file.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_file.lua


+ 1 - 1
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/play_stream.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_stream.lua

@@ -20,7 +20,7 @@ exaudio = require("exaudio")
 
 -- 音频初始化设置参数,exaudio.setup 传入参数
 local audio_setup_param ={
-    model= "es8311",          -- dac类型,可填入"es8311","es8211"
+    model= "es8311",          -- 音频编解码类型,可填入"es8311","es8211"
     i2c_id = 0,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
     pa_ctrl = gpio.AUDIOPA_EN,         -- 音频放大器电源控制管脚
     dac_ctrl = 20,        --  音频编解码芯片电源控制管脚,780ehv 默认使用20

+ 2 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/play_tts.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/play_tts.lua

@@ -20,8 +20,8 @@ local audio_setup_param ={
     model= "es8311",          -- 音频编解码类型,可填入"es8311","es8211"
     i2c_id = 0,          -- i2c_id,可填入0,1 并使用pins 工具配置对应的管脚
     pa_ctrl = gpio.AUDIOPA_EN,         -- 音频放大器电源控制管脚
-    dac_ctrl = 20,        --  音频编解码芯片电源控制管脚,780ehv 默认使用20  
-}
+    dac_ctrl = 20,        --  音频编解码芯片电源控制管脚,780ehv 默认使用20
+}}
 
 local function play_end(event)
     if event == exaudio.PLAY_DONE then

+ 204 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/readme.md

@@ -0,0 +1,204 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、play_file.lua: 播放音频文件,可支持wav,amr,mp3 格式音频
+
+3、play_tts: 支持文字转普通话输出需要固件支持
+
+4、play_stream: 流式播放音频,仅支持PCM 格式
+
+5、record_file: 录音到文件,仅支持PCM 格式
+
+6、record_stream:  流式录音,仅支持PCM。
+
+7、sample-6s.mp3/10.amr: 用于测试本地mp3和amr文件播放
+
+8、test.pcm: 用于测试pcm 流式播放(实际可以云端下载)
+
+
+**注意:目前不支持录音和放音同时进行**
+
+
+## 常量的介绍
+
+1、exaudio.PLAY_DONE : 当播放音频结束时,会在回调函数返回播放完成的时间
+
+2、exaudio.RECORD_DONE : 当录音结束时,会在回调函数返回播放完成的时间
+
+3、exaudio.AMR_NB : 仅录音时有用,表示使用AMR_NB 方式录音
+
+4、exaudio.AMR_WB : 仅录音时有用,表示使用AMR_WB 方式录音
+
+5、exaudio.PCM_8000/exaudio.PCM_16000/exaudio.PCM_24000/exaudio.PCM_32000 :  仅录音时有用,表示使用8000/16000/24000/32000/秒 的速度对音频进行采样
+
+
+## 演示功能概述
+
+1、play_flie.lua 自动播放一个sample-6s.mp3音乐,点powerkey 按键进行音频切换,播放10.amr文件,点击boot 按键停止音频播放
+
+2、play_tts.lua 播放一个TTS,点powerkey 按键进行tts 的音色切换,点击boot 按键停止音频播放
+
+3、play_stream.lua 流式播放PCM,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
+
+4、record_file.lua 录音到文件,演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+5、record_stream.lua 流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用
+
+
+## 演示硬件环境
+1、Air780EHV核心板+AirAUDIO_1000配件板+喇叭
+
+![alt text](https://docs.openLuat.com/cdn/image/Air780EHV+Airaudio1000.jpg)
+
+Air780EHV核心板和AirAudio_1000 配件板的硬件接线方式为:
+
+| Air780EHV核心板 | AirAUDIO_1000配件板 |
+| ---------------| -----------------   |
+| 3/MIC+         |     MIC+            |
+| 4/MIC-         |     MIC-            |
+| 5/SPK+         |     SPK+            |
+| 6/SPK-         |     SPK-            |
+| 19/GPIO22      |     PA_EN           |
+| 3V3            |     VCC             |
+| GND            |     GND             |
+
+2、TYPE-C USB数据线一根
+- Air780EHM核心板通过 TYPE-C USB 口供电;
+- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+
+## 演示软件环境
+
+1、[Luatools下载调试工具](https://docs.openluat.com/air780epm/common/Luatools/) 
+
+2、[Air780EHV V2016版本固件](https://cdn6.vue2.cn/Luat_tool_src/v2tools/LuatOS_Air780EHV/LuatOS-SoC_V2016_Air780EHV.zip)(理论上,2025年7月26日之后发布的固件都可以)选择支持TTS功能的1、3、5、7、13或101、103、105、107、113号固件。[不同版本区别请见](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+3、 luatos需要的脚本和资源文件
+- 脚本和资源文件[点我浏览所有文件](https://gitee.com/openLuat/LuatOS_demo_v2_temp/tree/master/module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV_audio)
+
+- 准备好软件环境之后,接下来查看[如何烧录项目文件到Air780EHV核心板](https://docs.openluat.com/air8000/luatos/common/download/),将本篇文章中演示使用的项目文件烧录到Air780EHV核心板中。
+
+4、[合宙 LuatIO 工具(GPIO 复用初始化配置)使用说明](https://docs.openluat.com/air780epm/common/luatio/)
+
+5、 lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
+
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、demo脚本代码main.lua中,按照自己的需求选择对应的功能
+
+- 如果需要测试播放音频文件,则选择play_file 文件
+
+- 如果需要测试播放tts,则选择play_tts 文件
+
+- 如果需要测试流式播放音频,则选择play_stream 文件
+
+- 如果需要测试录音频到文件,则选择record_file 文件
+
+- 如果需要测试流式录音,则选择record_stream 文件
+
+
+3、Luatools烧录内核固件和修改后的demo脚本代码
+
+4、 在测试播放音频文件的时候,点powerkey 按键进行音频切换,切换内容是MP3,AMR格式,切换是通过播放优先级进行区分的,注意音频格式仅仅支持:MP3,WAV,AMR,点击boot 按键停止音频播放
+
+5、 在测试播放TTS的时候,点powerkey 按键进行TTS 音色切换,点击boot 按键停止音频播放,注意:仅支持中文TTS。
+
+
+6、在进行流式播放测试的时候,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加,注意流式播放目前仅支持PCM 格式音频,可选择不同的采样率,以及位深
+
+7、在测试录音到文件(仅支持PCM),演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+8、在测试流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用。
+
+9、运行结果展示
+
+- 播放文件 (play_file.lua)
+
+自动播放一个sample-6s.mp3 音乐;
+
+点击powerkey 按键进行音频切换播放10.amr文件;
+
+点击boot 按键停止音频播放
+
+``` lua
+ I/user.开始播放音频文件
+ I/user.播放完成 true
+
+ I/user.切换播放
+ E/user.是否完成播放 true
+ I/user.播放完成 true
+
+ I/user.停止播放
+ ```
+
+ - 文字转语音 (play_tts.lua)
+
+ 播放一个TTS;
+
+点击powerkey 按键进行tts 的音色切换
+
+点击boot 按键停止音频播放
+
+``` lua
+ I/user.开始播放TTS
+ I/user.切换播放
+
+ E/user.是否完成播放 true
+ I/user.播放完成 true
+
+ I/user.停止播放
+
+```
+
+- 流式播放 (play_stream.lua)
+
+创建一个播放流式音频task(task_audio)和一个模拟获取流式音频的task(audio_get_data),此task通过流式传输不断向exaudio.play_stream_write填入播放的音频,播放task 不断播放传入流式音频。
+
+使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
+
+``` lua
+ I/user.开始流式获取音频数据
+ I/user.开始流式播报
+
+ I/user.减小音量55
+ I/user.增大音量75
+
+ I/user.播放状态 true
+ I/user.播放完成 true
+
+```
+
+- 录音到文件  (record_file.lua)
+
+主程序录音到/record.amr 文件
+
+使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
+
+``` lua
+ I/user.开始录制音频到文件
+
+ E/user.减小音量55
+ I/user.增大音量75
+
+ I/user.录音后文件大小 320000
+```
+
+- 流式录音(record_stream.lua),仅支持PCM格式
+
+主程序录音进行流式录音
+
+录音过程中不断的进行recode_data_callback回调,回调内容为音频流的地址和长度。
+
+``` lua
+ I/user.开始流式录制音频到文件
+ I/user.收到音频流,地址为: ZBUFF*:0C7F70A8 有效数据长度为: 9600
+ I/user.录音完成
+
+ E/user.减小音量55
+ I/user.增大音量75
+
+ I/user.录音后
+

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/record_file.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/record_file.lua


+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/record_stream.lua → module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/record_stream.lua


BIN
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/sample-6s.mp3


BIN
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/Air780EHV/test.pcm


+ 0 - 541
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/exaudio.lua

@@ -1,541 +0,0 @@
---[[
-@module exaudio
-@summary exaudio扩展库
-@version 1.1
-@date    2025.09.01
-@author  梁健
-@usage
-]]
-local exaudio = {}
-
--- 常量定义
-local I2S_ID = 0
-local I2S_MODE = 0          -- 0:主机 1:从机
-local I2S_SAMPLE_RATE = 16000
-local I2S_CHANNEL_FORMAT = i2s.MONO_R  
-local I2S_COMM_FORMAT = i2s.MODE_LSB   -- 可选MODE_I2S, MODE_LSB, MODE_MSB
-local I2S_CHANNEL_BITS = 16
-local MULTIMEDIA_ID = 0
-local EX_MSG_PLAY_DONE = "playDone"
-local ES8311_ADDR = 0x18    -- 7位地址
-local CHIP_ID_REG = 0x00    -- 芯片ID寄存器地址
-
--- 模块常量
-exaudio.PLAY_DONE = 1         --   音频播放完毕的事件之一
-exaudio.RECORD_DONE = 1       --   音频录音完毕的事件之一  
-exaudio.AMR_NB = 0
-exaudio.AMR_WB = 1
-exaudio.PCM_8000 = 2
-exaudio.PCM_16000 = 3 
-exaudio.PCM_24000 = 4
-exaudio.PCM_32000 = 5
-
-
--- 默认配置参数
-local audio_setup_param = {
-    model = "es8311",         -- dac类型: "es8311","es8211"
-    i2c_id = 0,               -- i2c_id: 0,1
-    pa_ctrl = 0,              -- 音频放大器电源控制管脚
-    dac_ctrl = 0,             -- 音频编解码芯片电源控制管脚
-    dac_delay = 3,            -- DAC启动前冗余时间(100ms)
-    pa_delay = 100,           -- DAC启动后延迟打开PA的时间(ms)
-    dac_time_delay = 600,     -- 播放完毕后PA与DAC关闭间隔(ms)
-    bits_per_sample = 16,     -- 采样位数
-    pa_on_level = 1           -- PA打开电平 1:高 0:低        
-}
-
-local audio_play_param = {
-    type = 0,                 -- 0:文件 1:TTS 2:流式
-    content = nil,            -- 播放内容
-    cbfnc = nil,              -- 播放完毕回调
-    priority = 0,             -- 优先级(数值越大越高)
-    sampling_rate = 16000,    -- 采样率(仅流式)
-    sampling_depth = 16,      -- 采样位深(仅流式)
-    signed_or_unsigned = true -- PCM是否有符号(仅流式)
-}
-
-local audio_record_param = {
-    format = 0,               -- 录制格式,支持exaudio.AMR_NB,exaudio.AMR_WB,exaudio.PCM_8000,exaudio.PCM_16000,exaudio.PCM_24000,exaudio.PCM_32000
-    time = 5,                 -- 录制时间(秒)
-    path = nil,               -- 文件路径或流式回调
-    cbfnc = nil               -- 录音完毕回调
-}
-
--- 内部变量
-local pcm_buff0 = nil
-local pcm_buff1 = nil
-local voice_vol = 55
-local mic_vol = 80
-
--- 定义全局队列表
-local audio_play_queue = {
-    data = {},       -- 存储字符串的数组
-    sequenceIndex = 1  -- 用于跟踪插入顺序的索引
-}
-
--- 向队列中添加字符串(按调用顺序插入)
-local function audio_play_queue_push(str)
-    if type(str) == "string" then
-        -- 存储格式: {index = 顺序索引, value = 字符串值}
-        table.insert(audio_play_queue.data, {
-            index = audio_play_queue.sequenceIndex,
-            value = str
-        })
-        audio_play_queue.sequenceIndex = audio_play_queue.sequenceIndex + 1
-        return true
-    end
-    return false
-end
-
--- 从队列中取出最早插入的字符串(按顺序取出)
-local function audio_play_queue_pop()
-    if #audio_play_queue.data > 0 then
-        -- 取出并移除第一个元素
-        local item = table.remove(audio_play_queue.data, 1)
-        return item.value  -- 返回值
-    end
-    return nil
-end
--- 清空队列中所有数据
-function audio_queue_clear()
-    -- 清空数组
-    audio_play_queue.data = {}
-    -- 重置顺序索引
-    audio_play_queue.sequenceIndex = 1
-    return true
-end
-
--- 工具函数:参数检查
-local function check_param(param, expected_type, name)
-    if type(param) ~= expected_type then
-        log.error(string.format("参数错误: %s 应为 %s 类型", name, expected_type))
-        return false
-    end
-    return true 
-end
-
--- 音频回调处理
-local function audio_callback(id, event, point)
-    -- log.info("audio_callback", "event:", event, 
-    --         "MORE_DATA:", audio.MORE_DATA, 
-    --         "DONE:", audio.DONE,
-    --         "RECORD_DATA:", audio.RECORD_DATA,
-    --         "RECORD_DONE:", audio.RECORD_DONE)
-
-    if event == audio.MORE_DATA then
-        audio.write(MULTIMEDIA_ID,audio_play_queue_pop())
-    elseif event == audio.DONE then
-        if type(audio_play_param.cbfnc) == "function" then
-            audio_play_param.cbfnc(exaudio.PLAY_DONE)
-        end
-        audio_queue_clear()  -- 清空流式播放数据队列
-        sys.publish(EX_MSG_PLAY_DONE)
-        
-    elseif event == audio.RECORD_DATA then
-        if type(audio_record_param.path) == "function" then
-            local buff, len = point == 0 and pcm_buff0 or pcm_buff1,
-                             point == 0 and pcm_buff0:used() or pcm_buff1:used()
-            audio_record_param.path(buff, len)
-        end
-        
-    elseif event == audio.RECORD_DONE then
-        if type(audio_record_param.cbfnc) == "function" then
-            audio_record_param.cbfnc(exaudio.RECORD_DONE)
-        end
-    end
-end
-
--- 读取ES8311芯片ID
-local function read_es8311_id()
-
-
-    -- 发送读取请求
-    local send_ok = i2c.send(audio_setup_param.i2c_id, ES8311_ADDR, CHIP_ID_REG)
-    if not send_ok then
-        log.error("发送芯片ID读取请求失败")
-        return false
-    end
-
-    -- 读取数据
-    local data = i2c.recv(audio_setup_param.i2c_id, ES8311_ADDR, 1)
-    if data and #data == 1 then
-        return true
-    end
-
-    log.error("读取ES8311芯片ID失败")
-    return false
-end
-
--- 音频硬件初始化
-local function audio_setup()
-    -- I2C配置
-    if not i2c.setup(audio_setup_param.i2c_id, i2c.FAST) then
-        log.error("I2C初始化失败")
-        return false
-    end
-    -- 初始化I2S
-    local result, data = i2s.setup(
-        I2S_ID, 
-        I2S_MODE, 
-        I2S_SAMPLE_RATE, 
-        audio_setup_param.bits_per_sample, 
-        I2S_CHANNEL_FORMAT, 
-        I2S_COMM_FORMAT,
-        I2S_CHANNEL_BITS
-    )
-
-    if not result then
-        log.error("I2S设置失败")
-        return false
-    end
-    -- 配置音频通道
-    audio.config(
-        MULTIMEDIA_ID, 
-        audio_setup_param.pa_ctrl, 
-        audio_setup_param.pa_on_level, 
-        audio_setup_param.dac_delay, 
-        audio_setup_param.pa_delay, 
-        audio_setup_param.dac_ctrl, 
-        1,  -- power_on_level
-        audio_setup_param.dac_time_delay
-    )
-    -- 设置总线
-    audio.setBus(
-        MULTIMEDIA_ID, 
-        audio.BUS_I2S,
-        {
-            chip = audio_setup_param.model,
-            i2cid = audio_setup_param.i2c_id,
-            i2sid = I2S_ID,
-            voltage = audio.VOLTAGE_1800
-        }
-    )
-
-  
-    -- 设置音量
-    audio.vol(MULTIMEDIA_ID, voice_vol)
-    audio.micVol(MULTIMEDIA_ID, mic_vol)
-    audio.pm(MULTIMEDIA_ID, audio.RESUME)
-    
-    -- 检查芯片连接
-    if audio_setup_param.model == "es8311" and not read_es8311_id() then
-        log.error("ES8311通讯失败,请检查硬件")
-        return false
-    end
-
-    -- 注册回调
-    audio.on(MULTIMEDIA_ID, audio_callback)
-    return true
-end
-
--- 模块接口:初始化
-function exaudio.setup(audioConfigs)
-    -- 检查必要参数
-    if not  audio  then
-        log.error("不支持audio 库,请选择支持audio 的core")
-        return false
-    end
-    if not audioConfigs or type(audioConfigs) ~= "table" then
-        log.error("配置参数必须为table类型")
-        return false
-    end
-    -- 检查codec型号
-    if not audioConfigs.model or 
-       (audioConfigs.model ~= "es8311" and audioConfigs.model ~= "es8211") then
-        log.error("请指定正确的codec型号(es8311或es8211)")
-        return false
-    end
-    audio_setup_param.model = audioConfigs.model
-    -- 针对ES8311的特殊检查
-    if audioConfigs.model == "es8311" then
-        if not check_param(audioConfigs.i2c_id, "number", "i2c_id") then
-            return false
-        end
-        audio_setup_param.i2c_id = audioConfigs.i2c_id
-    end
-
-    -- 检查功率放大器控制管脚
-    if audioConfigs.pa_ctrl == nil then
-        log.warn("pa_ctrl(功率放大器控制管脚)是控制pop 音的重要管脚,建议硬件设计加上")
-    end
-    audio_setup_param.pa_ctrl = audioConfigs.pa_ctrl
-
-    -- 检查功率放大器控制管脚
-    if audioConfigs.dac_ctrl == nil then
-        log.warn("dac_ctrl(音频编解码控制管脚)是控制pop 音的重要管脚,建议硬件设计加上")
-    end
-    audio_setup_param.dac_ctrl = audioConfigs.dac_ctrl
-
-
-    -- 处理可选参数
-    local optional_params = {
-        {name = "dac_delay", type = "number"},
-        {name = "pa_delay", type = "number"},
-        {name = "dac_time_delay", type = "number"},
-        {name = "bits_per_sample", type = "number"},
-        {name = "pa_on_level", type = "number"}
-    }
-
-    for _, param in ipairs(optional_params) do
-        if audioConfigs[param.name] ~= nil then
-            if check_param(audioConfigs[param.name], param.type, param.name) then
-                audio_setup_param[param.name] = audioConfigs[param.name]
-            else
-                return false
-            end
-        end
-    end
-
-    -- 确保采样位数有默认值
-    audio_setup_param.bits_per_sample = audio_setup_param.bits_per_sample or 16
-    return audio_setup()
-end
-
--- 模块接口:开始播放
-function exaudio.play_start(playConfigs)
-    if not playConfigs or type(playConfigs) ~= "table" then
-        log.error("播放配置必须为table类型")
-        return false
-    end
-
-    -- 检查播放类型
-    if not check_param(playConfigs.type, "number", "type") then
-        log.error("type必须为数值(0:文件,1:TTS,2:流式)")
-        return false
-    end
-    audio_play_param.type = playConfigs.type
-
-    -- 处理优先级
-    if playConfigs.priority ~= nil then
-        if check_param(playConfigs.priority, "number", "priority") then
-            if playConfigs.priority > audio_play_param.priority then
-                log.error("是否完成播放",audio.isEnd(MULTIMEDIA_ID))
-                if not audio.isEnd(MULTIMEDIA_ID) then
-                    if audio.play(MULTIMEDIA_ID) ~= true then
-                        return false
-                    end
-                    sys.waitUntil(EX_MSG_PLAY_DONE)
-                end
-                audio_play_param.priority = playConfigs.priority
-            end
-        else
-            return false
-        end
-    end
-
-    -- 处理不同播放类型
-    local play_type = audio_play_param.type
-    if play_type == 0 then  -- 文件播放
-        if not playConfigs.content then
-            log.error("文件播放需要指定content(文件路径或路径表)")
-            return false
-        end
-
-        local content_type = type(playConfigs.content)
-        if content_type == "table" then
-            for _, path in ipairs(playConfigs.content) do
-                if type(path) ~= "string" then
-                    log.error("播放列表元素必须为字符串路径")
-                    return false
-                end
-            end
-        elseif content_type ~= "string" then
-            log.error("文件播放content必须为字符串或路径表")
-            return false
-        end
-
-        audio_play_param.content = playConfigs.content
-        if audio.play(MULTIMEDIA_ID, audio_play_param.content) ~= true then
-            return false
-        end
-
-    elseif play_type == 1 then  -- TTS播放
-        if not audio.tts then
-            log.error("本固件不支持TTS,请更换支持TTS 的固件")
-            return false
-        end
-        if not check_param(playConfigs.content, "string", "content") then
-            log.error("TTS播放content必须为字符串")
-            return false
-        end
-        audio_play_param.content = playConfigs.content
-        if audio.tts(MULTIMEDIA_ID, audio_play_param.content)  ~= true  then
-            return false
-        end
-
-    elseif play_type == 2 then  -- 流式播放
-        if not check_param(playConfigs.sampling_rate, "number", "sampling_rate") then
-            return false
-        end
-        if not check_param(playConfigs.sampling_depth, "number", "sampling_depth") then
-            return false
-        end
-
-        audio_play_param.content = playConfigs.content
-        audio_play_param.sampling_rate = playConfigs.sampling_rate
-        audio_play_param.sampling_depth = playConfigs.sampling_depth
-        
-        if playConfigs.signed_or_unsigned ~= nil then
-            audio_play_param.signed_or_unsigned = playConfigs.signed_or_unsigned
-        end
-
-        audio.start(
-            MULTIMEDIA_ID, 
-            audio.PCM, 
-            1, 
-            playConfigs.sampling_rate, 
-            playConfigs.sampling_depth, 
-            audio_play_param.signed_or_unsigned
-        )
-        -- 发送初始数据
-        if audio.write(MULTIMEDIA_ID, string.rep("\0", 512)) ~= true then
-            return false
-        end
-    end
-
-    -- 处理回调函数
-    if playConfigs.cbfnc ~= nil then
-        if check_param(playConfigs.cbfnc, "function", "cbfnc") then
-            audio_play_param.cbfnc = playConfigs.cbfnc
-        else
-            return false
-        end
-    else
-        audio_play_param.cbfnc = nil
-    end
-    return true
-end
-
--- 模块接口:流式播放数据写入
-function exaudio.play_stream_write(data)
-    audio_play_queue_push(data)
-    return true
-end
-
--- 模块接口:停止播放
-function exaudio.play_stop()
-    return audio.play(MULTIMEDIA_ID)
-end
-
--- 模块接口:检查播放是否结束
-function exaudio.is_end()
-    return audio.isEnd(MULTIMEDIA_ID)
-end
-
--- 模块接口:获取错误信息
-function exaudio.get_error()
-    return audio.getError(MULTIMEDIA_ID)
-end
-
--- 模块接口:开始录音
-function exaudio.record_start(recodConfigs)
-    if not recodConfigs or type(recodConfigs) ~= "table" then
-        log.error("录音配置必须为table类型")
-        return false
-    end
-    -- 检查录音格式
-    if recodConfigs.format == nil or type(recodConfigs.format) ~= "number" or recodConfigs.format > 5 then
-        log.error("请指定正确的录音格式")
-        return false
-    end
-    audio_record_param.format = recodConfigs.format
-
-    -- 处理录音时间
-    if recodConfigs.time ~= nil then
-        if check_param(recodConfigs.time, "number", "time") then
-            audio_record_param.time = recodConfigs.time
-        else
-            return false
-        end
-    else
-        audio_record_param.time = 0
-    end
-
-    -- 处理存储路径/回调
-    if not recodConfigs.path then
-        log.error("必须指定录音路径或流式回调函数")
-        return false
-    end
-    audio_record_param.path = recodConfigs.path
-
-    -- 转换录音格式
-    local recod_format, amr_quailty
-    if audio_record_param.format == exaudio.AMR_NB then
-        recod_format = audio.AMR_NB
-        amr_quailty = 7
-    elseif audio_record_param.format == exaudio.AMR_WB then
-        recod_format = audio.AMR_WB
-        amr_quailty = 8
-    elseif audio_record_param.format == exaudio.PCM_8000 then
-        recod_format = 8000
-    elseif audio_record_param.format == exaudio.PCM_16000 then
-        recod_format = 16000
-    elseif audio_record_param.format == exaudio.PCM_24000 then
-        recod_format = 24000
-    elseif audio_record_param.format == exaudio.PCM_32000 then
-        recod_format = 32000
-    end
-
-    -- 处理回调函数
-    if recodConfigs.cbfnc ~= nil then
-        if check_param(recodConfigs.cbfnc, "function", "cbfnc") then
-            audio_record_param.cbfnc = recodConfigs.cbfnc
-        else
-            return false
-        end
-    else
-        audio_record_param.cbfnc = nil
-    end
-    -- 开始录音
-    local path_type = type(audio_record_param.path)
-    if path_type == "string" then
-        return audio.record(
-            MULTIMEDIA_ID, 
-            recod_format, 
-            audio_record_param.time, 
-            amr_quailty, 
-            audio_record_param.path
-        )
-    elseif path_type == "function" then
-        -- 初始化缓冲区
-        if not pcm_buff0 or not pcm_buff1 then
-            pcm_buff0 = zbuff.create(16000)
-            pcm_buff1 = zbuff.create(16000)
-        end
-        return audio.record(
-            MULTIMEDIA_ID, 
-            recod_format, 
-            audio_record_param.time, 
-            amr_quailty, 
-            nil, 
-            3,
-            pcm_buff0,
-            pcm_buff1
-        )
-    end
-    log.error("录音路径必须为字符串或函数")
-    return false
-end
-
--- 模块接口:停止录音
-function exaudio.record_stop()
-    return audio.recordStop(MULTIMEDIA_ID)
-end
-
--- 模块接口:设置音量
-function exaudio.vol(play_volume)
-    if check_param(play_volume, "number", "音量值") then
-        return audio.vol(MULTIMEDIA_ID, play_volume)
-    end
-    return false
-end
-
--- 模块接口:设置麦克风音量
-function exaudio.mic_vol(record_volume)
-    if check_param(record_volume, "number", "麦克风音量值") then
-        return audio.micVol(MULTIMEDIA_ID, record_volume)  
-    end
-    return false
-end
-
-return exaudio

+ 0 - 106
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/readme.md

@@ -1,106 +0,0 @@
-## 功能模块介绍
-
-1、main.lua:主程序入口;
-
-2、play_file.lua: 播放音频文件,可支持wav,amr,mp3 格式音频
-
-3、play_tts: 支持文字转普通话输出需要固件支持
-
-4、play_stream: 流式播放音频,仅支持PCM 格式,可以将音频推流到云端,用来对接大模型或者流式录音的应用。
-
-5、record_file: 录音到文件,仅支持PCM 格式
-
-6、record_stream:  流式录音,仅支持PCM,可以将音频流不断的拉取,可用来对接大模型
-
-7、1.mp3: 用于测试本地mp3文件播放
-
-8、test.pcm: 用于测试pcm 流式播放(实际可以云端下载)
-
-
-
-
-
-## 常量的介绍
-
-1、exaudio.PLAY_DONE : 当播放音频结束时,会在回调函数返回播放完成的时间
-
-2、exaudio.RECORD_DONE : 当录音结束时,会在回调函数返回播放完成的时间
-
-3、exaudio.AMR_NB : 仅录音时有用,表示使用AMR_NB 方式录音
-
-4、exaudio.AMR_WB : 仅录音时有用,表示使用AMR_WB 方式录音
-
-5、exaudio.PCM_8000/exaudio.PCM_16000/exaudio.PCM_24000/exaudio.PCM_32000 :  仅录音时有用,表示使用8000/16000/24000/32000/秒 的速度对音频进行采样
-
-
-## 演示功能概述
-
-1、play_flie.lua 自动播放一个1.mp3音乐,点powerkey 按键进行音频切换,点击boot 按键停止音频播放
-
-2、play_tts.lua 播放一个TTS,点powerkey 按键进行tts 的音色切换,点击boot 按键停止音频播放
-
-3、play_stream.lua 流式播放PCM,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加
-
-4、record_file.lua 录音到文件,演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
-
-5、record_stream.lua 流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用
-
-
-## 演示硬件环境
-
-![](https://docs.openluat.com/air8000/luatos/app/image/netdrv_multi.jpg)
-
-1、Air8000开发板一块
-
-2、喇叭一个
-
-2、插入喇叭到开发板中
-
-
-## 演示软件环境
-
-1、Luatools下载调试工具
-
-2、[Air8000 V2014版本固件](https://docs.openluat.com/air8000/luatos/firmware/)(理论上,2025年7月26日之后发布的固件都可以)
-
-
-## 演示核心步骤
-
-1、搭建好硬件环境
-
-2、demo脚本代码main.lua中,按照自己的需求选择对应的功能
-
-- 如果需要测试播放音频文件,则选择play_file 文件
-
-- 如果需要测试播放tts,则选择play_tts 文件
-
-- 如果需要测试流式播放音频,则选择play_stream 文件
-
-- 如果需要测试录音频到文件,则选择record_file 文件
-
-- 如果需要测试流式录音,则选择record_stream 文件
-
-
-3、Luatools烧录内核固件和修改后的demo脚本代码
-
-4、烧录成功后,自动开机运行,如果出现以下日志,播放或者或者录音完成
-
-``` lua
-I/user.播放完成 true
-I/user.录音完成 
-I/user.录音后文件大小 
-```
-
-5、 在测试播放音频文件的时候,点powerkey 按键进行音频切换,切换内容是MP3,AMR格式,切换是通过播放优先级进行区分的,注意音频格式仅仅支持:MP3,WAV,AMR,点击boot 按键停止音频播放
-
-6、 在测试播放TTS的时候,点powerkey 按键进行TTS 音色切换,点击boot 按键停止音频播放,注意:仅支持中文TTS。
-
-
-7、在进行流式播放测试的时候,使用test.pcm 模拟音频来源,通过流式传输不断填入播放的音频,使用powerkey 按键进行音量减小,点击boot 按键进行音量增加,注意流式播放目前仅支持PCM 格式音频,可选择不同的采样率,以及位深
-
-8、在测试录音到文件(仅支持PCM),演示了pcm 录音到文件,使用powerkey 按键进行录音音量减小,点击boot 按键进行录音音量增加
-
-9、在测试流式录音(仅支持PCM),不断输出录音的数据地址和录音长度,供给应用层调用
-
-
-

BIN
module/Air780EHM_Air780EHV_Air780EGH/demo/audio/音频硬件框架.png


+ 124 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/bit64_app.lua

@@ -0,0 +1,124 @@
+--[[
+@module  bit64_app
+@summary bit64_app应用功能模块 
+@version 1.0
+@date    2025.10.14
+@author  沈园园
+@usage
+本文件为bit64应用功能模块,核心业务逻辑为:
+1、演示在 32 位系统上对 64 位数据的基本算术运算和逻辑运算;
+
+本文件没有对外接口,直接在main.lua中require "bit64_app"就可以加载运行;
+]]
+
+--定义几个变量
+local data,b64,b32,a,b
+
+--设置调试风格1: I/main.lua:12 ABC DEF 123(在开头添加文件位置信息)
+log.style(1)
+
+--本文的64bit数据为:小端格式的9字节数据的字符串,最后一个字节是类型(0是整数,1是浮点), 前面8个字节是数据。
+--十进制转123456对应64bit十六进制字符串"40E2010000000000"
+
+if bit64 then
+	log.info("bit64 演示")
+    
+    --将数据进行 32 位和 64 位互转
+    --123456对应64bit的9字节数据HEX字符
+    data = "40E201000000000000"
+    --需将HEX字符串转成Lua字符串填入 
+    b32 = bit64.to32(data:fromHex())
+    log.info("i32", b32, mcu.x32(b32))    
+    
+    data = 12345678
+	b64 = bit64.to64(data)
+	b32 = bit64.to32(b64)
+	log.info("i32", b32, mcu.x32(b32))
+	data = -12345678
+	b64 = bit64.to64(data)
+	b32 = bit64.to32(b64)
+	log.info("i32", b32, mcu.x32(b32))
+	data = 12.34234
+	b64 = bit64.to64(data)
+	b32 = bit64.to32(b64)
+	log.info("f32", data, b32)
+	data = -12.34234
+	b64 = bit64.to64(data)
+	b32 = bit64.to32(b64)
+	log.info("f32", data, b32)
+
+    --64 位数据之间进行运算
+    --64bit数据格式化打印成字符串,用于显示值
+    --64bit数据加,a+b,a和b中有一个为浮点,则按照浮点运算
+    --64bit数据减,a-b,a和b中有一个为浮点,则按照浮点运算
+    --64bit数据乘,a*b,a和b中有一个为浮点,则按照浮点运算
+    --64bit数据除,a/b,a和b中有一个为浮点,则按照浮点运算
+	a = bit64.to64(87654321)
+	b = bit64.to64(12345678)
+	log.info("87654321+12345678=", bit64.show(bit64.plus(a,b)))
+	log.info("87654321-12345678=", bit64.show(bit64.minus(a,b)))
+	log.info("87654321*12345678=", bit64.show(bit64.multi(a,b)))
+	log.info("87654321/12345678=", bit64.show(bit64.pide(a,b)))
+
+	--64 位与 32 位数据之间进行运算
+    a = bit64.to64(87654321)
+	b = 1234567
+	log.info("87654321+1234567=", bit64.show(bit64.plus(a,b)))
+	log.info("87654321-1234567=", bit64.show(bit64.minus(a,b)))
+	log.info("87654321*1234567=", bit64.show(bit64.multi(a,b)))
+	log.info("87654321/1234567=", bit64.show(bit64.pide(a,b)))
+
+
+	--64 位数据之间,一个数是浮点数进行运算
+    a = bit64.to64(87654.326)
+	b = bit64.to64(12345)
+	log.info("87654.326+12345=", 87654.326 + 12345)
+	log.info("87654.326+12345=", bit64.show(bit64.plus(a,b)))
+	log.info("87654.326-12345=", bit64.show(bit64.minus(a,b)))
+	log.info("87654.326*12345=", bit64.show(bit64.multi(a,b)))
+	log.info("87654.326/12345=", bit64.show(bit64.pide(a,b)))
+
+	--64 位浮点数计算
+    a = bit64.to64(87654.32)
+	b = bit64.to64(12345.67)
+	log.info("float", "87654.32+12345.67=", 87654.32 + 12345.67)
+	log.info("double","87654.32+12345.67=", bit64.show(bit64.plus(a,b)))
+	log.info("double to float","87654.32+12345.67=", bit64.to32(bit64.plus(a,b)))
+	log.info("87654.32-12345.67=", bit64.show(bit64.minus(a,b)))
+	log.info("87654.32*12345.67=", bit64.show(bit64.multi(a,b)))
+	log.info("87654.32/12345.67=", bit64.show(bit64.pide(a,b)))
+	log.info("double to int64", "87654.32/12345.67=", bit64.show(bit64.pide(a,b,nil,true)))
+
+	--64 位数据移位操作
+    a = bit64.to64(0xc0000000)
+	b = 2
+	a = bit64.shift(a,8,true)
+	log.info("0xc0000000 << 8 =", bit64.show(a, 16))
+	log.info("0xc000000000+2=", bit64.show(bit64.plus(a,b), 16))
+	log.info("0xc000000000-2=", bit64.show(bit64.minus(a,b), 16))
+	log.info("0xc000000000*2=", bit64.show(bit64.multi(a,b), 16))
+	log.info("0xc000000000/2=", bit64.show(bit64.pide(a,b), 16))
+	log.style(0)
+
+	--将字符串转为 LongLong 数据
+    if bit64.strtoll then
+		local data = bit64.strtoll("864040064024194", 10)
+		log.info("data", data:toHex())
+		log.info("data", bit64.show(data))
+	end
+end
+
+--获取高精度 tick,输出转换好的 64 位结构
+local function sys_run_time()
+	local tick64, per = mcu.tick64(true)
+	local per_cnt = per * 1000000
+	while true do
+		tick64, per = mcu.tick64(true)
+		log.info("work time","当前时间", bit64.to32(bit64.pide(tick64,per_cnt)))
+		sys.wait(1000)
+	end
+end
+
+if mcu.tick64 then
+	sys.taskInit(sys_run_time)
+end

+ 55 - 98
module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/main.lua

@@ -1,114 +1,71 @@
-
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "bit64_test"
-VERSION = "1.0.0"
-
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.14
+@author  沈园园
+@usage
+本demo演示的核心功能为:
+演示在 32 位系统上对 64 位数据的基本算术运算和逻辑运算
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "luatos_bit64_app"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
 log.info("main", PROJECT, VERSION)
 
--- 引入必要的库文件(lua编写), 内部库不需要require
-sys = require("sys")
-
--- Air780E的AT固件默认会为开机键防抖, 导致部分用户刷机很麻烦
-if rtos.bsp() == "EC618" and pm and pm.PWK_MODE then
-    pm.power(pm.PWK_MODE, false)
-end
-
 
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
 if wdt then
-    --添加硬狗防止程序卡死,在支持的设备上启用这个功能
-    wdt.init(9000)--初始化watchdog设置为9s
-    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
 end
 
 
-local data,b64,b32,a,b
-
-if bit64 then
-	log.style(1)
-	log.info("bit64 演示")
-	data = 12345678
-	b64 = bit64.to64(data)
-	b32 = bit64.to32(b64)
-	log.info("i32", b32, mcu.x32(b32))
-	data = -12345678
-	b64 = bit64.to64(data)
-	b32 = bit64.to32(b64)
-	log.info("i32", b32, mcu.x32(b32))
-	data = 12.34234
-	b64 = bit64.to64(data)
-	b32 = bit64.to32(b64)
-	log.info("f32", data, b32)
-	data = -12.34234
-	b64 = bit64.to64(data)
-	b32 = bit64.to32(b64)
-	log.info("f32", data, b32)
-
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
 
-	a = bit64.to64(87654321)
-	b = bit64.to64(12345678)
-	log.info("87654321+12345678=", bit64.show(bit64.plus(a,b)))
-	log.info("87654321-12345678=", bit64.show(bit64.minus(a,b)))
-	log.info("87654321*12345678=", bit64.show(bit64.multi(a,b)))
-	log.info("87654321/12345678=", bit64.show(bit64.pide(a,b)))
 
-	a = bit64.to64(87654321)
-	b = 1234567
-	log.info("87654321+1234567=", bit64.show(bit64.plus(a,b)))
-	log.info("87654321-1234567=", bit64.show(bit64.minus(a,b)))
-	log.info("87654321*1234567=", bit64.show(bit64.multi(a,b)))
-	log.info("87654321/1234567=", bit64.show(bit64.pide(a,b)))
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
 
 
-	a = bit64.to64(87654.326)
-	b = bit64.to64(12345)
-	log.info("87654.326+12345=", 87654.326 + 12345)
-	log.info("87654.326+12345=", bit64.show(bit64.plus(a,b)))
-	log.info("87654.326-12345=", bit64.show(bit64.minus(a,b)))
-	log.info("87654.326*12345=", bit64.show(bit64.multi(a,b)))
-	log.info("87654.326/12345=", bit64.show(bit64.pide(a,b)))
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
 
-	a = bit64.to64(87654.32)
-	b = bit64.to64(12345.67)
-	log.info("float", "87654.32+12345.67=", 87654.32 + 12345.67)
-	log.info("double","87654.32+12345.67=", bit64.show(bit64.plus(a,b)))
-	log.info("double to float","87654.32+12345.67=", bit64.to32(bit64.plus(a,b)))
-	log.info("87654.32-12345.67=", bit64.show(bit64.minus(a,b)))
-	log.info("87654.32*12345.67=", bit64.show(bit64.multi(a,b)))
-	log.info("87654.32/12345.67=", bit64.show(bit64.pide(a,b)))
-	log.info("double to int64", "87654.32/12345.67=", bit64.show(bit64.pide(a,b,nil,true)))
-
-	a = bit64.to64(0xc0000000)
-	b = 2
-	a = bit64.shift(a,8,true)
-	log.info("0xc0000000 << 8 =", bit64.show(a, 16))
-	log.info("0xc000000000+2=", bit64.show(bit64.plus(a,b), 16))
-	log.info("0xc000000000-2=", bit64.show(bit64.minus(a,b), 16))
-	log.info("0xc000000000*2=", bit64.show(bit64.multi(a,b), 16))
-	log.info("0xc000000000/2=", bit64.show(bit64.pide(a,b), 16))
-	log.style(0)
-
-	if bit64.strtoll then
-		local data = bit64.strtoll("864040064024194", 10)
-		log.info("data", data:toHex())
-		log.info("data", bit64.show(data))
-	end
-end
-
-local function sys_run_time()
-	local tick64, per = mcu.tick64(true)
-	local per_cnt = per * 1000000
-	while true do
-		tick64, per = mcu.tick64(true)
-		log.info("work time","当前时间", bit64.to32(bit64.pide(tick64,per_cnt)))
-		sys.wait(1000)
-	end
-end
-
-if mcu.tick64 then
-	sys.taskInit(sys_run_time)
-end
+-- 加载bit64_app应用功能模块
+require "bit64_app"
 
 -- 用户代码已结束---------------------------------------------
 -- 结尾总是这一句
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 85 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/bit64/readme.md

@@ -0,0 +1,85 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、bit64_app.lua:32 位系统上对 64 位数据的基本算术运算和逻辑运算;
+
+## 演示功能概述
+
+1、创建一个task;
+
+2、演示在 32 位系统上对 64 位数据的基本算术运算和逻辑运算;
+
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/air780ehv/luatos/common/hwenv/image/Air780EHV2.png)
+
+1、Air780EHM/Air780EHV/Air780EGH核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、Air780EHM/Air780EHV/Air780EGH核心板和数据线的硬件接线方式为
+
+- Air780EHM/Air780EHV/Air780EGH核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
+- 核心板正面的 ON/OFF 拨动开关 拨到ON一端;
+
+
+## 演示软件环境
+
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
+
+2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+4、[Air780EGH 最新版本的内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、出现类似于下面的日志,就表示运行成功:
+
+``` lua
+[2025-10-15 11:10:19.702][000000000.210] I/bit64_luatos.lua:24 bit64 演示
+[2025-10-15 11:10:19.706][000000000.211] I/bit64_luatos.lua:31 i32 123456 0x1e240
+[2025-10-15 11:10:19.713][000000000.211] I/bit64_luatos.lua:36 i32 12345678 0xbc614e
+[2025-10-15 11:10:19.721][000000000.211] I/bit64_luatos.lua:40 i32 -12345678 0xff439eb2
+[2025-10-15 11:10:19.727][000000000.212] I/bit64_luatos.lua:44 f32   12.3423   12.3423
+[2025-10-15 11:10:19.734][000000000.212] I/bit64_luatos.lua:48 f32  -12.3423  -12.3423
+[2025-10-15 11:10:19.742][000000000.212] I/bit64_luatos.lua:58 87654321+12345678= 99999999
+[2025-10-15 11:10:19.747][000000000.213] I/bit64_luatos.lua:59 87654321-12345678= 75308643
+[2025-10-15 11:10:19.752][000000000.213] I/bit64_luatos.lua:60 87654321*12345678= 1082152022374638
+[2025-10-15 11:10:19.757][000000000.213] I/bit64_luatos.lua:61 87654321/12345678= 7
+[2025-10-15 11:10:19.763][000000000.213] I/bit64_luatos.lua:66 87654321+1234567= 88888888
+[2025-10-15 11:10:19.768][000000000.214] I/bit64_luatos.lua:67 87654321-1234567= 86419754
+[2025-10-15 11:10:19.773][000000000.214] I/bit64_luatos.lua:68 87654321*1234567= 108215132114007
+[2025-10-15 11:10:19.777][000000000.214] I/bit64_luatos.lua:69 87654321/1234567= 71
+[2025-10-15 11:10:19.784][000000000.214] I/bit64_luatos.lua:75 87654.326+12345=   99999.3
+[2025-10-15 11:10:19.789][000000000.215] I/bit64_luatos.lua:76 87654.326+12345= 99999.328125
+[2025-10-15 11:10:19.794][000000000.215] I/bit64_luatos.lua:77 87654.326-12345= 75309.328125
+[2025-10-15 11:10:19.799][000000000.215] I/bit64_luatos.lua:78 87654.326*12345= 1.082093e+09
+[2025-10-15 11:10:19.804][000000000.216] I/bit64_luatos.lua:79 87654.326/12345= 7.100391
+[2025-10-15 11:10:19.811][000000000.216] I/bit64_luatos.lua:84 float 87654.32+12345.67=  100000.0
+[2025-10-15 11:10:19.816][000000000.216] I/bit64_luatos.lua:85 double 87654.32+12345.67= 99999.990234
+[2025-10-15 11:10:19.823][000000000.217] I/bit64_luatos.lua:86 double to float 87654.32+12345.67=  100000.0
+[2025-10-15 11:10:19.827][000000000.217] I/bit64_luatos.lua:87 87654.32-12345.67= 75308.650391
+[2025-10-15 11:10:19.832][000000000.217] I/bit64_luatos.lua:88 87654.32*12345.67= 1.082151e+09
+[2025-10-15 11:10:19.836][000000000.218] I/bit64_luatos.lua:89 87654.32/12345.67= 7.100005
+[2025-10-15 11:10:19.842][000000000.218] I/bit64_luatos.lua:90 double to int64 87654.32/12345.67= 7
+[2025-10-15 11:10:19.849][000000000.218] I/bit64_luatos.lua:96 0xc0000000 << 8 = 0xc000000000
+[2025-10-15 11:10:19.853][000000000.218] I/bit64_luatos.lua:97 0xc000000000+2= 0xc000000002
+[2025-10-15 11:10:19.860][000000000.219] I/bit64_luatos.lua:98 0xc000000000-2= 0xbffffffffe
+[2025-10-15 11:10:19.867][000000000.219] I/bit64_luatos.lua:99 0xc000000000*2= 0x18000000000
+[2025-10-15 11:10:19.871][000000000.219] I/bit64_luatos.lua:100 0xc000000000/2= 0x6000000000
+[2025-10-15 11:10:19.876][000000000.219] I/user.data 827E1601D711030000 18
+[2025-10-15 11:10:19.882][000000000.220] I/user.data 864040064024194
+[2025-10-15 11:10:19.887][000000000.220] I/user.work time 当前时间 0
+[2025-10-15 11:10:20.345][000000001.220] I/user.work time 当前时间 1
+```

+ 282 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/crypto_app.lua

@@ -0,0 +1,282 @@
+--[[
+@module  crypto_app
+@summary crypto_app应用功能模块 
+@version 1.0
+@date    2025.10.15
+@author  沈园园
+@usage
+本文件为crypto_app应用功能模块,核心业务逻辑为:
+1、演示有关加解密的各种 API 的功能包含MD5,SHA,哈希(MD5,SHA),AES/DEC/3DES,CRC,checksum校验和;
+
+本文件没有对外接口,直接在main.lua中require "crypto_app"就可以加载运行;
+]]
+
+--加密运算主函数
+local function crypto_task_func()
+
+    -- MD5/哈希MD5,输出结果已经hex编码
+    log.info("md5", crypto.md5("abc"))
+    log.info("hmac_md5", crypto.hmac_md5("abc", "1234567890"))
+
+    -- SHA1/哈希SHA1,输出结果已经hex编码
+    log.info("sha1", crypto.sha1("abc"))
+    log.info("hmac_sha1", crypto.hmac_sha1("abc", "1234567890"))
+
+    -- SHA256/哈希SHA256,输出结果已经hex编码
+    log.info("sha256", crypto.sha256("abc"))
+    log.info("hmac_sha256", crypto.hmac_sha256("abc", "1234567890"))
+
+    -- SHA512/哈希SHA512,输出结果已经hex编码
+    log.info("sha512", crypto.sha512("abc"))
+    log.info("hmac_sha512", crypto.hmac_sha512("abc", "1234567890"))
+
+	-- AES加密, 未经Hex编码. AES-128-ECB 算法 对称加密,对齐方式:"ZERO"
+    local data_encrypt = crypto.cipher_encrypt("AES-128-ECB", "ZERO", "023001", "HZBIT@WLW/YSBKEY")
+	log.info("AES", "aes-128-ecb", data_encrypt:toHex())
+	local data_decrypt = crypto.cipher_decrypt("AES-128-ECB", "ZERO", data_encrypt, "HZBIT@WLW/YSBKEY")
+	log.info("AES", "aes-128-ecb", data_decrypt)
+
+    -- AES加密, 未经Hex编码. AES-128-ECB/CBC 算法 对称加密,对齐方式:"PKCS7"
+    local data_encrypt = crypto.cipher_encrypt("AES-128-ECB", "PKCS7", "12345678901234 > 123456", "1234567890123456")
+    local data2_encrypt = crypto.cipher_encrypt("AES-128-CBC", "PKCS7", "12345678901234 > 123456", "1234567890123456", "1234567890666666")
+    log.info("AES", "aes-128-ecb", data_encrypt:toHex())
+    log.info("AES", "aes-128-cbc", data2_encrypt:toHex())
+
+    -- AES解密, 未经Hex编码, AES-128-ECB/CBC 算法 对称解密,,对齐方式:"PKCS7"
+    local data_decrypt = crypto.cipher_decrypt("AES-128-ECB", "PKCS7", data_encrypt, "1234567890123456")
+    local data2_decrypt = crypto.cipher_decrypt("AES-128-CBC", "PKCS7", data2_encrypt, "1234567890123456", "1234567890666666")
+    log.info("AES", "aes-128-ecb", data_decrypt)
+    log.info("AES", "aes-128-cbc", data2_decrypt)
+    log.info("mem", rtos.meminfo("sys"))
+
+    -- DES-ECB 加解密
+    local data1 = crypto.cipher_encrypt("DES-ECB", "PKCS7", "abcdefg", "12345678")
+    if data1 then 
+        log.info("des", data1:toHex())
+        local data2 = crypto.cipher_decrypt("DES-ECB", "PKCS7", data1, "12345678")
+        log.info("des", data2)
+    else
+        log.info("des", "当前固件不支持DES/3DES")
+    end
+
+    -- 3DES-ECB 加解密
+    local data1 = crypto.cipher_encrypt("DES-EDE3-ECB", "PKCS7", "abcdefg!!--ZZSS", "123456781234567812345678")
+    if data1 then 
+        log.info("3des", data1:toHex())
+        local data2 = crypto.cipher_decrypt("DES-EDE3-ECB", "PKCS7", data1, "123456781234567812345678")
+        log.info("3des", data2)
+    else
+        log.info("3des", "当前固件不支持DES/3DES")
+    end
+  
+    -- 计算CRC16
+    local originStr = "123456sdfdsfdsfdsffdsfdsfsdfs1234"
+    local crc16 = crypto.crc16("MODBUS",originStr)
+    log.info("crc16", crc16)
+    
+    -- 计算CRC16 modbus
+    local crc16 = crypto.crc16_modbus("123456sdfdsfdsfdsffdsfdsfsdfs1234")
+    log.info("crc16", crc16)
+    crc16 = crypto.crc16_modbus("123456sdfdsfdsfdsffdsfdsfsdfs1234", 0xFFFF)
+    log.info("crc16", crc16)
+    
+    -- 计算CRC32
+    local data = "123456sdfdsfdsfdsffdsfdsfsdfs1234" 
+    local crc32 = crypto.crc32(data)
+    log.info("crc32", crc32) --21438764
+    -- start和poly可选, 是 2025.4.14 新增的参数
+    local crc32 = crypto.crc32(data, 0xFFFFFFFF, 0x04C11DB7, 0xFFFFFFFF) --等同于crypto.crc32(data)
+    log.info("crc32", crc32)
+
+    -- 计算CRC8
+    local data= "sdfdsfdsfdsffdsfdsfsdfs1234"
+    local crc8 = crypto.crc8(data)
+    log.info("crc8", crc8)
+    local crc8 = crypto.crc8(data, 0x31, 0xff, false)
+    log.info("crc8", crc8)  
+        
+    -- 计算CRC7
+    if crypto.crc7 then
+        local result = crypto.crc7("abc", 0xE5, 0x00)
+        log.info("crc7", result, string.format("%02X", result))  --50 32
+    else
+        log.info("crypto", "当前固件不支持crypto.crc7")
+    end
+    
+    log.info("随机数测试")
+    for i=1, 10 do
+        sys.wait(100)
+        log.info("crypto", "真随机数",string.unpack("I",crypto.trng(4)))
+        -- log.info("crypto", "伪随机数",math.random()) -- 输出的是浮点数,不推荐
+        -- log.info("crypto", "伪随机数",math.random(1, 65525)) -- 不推荐
+    end
+
+    -- totp的密钥
+    log.info("totp的密钥")
+    local secret = "VK54ZXPO74ISEM2E"
+    --写死时间戳用来测试
+    local ts = 1646796576
+    --生成十分钟的动态码验证下
+    for i=1,600,30 do
+        local r = crypto.totp(secret,ts+i)
+        local time = os.date("*t",ts+i + 8*3600)--东八区
+        log.info("totp", string.format("%06d" ,r),time.hour,time.min,time.sec)
+    end
+    
+    --将数据进行base64编码
+    -- 本函数与 string.toBase64 是同一个
+    local bdata = crypto.base64_encode("123")
+    log.info("base64", "encode", bdata)
+    
+    --将数据进行base64解码
+    -- 本函数与 string.fromBase64 是同一个
+    local data = crypto.base64_decode("MTIz")
+    log.info("base64", "decode", bdata, data)--123    
+
+    -- 打印所有支持的cipher
+    if crypto.cipher_list then
+        log.info("cipher", "list", json.encode(crypto.cipher_list()))
+    else
+        log.info("cipher", "当前固件不支持crypto.cipher_list")
+    end
+    -- 打印所有支持的cipher suites
+    if crypto.cipher_suites then
+        log.info("cipher", "suites", json.encode(crypto.cipher_suites()))
+    else
+        log.info("cipher", "当前固件不支持crypto.cipher_suites")
+    end
+    
+    -- 计算文件的hash值(md5/sha1/sha256及hmac形式)
+    log.info("文件hash值测试")
+    if crypto.md_file then
+        -- 无hmac的hash值
+        log.info("md5", crypto.md_file("MD5", "/luadb/logo.jpg"))
+        log.info("sha1", crypto.md_file("SHA1", "/luadb/logo.jpg"))
+        log.info("sha256", crypto.md_file("SHA256", "/luadb/logo.jpg"))
+        
+        -- 带hmac的hash值
+        log.info("hmac_md5", crypto.md_file("MD5", "/luadb/logo.jpg", "123456"))
+        log.info("hmac_sha1", crypto.md_file("SHA1", "/luadb/logo.jpg", "123456"))
+        log.info("hmac_sha256", crypto.md_file("SHA256", "/luadb/logo.jpg", "123456"))
+    else
+        log.info("文件hash值测试", "当前固件不支持crypto.md_file")
+    end
+    
+    --计算数据的hash值(md5/sha1/sha256及hmac形式)
+    if crypto.md then
+        -- 无hmac的hash值
+        log.info("md5", crypto.md("MD5", "1234567890"))
+        log.info("sha1", crypto.md("SHA1", "1234567890"))
+        log.info("sha256", crypto.md("SHA256", "1234567890"))
+        
+        -- 带hmac的hash值
+        log.info("hmac_md5", crypto.md("MD5", "1234567890", "123456"))
+        log.info("hmac_sha1", crypto.md("SHA1", "1234567890", "123456"))
+        log.info("hmac_sha256", crypto.md("SHA256", "1234567890", "123456"))
+    else
+        log.info("数据hash值测试", "当前固件不支持crypto.md")    
+    end
+    
+    -- 流式hash测试
+    log.info("流式hash测试")
+    if crypto.hash_init then
+        -- MD5
+        local md5_obj = crypto.hash_init("MD5")
+        crypto.hash_update(md5_obj, "1234567890")
+        crypto.hash_update(md5_obj, "1234567890")
+        crypto.hash_update(md5_obj, "1234567890")
+        crypto.hash_update(md5_obj, "1234567890")
+        local md5_result = crypto.hash_finish(md5_obj)
+        log.info("md5_stream", md5_result)
+        log.info("md5", crypto.md5("1234567890123456789012345678901234567890"))
+
+        -- HMAC_MD5
+        local hmac_md5_obj = crypto.hash_init("MD5", "1234567890")
+        crypto.hash_update(hmac_md5_obj, "1234567890")
+        crypto.hash_update(hmac_md5_obj, "1234567890")
+        crypto.hash_update(hmac_md5_obj, "1234567890")
+        crypto.hash_update(hmac_md5_obj, "1234567890")
+        local hmac_md5_result = crypto.hash_finish(hmac_md5_obj)
+        log.info("hmac_md5_stream", hmac_md5_result)
+        log.info("hmac_md5", crypto.hmac_md5("1234567890123456789012345678901234567890", "1234567890"))
+
+        -- SHA1
+        local sha1_obj = crypto.hash_init("SHA1")
+        crypto.hash_update(sha1_obj, "1234567890")
+        crypto.hash_update(sha1_obj, "1234567890")
+        crypto.hash_update(sha1_obj, "1234567890")
+        crypto.hash_update(sha1_obj, "1234567890")
+        local sha1_result = crypto.hash_finish(sha1_obj)
+        log.info("sha1_stream", sha1_result)
+        log.info("sha1", crypto.sha1("1234567890123456789012345678901234567890"))
+
+        -- HMAC_SHA1
+        local hmac_sha1_obj = crypto.hash_init("SHA1", "1234567890")
+        crypto.hash_update(hmac_sha1_obj, "1234567890")
+        crypto.hash_update(hmac_sha1_obj, "1234567890")
+        crypto.hash_update(hmac_sha1_obj, "1234567890")
+        crypto.hash_update(hmac_sha1_obj, "1234567890")
+        local hmac_sha1_result = crypto.hash_finish(hmac_sha1_obj)
+        log.info("hmac_sha1_stream", hmac_sha1_result)
+        log.info("hmac_sha1", crypto.hmac_sha1("1234567890123456789012345678901234567890", "1234567890"))
+
+        -- SHA256
+        local sha256_obj = crypto.hash_init("SHA256")
+        crypto.hash_update(sha256_obj, "1234567890")
+        crypto.hash_update(sha256_obj, "1234567890")
+        crypto.hash_update(sha256_obj, "1234567890")
+        crypto.hash_update(sha256_obj, "1234567890")
+        local sha256_result = crypto.hash_finish(sha256_obj)
+        log.info("sha256_stream", sha256_result)
+        log.info("sha256", crypto.sha256("1234567890123456789012345678901234567890"))
+
+        -- HMAC_SHA256
+        local hmac_sha256_obj = crypto.hash_init("SHA256", "1234567890")
+        crypto.hash_update(hmac_sha256_obj, "1234567890")
+        crypto.hash_update(hmac_sha256_obj, "1234567890")
+        crypto.hash_update(hmac_sha256_obj, "1234567890")
+        crypto.hash_update(hmac_sha256_obj, "1234567890")
+        local hmac_sha256_result = crypto.hash_finish(hmac_sha256_obj)
+        log.info("hmac_sha256_stream", hmac_sha256_result)
+        log.info("hmac_sha256", crypto.hmac_sha256("1234567890123456789012345678901234567890", "1234567890"))
+
+        -- SHA512
+        local sha512_obj = crypto.hash_init("SHA512")
+        if sha512_obj then
+            crypto.hash_update(sha512_obj, "1234567890")
+            crypto.hash_update(sha512_obj, "1234567890")
+            crypto.hash_update(sha512_obj, "1234567890")
+            crypto.hash_update(sha512_obj, "1234567890")
+            local sha512_result = crypto.hash_finish(sha512_obj)
+            log.info("sha512_stream", sha512_result)
+            log.info("sha512", crypto.sha512("1234567890123456789012345678901234567890"))
+        end
+
+        -- HMAC_SHA512
+        local hmac_sha512_obj = crypto.hash_init("SHA512", "1234567890")
+        if hmac_sha512_obj then
+            crypto.hash_update(hmac_sha512_obj, "1234567890")
+            crypto.hash_update(hmac_sha512_obj, "1234567890")
+            crypto.hash_update(hmac_sha512_obj, "1234567890")
+            crypto.hash_update(hmac_sha512_obj, "1234567890")
+            local hmac_sha512_result = crypto.hash_finish(hmac_sha512_obj)
+            log.info("hmac_sha512_stream", hmac_sha512_result)
+            log.info("hmac_sha512", crypto.hmac_sha512("1234567890123456789012345678901234567890", "1234567890"))
+        end
+    else
+        log.info("crypto", "当前固件不支持crypto.hash_init")
+    end
+   
+    if crypto.checksum then
+        log.info("checksum", "OK", string.char(crypto.checksum("OK")):toHex())
+        log.info("checksum", "357E", string.char(crypto.checksum("357E", 1)):toHex())
+    else
+        log.info("checksum", "当前固件不支持crypto.checksum")
+    end    
+
+    log.info("crypto", "ALL Done")
+    sys.wait(100000)
+end    
+
+--创建一个task,并且运行task的主函数crypto_task_func
+sys.taskInit(crypto_task_func)

+ 55 - 226
module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/main.lua

@@ -1,242 +1,71 @@
-
--- LuaTools需要PROJECT和VERSION这两个信息
-PROJECT = "cryptodemo"
-VERSION = "1.0.0"
-
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 1.0
+@date    2025.10.15
+@author  沈园园
+@usage
+本demo演示的核心功能为:
+演示有关加解密的各种 API 的功能包含MD5,SHA,哈希(MD5,SHA),AES/DEC/3DES,CRC,checksum校验和
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "luatos_crypto_app"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
 log.info("main", PROJECT, VERSION)
 
--- sys库是标配
-_G.sys = require("sys")
-
--- Air780E的AT固件默认会为开机键防抖, 导致部分用户刷机很麻烦
-if rtos.bsp() == "EC618" and pm and pm.PWK_MODE then
-    pm.power(pm.PWK_MODE, false)
-end
-
 
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
 if wdt then
-    --添加硬狗防止程序卡死,在支持的设备上启用这个功能
-    wdt.init(9000)--初始化watchdog设置为9s
-    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
 end
 
-sys.taskInit(function()
-
-    sys.wait(1000)
-
-    -- MD5,输出结果已经hex编码
-    log.info("md5", crypto.md5("abc"))
-    log.info("hmac_md5", crypto.hmac_md5("abc", "1234567890"))
-
-    -- SHA1,输出结果已经hex编码
-    log.info("sha1", crypto.sha1("abc"))
-    log.info("hmac_sha1", crypto.hmac_sha1("abc", "1234567890"))
-
-    -- SHA256,输出结果已经hex编码
-    log.info("sha256", crypto.sha256("abc"))
-    log.info("hmac_sha256", crypto.hmac_sha256("abc", "1234567890"))
-
-    -- SHA512,输出结果已经hex编码
-    log.info("sha512", crypto.sha512("abc"))
-    log.info("hmac_sha512", crypto.hmac_sha512("abc", "1234567890"))
-
-	local data_encrypt = crypto.cipher_encrypt("AES-128-ECB", "ZERO", "023001", "HZBIT@WLW/YSBKEY")
-	log.info("AES", "aes-128-ecb", data_encrypt:toHex())
-	local data_decrypt = crypto.cipher_decrypt("AES-128-ECB", "ZERO", data_encrypt, "HZBIT@WLW/YSBKEY")
-	log.info("AES", "aes-128-ecb", data_decrypt)
-
-    -- AES加密, 未经Hex编码. AES-128-ECB 算法
-    local data_encrypt = crypto.cipher_encrypt("AES-128-ECB", "PKCS7", "12345678901234 > 123456", "1234567890123456")
-    local data2_encrypt = crypto.cipher_encrypt("AES-128-CBC", "PKCS7", "12345678901234 > 123456", "1234567890123456", "1234567890666666")
-    log.info("AES", "aes-128-ecb", data_encrypt:toHex())
-    log.info("AES", "aes-128-cbc", data2_encrypt:toHex())
-
-    -- AES解密, 未经Hex编码
-    local data_decrypt = crypto.cipher_decrypt("AES-128-ECB", "PKCS7", data_encrypt, "1234567890123456")
-    local data2_decrypt = crypto.cipher_decrypt("AES-128-CBC", "PKCS7", data2_encrypt, "1234567890123456", "1234567890666666")
-    log.info("AES", "aes-128-ecb", data_decrypt)
-    log.info("AES", "aes-128-cbc", data2_decrypt)
-    log.info("mem", rtos.meminfo("sys"))
-
-    -- DES-ECB 加解密
-    local data1 = crypto.cipher_encrypt("DES-ECB", "PKCS7", "abcdefg", "12345678")
-    if data1 then -- DES-ECB 在某些平台不支持的
-        log.info("des", data1:toHex())
-        local data2 = crypto.cipher_decrypt("DES-ECB", "PKCS7", data1, "12345678")
-        log.info("des", data2)
-    else
-        log.info("des", "当前固件不支持DES/3DES")
-    end
-
-    -- 3DES-ECB 加解密
-    local data1 = crypto.cipher_encrypt("DES-EDE3-ECB", "PKCS7", "abcdefg!!--ZZSS", "123456781234567812345678")
-    if data1 then -- DES-ECB 在某些平台不支持的
-        log.info("3des", data1:toHex())
-        local data2 = crypto.cipher_decrypt("DES-EDE3-ECB", "PKCS7", data1, "123456781234567812345678")
-        log.info("3des", data2)
-    else
-        log.info("3des", "当前固件不支持DES/3DES")
-    end
-
-
-    -- 打印所有支持的cipher
-    if crypto.cipher_list then
-        log.info("cipher", "list", json.encode(crypto.cipher_list()))
-    else
-        log.info("cipher", "当前固件不支持crypto.cipher_list")
-    end
-    -- 打印所有支持的cipher suites
-    if crypto.cipher_suites then
-        log.info("cipher", "suites", json.encode(crypto.cipher_suites()))
-    else
-        log.info("cipher", "当前固件不支持crypto.cipher_suites")
-    end
-
-    ---------------------------------------
-    log.info("随机数测试")
-    for i=1, 10 do
-        sys.wait(100)
-        log.info("crypto", "真随机数",string.unpack("I",crypto.trng(4)))
-        -- log.info("crypto", "伪随机数",math.random()) -- 输出的是浮点数,不推荐
-        -- log.info("crypto", "伪随机数",math.random(1, 65525)) -- 不推荐
-    end
-
-    -- totp的密钥
-    log.info("totp的密钥")
-    local secret = "VK54ZXPO74ISEM2E"
-    --写死时间戳用来测试
-    local ts = 1646796576
-    --生成十分钟的动态码验证下
-    for i=1,600,30 do
-        local r = crypto.totp(secret,ts+i)
-        local time = os.date("*t",ts+i + 8*3600)--东八区
-        log.info("totp", string.format("%06d" ,r),time.hour,time.min,time.sec)
-    end
-
-    -- 文件测试
-    log.info("文件hash值测试")
-    if crypto.md_file then
-        log.info("md5", crypto.md_file("MD5", "/luadb/logo.jpg"))
-        log.info("sha1", crypto.md_file("SHA1", "/luadb/logo.jpg"))
-        log.info("sha256", crypto.md_file("SHA256", "/luadb/logo.jpg"))
-        
-        log.info("hmac_md5", crypto.md_file("MD5", "/luadb/logo.jpg", "123456"))
-        log.info("hmac_sha1", crypto.md_file("SHA1", "/luadb/logo.jpg", "123456"))
-        log.info("hmac_sha256", crypto.md_file("SHA256", "/luadb/logo.jpg", "123456"))
-    else
-        log.info("文件hash值测试", "当前固件不支持crypto.md_file")
-    end
-
-    if crypto.checksum then
-        log.info("checksum", "OK", string.char(crypto.checksum("OK")):toHex())
-        log.info("checksum", "357E", string.char(crypto.checksum("357E", 1)):toHex())
-    else
-        log.info("checksum", "当前固件不支持crypto.checksum")
-    end
-
-    -- 流式hash测试
-    log.info("流式hash测试")
-    if crypto.hash_init then
-        -- MD5
-        local md5_obj = crypto.hash_init("MD5")
-        crypto.hash_update(md5_obj, "1234567890")
-        crypto.hash_update(md5_obj, "1234567890")
-        crypto.hash_update(md5_obj, "1234567890")
-        crypto.hash_update(md5_obj, "1234567890")
-        local md5_result = crypto.hash_finish(md5_obj)
-        log.info("md5_stream", md5_result)
-        log.info("md5", crypto.md5("1234567890123456789012345678901234567890"))
-
-        -- HMAC_MD5
-        local hmac_md5_obj = crypto.hash_init("MD5", "1234567890")
-        crypto.hash_update(hmac_md5_obj, "1234567890")
-        crypto.hash_update(hmac_md5_obj, "1234567890")
-        crypto.hash_update(hmac_md5_obj, "1234567890")
-        crypto.hash_update(hmac_md5_obj, "1234567890")
-        local hmac_md5_result = crypto.hash_finish(hmac_md5_obj)
-        log.info("hmac_md5_stream", hmac_md5_result)
-        log.info("hmac_md5", crypto.hmac_md5("1234567890123456789012345678901234567890", "1234567890"))
-
-        -- SHA1
-        local sha1_obj = crypto.hash_init("SHA1")
-        crypto.hash_update(sha1_obj, "1234567890")
-        crypto.hash_update(sha1_obj, "1234567890")
-        crypto.hash_update(sha1_obj, "1234567890")
-        crypto.hash_update(sha1_obj, "1234567890")
-        local sha1_result = crypto.hash_finish(sha1_obj)
-        log.info("sha1_stream", sha1_result)
-        log.info("sha1", crypto.sha1("1234567890123456789012345678901234567890"))
-
-        -- HMAC_SHA1
-        local hmac_sha1_obj = crypto.hash_init("SHA1", "1234567890")
-        crypto.hash_update(hmac_sha1_obj, "1234567890")
-        crypto.hash_update(hmac_sha1_obj, "1234567890")
-        crypto.hash_update(hmac_sha1_obj, "1234567890")
-        crypto.hash_update(hmac_sha1_obj, "1234567890")
-        local hmac_sha1_result = crypto.hash_finish(hmac_sha1_obj)
-        log.info("hmac_sha1_stream", hmac_sha1_result)
-        log.info("hmac_sha1", crypto.hmac_sha1("1234567890123456789012345678901234567890", "1234567890"))
 
-        -- SHA256
-        local sha256_obj = crypto.hash_init("SHA256")
-        crypto.hash_update(sha256_obj, "1234567890")
-        crypto.hash_update(sha256_obj, "1234567890")
-        crypto.hash_update(sha256_obj, "1234567890")
-        crypto.hash_update(sha256_obj, "1234567890")
-        local sha256_result = crypto.hash_finish(sha256_obj)
-        log.info("sha256_stream", sha256_result)
-        log.info("sha256", crypto.sha256("1234567890123456789012345678901234567890"))
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
 
-        -- HMAC_SHA256
-        local hmac_sha256_obj = crypto.hash_init("SHA256", "1234567890")
-        crypto.hash_update(hmac_sha256_obj, "1234567890")
-        crypto.hash_update(hmac_sha256_obj, "1234567890")
-        crypto.hash_update(hmac_sha256_obj, "1234567890")
-        crypto.hash_update(hmac_sha256_obj, "1234567890")
-        local hmac_sha256_result = crypto.hash_finish(hmac_sha256_obj)
-        log.info("hmac_sha256_stream", hmac_sha256_result)
-        log.info("hmac_sha256", crypto.hmac_sha256("1234567890123456789012345678901234567890", "1234567890"))
 
-        -- SHA512
-        local sha512_obj = crypto.hash_init("SHA512")
-        if sha512_obj then
-            crypto.hash_update(sha512_obj, "1234567890")
-            crypto.hash_update(sha512_obj, "1234567890")
-            crypto.hash_update(sha512_obj, "1234567890")
-            crypto.hash_update(sha512_obj, "1234567890")
-            local sha512_result = crypto.hash_finish(sha512_obj)
-            log.info("sha512_stream", sha512_result)
-            log.info("sha512", crypto.sha512("1234567890123456789012345678901234567890"))
-        end
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
 
-        -- HMAC_SHA512
-        local hmac_sha512_obj = crypto.hash_init("SHA512", "1234567890")
-        if hmac_sha512_obj then
-            crypto.hash_update(hmac_sha512_obj, "1234567890")
-            crypto.hash_update(hmac_sha512_obj, "1234567890")
-            crypto.hash_update(hmac_sha512_obj, "1234567890")
-            crypto.hash_update(hmac_sha512_obj, "1234567890")
-            local hmac_sha512_result = crypto.hash_finish(hmac_sha512_obj)
-            log.info("hmac_sha512_stream", hmac_sha512_result)
-            log.info("hmac_sha512", crypto.hmac_sha512("1234567890123456789012345678901234567890", "1234567890"))
-        end
-    else
-        log.info("crypto", "当前固件不支持crypto.hash_init")
-    end
 
-    log.info("crc7测试")
-    if crypto.crc7 then
-        local result = crypto.crc7(string.char(0xAA), 0xE5, 0x00)
-        log.info("crc7测试", result, string.format("%02X", result))
-    else
-        log.info("crypto", "当前固件不支持crypto.crc7")
-    end
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
 
-    log.info("crypto", "ALL Done")
-    sys.wait(100000)
-end)
+-- 加载crypto_app应用功能模块
+require "crypto_app"
 
 -- 用户代码已结束---------------------------------------------
 -- 结尾总是这一句
 sys.run()
--- sys.run()之后后面不要加任何语句!!!!!
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 152 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/crypto/readme.md

@@ -0,0 +1,152 @@
+## 功能模块介绍
+
+1、main.lua:主程序入口;
+
+2、crypto_app.lua:演示有关加解密的各种 API 的功能包含MD5,SHA,哈希(MD5,SHA),AES/DEC/3DES,CRC,checksum校验和;
+
+3、logo.jpg:待加密文件
+
+## 演示功能概述
+
+1、创建一个task;
+
+2、演示有关加解密的各种 API 的功能;
+
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/air780ehv/luatos/common/hwenv/image/Air780EHV2.png)
+
+1、Air780EHM/Air780EHV/Air780EGH核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、Air780EHM/Air780EHV/Air780EGH核心板和数据线的硬件接线方式为
+
+- Air780EHM/Air780EHV/Air780EGH核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
+- 核心板正面的 ON/OFF 拨动开关 拨到ON一端;
+
+
+## 演示软件环境
+
+1、[Luatools下载调试工具](https://docs.openluat.com/air780ehv/luatos/common/download/)
+
+2、[Air780EHM 最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+3、[Air780EHV 最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+4、[Air780EGH 最新版本的内核固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、Luatools烧录内核固件和demo脚本代码
+
+3、烧录成功后,自动开机运行
+
+4、出现类似于下面的日志,就表示运行成功:
+
+``` lua
+[2025-10-16 15:00:32.204][000000000.203] I/user.main luatos_crypto_app 001.000.000
+[2025-10-16 15:00:32.794][000000001.212] I/user.md5 900150983CD24FB0D6963F7D28E17F72
+[2025-10-16 15:00:32.794][000000001.213] I/user.hmac_md5 416478FC0ACE1C4AB37F85F4F86A16B1
+[2025-10-16 15:00:32.794][000000001.213] I/user.sha1 A9993E364706816ABA3E25717850C26C9CD0D89D
+[2025-10-16 15:00:32.805][000000001.214] I/user.hmac_sha1 DAE54822C0DAF6C115C97B0AD62C7BCBE9D5E6FC
+[2025-10-16 15:00:32.810][000000001.214] I/user.sha256 BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD
+[2025-10-16 15:00:32.814][000000001.215] I/user.hmac_sha256 86055184805B4A466A7BE398FF4A7159F9055EA7EEF339FC94DCEC6F165898BA
+[2025-10-16 15:00:32.814][000000001.216] I/user.sha512 DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA20A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F
+[2025-10-16 15:00:32.826][000000001.217] I/user.hmac_sha512 0F92B9AC88949E0BF7C9F1E6F9901BAB8EDFDC9E561DFDE428BC4339961A0569AD01B44343AA56E439949655D15C4D28492D459E75015489920243F3C9986F2A
+[2025-10-16 15:00:32.834][000000001.217] D/crypto zero padding
+[2025-10-16 15:00:32.842][000000001.218] I/user.AES aes-128-ecb 013EEA6EBACCBD7AD990FAEF75FB99C4 32
+[2025-10-16 15:00:32.844][000000001.218] I/user.AES aes-128-ecb 023001
+[2025-10-16 15:00:32.844][000000001.219] I/user.AES aes-128-ecb A37DE67837A1A3006E47A7BC25AA0ECC030B4E058E1972FE5B257FD8C3436142 64
+[2025-10-16 15:00:32.858][000000001.219] I/user.AES aes-128-cbc 26D98EA512AE92BC487536B83F2BE99B467649A9700338F4B4FF75AA2654DD2C 64
+[2025-10-16 15:00:32.864][000000001.220] I/user.AES aes-128-ecb 12345678901234 > 123456
+[2025-10-16 15:00:32.864][000000001.220] I/user.AES aes-128-cbc 12345678901234 > 123456
+[2025-10-16 15:00:32.874][000000001.220] I/user.mem 2376584 49564 58880
+[2025-10-16 15:00:32.884][000000001.221] I/user.des 486CB8B81CACCDB7 16
+[2025-10-16 15:00:32.889][000000001.221] I/user.des abcdefg
+[2025-10-16 15:00:32.894][000000001.222] I/user.3des 14619F067B425995D0CD975B85491D98 32
+[2025-10-16 15:00:32.894][000000001.223] I/user.3des abcdefg!!--ZZSS
+[2025-10-16 15:00:32.894][000000001.223] I/user.crc16 54188
+[2025-10-16 15:00:32.905][000000001.224] I/user.crc16 54188
+[2025-10-16 15:00:32.905][000000001.224] I/user.crc16 54188
+[2025-10-16 15:00:32.914][000000001.224] I/user.crc32 21438764
+[2025-10-16 15:00:32.914][000000001.225] I/user.crc32 21438764
+[2025-10-16 15:00:32.924][000000001.225] I/user.crc8 197
+[2025-10-16 15:00:32.924][000000001.225] I/user.crc8 243
+[2025-10-16 15:00:32.937][000000001.226] I/user.crc7 50 32
+[2025-10-16 15:00:32.937][000000001.226] I/user.随机数测试
+[2025-10-16 15:00:32.944][000000001.334] I/user.crypto 真随机数 -939521674 5
+[2025-10-16 15:00:33.016][000000001.434] I/user.crypto 真随机数 -1872085296 5
+[2025-10-16 15:00:33.111][000000001.534] I/user.crypto 真随机数 517455164 5
+[2025-10-16 15:00:33.223][000000001.634] I/user.crypto 真随机数 657132096 5
+[2025-10-16 15:00:33.311][000000001.734] I/user.crypto 真随机数 -1080191914 5
+[2025-10-16 15:00:33.421][000000001.834] I/user.crypto 真随机数 -833578535 5
+[2025-10-16 15:00:33.532][000000001.942] I/user.crypto 真随机数 420499958 5
+[2025-10-16 15:00:33.626][000000002.042] I/user.crypto 真随机数 1233025030 5
+[2025-10-16 15:00:33.720][000000002.142] I/user.crypto 真随机数 2138275442 5
+[2025-10-16 15:00:33.830][000000002.242] I/user.crypto 真随机数 182791818 5
+[2025-10-16 15:00:33.830][000000002.242] I/user.totp的密钥
+[2025-10-16 15:00:33.830][000000002.243] I/user.totp 522113 19 29 37
+[2025-10-16 15:00:33.830][000000002.244] I/user.totp 964300 19 30 7
+[2025-10-16 15:00:33.830][000000002.245] I/user.totp 987714 19 30 37
+[2025-10-16 15:00:33.830][000000002.246] I/user.totp 037499 19 31 7
+[2025-10-16 15:00:33.830][000000002.246] I/user.totp 699697 19 31 37
+[2025-10-16 15:00:33.830][000000002.247] I/user.totp 548191 19 32 7
+[2025-10-16 15:00:33.845][000000002.248] I/user.totp 747517 19 32 37
+[2025-10-16 15:00:33.845][000000002.249] I/user.totp 243319 19 33 7
+[2025-10-16 15:00:33.861][000000002.249] I/user.totp 147474 19 33 37
+[2025-10-16 15:00:33.877][000000002.250] I/user.totp 039992 19 34 7
+[2025-10-16 15:00:33.877][000000002.251] I/user.totp 628512 19 34 37
+[2025-10-16 15:00:33.877][000000002.252] I/user.totp 529018 19 35 7
+[2025-10-16 15:00:33.892][000000002.253] I/user.totp 994006 19 35 37
+[2025-10-16 15:00:33.892][000000002.253] I/user.totp 851359 19 36 7
+[2025-10-16 15:00:33.907][000000002.254] I/user.totp 943237 19 36 37
+[2025-10-16 15:00:33.908][000000002.255] I/user.totp 410702 19 37 7
+[2025-10-16 15:00:33.908][000000002.256] I/user.totp 082993 19 37 37
+[2025-10-16 15:00:33.908][000000002.256] I/user.totp 193281 19 38 7
+[2025-10-16 15:00:33.908][000000002.257] I/user.totp 781573 19 38 37
+[2025-10-16 15:00:33.924][000000002.258] I/user.totp 243288 19 39 7
+[2025-10-16 15:00:33.924][000000002.258] I/user.base64 encode MTIz
+[2025-10-16 15:00:33.924][000000002.259] I/user.base64 decode MTIz 123
+[2025-10-16 15:00:33.924][000000002.260] I/user.cipher list ["AES-128-ECB","AES-192-ECB","AES-256-ECB","AES-128-CBC","AES-192-CBC","AES-256-CBC","AES-128-CTR","AES-192-CTR","AES-256-CTR","AES-128-GCM","AES-192-GCM","AES-256-GCM","AES-128-CCM","AES-192-CCM","AES-256-CCM","DES-ECB","DES-EDE-ECB","DES-EDE3-ECB","DES-CBC","DES-EDE-CBC","DES-EDE3-CBC"]
+[2025-10-16 15:00:33.939][000000002.266] I/user.cipher suites 
+[2025-10-16 15:00:33.939][000000002.266] ["TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384","TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384","TLS-ECDHE-ECDSA-WITH-AES-256-CCM","TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384","TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384","TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA","TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA","TLS-ECDHE-ECDSA-WITH-AES-256-CCM-8","TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256","TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256","TLS-ECDHE-ECDSA-WITH-AES-128-CCM","TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256","TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256","TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA","TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA","TLS-ECDHE-ECDSA-WITH-AES-128-CCM-8","TLS-RSA-WITH-AES-256-GCM-SHA384","TLS-RSA-WITH-AES-256-CCM","TLS-RSA-WITH-AES-256-CBC-SHA256","TLS-RSA-WITH-AES-256-CBC-SHA","TLS-RSA-WITH-AES-256-CCM-8","TLS-RSA-WITH-AES-128-GCM-SHA256","TLS-RSA-WITH-AES-128-CCM","TLS-RSA-WITH-AES-128-CBC-SHA256","TLS-RSA-WITH-AES-128-CBC-SHA","TLS-RSA-WITH-AES-128-CCM-8","TLS-RSA-PSK-WITH-AES-256-GCM-SHA384","TLS-RSA-PSK-WITH-AES-256-CBC-SHA384","TLS-RSA-PSK-WITH-AES-256-CBC-SHA","TLS-RSA-PSK-WITH-AES-128-GCM-SHA256","TLS-RSA-PSK-WITH-AES-128-CBC-SHA256","TLS-RSA-PSK-WITH-AES-128-CBC-SHA","TLS-PSK-WITH-AES-256-GCM-SHA384","TLS-PSK-WITH-AES-256-CCM","TLS-PSK-WITH-AES-256-CBC-SHA384","TLS-PSK-WITH-AES-256-CBC-SHA","TLS-PSK-WITH-AES-256-CCM-8","TLS-PSK-WITH-AES-128-GCM-SHA256","TLS-PSK-WITH-AES-128-CCM","TLS-PSK-WITH-AES-128-CBC-SHA256","TLS-PSK-WITH-AES-128-CBC-SHA","TLS-PSK-WITH-AES-128-CCM-8"]
+[2025-10-16 15:00:33.955][000000002.266] I/user.文件hash值测试
+[2025-10-16 15:00:33.955][000000002.269] I/user.md5 D364D04CCD734D2757B25F3216CC431B
+[2025-10-16 15:00:33.955][000000002.273] I/user.sha1 04DA64874D415B1FB9CDD2E89927397D8F48C441
+[2025-10-16 15:00:33.955][000000002.284] I/user.sha256 0C601722B4BD2BC1A76BC3701F4EB646F5119C31702852978C326CD8D7C9212C
+[2025-10-16 15:00:33.955][000000002.287] I/user.hmac_md5 B5F5A687DB904DB67D3311899932DD61
+[2025-10-16 15:00:33.955][000000002.290] I/user.hmac_sha1 2A0B55C290D54ADF2F185EAE460254D267C7CE1F
+[2025-10-16 15:00:33.971][000000002.301] I/user.hmac_sha256 6E443C84D4D7A4721A9A195EB9038EDCFA8A8F60A3F1E8A0090B57B3BAB4942E
+[2025-10-16 15:00:33.971][000000002.301] I/user.md5
+[2025-10-16 15:00:33.971][000000002.302] I/user.sha1
+[2025-10-16 15:00:33.971][000000002.302] I/user.sha256
+[2025-10-16 15:00:33.971][000000002.303] I/user.hmac_md5
+[2025-10-16 15:00:33.971][000000002.303] I/user.hmac_sha1
+[2025-10-16 15:00:33.971][000000002.304] I/user.hmac_sha256
+[2025-10-16 15:00:33.971][000000002.304] I/user.流式hash测试
+[2025-10-16 15:00:33.986][000000002.304] I/user.md5_stream F5BF3E984432AE6F9F98840951E5CEF3
+[2025-10-16 15:00:33.986][000000002.305] I/user.md5 F5BF3E984432AE6F9F98840951E5CEF3
+[2025-10-16 15:00:33.986][000000002.306] I/user.hmac_md5_stream 45527D9407615C3A44F475BB7172752A
+[2025-10-16 15:00:33.986][000000002.306] I/user.hmac_md5 45527D9407615C3A44F475BB7172752A
+[2025-10-16 15:00:33.986][000000002.307] I/user.sha1_stream C61A2C245CB07A04482CE5B662AE67DBDBE010DB
+[2025-10-16 15:00:33.986][000000002.307] I/user.sha1 C61A2C245CB07A04482CE5B662AE67DBDBE010DB
+[2025-10-16 15:00:33.986][000000002.308] I/user.hmac_sha1_stream 88471065B8C5F64057418A0A58353A46E7841DE7
+[2025-10-16 15:00:33.986][000000002.308] I/user.hmac_sha1 88471065B8C5F64057418A0A58353A46E7841DE7
+[2025-10-16 15:00:34.002][000000002.309] I/user.sha256_stream A4EBDD541454B84CC670C9F1F5508BAF67FFD3FE59B883267808781F992A0B1D
+[2025-10-16 15:00:34.002][000000002.309] I/user.sha256 A4EBDD541454B84CC670C9F1F5508BAF67FFD3FE59B883267808781F992A0B1D
+[2025-10-16 15:00:34.002][000000002.310] I/user.hmac_sha256_stream EAF715932F064E462893B7FE04442E2C25ECF2F7C560820A648D0D94BEAEB581
+[2025-10-16 15:00:34.007][000000002.311] I/user.hmac_sha256 EAF715932F064E462893B7FE04442E2C25ECF2F7C560820A648D0D94BEAEB581
+[2025-10-16 15:00:34.007][000000002.311] I/user.sha512_stream 3A8529D8F0C7B1AD2FA54C944952829B718D5BEB4FF9BA8F4A849E02FE9A272DAF59AE3BD06DDE6F01DF863D87C8BA4AB016AC576B59A19078C26D8DBE63F79E
+[2025-10-16 15:00:34.007][000000002.312] I/user.sha512 3A8529D8F0C7B1AD2FA54C944952829B718D5BEB4FF9BA8F4A849E02FE9A272DAF59AE3BD06DDE6F01DF863D87C8BA4AB016AC576B59A19078C26D8DBE63F79E
+[2025-10-16 15:00:34.007][000000002.313] I/user.hmac_sha512_stream 18F74340A0048090521796F52A66A45B77AF5981512951B938BFCB026F2D6ED7945B0682731E4E7E3CA72021E5E8AB9810769E48C83F440DC73D6C942215E29E
+[2025-10-16 15:00:34.018][000000002.314] I/user.hmac_sha512 18F74340A0048090521796F52A66A45B77AF5981512951B938BFCB026F2D6ED7945B0682731E4E7E3CA72021E5E8AB9810769E48C83F440DC73D6C942215E29E
+[2025-10-16 15:00:34.018][000000002.315] I/user.checksum OK 04 2
+[2025-10-16 15:00:34.018][000000002.315] I/user.checksum 357E E4 2
+[2025-10-16 15:00:34.018][000000002.315] I/user.crypto ALL Done
+
+```

+ 2 - 2
module/Air780EHM_Air780EHV_Air780EGH/demo/fota2/iot_server/air_srv_fota.lua

@@ -43,7 +43,7 @@ local fota_running = false
 --   2表示url错误
 --   3表示服务器断开
 --   4表示接收报文错误
---   5表示使用iot平台VERSION需要使用 xxx.yyy.zzz形式
+--   5缺少必要的PROJECT_KEY参数
 local function fota_cb(ret)
     log.info("fota", ret)
     -- fota结束,无论成功还是失败,都释放fota_running标志
@@ -65,7 +65,7 @@ local function fota_cb(ret)
             "2) 服务器返回 4xx/5xx 等异常状态码 —— 请确认升级包已上传、URL 正确、鉴权信息有效;\n" ..
             "3) 已经是最新版本,无需升级")
     elseif ret == 5 then
-        log.info("版本号书写错误", "iot平台版本号需要使用xxx.yyy.zzz形式")
+        log.info("缺少必要的PROJECT_KEY参数")
     else
         log.info("不是上面几种情况 ret为", ret)
     end

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.