#include "common.h" #ifdef USE_LUA_ST_UART #include "lua_base.h" #include "bsp_uart.h" #include "ymodem.h" #ifdef USE_LWIP #include "rd_udp.h" /* UDP 链路句柄 (上位机 provision 经 telnet 下发 c=UDP(...)) */ #endif #ifdef PRINT_ID #ifdef USE_USB #include "bsp_usb.h" #endif #endif /* 句柄类型 (主仓库 lacmd.c 同款语义) */ #define COM_TYPE_SERIAL 0 #define COM_TYPE_UDP 1 // 串口对象结构体 typedef struct { TComCtrl *m_ptComCtrl; int m_iType; /* COM_TYPE_* */ int m_iOwned; /* 1=自建句柄 GC 关闭 (仅 UDP); 0=借用, 维持 robot 现状不关 */ } stm32_com_t; TComCtrl *g_ptCom1; TComCtrl *g_ptCom0; int COM1_Send(char *_pBuffer, uint32_t _iSize) { TUartUserData *ptUartUserData = (TUartUserData *)g_ptCom1->m_pUserData; HAL_GPIO_WritePin(RS485_2_DIR_GPIO_Port, RS485_2_DIR_Pin,GPIO_PIN_SET); int iRet = HAL_UART_Transmit(ptUartUserData->m_uart, (uint8_t *)_pBuffer, _iSize, 100); HAL_GPIO_WritePin(RS485_2_DIR_GPIO_Port, RS485_2_DIR_Pin,GPIO_PIN_RESET); if (iRet != HAL_OK) return RD_FAILURE; return (int)_iSize; } int COM0_Send(char *_pBuffer, uint32_t _iSize) { TUartUserData *ptUartUserData = (TUartUserData *)g_ptCom0->m_pUserData; return HAL_UART_Transmit(ptUartUserData->m_uart, (uint8_t *)_pBuffer, _iSize, 100); } // 构造函数: st.COM(1, 115200) static int l_com_new(lua_State *L) { int id = luaL_checkinteger(L, 1); int baudrate = luaL_optinteger(L, 2, 115200); // 默认波特率9600 // 创建 userdata stm32_com_t *u = (stm32_com_t*)lua_newuserdata(L, sizeof(stm32_com_t)); u->m_iType = COM_TYPE_SERIAL; u->m_iOwned = 0; /* 串口句柄维持 robot 现状: 不随 GC 关闭 */ if (id == 1 || id == 2) { TUartUserData *ptUartUserData = UART_userdata_init(LUACOM_ID, baudrate, 4096); if (NULL == ptUartUserData) { lua_print("com is already used\n"); u->m_ptComCtrl = g_ptCom1; luaL_getmetatable(L, "stm32.COMMT"); lua_setmetatable(L, -2); return 1; } g_ptCom1 = rd_ComCreate(NULL, NULL, COM1_Send, ptUartUserData->m_buf_size, ptUartUserData); UART_IT_init(g_ptCom1); u->m_ptComCtrl = g_ptCom1; } else if (id == 0) { #ifdef PRINT_ID #ifdef USE_USB g_ptCom0 = bsp_usb_init(); #endif #else TUartUserData *ptUartUserData = UART_userdata_init(0, baudrate, 4096); if (NULL == ptUartUserData) { lua_print("com is already used\n"); } g_ptCom0 = rd_ComCreate(NULL, NULL, COM0_Send, ptUartUserData->m_buf_size, ptUartUserData); UART_IT_init(g_ptCom0); #endif u->m_ptComCtrl = g_ptCom0; } // 设置 Metatable luaL_getmetatable(L, "stm32.COMMT"); lua_setmetatable(L, -2); return 1; } // 方法: COM:write(str) static int l_com_write(lua_State *L) { stm32_com_t *u = (stm32_com_t*)luaL_checkudata(L, 1, "stm32.COMMT"); size_t len; const char *str = luaL_checklstring(L, 2, &len); // 支持二进制数据 rd_ComSend(u->m_ptComCtrl, (char *)str, len); lua_pushinteger(L, len); // 返回发送的字节数 return 1; } // 方法: COM:read(mode) static int l_com_read(lua_State *L) { int iRet = 1; char ch = 0; stm32_com_t *u = (stm32_com_t*)luaL_checkudata(L, 1, "stm32.COMMT"); int mode = luaL_optinteger(L, 2, 0); // 默认不使用16进制 char *buf = (char*)RD_MALLOC(CONFIG_UART_BUFFER_SIZE); int iLen = 0; if (!buf) return luaL_error(L, "Memory alloc failed"); while(iRet) { __disable_irq(); iRet = rd_ComRead(u->m_ptComCtrl, &ch, 1); __enable_irq(); if (iRet > 0) { if (3 == mode) { lua_print("%02X ", ch); } else { lua_print("%c", ch); } if (iLen >= CONFIG_UART_BUFFER_SIZE) break; buf[iLen++] = ch; } } if (iLen > 0) { lua_pushlstring(L, buf, iLen); } else { lua_pushstring(L, ""); } RD_FREE(buf); return 1; } // 方法: COM:ry(filepath) — YMODEM 接收文件 static int l_com_ry(lua_State *L) { stm32_com_t *u = (stm32_com_t*)luaL_checkudata(L, 1, "stm32.COMMT"); const char *path = luaL_checkstring(L, 2); lua_print("ry: receiving '%s' ...\n", path); int res = rym_download_file(u->m_ptComCtrl, path); lua_pushinteger(L, res); return 1; } // 方法: COM:sy(filepath) — YMODEM 发送文件 static int l_com_sy(lua_State *L) { stm32_com_t *u = (stm32_com_t*)luaL_checkudata(L, 1, "stm32.COMMT"); const char *path = luaL_checkstring(L, 2); lua_print("sy: sending '%s' ...\n", path); int res = rym_upload_file(u->m_ptComCtrl, path); lua_pushinteger(L, res); return 1; } // 构造函数: CON() — 控制台串口句柄 (借用语义) // MCU(USE_PRINT) 上 lua 终端自身的收发(printf/fgetc)都走 g_ptUartCtrl, // CON() 把它包成 COMMT 句柄, 便可在控制台 REPL 直接对控制台串口 ymodem: // c:ry('.') -- 接收上位机发来的文件到当前目录 // c:sy('f.bin') -- 把本地文件发给上位机 // 借用句柄 GC 不关闭 (m_iOwned=0), 控制台生命周期归 Myprint; // 无控制台句柄的平台(如 Linux 桌面构建)返回 nil static int l_con_new(lua_State *L) { #if defined(USE_PRINT) extern TComCtrl *Myprint_GetCtrl(void); TComCtrl *ptCtrl = Myprint_GetCtrl(); if (NULL == ptCtrl) { lua_print("CON: console port not ready\n"); lua_pushnil(L); return 1; } stm32_com_t *u = (stm32_com_t *)lua_newuserdata(L, sizeof(stm32_com_t)); u->m_iType = COM_TYPE_SERIAL; u->m_iOwned = 0; u->m_ptComCtrl = ptCtrl; luaL_getmetatable(L, "stm32.COMMT"); lua_setmetatable(L, -2); return 1; #else lua_pushnil(L); return 1; #endif } #ifdef USE_LWIP // __gc: 仅自建 UDP 句柄关闭 (上位机 provision 行 c=nil collectgarbage() // 重绑旧句柄、telnet 断线重连 lua_init 重建 Lua 态时, 靠这里释放旧 UDP // 链路/8007 端口, 否则二次 Rd_UdpCreate bind 失败); // 串口/CON 借用句柄维持 robot 现状 (不关, 生命周期归宿主) static int l_com_gc(lua_State *L) { stm32_com_t *u = (stm32_com_t*)luaL_testudata(L, 1, "stm32.COMMT"); if (u != NULL && u->m_iOwned && u->m_iType == COM_TYPE_UDP && u->m_ptComCtrl != NULL) { Rd_UdpClose(u->m_ptComCtrl); u->m_ptComCtrl = NULL; } return 0; } // 构造函数: UDP(localport, "remoteip" [,remoteport [,txsrcport]]) — UDP 链路句柄 // 上位机连接后自动下发 c=UDP(8007,"<本端IP>",9007) 建 ymodem 文件链路, // 之后 c:ry('.')/c:sy('f') 与串口句柄用法完全一致 (rym_* 按 TComCtrl 分派) static int l_udp_new(lua_State *L) { if (lua_gettop(L) == 0) { lua_print("Usage: UDP(localport, \"remoteip\" [,remoteport [,txsrcport]])\n"); lua_print(" UDP(8007, \"192.168.1.100\", 9007) -- 固定直连: bind 8007 rx, 发往 9007\n"); return 0; } int localport = (int)luaL_checkinteger(L, 1); const char *ip = luaL_checkstring(L, 2); int remoteport = (int)luaL_optinteger(L, 3, 0); int txsrcport = (int)luaL_optinteger(L, 4, 0); TRdUdpCfg stUdpCfg = { .m_psRemoteIp = ip, .m_iRemotePort = remoteport, .m_iLocalPort = localport, .m_iTxSrcPort = txsrcport, .m_uiBufSize = 4096, .m_psName = "lua", .m_pfCheck = NULL, /* 数据报全透传: ymodem 原始字节语义 */ .m_pfDecode = NULL, }; TComCtrl *ptComCtrl = Rd_UdpCreate(&stUdpCfg); if (ptComCtrl == NULL) { return luaL_error(L, "UDP(%d, %s, %d) create failed", localport, ip, remoteport); } stm32_com_t *u = (stm32_com_t*)lua_newuserdata(L, sizeof(stm32_com_t)); u->m_ptComCtrl = ptComCtrl; u->m_iType = COM_TYPE_UDP; u->m_iOwned = 1; // 自建句柄: GC 时关闭 (重连 provision 幂等的关键) luaL_getmetatable(L, "stm32.COMMT"); lua_setmetatable(L, -2); lua_print("UDP bind %d txsrc %d -> %s:%d\n", localport, txsrcport, ip, remoteport); return 1; } #endif /* USE_LWIP */ // 模块注册 int luaopen_stm32_com(lua_State *L) { // 1. 注册 Metatable if (luaL_newmetatable(L, "stm32.COMMT")) { lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); const luaL_Reg uart_methods[] = { {"write", l_com_write}, {"read", l_com_read}, {"ry", l_com_ry}, {"sy", l_com_sy}, #ifdef USE_LWIP {"__gc", l_com_gc}, #endif {NULL, NULL} }; const luaL_Reg *l; for (l = uart_methods; l->name != NULL; l++) { lua_pushcfunction(L, l->func); lua_setfield(L, -2, l->name); } } lua_pop(L, 1); // 2. 创建模块表 lua_newtable(L); // 3. 注册构造函数 lua_pushcfunction(L, l_com_new); lua_setfield(L, -2, "new"); lua_pushcfunction(L, l_con_new); lua_setfield(L, -2, "console"); #ifdef USE_LWIP lua_pushcfunction(L, l_udp_new); lua_setfield(L, -2, "udp"); #endif return 1; } #endif