From 524add7ed66bd6d19bb07efc9bb24e745de880a8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E5=AD=99=E6=98=A5=E9=98=B3?= Date: Mon, 1 Dec 2025 09:46:20 +0800 Subject: [PATCH] =?utf8?q?spi=E4=BC=A0=E8=BE=93=E6=96=87=E4=BB=B6=E6=B5=8B?= =?utf8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- cat1modem/src/LeiNiao/httpfiledownload.py | 297 +++++++++++- cat1modem/src/LeiNiao/main_init.py | 10 +- .../config/target_config/brandy/config.py | 3 +- code/sdk/zhihui/utils/custom_utils_at_cmd.c | 71 +++ code/sdk/zhihui/utilscpp/custom_utilscpp.cpp | 421 +++++++++++++++++- code/sdk/zhihui/utilscpp/custom_utilscpp.h | 5 + 6 files changed, 784 insertions(+), 23 deletions(-) diff --git a/cat1modem/src/LeiNiao/httpfiledownload.py b/cat1modem/src/LeiNiao/httpfiledownload.py index 67a52d6a0..b5bc29f0a 100644 --- a/cat1modem/src/LeiNiao/httpfiledownload.py +++ b/cat1modem/src/LeiNiao/httpfiledownload.py @@ -24,6 +24,36 @@ import urandom from usr.EventMesh import eventMesh import usocket import _thread +from machine import SPI + +# spi - 通道选择[0,1],int类型。 + +# mode - SPI 的工作模式,int类型,说明如下: +# 时钟极性CPOL:即SPI空闲时,时钟信号SCLK的电平(0:空闲时低电平; 1:空闲时高电平) +# 0 : CPOL=0, CPHA=0 +# 1 : CPOL=0, CPHA=1 +# 2 : CPOL=1, CPHA=0 +# 3 : CPOL=1, CPHA=1 + +# clk - 时钟频率,说明如下: +# 0 : 100kHz +# 1 : 200kHz +# 2 : 300MHz +# 3 : 400MHz +# 4 : 500MHz +# 5 : 600MHz +# 6 : 700MHz +# 7 : 812.5kHz +# 8 : 1.625MHz +# 9 : 3.25MHz +# 10 : 6.5MHz +# 11 : 13MHz +# 12 : 26MHz +# 13 : 52MHz + +# 52MHz时钟频率对硬件要求较高。 +# spi_obj = SPI(1, 0, 1) + REQUESTS_AVAILABLE = True @@ -221,10 +251,10 @@ class HttpFileDownloader: class HttpFileDownloadTcpServer: """ HTTP 文件下载 TCP 服务器 - 监听 TCP 连接,接收 URL 请求,下载文件并通过 socket 发送数据 + 监听 TCP 连接,接收 URL 请求,下载文件并通过 SPI 发送数据 """ - def __init__(self, host='172.16.1.2', port=5002, timeout=30, sizeof=255): + def __init__(self, host='172.16.1.2', port=5002, timeout=30, sizeof=255, spi_obj=None): """ 初始化 TCP 服务器 @@ -233,6 +263,7 @@ class HttpFileDownloadTcpServer: port: 监听端口,默认 5002 timeout: 下载超时时间(秒),默认 30 秒 sizeof: 读取缓冲区的数据块大小,默认 255 字节 + spi_obj: SPI 对象,用于发送数据。如果为 None,则自动创建 SPI(1, 0, 1) """ self.host = host self.port = port @@ -241,6 +272,15 @@ class HttpFileDownloadTcpServer: self.sock = None self.running = False self.downloader = HttpFileDownloader(timeout=timeout, sizeof=sizeof) + + # 初始化 SPI 对象 + if spi_obj is None: + # 默认创建 SPI(1, 0, 1): 通道1, 模式0, 时钟频率1(1.625MHz) + self.spi_obj = SPI(2, 0, 9) + clog.info("Created default SPI object: SPI(2, 0, 9)") + else: + self.spi_obj = spi_obj + clog.info("Using provided SPI object") def __socket_init(self): """初始化 TCP socket""" @@ -294,10 +334,10 @@ class HttpFileDownloadTcpServer: clog.info("Received download request for URL: {}".format(url)) - # 发送确认消息 - conn.send(b'OK: Starting download...\n') + # 发送确认消息(通过 socket) + conn.send(b'OK: Starting download via SPI...\n') - # 下载文件并通过 socket 发送 + # 下载文件并通过 SPI 发送(数据通过 SPI,状态信息通过 socket) success, download_time, file_size = self.__download_and_send(conn, url) if success: @@ -316,12 +356,33 @@ class HttpFileDownloadTcpServer: pass clog.info("Client connection closed: {}:{}".format(addr[0], addr[1])) + def close(self): + """ + 关闭SPI对象,释放资源 + """ + if self.spi_obj is not None: + try: + # 尝试调用close方法 + if hasattr(self.spi_obj, 'close'): + self.spi_obj.close() + clog.info("SPI object closed via close() method") + # 尝试调用deinit方法(MicroPython常用) + elif hasattr(self.spi_obj, 'deinit'): + self.spi_obj.deinit() + clog.info("SPI object closed via deinit() method") + else: + clog.warning("SPI object does not have close() or deinit() method") + except Exception as e: + clog.error("Error closing SPI object: {}".format(e)) + finally: + self.spi_obj = None + def __download_and_send(self, conn, url): """ - 下载文件并通过 socket 发送数据 + 下载文件并通过 SPI 发送数据 Args: - conn: 客户端连接对象 + conn: 客户端连接对象(用于接收 URL 请求,数据通过 SPI 发送) url: 要下载的文件 URL Returns: @@ -351,19 +412,41 @@ class HttpFileDownloadTcpServer: except (ValueError, TypeError): total_size = 0 - # 发送文件大小信息(可选) + # 通过 socket 发送文件大小信息(可选,用于客户端确认) if total_size > 0: size_info = "Content-Length: {}\n".format(total_size).encode('utf-8') - conn.send(size_info) + try: + conn.send(size_info) + except: + pass # 如果 socket 发送失败,继续通过 SPI 发送数据 - # 下载数据并通过 socket 发送 + # 下载数据并通过 SPI 发送 downloaded_size = 0 # response.content 是 bytes 类型生成器 for chunk in response.content: if chunk: # 过滤掉空块 try: - conn.send(chunk) + # 通过 SPI 发送数据块 + # SPI API: write(data, datasize) 或 write_read(read_buf, write_data, datasize, timeout) + chunk_len = len(chunk) + if hasattr(self.spi_obj, 'write'): + # write(data, datasize) - datasize 参数必需 + self.spi_obj.write(chunk, chunk_len) + elif hasattr(self.spi_obj, 'write_read'): + # write_read(read_buf, write_data, datasize, timeout) + # 或者 write_read(read_buf, write_data, datasize) + read_buf = bytearray(chunk_len) + try: + # 尝试带timeout的调用 + self.spi_obj.write_read(read_buf, chunk, chunk_len, 5) + except TypeError: + # 尝试不带timeout的调用 + self.spi_obj.write_read(read_buf, chunk, chunk_len) + else: + clog.error("SPI object does not have write or write_read method") + return False, 0.0, -1 + downloaded_size += len(chunk) # 显示进度(每发送一定数据量后打印一次) @@ -374,20 +457,23 @@ class HttpFileDownloadTcpServer: clog.info("Progress: {:.2f}% ({}/{}) bytes".format( progress, downloaded_size, total_size)) except Exception as e: - clog.error("Error sending data: {}".format(e)) + clog.error("Error sending data via SPI: {}".format(e)) return False, 0.0, -1 # 记录结束时间并计算耗时 end_time = utime.time() download_time = end_time - start_time - clog.info("Download and send completed: {} bytes in {:.3f}s".format( + clog.info("Download and send via SPI completed: {} bytes in {:.3f}s".format( downloaded_size, download_time)) + # 注意:TCP服务器模式下,SPI对象在服务器运行期间保持打开,不在这里关闭 + # 只有在服务器停止时才关闭SPI + return True, download_time, downloaded_size except Exception as e: - clog.error("Error downloading and sending file: {}".format(e)) + clog.error("Error downloading and sending file via SPI: {}".format(e)) return False, 0.0, -1 def __server_loop(self): @@ -435,6 +521,8 @@ class HttpFileDownloadTcpServer: except: pass self.sock = None + # 关闭SPI对象 + self.close() clog.info("HTTP file download TCP server stopped") def is_running(self): @@ -442,9 +530,186 @@ class HttpFileDownloadTcpServer: return self.running -# 保持原有功能的简单函数(向后兼容) +# ==================== SPI传输速度测试模式 ==================== +# 直接从固定URL下载文件并通过SPI发送,用于测试SPI传输速度 +# 不再通过TCP接收URL指令 + +# 固定下载地址(与MCU侧AT指令AT^HTTPDLCLIENT使用的地址一致) +DEFAULT_TEST_URL = "https://file-virtual-person-qcl.test.leiniao.com/ambybox/Emotions/StraightEyes/Amber/WorkAndStudy.mp4" + +class HttpFileDownloadSpiTest: + """ + HTTP文件下载SPI传输速度测试 + 直接从固定URL下载文件并通过SPI发送,用于测试SPI传输速度 + """ + + def __init__(self, url=None, timeout=30, sizeof=255, spi_obj=None): + """ + 初始化SPI传输测试 + + Args: + url: 下载URL,默认使用DEFAULT_TEST_URL + timeout: 下载超时时间(秒),默认30秒 + sizeof: 读取缓冲区的数据块大小,默认255字节 + spi_obj: SPI对象,用于发送数据。如果为None,则自动创建SPI(1, 0, 12) + """ + self.url = url if url else DEFAULT_TEST_URL + self.timeout = timeout + self.sizeof = sizeof + self.downloader = HttpFileDownloader(timeout=timeout, sizeof=sizeof) + + # 初始化SPI对象 + if spi_obj is None: + # 默认创建SPI(2, 0, 1): 通道2, 模式0, 时钟频率1(26MHz) + # 注意:Python端SPI2对应MCU端SPI1 + # 频率参数1表示26MHz(根据实际SPI驱动实现) + self.spi_obj = SPI(2, 0, 9) + clog.info("Created default SPI object: SPI(2, 0, 9) for 26MHz") + else: + self.spi_obj = spi_obj + clog.info("Using provided SPI object") + + def close(self): + """ + 关闭SPI对象,释放资源 + """ + if self.spi_obj is not None: + try: + # 尝试调用close方法 + if hasattr(self.spi_obj, 'close'): + self.spi_obj.close() + clog.info("SPI object closed via close() method") + # 尝试调用deinit方法(MicroPython常用) + elif hasattr(self.spi_obj, 'deinit'): + self.spi_obj.deinit() + clog.info("SPI object closed via deinit() method") + else: + clog.warning("SPI object does not have close() or deinit() method") + except Exception as e: + clog.error("Error closing SPI object: {}".format(e)) + finally: + self.spi_obj = None + + def start_download_and_send(self): + """ + 开始下载文件并通过SPI发送(用于测试SPI传输速度) + + Returns: + tuple: (success: bool, download_time: float, file_size: int) + """ + try: + clog.info("Starting SPI speed test: {}".format(self.url)) + + # 记录开始时间 + start_time = utime.time() + + # 发送GET请求 + response = request.get( + self.url, + sizeof=self.sizeof, + decode=False # 不进行UTF-8解码,直接获取bytes + ) + + # 检查响应状态码 + if response.status_code != 200: + clog.error("Error: HTTP error {}: {}".format(response.status_code, self.url)) + self.close() # 关闭SPI + return False, 0.0, -1 + + # 获取文件总大小(如果服务器提供了Content-Length) + total_size = 0 + if 'Content-Length' in response.headers: + try: + total_size = int(response.headers['Content-Length']) + clog.info("File size: {} bytes ({:.2f} MB)".format( + total_size, total_size / 1024.0 / 1024.0)) + except (ValueError, TypeError): + total_size = 0 + + # 下载数据并通过SPI发送 + downloaded_size = 0 + + # response.content是bytes类型生成器 + for chunk in response.content: + if chunk: # 过滤掉空块 + try: + # 通过SPI发送数据块 + # SPI API: write(data, datasize) 或 write_read(read_buf, write_data, datasize, timeout) + chunk_len = len(chunk) + if hasattr(self.spi_obj, 'write'): + # write(data, datasize) - datasize 参数必需 + self.spi_obj.write(chunk, chunk_len) + elif hasattr(self.spi_obj, 'write_read'): + # write_read(read_buf, write_data, datasize, timeout) + # 或者 write_read(read_buf, write_data, datasize) + read_buf = bytearray(chunk_len) + try: + # 尝试带timeout的调用 + self.spi_obj.write_read(read_buf, chunk, chunk_len, 5) + except TypeError: + # 尝试不带timeout的调用 + self.spi_obj.write_read(read_buf, chunk, chunk_len) + else: + clog.error("SPI object does not have write or write_read method") + self.close() # 关闭SPI + return False, 0.0, -1 + + downloaded_size += len(chunk) + + # 显示进度(每发送64KB打印一次,减少打印数量) + if total_size > 0: + progress = (downloaded_size / total_size) * 100 + # 每发送64KB打印一次进度 + if downloaded_size % (64 * 1024) < self.sizeof: + clog.info("SPI TX Progress: {:.2f}% ({}/{}) bytes".format( + progress, downloaded_size, total_size)) + except Exception as e: + clog.error("Error sending data via SPI: {}".format(e)) + self.close() # 关闭SPI + return False, 0.0, -1 + + # 记录结束时间并计算耗时 + end_time = utime.time() + download_time = end_time - start_time + + # 计算传输速度 + speed_kbps = (downloaded_size * 8.0) / download_time / 1024.0 # Kbps + speed_mbps = speed_kbps / 1024.0 # Mbps + + if speed_mbps >= 1.0: + clog.info("SPI speed test completed: {} bytes in {:.3f}s, Speed: {:.2f} Mbps ({:.2f} Kbps)".format( + downloaded_size, download_time, speed_mbps, speed_kbps)) + else: + clog.info("SPI speed test completed: {} bytes in {:.3f}s, Speed: {:.2f} Kbps".format( + downloaded_size, download_time, speed_kbps)) + + # 传输完成后关闭SPI + self.close() + + return True, download_time, downloaded_size + + except Exception as e: + clog.error("Error in SPI speed test: {}".format(e)) + self.close() # 确保异常时也关闭SPI + return False, 0.0, -1 + +# 创建并启动SPI传输速度测试(自动执行) +def start_spi_speed_test(): + """启动SPI传输速度测试""" + test = HttpFileDownloadSpiTest() + try: + success, download_time, file_size = test.start_download_and_send() + if success: + clog.info("SPI speed test PASSED") + else: + clog.error("SPI speed test FAILED") + return success, download_time, file_size + finally: + # 确保SPI对象被关闭(start_download_and_send内部已关闭,这里作为双重保险) + test.close() -HttpFileDownloadTcpServer().start() +# 自动启动SPI传输速度测试(替代原来的TCP服务器) +start_spi_speed_test() # 示例用法 # if __name__ == '__main__': # # 方式1: 使用类下载并保存文件 diff --git a/cat1modem/src/LeiNiao/main_init.py b/cat1modem/src/LeiNiao/main_init.py index 065fe3bd3..7e8138a02 100644 --- a/cat1modem/src/LeiNiao/main_init.py +++ b/cat1modem/src/LeiNiao/main_init.py @@ -33,11 +33,11 @@ try: print("检测文件成功!!!!!!!\r\n",'cat1service') except Exception as e: print("检测文件失败!!!!!!!{}\r\n",'cat1service') - try: - from usr.httpfiledownload import HttpFileDownloadTcpServer - print("检测文件成功!!!!!!!\r\n",'httpfiledownload') - except Exception as e: - print("检测文件失败!!!!!!!{}\r\n",'httpfiledownload') + # try: + # from usr.httpfiledownload import HttpFileDownloadTcpServer + # print("检测文件成功!!!!!!!\r\n",'httpfiledownload') + # except Exception as e: + # print("检测文件失败!!!!!!!{}\r\n",'httpfiledownload') ota_flag = 1 print("检测文件成功!!!!!!!\r\n") diff --git a/code/sdk/build/config/target_config/brandy/config.py b/code/sdk/build/config/target_config/brandy/config.py index 40bd6a58e..dcb5428a2 100644 --- a/code/sdk/build/config/target_config/brandy/config.py +++ b/code/sdk/build/config/target_config/brandy/config.py @@ -248,6 +248,7 @@ target = { '_ALL_SOURCE', 'SUPPORT_CXX', 'I2C_SLAVE_REG_ADDR_4BYTE', 'CFG_DRIVERS_NANDFLASH', "-:TARGET_CHIP_BRANDY=1", "-:BRANDY_CHIP_V100=1", 'ENABLE_UIKIT', 'SW_UART_DEBUG', "HASH_MEM_COPY", 'ENABLE_ECC', 'SUPPORT_RECOVERY_DISPLAY', + 'CONFIG_SPI_SUPPORT_DMA=1', 'CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH', 'CONFIG_SPI_AUTO_SWITCH_DMA_THRESHOLD=0x8', 'MBEDTLS_NO_PLATFORM_ENTROPY', 'MBEDTLS_ENTROPY_HARDWARE_ALT', 'MARKET_ENABLE'], 'ram_component': ["algorithm", "dfx_port_brandy", "dfx_update", "osal", "arch_port", "-:testsuite", "liteos_port", 'lcd', 'qspi_display', 'dfx_file_operation', @@ -277,7 +278,7 @@ target = { 'PRE_ASIC', 'BRANDY_PRODUCT_EVB4', '_ALL_SOURCE', '__LITEOS__', 'SUPPORT_CXX', 'ENABLE_UIKIT', "-:TARGET_CHIP_BRANDY=1", "-:BRANDY_CHIP_V100=1", 'MULTIPLEXING_UART', - 'CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH', 'CONFIG_SPI_AUTO_SWITCH_DMA_THRESHOLD=0x8', + 'CONFIG_SPI_SUPPORT_DMA=1', 'CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH', 'CONFIG_SPI_AUTO_SWITCH_DMA_THRESHOLD=0x8', 'CONFIG_I2C_SUPPORT_POLL_AND_DMA_AUTO_SWITCH', 'CONFIG_I2C_POLL_AND_DMA_AUTO_SWITCH_THRESHOLD=0x8'], 'ram_component': ["osal", "arch_port", "liteos_port", 'psram', 'pm_service', 'pm_brandy', 'hal_mips', 'lcd', 'qspi_display', 'hal_dma_v100', "dfx_port_brandy", diff --git a/code/sdk/zhihui/utils/custom_utils_at_cmd.c b/code/sdk/zhihui/utils/custom_utils_at_cmd.c index f10d4c07d..e8c4d3da7 100644 --- a/code/sdk/zhihui/utils/custom_utils_at_cmd.c +++ b/code/sdk/zhihui/utils/custom_utils_at_cmd.c @@ -52,6 +52,9 @@ #include "ppg_hal.h" #endif +/* Include custom_utilscpp.h for SPI1 receive functions */ +#include "custom_utilscpp.h" + #define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) #define CUSTOM_UTILS_MAX_ARGC 8 #define CUSTOM_UTILS_MAX_ARGV_LEN 256 @@ -1912,6 +1915,69 @@ static uint32_t custom_lowpower_alarm_test(uint8_t *para, uint32_t paraLen, char return ERRCODE_SUCC; } +/* ==================== SPI1 Client Receive Test AT Commands ==================== */ + +/** + * @brief AT^SPI1RXSTART - Start SPI1 client receive data test + * Usage: AT^SPI1RXSTART + */ +static uint32_t custom_spi1_rx_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + wstp_print("AT^SPI1RXSTART: Starting SPI1 client receive test...\n"); + custom_spi1_client_receive_start(); + wstp_print("AT^SPI1RXSTART: SPI1 receive started successfully\n"); + + return ERRCODE_SUCC; +} + +/** + * @brief AT^SPI1RXSTOP - Stop SPI1 client receive data test + * Usage: AT^SPI1RXSTOP + */ +static uint32_t custom_spi1_rx_stop(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + wstp_print("AT^SPI1RXSTOP: Stopping SPI1 client receive test...\n"); + custom_spi1_client_receive_stop(); + wstp_print("AT^SPI1RXSTOP: SPI1 receive stopped successfully\n"); + + return ERRCODE_SUCC; +} + +/** + * @brief AT^SPI1RXSTAT - Get SPI1 receive statistics + * Usage: AT^SPI1RXSTAT + */ +static uint32_t custom_spi1_rx_stat(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(para); + UNUSED(paraLen); + UNUSED(strCommonCmd); + + uint32_t total_received = custom_spi1_client_get_total_received(); + float total_kb = total_received / 1024.0f; + float total_mb = total_kb / 1024.0f; + + if (total_mb >= 1.0f) { + wstp_print("AT^SPI1RXSTAT: Total received: %u bytes (%.2f MB, %.2f KB)\n", + total_received, total_mb, total_kb); + } else if (total_kb >= 1.0f) { + wstp_print("AT^SPI1RXSTAT: Total received: %u bytes (%.2f KB)\n", + total_received, total_kb); + } else { + wstp_print("AT^SPI1RXSTAT: Total received: %u bytes\n", total_received); + } + + return ERRCODE_SUCC; +} + static const at_cmd_table_t g_custom_utils_cmd[] = { { (char *)"AT^CUSTOM_SN", custom_utils_setsn, (char *)"123456789ABCDEF2345678" }, { (char *)"AT^CUSTOM_SN_REQ", custom_utils_getsn, (char *)" " }, @@ -2018,6 +2084,11 @@ static const at_cmd_table_t g_custom_utils_cmd[] = { { (char *)"AT^SETUART", custom_set_uart_status, (char *)" " }, { (char *)"AT^GETBTMAC", custom_get_btmac, (char *)" " }, { (char *)"AT^LOWPOWER", custom_lowpower_alarm_test, (char *)" " }, + + /* SPI1 client receive test AT commands */ + { (char *)"AT^SPI1RXSTART", custom_spi1_rx_start, (char *)" " }, + { (char *)"AT^SPI1RXSTOP", custom_spi1_rx_stop, (char *)" " }, + { (char *)"AT^SPI1RXSTAT", custom_spi1_rx_stat, (char *)" " }, }; diff --git a/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp b/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp index bc7395d6f..df6a29488 100644 --- a/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp +++ b/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp @@ -80,13 +80,28 @@ extern "C" { #include "upg_alloc.h" - +/* SPI driver includes */ +#include "driver/spi.h" +#include "hal_spi.h" +#include "driver/tcxo.h" +#include "thread_init.h" +#include "spi_porting.h" #define IN_BUF_SIZE (1 << 8) #define OUT_BUF_SIZE (1 << 8) static osTimerId_t g_bt_scan_timer = NULL; +/* SPI1 client receive data variables */ +static osTimerId_t g_spi1_stats_timer = NULL; +static uint8_t g_spi1_receive_running = 0; +static uint32_t g_spi1_total_received = 0; +static uint32_t g_spi1_last_received = 0; +static uint32_t g_spi1_last_print_time = 0; +#define SPI1_RECV_BUFFER_SIZE 4096 // DMA模式下使用4K缓冲区,提高传输效率,减少DMA配置次数 +#define SPI1_BUS_ID SPI_BUS_1 +#define SPI1_RECV_TIMEOUT_MS 50 // 减小超时时间到50ms,提高读取频率,避免FIFO溢出 + static SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream, UInt64 unpackSize) { @@ -569,4 +584,408 @@ int custom_stop_play_audio(void) printf("=== AudioPlayer stop process completed ===\n"); return 0; +} + +/* ==================== SPI1 Client Receive Data Functions ==================== */ + +/** + * @brief SPI1 statistics print callback (called by timer every 1 second) + */ +static void custom_spi1_stats_callback(void) +{ + if (!g_spi1_receive_running) { + return; + } + + uint32_t current_time = uapi_tcxo_get_us() / 1000; // Convert to milliseconds + uint32_t bytes_received_this_second = g_spi1_total_received - g_spi1_last_received; + + // Only print if there's data received (rate > 0) + if (bytes_received_this_second > 0) { + float rate_kbps = (bytes_received_this_second * 8.0f) / 1024.0f; // Convert to Kbps + float rate_mbps = rate_kbps / 1024.0f; // Convert to Mbps + + if (rate_mbps >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 RX: Total=%u bytes, Rate=%.2f Mbps (%.2f Kbps), This second=%u bytes", + g_spi1_total_received, rate_mbps, rate_kbps, bytes_received_this_second); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 RX: Total=%u bytes, Rate=%.2f Kbps, This second=%u bytes", + g_spi1_total_received, rate_kbps, bytes_received_this_second); + } + } + + // Update last received count for next calculation + g_spi1_last_received = g_spi1_total_received; + g_spi1_last_print_time = current_time; +} + +/** + * @brief SPI1 receive data task function + */ +static void custom_spi1_receive_task_func(void *pvParameters) +{ + UNUSED(pvParameters); + + spi_bus_t bus = SPI1_BUS_ID; + uint8_t spi_buffer[SPI1_RECV_BUFFER_SIZE]; + errcode_t ret; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 receive task started"); + + // Initialize SPI1 as Slave (Client) mode + spi_attr_t attr = {0}; + spi_extra_attr_t extra_attr = {0}; + + attr.is_slave = true; // Slave模式(Client) + attr.slave_num = 0; // Slave编号 + attr.bus_clk = 32000000; // 总线时钟32MHz + attr.freq_mhz = 0; // Slave模式下由Master控制(26MHz由Master设置) + attr.clk_polarity = SPI_CFG_CLK_CPOL_0; // CPOL=0 + attr.clk_phase = SPI_CFG_CLK_CPHA_0; // CPHA=0 (Mode 0) + attr.frame_format = SPI_CFG_FRAME_FORMAT_MOTOROLA_SPI; + attr.spi_frame_format = (uint32_t)HAL_SPI_FRAME_FORMAT_STANDARD; // 标准SPI格式(非QUAD) + attr.frame_size = (uint32_t)HAL_SPI_FRAME_SIZE_8; // 8位数据(与Python端匹配) + attr.tmod = HAL_SPI_TRANS_MODE_RX; // 只接收模式 + attr.sste = SPI_CFG_SSTE_DISABLE; + attr.ndf = 1; + + // 启用DMA模式,避免FIFO溢出(必须使用DMA,否则FIFO会一直full) + extra_attr.tx_use_dma = false; + extra_attr.rx_use_dma = true; // 启用DMA接收模式,避免FIFO溢出 + memset(&extra_attr.qspi_param, 0, sizeof(extra_attr.qspi_param)); + memset(&extra_attr.sspi_param, 0, sizeof(extra_attr.sspi_param)); + + // Deinitialize SPI first (if already initialized) + uapi_spi_deinit(bus); + + // Initialize SPI + ret = uapi_spi_init(bus, &attr, &extra_attr); + if (ret != ERRCODE_SUCC) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SPI1 init failed: 0x%x", ret); + g_spi1_receive_running = 0; + return; + } + + // Enable DMA mode for receiving (to avoid FIFO overflow) + // DMA mode allows data to be transferred directly to buffer without CPU intervention + // 必须使用DMA,否则FIFO会一直full +#if defined(CONFIG_SPI_SUPPORT_DMA) && (CONFIG_SPI_SUPPORT_DMA == 1) + // 检查缓冲区地址对齐(DMA通常要求4字节对齐) + uint32_t buffer_addr = (uint32_t)(uintptr_t)spi_buffer; + if (buffer_addr % 4 != 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 buffer not 4-byte aligned: addr=0x%x, alignment=%u", + buffer_addr, buffer_addr % 4); + } + + // 检查缓冲区大小是否满足DMA要求(对于8-bit宽度,应该是1字节对齐,但通常4字节对齐更好) + if (SPI1_RECV_BUFFER_SIZE % 4 != 0) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 buffer size not 4-byte aligned: size=%u", SPI1_RECV_BUFFER_SIZE); + } + + spi_dma_config_t dma_cfg = { + .src_width = 0, // 8-bit data width (0 = 8-bit, 1 = 16-bit, 2 = 32-bit) + .dest_width = 0, // 8-bit data width + .burst_length = 0, // Burst length (0 = single transfer, 1 = 4, 2 = 8, 3 = 16) + .priority = 0 // DMA priority (0 = lowest) + }; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 Setting DMA mode: src_width=%u, dest_width=%u, burst_length=%u, priority=%u, buffer_addr=0x%x, buffer_size=%u", + dma_cfg.src_width, dma_cfg.dest_width, dma_cfg.burst_length, dma_cfg.priority, + buffer_addr, SPI1_RECV_BUFFER_SIZE); + + ret = uapi_spi_set_dma_mode(bus, true, &dma_cfg); + if (ret != ERRCODE_SUCC) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 DMA mode enable failed: 0x%x, buffer_addr=0x%x, buffer_size=%u", + ret, buffer_addr, SPI1_RECV_BUFFER_SIZE); + // 如果DMA配置失败,尝试使用默认配置(NULL参数) + ret = uapi_spi_set_dma_mode(bus, true, NULL); + if (ret != ERRCODE_SUCC) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 DMA mode enable failed with default config: 0x%x", ret); + // DMA配置失败,但继续运行(可能会在读取时失败,但至少可以尝试) + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 DMA mode enabled with default config"); + } + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 DMA mode enabled for RX (prevents FIFO overflow)"); + } +#endif /* CONFIG_SPI_SUPPORT_DMA */ + + // Verify SPI configuration after initialization + spi_attr_t verify_attr = {0}; + ret = uapi_spi_get_attr(bus, &verify_attr); + if (ret == ERRCODE_SUCC) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 config verification: is_slave=%d, tmod=%u, clk_polarity=%u, clk_phase=%u, frame_size=%u", + verify_attr.is_slave, verify_attr.tmod, verify_attr.clk_polarity, + verify_attr.clk_phase, verify_attr.frame_size); + } else { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "Failed to get SPI1 attributes: 0x%x", ret); + } + + // Verify device mode via porting layer + spi_mode_t device_mode = spi_porting_get_device_mode(bus); + if (device_mode == SPI_MODE_SLAVE) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 device mode verified: SLAVE mode"); + } else { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "SPI1 device mode MISMATCH! Expected SLAVE, got mode=%d", device_mode); + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 initialized (Slave mode, 26MHz expected)"); + + // Reset statistics for this session + g_spi1_total_received = 0; + g_spi1_last_received = 0; + g_spi1_last_print_time = uapi_tcxo_get_us() / 1000; + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 receive session started, statistics reset"); + + // Start statistics timer (print every 1 second) + if (g_spi1_stats_timer == NULL) { + g_spi1_stats_timer = osTimerNew((osTimerFunc_t)custom_spi1_stats_callback, osTimerPeriodic, NULL, NULL); + } + if (g_spi1_stats_timer) { + osTimerStart(g_spi1_stats_timer, 1000); // 1 second interval + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 statistics timer started"); + } + + // Main receive loop + uint32_t timeout_count = 0; + uint32_t success_count = 0; + uint32_t error_count = 0; + + while (g_spi1_receive_running) { + spi_xfer_data_t spi_data = { + .rx_buff = spi_buffer, + .rx_bytes = sizeof(spi_buffer) // 期望接收的最大字节数(4K),但实际接收可能小于此值 + }; + + // Receive data via SPI1 (Slave mode) using DMA + // 注意: + // 1. uapi_spi_slave_read 返回后,spi_data.rx_bytes 会被更新为实际接收的字节数 + // 2. 即使缓冲区是4K,如果Master只发送了部分数据,rx_bytes会反映实际接收的字节数 + // 3. DMA会在以下情况返回: + // - 缓冲区填满(rx_bytes == 4K) + // - Master停止发送,DMA检测到传输结束(rx_bytes < 4K,但传输完成) + // - 超时(50ms内没有数据或传输未完成) + ret = uapi_spi_slave_read(bus, &spi_data, SPI1_RECV_TIMEOUT_MS); + + if (ret == ERRCODE_SUCC) { + // 检查实际接收的字节数(函数返回后,rx_bytes 会被更新为实际接收的字节数) + if (spi_data.rx_bytes > 0) { + // Data received successfully (可能小于4K,这是正常的) + // 使用实际接收的字节数,而不是缓冲区大小 + g_spi1_total_received += spi_data.rx_bytes; + success_count++; + + // 打印接收到的数据信息(每接收一定数据量后打印一次,减少打印) + // 注意:即使只接收到部分数据(小于4K),也会正常处理 + if (g_spi1_total_received % (64 * 1024) < sizeof(spi_buffer)) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 RX: Received %u bytes in this call (buffer=%u), Total: %u bytes", + spi_data.rx_bytes, sizeof(spi_buffer), g_spi1_total_received); + } + } else { + // 返回成功但没有数据(rx_bytes == 0),可能是FIFO为空或传输已完成 + // 继续等待下一次传输(不打印,避免刷屏) + continue; + } + } else if (ret == ERRCODE_SPI_TIMEOUT) { + // Timeout is normal when no data is being sent or Master stops sending + // 超时是正常的,说明在50ms内没有接收到数据或传输未完成 + // 不打印timeout信息,避免刷屏 + timeout_count++; + continue; + } else { + // Other error, log error information + error_count++; + const char* error_name = "UNKNOWN"; + bool should_disable_dma = false; + + if (ret == 0x80001332) { // ERRCODE_SPI_MODE_MISMATCH + error_name = "SPI_MODE_MISMATCH"; + } else if (ret == 0x8000133E) { // ERRCODE_SPI_INVALID_TMODE + error_name = "SPI_INVALID_TMODE"; + } else if (ret == 0x80001334) { // ERRCODE_SPI_RX_FIFO_FULL + error_name = "SPI_RX_FIFO_FULL"; + // FIFO满可能是配置不匹配或读取不及时,打印一次详细诊断信息 + if (error_count == 1) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 RX_FIFO_FULL: Check SPI config match!"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 MCU config: frame_size=7(8bit), mode=0(CPOL=0,CPHA=0), slave"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 Python should: SPI(2, 0, 9) - mode=0, 8bit, master"); + } + } else if (ret == ERRCODE_SPI_TIMEOUT) { + error_name = "SPI_TIMEOUT"; + } else if (ret == ERRCODE_SPI_DMA_CONFIG_ERROR || ret == 0x80001336) { // ERRCODE_SPI_DMA_CONFIG_ERROR + error_name = "SPI_DMA_CONFIG_ERROR"; + // DMA配置错误,可能是handshaking配置失败或DMA通道不可用 + // 检查可能的原因:缓冲区对齐、DMA通道、handshaking配置 + if (error_count == 1) { + uint32_t buffer_addr = (uint32_t)(uintptr_t)spi_buffer; + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 DMA_CONFIG_ERROR: DMA configuration failed!"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 Possible causes: buffer alignment, DMA channel unavailable, or handshaking config"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 Buffer: addr=0x%x (align=%u), size=%u, bus=SPI_BUS_1", + buffer_addr, buffer_addr % 4, SPI1_RECV_BUFFER_SIZE); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 DMA config: src_width=0(8bit), dest_width=0(8bit), burst_length=0"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 Expected handshaking: HAL_DMA_HANDSHAKING_SPI_MS0_RX for SPI_BUS_1"); + } + } else if (ret == ERRCODE_SPI_DMA_TRANSFER_ERROR || ret == 0x80001337) { // ERRCODE_SPI_DMA_TRANSFER_ERROR + error_name = "SPI_DMA_TRANSFER_ERROR"; + // DMA传输错误,但不禁用DMA(因为禁用DMA会导致FIFO一直full) + // 注意:DMA传输错误在某些情况下是正常的(如数据少、传输间隙等),不应该重新配置 + if (error_count == 1) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 DMA_TRANSFER_ERROR: DMA transfer error (may be normal in some cases, will continue)"); + } + } else if (ret == ERRCODE_INVALID_PARAM || ret == 0x80000001) { // ERRCODE_INVALID_PARAM + error_name = "INVALID_PARAM"; + // 无效参数错误,可能是DMA配置参数有问题或数据对齐问题 + // 注意:在某些情况下(如数据量少、传输间隙等),这个错误可能是正常的 + // 不应该重新配置DMA,应该继续尝试下一次读取 + if (error_count == 1) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 INVALID_PARAM: Invalid parameter (may be normal in some cases, will continue)"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "SPI1 Buffer size: %u bytes, rx_bytes=%u", SPI1_RECV_BUFFER_SIZE, spi_data.rx_bytes); + } + } + + // 打印错误信息(前5次,之后每100次打印一次,减少刷屏) + if (error_count <= 5 || error_count % 100 == 0) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, + "SPI1 read error: 0x%x (%s), rx_bytes=%u (error_count=%u)", + ret, error_name, spi_data.rx_bytes, error_count); + } + // 继续尝试接收 + continue; + } + } + + // 打印最终统计 + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 receive loop ended: success_count=%u, timeout_count=%u, error_count=%u", + success_count, timeout_count, error_count); + + // Stop statistics timer + if (g_spi1_stats_timer) { + osTimerStop(g_spi1_stats_timer); + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 statistics timer stopped"); + } + + // Print final statistics for this session + uint32_t session_total = g_spi1_total_received; + float total_kb = session_total / 1024.0f; + float total_mb = total_kb / 1024.0f; + + if (total_mb >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 receive session ended. Total received in this session: %u bytes (%.2f MB, %.2f KB)", + session_total, total_mb, total_kb); + } else if (total_kb >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 receive session ended. Total received in this session: %u bytes (%.2f KB)", + session_total, total_kb); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 receive session ended. Total received in this session: %u bytes", + session_total); + } + + // Deinitialize SPI + uapi_spi_deinit(bus); +} + +/** + * @brief Start SPI1 client receive data task + */ +void custom_spi1_client_receive_start(void) +{ + if (g_spi1_receive_running) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI1 receive is already running"); + return; + } + + g_spi1_receive_running = 1; + + // Create task for SPI receive + app_task_definition_t taskcfg = {0}; + taskcfg.create_queue_handle = NULL; + taskcfg.attr.name = "spi1_client_rx"; + taskcfg.attr.priority = TASK_PRIORITY_APP; + taskcfg.attr.stack_size = 0x2000; // 8KB stack + taskcfg.func = custom_spi1_receive_task_func; + taskcfg.use_mem = USE_EXTEND_MEM; + + errcode_t ret = uapi_create_osThread_custom(&taskcfg); + if (ret != ERRCODE_SUCC) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Failed to create SPI1 receive task: 0x%x", ret); + g_spi1_receive_running = 0; + return; + } + + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "SPI1 client receive started"); +} + +/** + * @brief Stop SPI1 client receive data task + */ +void custom_spi1_client_receive_stop(void) +{ + if (!g_spi1_receive_running) { + WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP, "SPI1 receive is not running"); + return; + } + + // Save total received before stopping + uint32_t session_total = g_spi1_total_received; + + g_spi1_receive_running = 0; + + // Stop and delete statistics timer + if (g_spi1_stats_timer) { + osTimerStop(g_spi1_stats_timer); + osTimerDelete(g_spi1_stats_timer); + g_spi1_stats_timer = NULL; + } + + // Print final statistics + float total_kb = session_total / 1024.0f; + float total_mb = total_kb / 1024.0f; + + if (total_mb >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 client receive stopped. Total received in this session: %u bytes (%.2f MB, %.2f KB)", + session_total, total_mb, total_kb); + } else if (total_kb >= 1.0f) { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 client receive stopped. Total received in this session: %u bytes (%.2f KB)", + session_total, total_kb); + } else { + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, + "SPI1 client receive stopped. Total received in this session: %u bytes", + session_total); + } +} + +/** + * @brief Get total bytes received in current SPI1 receive session + * @return Total bytes received (0 if not running or no data received) + */ +uint32_t custom_spi1_client_get_total_received(void) +{ + return g_spi1_total_received; } \ No newline at end of file diff --git a/code/sdk/zhihui/utilscpp/custom_utilscpp.h b/code/sdk/zhihui/utilscpp/custom_utilscpp.h index 47b15938a..eadcb9297 100644 --- a/code/sdk/zhihui/utilscpp/custom_utilscpp.h +++ b/code/sdk/zhihui/utilscpp/custom_utilscpp.h @@ -37,6 +37,11 @@ void custom_factory_bt_scan_stop(void); int custom_play_audio(const char* filePath, float volume); int custom_stop_play_audio(void); +/* SPI1 client receive data functions */ +void custom_spi1_client_receive_start(void); +void custom_spi1_client_receive_stop(void); +uint32_t custom_spi1_client_get_total_received(void); // Get total bytes received in current session + /** * input: * func 函数指针 -- 2.17.1