移植了letter shell 和 at command 测试通过

This commit is contained in:
小马_666 2024-11-30 13:40:35 +08:00
parent 2479103ee4
commit 9ce31192c3
40 changed files with 9864 additions and 56 deletions

View File

@ -14,7 +14,7 @@
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release" cleanCommand="${cross_rm} -rf" description="" id="ilg.gnumcueclipse.managedbuild.cross.riscv.config.elf.release.1008047074" name="obj" parent="ilg.gnumcueclipse.managedbuild.cross.riscv.config.elf.release">
<configuration artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release" cleanCommand="${cross_rm} -rf" description="" errorParsers="org.eclipse.cdt.core.GASErrorParser;org.eclipse.cdt.core.GmakeErrorParser;org.eclipse.cdt.core.GLDErrorParser;org.eclipse.cdt.core.CWDLocator;org.eclipse.cdt.core.GCCErrorParser" id="ilg.gnumcueclipse.managedbuild.cross.riscv.config.elf.release.1008047074" name="obj" parent="ilg.gnumcueclipse.managedbuild.cross.riscv.config.elf.release">
<folderInfo id="ilg.gnumcueclipse.managedbuild.cross.riscv.config.elf.release.1008047074." name="/" resourcePath="">
<toolChain id="ilg.gnumcueclipse.managedbuild.cross.riscv.toolchain.elf.release.231146001" name="RISC-V Cross GCC" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.toolchain.elf.release">
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.addtools.createflash.1311852988" name="Create flash image" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.addtools.createflash" useByScannerDiscovery="false" value="true" valueType="boolean"/>
@ -60,13 +60,18 @@
<tool id="ilg.gnumcueclipse.managedbuild.cross.riscv.tool.c.compiler.1731377187" name="GNU RISC-V Cross C Compiler" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.tool.c.compiler">
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.compiler.include.paths.1567947810" name="Include paths (-I)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.compiler.include.paths" useByScannerDiscovery="true" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/Startup}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/APP/include}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/Profile/include}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/common/AT-Command-master/include}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/common/letter-shell-master/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/common/LwUtil}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/common/lwrb}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/StdPeriphDriver/inc}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/HAL/include}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/Ld}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/LIB}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/RVMSIS}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/bsp/inc}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/APP/include}&quot;"/>
</option>
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.compiler.std.2020844713" name="Language standard" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.compiler.std" useByScannerDiscovery="true" value="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.compiler.std.gnu99" valueType="enumerated"/>
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.compiler.defs.177116515" name="Defined symbols (-D)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.compiler.defs" useByScannerDiscovery="true" valueType="definedSymbols">
@ -90,7 +95,7 @@
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/Ld/Link.ld}&quot;"/>
</option>
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.nostart.913830613" name="Do not use standard start files (-nostartfiles)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.nostart" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.usenewlibnano.239404511" name="Use newlib-nano (--specs=nano.specs)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.usenewlibnano" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.usenewlibnano.239404511" name="Use newlib-nano (--specs=nano.specs)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.usenewlibnano" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.usenewlibnosys.351964161" name="Do not use syscalls (--specs=nosys.specs)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.usenewlibnosys" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="true" id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.otherobjs.16994550" name="Other objects" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.otherobjs" useByScannerDiscovery="false" valueType="userObjs"/>
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.strip.1780837525" name="Omit all symbol information (-s)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.c.linker.strip" useByScannerDiscovery="false" value="false" valueType="boolean"/>
@ -120,7 +125,9 @@
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.cpp.linker.usenewlibnano.1540675679" name="Use newlib-nano (--specs=nano.specs)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.cpp.linker.usenewlibnano" value="true" valueType="boolean"/>
</tool>
<tool id="ilg.gnumcueclipse.managedbuild.cross.riscv.tool.archiver.1292785366" name="GNU RISC-V Cross Archiver" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.tool.archiver"/>
<tool id="ilg.gnumcueclipse.managedbuild.cross.riscv.tool.createflash.1801165667" name="GNU RISC-V Cross Create Flash Image" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.tool.createflash"/>
<tool id="ilg.gnumcueclipse.managedbuild.cross.riscv.tool.createflash.1801165667" name="GNU RISC-V Cross Create Flash Image" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.tool.createflash">
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.createflash.choice.941633854" name="Output file format (-O)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.createflash.choice" useByScannerDiscovery="false" value="ilg.gnumcueclipse.managedbuild.cross.riscv.option.createflash.choice.binary" valueType="enumerated"/>
</tool>
<tool id="ilg.gnumcueclipse.managedbuild.cross.riscv.tool.createlisting.1356766765" name="GNU RISC-V Cross Create Listing" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.tool.createlisting">
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.createlisting.source.2052761852" name="Display source (--source|-S)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.createlisting.source" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option id="ilg.gnumcueclipse.managedbuild.cross.riscv.option.createlisting.allheaders.439659821" name="Display all headers (--all-headers|-x)" superClass="ilg.gnumcueclipse.managedbuild.cross.riscv.option.createlisting.allheaders" useByScannerDiscovery="false" value="true" valueType="boolean"/>
@ -145,6 +152,8 @@
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="RVMSIS"/>
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="Startup"/>
<entry excluding="CH59x_pwm.c|CH59x_usbdev.c|CH59x_usbhostClass.c|CH59x_usbhostBase.c|CH59x_spi0.c|CH59x_timer0.c|CH59x_timer1.c|CH59x_timer2.c|CH59x_timer3.c|CH59x_uart0.c|CH59x_uart2.c|CH59x_uart3.c" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="StdPeriphDriver"/>
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="bsp"/>
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="common"/>
</sourceEntries>
</configuration>
</storageModule>
@ -166,4 +175,5 @@
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
<storageModule moduleId="refreshScope"/>
</cproject>

View File

@ -5,7 +5,7 @@
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="-427504215130445276" id="ilg.gnumcueclipse.managedbuild.cross.riscv.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT RISC-V Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} ${cross_toolchain_flags} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="-612898175655791173" id="ilg.gnumcueclipse.managedbuild.cross.riscv.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT RISC-V Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} ${cross_toolchain_flags} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<language-scope id="org.eclipse.cdt.core.gcc"/>
<language-scope id="org.eclipse.cdt.core.g++"/>
</provider>

View File

@ -0,0 +1,22 @@
eclipse.preferences.version=1
encoding//APP/bsp_wf5803.c=GBK
encoding//APP/include/bsp_wf5803.h=GBK
encoding//APP/peripheral_main.c=GBK
encoding//HAL/MCU.c=GBK
encoding//HAL/SLEEP.c=GBK
encoding//HAL/include/CONFIG.h=GBK
encoding//LIB/CH58xBLE_LIB.h=GBK
encoding//LIB/CH58xBLE_ROM.h=GBK
encoding//Ld/Link.ld=GBK
encoding//RVMSIS/core_riscv.h=GBK
encoding//Startup/startup_CH585.S=GBK
encoding//StdPeriphDriver/CH58x_sys.c=GBK
encoding//StdPeriphDriver/CH58x_uart1.c=GBK
encoding//StdPeriphDriver/inc/CH585SFR.h=GBK
encoding//bsp/inc/bsp_ml307r.h=GBK
encoding//bsp/inc/bsp_uart.h=GBK
encoding//bsp/src/bsp_ml307r.c=GBK
encoding//bsp/src/bsp_tim.c=GBK
encoding//bsp/src/bsp_uart.c=GBK
encoding//common/lwrb/lwrb.c=GBK
encoding//common/lwrb/lwrb.h=GBK

View File

@ -16,8 +16,19 @@
#include "HAL.h"
#include "gattprofile.h"
#include "peripheral.h"
#include "bsp_wf5803.h"
#include "bsp_ml307r.h"
#include "bsp_uart.h"
#include "log.h"
#undef LOG_ENABLE
#define LOG_ENABLE 1
#define SYSTICK_INTERVAL (1)
uint8_t TxBuff[] = "This is a uart3 tx example\r\n";
/*********************************************************************
* GLOBAL TYPEDEFS
@ -39,10 +50,11 @@ __HIGH_CODE
__attribute__((noinline))
void Main_Circulation()
{
PRINT("Main_Circulation\n");
logDebug("Main_Circulation\n");
while(1)
{
TMOS_SystemProcess();
}
}
@ -60,45 +72,37 @@ int main(void)
#endif
HSECFG_Capacitance(HSECap_18p);
SetSysClock(CLK_SOURCE_HSE_PLL_62_4MHz);
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)
SysTick_Config( GetSysClock() / 1000 * SYSTICK_INTERVAL); //É趨àÖàªÊ±¼ä1ms
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == FALSE)
GPIOA_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);
GPIOB_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);
#endif
#ifdef DEBUG
// GPIOA_SetBits(GPIO_Pin_14);
// GPIOPinRemap(ENABLE, RB_PIN_UART0);
// GPIOA_ModeCfg(GPIO_Pin_14, GPIO_ModeOut_PP_5mA);
// UART0_DefInit();
// GPIOA_SetBits(GPIO_Pin_9);
// // GPIOPinRemap(ENABLE, RB_PIN_UART1);
// GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
// // UART0_DefInit();
// UART1_DefInit();
/* 配置串口1先配置IO口模式再配置串口 */
GPIOA_SetBits(GPIO_Pin_9);
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU); // RXD-配置上拉输入
GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出注意先让IO口输出高电平
UART1_DefInit();
BSP_UART3_Init();
logDebug("%s", TxBuff);
#endif
PRINT("Start @ChipID=%02X\n", R8_CHIP_ID);
PRINT("%s\n", VER_LIB);
CH58x_BLEInit();
PRINT("BLE init ok\n");
HAL_Init();
PRINT("HAL init ok\n");
GAPRole_PeripheralInit();
PRINT("GAP init ok\n");
Peripheral_Init();
PRINT("Peripheral init ok\n");
BSP_Ml307r_Init();
logDebug("Start @ChipID=%02X\n", R8_CHIP_ID);
logDebug("%s\n", VER_LIB);
BSP_PRESS_Init();
PRINT("BSP init ok\n");
// CH58x_BLEInit();
// logDebug("BLE init ok\n");
// HAL_Init();
// logDebug("HAL init ok\n");
// GAPRole_PeripheralInit();
// logDebug("GAP init ok\n");
// Peripheral_Init();
// logDebug("Peripheral init ok\n");
// BSP_PRESS_Init();
// logDebug("BSP init ok\n");
Main_Circulation();
PRINT("Main_Circulation\n");
logDebug("Main_Circulation\n");
}
/******************************** endfile @ main ******************************/

View File

@ -13,6 +13,7 @@
/******************************************************************************/
/* 头文件包含 */
#include "HAL.h"
#include "bsp_uart.h"
tmosTaskID halTaskID;
uint32_t g_LLE_IRQLibHandlerLocation;
@ -233,6 +234,8 @@ void HAL_Init()
#if(defined HAL_SLEEP) && (HAL_SLEEP == TRUE)
HAL_SleepInit();
#endif
#if(defined HAL_LED) && (HAL_LED == TRUE)
HAL_LedInit();
#endif

View File

@ -28,7 +28,7 @@
__HIGH_CODE
uint32_t CH58x_LowPower(uint32_t time)
{
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == FALSE)
volatile uint32_t i;
uint32_t time_tign, time_sleep, time_curr;
unsigned long irq_status;

View File

@ -66,6 +66,9 @@ SECTIONS
*(.text.*)
*(.rodata)
*(.rodata*)
_shell_command_start = .;
KEEP (*(shellCommand))
_shell_command_end = .;
*(.sdata2.*)
*(.glue_7)
*(.glue_7t)

View File

@ -11,6 +11,8 @@
*******************************************************************************/
#include "CH58x_common.h"
#include "bsp_uart.h"
volatile MachineMode_Call_func gs_machine_mode_func;
@ -543,31 +545,37 @@ void mDelaymS(uint16_t t)
}while(--t);
}
#ifdef DEBUG
//#ifdef DEBUG
//int _write(int fd, char *buf, int size)
//{
// int i;
// for(i = 0; i < size; i++)
// {
//#if DEBUG == Debug_UART0
// while(R8_UART0_TFC == UART_FIFO_SIZE); /* 等待数据发送 */
// R8_UART0_THR = *buf++; /* 发送数据 */
//#elif DEBUG == Debug_UART1
// while(R8_UART1_TFC == UART_FIFO_SIZE); /* 等待数据发送 */
// R8_UART1_THR = *buf++; /* 发送数据 */
//#elif DEBUG == Debug_UART2
// while(R8_UART2_TFC == UART_FIFO_SIZE); /* 等待数据发送 */
// R8_UART2_THR = *buf++; /* 发送数据 */
//#elif DEBUG == Debug_UART3
// while(R8_UART3_TFC == UART_FIFO_SIZE); /* 等待数据发送 */
// R8_UART3_THR = *buf++; /* 发送数据 */
//#endif
// }
// return size;
//}
//#endif
int _write(int fd, char *buf, int size)
{
int i;
for(i = 0; i < size; i++)
{
#if DEBUG == Debug_UART0
while(R8_UART0_TFC == UART_FIFO_SIZE); /* 等待数据发送 */
R8_UART0_THR = *buf++; /* 发送数据 */
#elif DEBUG == Debug_UART1
while(R8_UART1_TFC == UART_FIFO_SIZE); /* 等待数据发送 */
R8_UART1_THR = *buf++; /* 发送数据 */
#elif DEBUG == Debug_UART2
while(R8_UART2_TFC == UART_FIFO_SIZE); /* 等待数据发送 */
R8_UART2_THR = *buf++; /* 发送数据 */
#elif DEBUG == Debug_UART3
while(R8_UART3_TFC == UART_FIFO_SIZE); /* 等待数据发送 */
R8_UART3_THR = *buf++; /* 发送数据 */
#endif
}
BSP_Uart3_Send_Data(buf, size);
return size;
}
#endif
/*********************************************************************
* @fn _sbrk
*

40
bsp/inc/bsp_ml307r.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef __BSP_ML307R_H__
#define __BSP_ML307R_H__
#include "CH58x_common.h"
/*4G 친욥왠齡多신*/
#define ENABLE_3_8_V GPIO_Pin_3 //PB3
#define USIM_DECT_PIN GPIO_Pin_5 //PB5
#define ML307_PWR_PIN GPIO_Pin_6 //PB6
#define ML307_RST_PIN GPIO_Pin_7 //PB7
#define ML307_UART_TX_PIN GPIO_Pin_13 //PB13
#define ML307_UART_RX_PIN GPIO_Pin_12 //PB12
void BSP_Ml307r_Init(void);
void Ml307r_Loop(void);
#endif //!@__BSP_ML307R_H__

28
bsp/inc/bsp_tim.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef __BSP_TIM_H__
#define __BSP_TIM_H__
#include "CH58x_common.h"
uint32_t BSP_Get_Tick(void);
#endif //!@__BSP_TIM_H__

37
bsp/inc/bsp_uart.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef __BSP_UART_H__
#define __BSP_UART_H__
#include "CH585SFR.h"
#define UART3_RX_PIN GPIO_Pin_20 //PB20 ÈÕÖ¾´òÓ¡
#define UART3_TX_PIN GPIO_Pin_21 // PB21
void BSP_UART1_Init(void);
unsigned int BSP_Uart1_Receive_Data(void *buf, unsigned int len);
unsigned int BSP_Uart1_Send_Data(const void *buf, unsigned int len);
void BSP_UART3_Init(void);
unsigned int BSP_Uart3_Receive_Data(void *buf, unsigned int len);
unsigned int BSP_Uart3_Send_Data(const void *buf, unsigned int len);
void BSP_Shell_Loop(void);
#endif

266
bsp/src/bsp_ml307r.c Normal file
View File

@ -0,0 +1,266 @@
#include "bsp_ml307r.h"
#include "at_chat.h"
#include "at_port.h"
#include "bsp_uart.h"
#include "log.h"
#include "stdio.h"
#include "CONFIG.h"
#undef LOG_ENABLE
#define LOG_ENABLE 1
#define ML307_PERIODIC_EVT (0x0001 << 0)
static tmosTaskID ml307_task_id = INVALID_TASK_ID;
at_obj_t *ml307r_obj;
void At_Debug(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
void Ml307_Delay_Ms(uint16_t ms)
{
DelayMs(ms);
}
void Ml307_Delay_us(uint16_t us)
{
DelayUs(us);
}
static const at_adapter_t at_adapter =
{
.write = BSP_Uart1_Send_Data,
.read = BSP_Uart1_Receive_Data,
.debug = At_Debug,
.recv_bufsize = 256,
.urc_bufsize = 512
};
//模块准备就绪
static int URC_Module_Ready_Cb(at_urc_info_t *info)
{
logDebug("module ready complete\r\n");
return 0;
}
/**
* @brief urc订阅表
*/
static const urc_item_t urc_table[] =
{
{.prefix = "+CPIN: READY", .endmark = '\n', .handler = URC_Module_Ready_Cb},//模块准备就绪
};
static void Ml307_Power_On(void)
{
#define ML307_POWER_ON_TIME 3
GPIOB_SetBits(ML307_PWR_PIN);
Ml307_Delay_Ms(ML307_POWER_ON_TIME * 1000);
GPIOB_ResetBits(ML307_PWR_PIN);
}
static void Ml307_Power_Off(void)
{
#define M307_POWER_OFF_TIME 4
GPIOB_SetBits(ML307_PWR_PIN);
Ml307_Delay_Ms(M307_POWER_OFF_TIME * 1000);
GPIOB_ResetBits(ML307_PWR_PIN);
Ml307_Delay_Ms(100);
}
int Ml307r_Gpio_Init(void)
{
GPIOB_SetBits(ENABLE_3_8_V);
GPIOB_ModeCfg(ENABLE_3_8_V, GPIO_ModeOut_PP_5mA);
GPIOB_ResetBits(ML307_RST_PIN);
GPIOB_ModeCfg(ML307_RST_PIN, GPIO_ModeOut_PP_5mA);
GPIOB_ModeCfg(USIM_DECT_PIN, GPIO_ModeIN_Floating);
GPIOB_ModeCfg(ML307_PWR_PIN, GPIO_ModeOut_PP_5mA);
return 0;
}
static int Module_Read_State(at_env_t *e)
{
switch (e->state)
{
case 0:
e->obj->adap->debug("Check if the Module is ready\r\n");
e->println(e, "AT");
e->reset_timer(e);
e->state++;
break;
case 1:
if (e->contains(e, "OK"))
{
e->recvclr(e);
e->finish(e, AT_RESP_OK);
e->obj->adap->debug("Module is ready\r\n");
}
if (e->is_timeout(e, 5000))
{
e->state--;
if (++e->i > 3)
{
e->obj->adap->debug("Module error\r\n");
e->finish(e, AT_RESP_ERROR);
}
}
break;
}
return 0;
}
static void Module_Get_Imei_Cb(at_response_t *r)
{
char imei[16] = {0};
if (r->code == AT_RESP_OK)
{
if (sscanf(r->prefix, "+GSN:%s", imei) == 1)
{
r->obj->adap->debug("imei: %s\r\n", r->prefix);
}
}
else
{
r->obj->adap->debug("'AT+GSN=1' command response failed!\r\n");
}
}
/*
* @brief IMEI数据
* @return
*/
static void Module_Get_Imei(void)
{
at_attr_t attr;
at_attr_deinit(&attr);
attr.prefix = "+GSN:";
attr.cb = Module_Get_Imei_Cb;
at_send_singlline(ml307r_obj, &attr, "AT+GSN=1");
}
static void BSP_Read_Module(void)
{
at_do_work(ml307r_obj, NULL, Module_Read_State); // 重启后生效
}
static void Module_Get_Imsi_Cb(at_response_t *r)
{
char imsi[60] = {0};
if (r->code == AT_RESP_OK)
{
if (sscanf(r->prefix, "%s\r\n", imsi) == 1)
{
r->obj->adap->debug("imsi:%s\r\n", imsi);
}
}
else
{
r->obj->adap->debug("'AT+CIMI' command response failed!\r\n");
}
}
/*
* @brief IMSI数据
* @return
*/
static void Module_Get_Imsi(void)
{
at_attr_t attr;
at_attr_deinit(&attr);
attr.prefix = "46";
attr.suffix = "\r\n";
attr.cb = Module_Get_Imsi_Cb;
at_send_singlline(ml307r_obj, &attr, "AT+CIMI");
}
static void Module_Get_Iccid_Cb(at_response_t *r)
{
char iccid[21] = {0};
if (r->code == AT_RESP_OK)
{
if (sscanf(r->prefix, "+MCCID: %s\r\n", iccid) == 1)
{
r->obj->adap->debug("iccid: %s\r\n", iccid);
}
}
else
{
r->obj->adap->debug("'AT+QCCID' command response failed!\r\n");
}
}
/*
* @brief ICCID数据
* @return
*/
static void Module_Get_Iccid(void)
{
at_attr_t attr;
at_attr_deinit(&attr);
attr.prefix = "+MCCID:";
attr.cb = Module_Get_Iccid_Cb;
at_send_singlline(ml307r_obj, &attr, "AT+MCCID");
}
void Ml307r_Loop(void)
{
at_obj_process(ml307r_obj);
}
__HIGH_CODE
__attribute__((noinline))
uint16_t Ml307r_Handle(uint8_t task_id, uint16_t events)
{
if (events & ML307_PERIODIC_EVT)
{
Ml307r_Loop();
tmos_start_task(ml307_task_id, ML307_PERIODIC_EVT, MS1_TO_SYSTEM_TIME(5));
return (events ^ ML307_PERIODIC_EVT);
}
return 0;
}
void BSP_Ml307r_Init(void)
{
Ml307r_Gpio_Init();
Ml307_Power_On();
BSP_UART1_Init();
ml307r_obj = at_obj_create(&at_adapter);//Create AT communication object
at_obj_set_urc(ml307r_obj, urc_table, sizeof(urc_table) / sizeof(urc_table[0]));
ml307_task_id = TMOS_ProcessEventRegister(Ml307r_Handle);
tmos_set_event(ml307_task_id, ML307_PERIODIC_EVT);
BSP_Read_Module();
Module_Get_Imei();
Module_Get_Imsi();
Module_Get_Iccid();
}

30
bsp/src/bsp_tim.c Normal file
View File

@ -0,0 +1,30 @@
#include "bsp_tim.h"
// tick_1ms_cnt在SysTick_Handler()中1ms +1
volatile uint32_t tick_1ms_cnt = 0;
/**
* @description: SysTick1ms
* @return {uint32_t}
*/
uint32_t BSP_Get_Tick(void)
{
/* Platform implementation */
return tick_1ms_cnt;
}
// SysTick中断函数
__INTERRUPT
__HIGH_CODE
void SysTick_Handler()
{
static uint8_t cnt_ms = 0;
SysTick->SR = 0; // 清除中断标志
tick_1ms_cnt++;
cnt_ms++;
}

227
bsp/src/bsp_uart.c Normal file
View File

@ -0,0 +1,227 @@
#include "bsp_uart.h"
#include "bsp_ml307r.h"
#include "lwrb.h"
#include "CH58x_uart.h"
#include "shell_port.h"
#define UART1_RX_BUFFER_LENGTH 512U
#define UART1_TX_BUFFER_LENGTH 512U
#define UART3_RX_BUFFER_LENGTH 1024U
#define UART3_TX_BUFFER_LENGTH 1024U
lwrb_t uart1_rx_t;
lwrb_t uart1_tx_t;
uint8_t uart1_tx_buf[UART1_TX_BUFFER_LENGTH] = {0};
uint8_t uart1_rx_buf[UART1_RX_BUFFER_LENGTH] = {0};
lwrb_t uart3_rx_t;
lwrb_t uart3_tx_t;
uint8_t uart3_tx_buf[UART3_TX_BUFFER_LENGTH] = {0};
uint8_t uart3_rx_buf[UART3_RX_BUFFER_LENGTH] = {0};
//串口接收
unsigned int BSP_Uart1_Receive_Data(void *buf, unsigned int len)
{
return lwrb_read(&uart1_rx_t, buf, len);
}
//串口发送数据
unsigned int BSP_Uart1_Send_Data(const void *buf, unsigned int len)
{
unsigned int ret;
ret = lwrb_write(&uart1_tx_t, buf, len);
UART1_INTCfg(ENABLE, RB_IER_THR_EMPTY);
return ret;
}
//串口接收
unsigned int BSP_Uart3_Receive_Data(void *buf, unsigned int len)
{
return lwrb_write(&uart3_rx_t, buf, len);
}
//串口发送数据
unsigned int BSP_Uart3_Send_Data(const void *buf, unsigned int len)
{
unsigned int ret;
ret = lwrb_write(&uart3_tx_t, buf, len);
UART3_INTCfg(ENABLE, RB_IER_THR_EMPTY);
return ret;
}
/**
* \brief Buffer event function
*/
static void Uart1_evt_fn(struct lwrb* buff, lwrb_evt_type_t evt, lwrb_sz_t bp)
{
switch (evt)
{
case LWRB_EVT_RESET:
printf("[EVT] Buffer reset event!\r\n");
break;
case LWRB_EVT_READ:
printf("[EVT] Buffer read event: %d byte(s)!\r\n", (int)bp);
break;
case LWRB_EVT_WRITE:
printf("[EVT] Buffer write event: %d byte(s)!\r\n", (int)bp);
break;
default: break;
}
}
void UART1_FifoInit(void)
{
lwrb_init(&uart1_tx_t, uart1_tx_buf, sizeof(uart1_tx_buf));
lwrb_init(&uart1_rx_t, uart1_rx_buf, sizeof(uart1_rx_buf));
// lwrb_set_evt_fn(&uart1_rx_t, Uart1_evt_fn);
}
void UART3_FifoInit(void)
{
lwrb_init(&uart3_tx_t, uart3_tx_buf, sizeof(uart3_tx_buf));
lwrb_init(&uart3_rx_t, uart3_rx_buf, sizeof(uart3_rx_buf));
lwrb_set_evt_fn(&uart1_rx_t, Uart1_evt_fn);
}
void BSP_UART1_Init(void)
{
GPIOPinRemap(ENABLE, RB_PIN_UART1);
/* 配置串口1先配置IO口模式再配置串口 */
GPIOB_SetBits(ML307_UART_RX_PIN);
GPIOB_ModeCfg(ML307_UART_RX_PIN, GPIO_ModeIN_PU); // RXD-配置上拉输入
GPIOB_ModeCfg(ML307_UART_TX_PIN, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出注意先让IO口输出高电平
UART1_DefInit();
UART1_ByteTrigCfg(UART_1BYTE_TRIG);
// 中断方式接收数据
UART1_INTCfg(ENABLE, RB_IER_LINE_STAT | RB_IER_RECV_RDY | RB_IER_THR_EMPTY);
PFIC_EnableIRQ(UART1_IRQn);
UART1_FifoInit();
}
void BSP_UART3_Init(void)
{
GPIOPinRemap(ENABLE, RB_PIN_UART3);
/* 配置串口3先配置IO口模式再配置串口 */
GPIOB_SetBits(UART3_TX_PIN);
GPIOB_ModeCfg(UART3_RX_PIN, GPIO_ModeIN_PU); // RXD-配置上拉输入
GPIOB_ModeCfg(UART3_TX_PIN, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出注意先让IO口输出高电平
UART3_DefInit();
UART3_ByteTrigCfg(UART_1BYTE_TRIG);
// 中断方式接收数据
UART3_INTCfg(ENABLE, RB_IER_LINE_STAT | RB_IER_RECV_RDY | RB_IER_THR_EMPTY);
PFIC_EnableIRQ(UART3_IRQn);
UART3_FifoInit();
userShellInit();
}
/*********************************************************************
* @fn UART1_IRQHandler
*
* @brief UART1中断函数
*
* @return none
*/
__INTERRUPT
__HIGH_CODE
void UART1_IRQHandler(void)
{
uint8_t data;
switch(UART1_GetITFlag())
{
case UART_II_LINE_STAT: // 线路状态错误
{
// UART1_GetLinSTA();
break;
}
case UART_II_RECV_RDY:
data = UART1_RecvByte();
lwrb_write(&uart1_rx_t, &data, 1);
break;
case UART_II_RECV_TOUT: // 接收数据
break;
case UART_II_THR_EMPTY: // 发送缓存区空,可继续发送
if(lwrb_read(&uart1_tx_t, &data, 1))
{
UART1_SendByte(data);
}
else
{
UART1_INTCfg(DISABLE, RB_IER_THR_EMPTY);
}
break;
case UART_II_MODEM_CHG: // 只支持串口0
break;
default:
break;
}
}
/*********************************************************************
* @fn UART3_IRQHandler
*
* @brief UART1中断函数
*
* @return none
*/
__INTERRUPT
__HIGH_CODE
void UART3_IRQHandler(void)
{
uint8_t data;
switch(UART3_GetITFlag())
{
case UART_II_LINE_STAT: // 线路状态错误
{
// UART1_GetLinSTA();
break;
}
case UART_II_RECV_RDY:
case UART_II_RECV_TOUT: //接收超时
while(R8_UART3_RFC)
{
shellHandler(&shell, R8_UART3_RBR);
}
break;
case UART_II_THR_EMPTY: // 发送缓存区空,可继续发送
if(lwrb_get_full(&uart3_tx_t))
{
lwrb_read(&uart3_tx_t, &data, 1);
UART3_SendByte(data);
}
else
{
UART3_INTCfg(DISABLE, RB_IER_THR_EMPTY);
}
break;
case UART_II_MODEM_CHG: // 只支持串口0
break;
default:
break;
}
}

View File

@ -0,0 +1,347 @@
/******************************************************************************
* @brief AT command communication management V2
*
* Copyright (c) 2020~2022, <morro_luo@163.com>
*
* SPDX-License-Identifier: Apathe-2.0
*
* Change Logs:
* Date Author Notes
* 2020-01-02 roger.luo Initial version
* 2021-01-20 roger.luo Add debugging interface, and at the same time
* solve the problem of uninitialized linked list
* leading to segment fault.
* 2021-03-03 roger.luo Fix the problem of frequent print receive timeout
* caused by not clearing the URC counter.
* 2022-10-01 roger.luo Redesign the entire AT framework and release V2.0.
* 2023-01-09 roger.luo Optimized the processing logic when multi-line
* command execution fails.
* 2023-01-10 roger.luo Resolve URC message containing ':' character
* causing parsing failure.
* 2023-02-23 roger.luo Added a temporary disable interface for URC to
* solve the problem of URC matching exception when
* receiving binary data from the modem.
* 2023-10-27 roger.luo Added the function of transparent data transmission.
******************************************************************************/
#ifndef _AT_CHAT_H_
#define _AT_CHAT_H_
#include "at_port.h"
#include <stdbool.h>
#include <stdarg.h>
struct at_obj;
struct at_adapter;
struct at_response;
/**
*@brief AT work running state.
*/
typedef enum {
AT_WORK_STAT_IDLE,
AT_WORK_STAT_READY,
AT_WORK_STAT_RUN,
AT_WORK_STAT_FINISH,
AT_WORK_STAT_ABORT,
} at_work_state;
/**
*@brief AT command response code
*/
typedef enum {
AT_RESP_OK = 0,
AT_RESP_ERROR,
AT_RESP_TIMEOUT,
AT_RESP_ABORT
} at_resp_code;
/**
*@brief AT command request priority
*/
typedef enum {
AT_PRIORITY_LOW = 0,
AT_PRIORITY_HIGH
} at_cmd_priority;
/**
*@brief URC frame receiving status
*/
typedef enum {
URC_RECV_OK = 0, /* URC frame received successfully. */
URC_RECV_TIMEOUT /* Receive timeout (The frame prefix is matched but the suffix is not matched within AT_URC_TIMEOUT) */
} urc_recv_status;
/**
* @brief URC frame info.
*/
typedef struct {
urc_recv_status status; /* URC frame receiving status.*/
char *urcbuf; /* URC frame buffer*/
int urclen; /* URC frame buffer length*/
} at_urc_info_t;
/**
* @brief URC subscription item.
*/
typedef struct {
const char *prefix; /* URC frame prefix,such as '+CSQ:'*/
const char endmark; /* URC frame end mark (can only be selected from AT_URC_END_MARKS)*/
/**
* @brief URC handler (triggered when matching prefix and mark are match)
* @params info - URC frame info.
* @return Indicates the remaining unreceived bytes of the current URC frame.
* @retval 0 Indicates that the current URC frame has been completely received
* @retval n It still needs to wait to receive n bytes (AT manager continues
* to receive the remaining data and continues to call back this interface).
*/
int (*handler)(at_urc_info_t *info);
} urc_item_t;
/**
* @brief AT response information
*/
typedef struct {
struct at_obj *obj; /* AT object*/
void *params; /* User parameters (referenced from ->at_attr_t.params)*/
at_resp_code code; /* AT command response code.*/
unsigned short recvcnt; /* Receive data length*/
char *recvbuf; /* Receive buffer (raw data)*/
/* Pointer to the receiving content prefix, valid when code=AT_RESP_OK,
if no prefix is specified, it pointer to recvbuf
*/
char *prefix;
/* Pointer to the receiving content suffix, valid when code=AT_RESP_OK,
if no suffix is specified, it pointer to recvbuf
*/
char *suffix;
} at_response_t;
/**
* @brief The configuration for transparent transmission mode.
*/
typedef struct {
/**
* @brief Exit command(Example:AT+TRANS=0). When this command is matched through the
* read interface, the on_exit event is generated.
*/
const char *exit_cmd;
/**
* @brief Exit event, triggered when the exit command is currently matched. At this time,
* you can invoke 'at_raw_transport_exit' to exit the transparent transport mode.
*/
void (*on_exit)(void);
/**
* @brief Writting interface for transparent data transmission
* @param buf Data buffer
* @param len data length
* @return Indicates the length of the written data
*/
unsigned int (*write)(const void *buf, unsigned int len);
/**
* @brief Reading interface for transparent data transmission
* @param buf Data buffer
* @param len data length
* @return The length of the data actually read
*/
unsigned int (*read) (void *buf, unsigned int len);
} at_raw_trans_conf_t;
/**
* @brief AT interface adapter
*/
typedef struct {
//Lock, used in OS environment, fill in NULL if not required.
void (*lock)(void);
//Unlock, used in OS environment, fill in NULL if not required.
void (*unlock)(void);
/**
* @brief Data write operation (non-blocking)
* @param buf Data buffer
* @param len data length
* @return Indicates the length of the written data
*/
unsigned int (*write)(const void *buf, unsigned int len);
/**
* @brief Data read operation (non-blocking)
* @param buf Data buffer
* @param len data length
* @return The length of the data actually read
*/
unsigned int (*read)(void *buf, unsigned int len);
/**
* @brief AT error event ( if not required, fill in NULL)
*/
void (*error)(at_response_t *);
/**
* @brief Log output interface, which can print the complete AT interaction
* process, fill in NULL if not required.
*/
void (*debug)(const char *fmt, ...);
#if AT_URC_WARCH_EN
//URC buffer size, set according to the actual maximum URC frame when used.
unsigned short urc_bufsize;
#endif
//Command response receiving buffer size, set according to the actual maximum command response length
unsigned short recv_bufsize;
} at_adapter_t;
/**
* @brief Public environment for AT work
*/
typedef struct at_env {
struct at_obj *obj;
//Public variables (add as needed), these values are reset every time a new work starts.
int i, j, state;
//User parameters (referenced from ->at_attr_t.params)
void *params;
//Set the next polling wait interval (only takes effect once)
void (*next_wait)(struct at_env *self, unsigned int ms);
//Reset timer
void (*reset_timer)(struct at_env *self);
//Timeout indication
bool (*is_timeout)(struct at_env *self, unsigned int ms);
//Formatted printout with newlines
void (*println)(struct at_env *self, const char *fmt, ...);
//Find a keyword from the received content
char * (*contains)(struct at_env *self, const char *str);
//Get receives buffer
char * (*recvbuf)(struct at_env *self);
//Get receives buffer length
unsigned int(*recvlen)(struct at_env *self);
//Clear the receives buffer
void (*recvclr)(struct at_env *self);
//Indicates whether the current work has been abort
bool (*disposing)(struct at_env *self);
//End the work and set the response code
void (*finish)(struct at_env *self, at_resp_code code);
} at_env_t;
/**
*@brief AT execution callback
*@param r AT response information (including execution results, content information returned by the device)
*/
typedef void (*at_callback_t)(at_response_t *r);
/**
*@brief AT work polling handler
*@param env The public operating environment for AT work, including some common
* variables and relevant interfaces needed to communicate AT commands.
*@return Work processing status, which determines whether to continue running the
* work on the next polling cycle.
*
* @retval true Indicates that the current work processing has been finished,
* and the work response code is set to AT_RESP_OK
* @retval false Indicates unfinished work processing, keep running.
*@note It must be noted that if the env->finish() operation is invoked in the
* current work, the work will be forcibly terminated regardless of the return value.
*/
typedef int (*at_work_t)(at_env_t *env);
#if AT_WORK_CONTEXT_EN
/**
*@brief AT work item context (used to monitor the entire life cycle of AT work item)
*/
typedef struct {
at_work_state work_state; /* Indicates the state at which the AT work item is running. */
at_resp_code code; /* Indicates the response code after the AT command has been run.*/
unsigned short bufsize; /* Indicates receive buffer size*/
unsigned short resplen; /* Indicates the actual response valid data length*/
unsigned char *respbuf; /* Point to the receive buffer*/
} at_context_t;
#endif
/**
*@brief AT attributes
*/
typedef struct {
#if AT_WORK_CONTEXT_EN
at_context_t *ctx; /* Pointer to the Work context. */
#endif
void *params; /* User parameter, fill in NULL if not required. */
const char *prefix; /* Response prefix, fill in NULL if not required. */
const char *suffix; /* Response suffix, fill in NULL if not required. */
at_callback_t cb; /* Response callback handler, Fill in NULL if not required. */
unsigned short timeout; /* Response timeout(ms).. */
unsigned char retry; /* Response error retries. */
at_cmd_priority priority; /* Command execution priority. */
} at_attr_t;
/**
*@brief AT object.
*/
typedef struct at_obj {
const at_adapter_t *adap;
#if AT_RAW_TRANSPARENT_EN
const at_raw_trans_conf_t *raw_conf;
#endif
void *user_data;
} at_obj_t;
at_obj_t *at_obj_create(const at_adapter_t *);
void at_obj_destroy(at_obj_t *at);
bool at_obj_busy(at_obj_t *at);
void at_obj_set_user_data(at_obj_t *at, void *user_data);
void *at_obj_get_user_data(at_obj_t *at);
#if AT_URC_WARCH_EN
void at_obj_set_urc(at_obj_t *at, const urc_item_t *tbl, int count);
int at_obj_get_urcbuf_count(at_obj_t *at);
void at_obj_urc_set_enable(at_obj_t *at, int enable, unsigned short timeout);
#endif
void at_obj_process(at_obj_t *at);
void at_attr_deinit(at_attr_t *attr);
bool at_exec_cmd(at_obj_t *at, const at_attr_t *attr, const char *cmd, ...);
bool at_exec_vcmd(at_obj_t *at, const at_attr_t *attr, const char *cmd, va_list va);
bool at_send_singlline(at_obj_t *at, const at_attr_t *attr, const char *singlline);
bool at_send_multiline(at_obj_t *at, const at_attr_t *attr, const char **multiline);
bool at_send_data(at_obj_t *at, const at_attr_t *attr, const void *databuf, unsigned int bufsize);
bool at_custom_cmd(at_obj_t *at, const at_attr_t *attr, void (*sender)(at_env_t *env));
bool at_do_work(at_obj_t *at, void *params, at_work_t work);
void at_work_abort_all(at_obj_t *at);
#if AT_MEM_WATCH_EN
unsigned int at_max_used_memory(void);
unsigned int at_cur_used_memory(void);
#endif
#if AT_WORK_CONTEXT_EN
void at_context_init(at_context_t *ctx, void *respbuf, unsigned bufsize);
void at_context_attach(at_attr_t *attr, at_context_t *ctx);
at_work_state at_work_get_state(at_context_t *ctx);
bool at_work_is_finish(at_context_t *ctx);
at_resp_code at_work_get_result(at_context_t *ctx);
#endif //End of AT_WORK_CONTEXT_EN
#if AT_RAW_TRANSPARENT_EN
void at_raw_transport_enter(at_obj_t *obj, const at_raw_trans_conf_t *conf);
void at_raw_transport_exit(at_obj_t *obj);
#endif //End of AT_RAW_TRANSPARENT_EN
#endif //End of _AT_CHAT_H_

View File

@ -0,0 +1,80 @@
/**
* @Brief: The AT component drives the interface implementation
* @Author: roger.luo
* @Date: 2021-04-04
* @Last Modified by: roger.luo
* @Last Modified time: 2021-11-27
*/
#ifndef __AT_PORT_H__
#define __AT_PORT_H__
/**
*@brief Default correct response identifier.
*/
#define AT_DEF_RESP_OK "OK"
/**
*@brief Default error response identifier.
*/
#define AT_DEF_RESP_ERR "ERROR"
/**
*@brief Default command timeout (ms)
*/
#define AT_DEF_TIMEOUT 500
/**
*@brief Number of retries when a command timeout/error occurs.
*/
#define AT_DEF_RETRY 3
/**
*@brief Default URC frame receive timeout (ms).
*/
#define AT_URC_TIMEOUT 500
/**
*@brief Maximum AT command send data length (only for variable parameter commands).
*/
#define AT_MAX_CMD_LEN 256
/**
*@brief Maximum number of work in queue (limit memory usage).
*/
#define AT_LIST_WORK_COUNT 32
/**
*@brief Enable URC watcher.
*/
#define AT_URC_WARCH_EN 1
/**
*@brief A list of specified URC end marks (fill in as needed, the fewer the better).
*/
#define AT_URC_END_MARKS ":,\n"
/**
*@brief Enable memory watcher.
*/
#define AT_MEM_WATCH_EN 1u
/**
*@brief Maximum memory usage limit (Valid when AT_MEM_WATCH_EN is enabled)
*/
#define AT_MEM_LIMIT_SIZE (8 * 1024)
/**
*@brief Enable AT work context interfaces.
*/
#define AT_WORK_CONTEXT_EN 1u
/**
* @brief Supports raw data transparent transmission
*/
#define AT_RAW_TRANSPARENT_EN 1u
void *at_malloc(unsigned int nbytes);
void at_free(void *ptr);
unsigned int at_get_ms(void);
#endif

View File

@ -0,0 +1,705 @@
#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H
#include <stddef.h>
#if !defined(__GNUC__)
#define typeof (struct list_head)
#endif
#undef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#if !defined(__GNUC__)
#define container_of(ptr, type, member) ( \
(type *)( (char *)(ptr) - offsetof(type,member) ))
#else
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#endif
/* copy from <linux/poison.h>, */
/*
* used to verify that nobody uses non-initialized list entries.
*/
#define LIST_POISON1 ((void *) 0x0)
#define LIST_POISON2 ((void *) 0x0)
#ifndef ARCH_HAS_PREFETCH
#define ARCH_HAS_PREFETCH
static inline void prefetch(const void *x) {;}
#endif
/*
* Simple doubly linked list implementation.
*
* Some of the internal functions ("__xxx") are useful when
* manipulating whole lists rather than single entries, as
* sometimes we already know the next/prev entries and we can
* generate better code by using them directly rather than
* using the generic single-entry routines.
*/
struct list_head {
struct list_head *next, *prev;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
/*
* Delete a list entry by making the prev/next entries
* point to each other.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
next->prev = prev;
prev->next = next;
}
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
/**
* list_replace - replace old entry by new one
* @old : the element to be replaced
* @new : the new element to insert
*
* If @old was empty, it will be overwritten.
*/
static inline void list_replace(struct list_head *old,
struct list_head *new)
{
new->next = old->next;
new->next->prev = new;
new->prev = old->prev;
new->prev->next = new;
}
static inline void list_replace_init(struct list_head *old,
struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old);
}
/**
* list_del_init - deletes entry from list and reinitialize it.
* @entry: the element to delete from the list.
*/
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
/**
* list_move - delete from one list and add as another's head
* @list: the entry to move
* @head: the head that will precede our entry
*/
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del(list->prev, list->next);
list_add(list, head);
}
/**
* list_move_tail - delete from one list and add as another's tail
* @list: the entry to move
* @head: the head that will follow our entry
*/
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del(list->prev, list->next);
list_add_tail(list, head);
}
/**
* list_is_last - tests whether @list is the last entry in list @head
* @list: the entry to test
* @head: the head of the list
*/
static inline int list_is_last(const struct list_head *list,
const struct list_head *head)
{
return list->next == head;
}
/**
* list_empty - tests whether a list is empty
* @head: the list to test.
*/
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
/**
* list_empty_careful - tests whether a list is empty and not being modified
* @head: the list to test
*
* Description:
* tests whether a list is empty _and_ checks that no other CPU might be
* in the process of modifying either member (next or prev)
*
* NOTE: using list_empty_careful() without synchronization
* can only be safe if the only activity that can happen
* to the list entry is list_del_init(). Eg. it cannot be used
* if another CPU could re-list_add() it.
*/
static inline int list_empty_careful(const struct list_head *head)
{
struct list_head *next = head->next;
return (next == head) && (next == head->prev);
}
/**
* list_is_singular - tests whether a list has just one entry.
* @head: the list to test.
*/
static inline int list_is_singular(const struct list_head *head)
{
return !list_empty(head) && (head->next == head->prev);
}
static inline void __list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
{
struct list_head *new_first = entry->next;
list->next = head->next;
list->next->prev = list;
list->prev = entry;
entry->next = list;
head->next = new_first;
new_first->prev = head;
}
/**
* list_cut_position - cut a list into two
* @list: a new list to add all removed entries
* @head: a list with entries
* @entry: an entry within head, could be the head itself
* and if so we won't cut the list
*
* This helper moves the initial part of @head, up to and
* including @entry, from @head to @list. You should
* pass on @entry an element you know is on @head. @list
* should be an empty list or a list you do not care about
* losing its data.
*
*/
static inline void list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
{
if (list_empty(head))
return;
if (list_is_singular(head) &&
(head->next != entry && head != entry))
return;
if (entry == head)
INIT_LIST_HEAD(list);
else
__list_cut_position(list, head, entry);
}
static inline void __list_splice(const struct list_head *list,
struct list_head *prev,
struct list_head *next)
{
struct list_head *first = list->next;
struct list_head *last = list->prev;
first->prev = prev;
prev->next = first;
last->next = next;
next->prev = last;
}
/**
* list_splice - join two lists, this is designed for stacks
* @list: the new list to add.
* @head: the place to add it in the first list.
*/
static inline void list_splice(const struct list_head *list,
struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head, head->next);
}
/**
* list_splice_tail - join two lists, each list being a queue
* @list: the new list to add.
* @head: the place to add it in the first list.
*/
static inline void list_splice_tail(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head->prev, head);
}
/**
* list_splice_init - join two lists and reinitialise the emptied list.
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* The list at @list is reinitialised
*/
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
}
/**
* list_splice_tail_init - join two lists and reinitialise the emptied list
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* Each of the lists is a queue.
* The list at @list is reinitialised
*/
static inline void list_splice_tail_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head->prev, head);
INIT_LIST_HEAD(list);
}
}
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* list_first_entry - get the first element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*
* Note, that list is expected to be not empty.
*/
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
/**
* __list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*
* This variant differs from list_for_each() in that it's the
* simplest possible list iteration code, no prefetching is done.
* Use this for code that knows the list to be very short (empty
* or 1 entry) most of the time.
*/
#define __list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/**
* list_for_each_prev - iterate over a list backwards
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \
pos = pos->prev)
/**
* list_for_each_safe - iterate over a list safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/**
* list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_prev_safe(pos, n, head) \
for (pos = (head)->prev, n = pos->prev; \
prefetch(pos->prev), pos != (head); \
pos = n, n = pos->prev)
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
prefetch(pos->member.next), &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_reverse - iterate backwards over list of given type.
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member); \
prefetch(pos->member.prev), &pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
* list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
* @pos: the type * to use as a start point
* @head: the head of the list
* @member: the name of the list_struct within the struct.
*
* Prepares a pos entry for use as a start point in list_for_each_entry_continue().
*/
#define list_prepare_entry(pos, head, member) \
((pos) ? : list_entry(head, typeof(*pos), member))
/**
* list_for_each_entry_continue - continue iteration over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Continue to iterate over list of given type, continuing after
* the current position.
*/
#define list_for_each_entry_continue(pos, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member); \
prefetch(pos->member.next), &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_continue_reverse - iterate backwards from the given point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Start to iterate over list of given type backwards, continuing after
* the current position.
*/
#define list_for_each_entry_continue_reverse(pos, head, member) \
for (pos = list_entry(pos->member.prev, typeof(*pos), member); \
prefetch(pos->member.prev), &pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
* list_for_each_entry_from - iterate over list of given type from the current point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type, continuing from current position.
*/
#define list_for_each_entry_from(pos, head, member) \
for (; prefetch(pos->member.next), &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_continue
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type, continuing after current point,
* safe against removal of list entry.
*/
#define list_for_each_entry_safe_continue(pos, n, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_from
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type from current point, safe against
* removal of list entry.
*/
#define list_for_each_entry_safe_from(pos, n, head, member) \
for (n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_reverse
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate backwards over list of given type, safe against removal
* of list entry.
*/
#define list_for_each_entry_safe_reverse(pos, n, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member), \
n = list_entry(pos->member.prev, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.prev, typeof(*n), member))
/*
* Double linked lists with a single pointer list head.
* Mostly useful for hash tables where the two pointer list head is
* too wasteful.
* You lose the ability to access the tail in O(1).
*/
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
h->next = NULL;
h->pprev = NULL;
}
static inline int hlist_unhashed(const struct hlist_node *h)
{
return !h->pprev;
}
static inline int hlist_empty(const struct hlist_head *h)
{
return !h->first;
}
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next;
if (next)
next->pprev = pprev;
}
static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}
static inline void hlist_del_init(struct hlist_node *n)
{
if (!hlist_unhashed(n)) {
__hlist_del(n);
INIT_HLIST_NODE(n);
}
}
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
struct hlist_node *first = h->first;
n->next = first;
if (first)
first->pprev = &n->next;
h->first = n;
n->pprev = &h->first;
}
/* next must be != NULL */
static inline void hlist_add_before(struct hlist_node *n,
struct hlist_node *next)
{
n->pprev = next->pprev;
n->next = next;
next->pprev = &n->next;
*(n->pprev) = n;
}
static inline void hlist_add_after(struct hlist_node *n,
struct hlist_node *next)
{
next->next = n->next;
n->next = next;
next->pprev = &n->next;
if(next->next)
next->next->pprev = &next->next;
}
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
#define hlist_for_each(pos, head) \
for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
pos = pos->next)
#define hlist_for_each_safe(pos, n, head) \
for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
pos = n)
/**
* hlist_for_each_entry - iterate over list of given type
* @tpos: the type * to use as a loop cursor.
* @pos: the &struct hlist_node to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry(tpos, pos, head, member) \
for (pos = (head)->first; \
pos && ({ prefetch(pos->next); 1;}) && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
/**
* hlist_for_each_entry_continue - iterate over a hlist continuing after current point
* @tpos: the type * to use as a loop cursor.
* @pos: the &struct hlist_node to use as a loop cursor.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_continue(tpos, pos, member) \
for (pos = (pos)->next; \
pos && ({ prefetch(pos->next); 1;}) && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
/**
* hlist_for_each_entry_from - iterate over a hlist continuing from current point
* @tpos: the type * to use as a loop cursor.
* @pos: the &struct hlist_node to use as a loop cursor.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_from(tpos, pos, member) \
for (; pos && ({ prefetch(pos->next); 1;}) && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
/**
* hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @tpos: the type * to use as a loop cursor.
* @pos: the &struct hlist_node to use as a loop cursor.
* @n: another &struct hlist_node to use as temporary storage
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \
for (pos = (head)->first; \
pos && ({ n = pos->next; 1; }) && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = n)
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
/**
* @Brief: The AT component drives the interface implementation
* @Author: roger.luo
* @Date: 2021-04-04
* @Last Modified by: roger.luo
* @Last Modified time: 2021-11-27
*/
#include <stddef.h>
#include <stdlib.h>
#include "bsp_tim.h"
/**
* @brief Custom malloc for AT component.
*/
void *at_malloc(unsigned int nbytes)
{
return malloc(nbytes);
}
/**
* @brief Custom free for AT component.
*/
void at_free(void *ptr)
{
free(ptr);
}
/**
* @brief Gets the total number of milliseconds in the system.
*/
unsigned int at_get_ms(void)
{
return BSP_Get_Tick();
}

26
common/LwUtil/README.md Normal file
View File

@ -0,0 +1,26 @@
# C language utility library
Set of different functions and macros usually used in the various applications.
## Features
- Support for C99 or later
- Support for minimal and maximal values
- Support for absolute values
- Support for unused macros
- Support for storing and loading data from array in little- or big- endian formats
- Support for retrieving number of elements for statically allocated array
- Support for bitwise operations to set, clear, check or toggle bits
- Compilation-time assert feature
- User friendly MIT license
## How to use
Usage is very simply. Add `lwutil.c` file to compilation flag and make sure compiler has access to `lwutil.h` for include paths.
## Contribute
This is a generic library and welcomes different contributions.
We accept issue reports or, even better, pull-requests with bugs or new features.
Use [c-code-style](https://github.com/MaJerle/c-code-style) rules for coding and keep the same coding consistency.

168
common/LwUtil/lwutil.c Normal file
View File

@ -0,0 +1,168 @@
/*
* @Author : stark1898y 1658608470@qq.com
* @Date : 2024-06-14 13:25:23
* @LastEditors : stark1898y 1658608470@qq.com
* @LastEditTime : 2024-06-14 13:25:48
* @FilePath : \wuxi_alarm_ch32v303rct6_rtt\common\LwUtil\lwutil.c
* @Description :
*
* Copyright (c) 2024 by yzy, All Rights Reserved.
*/
/**
* \file lwutil.c
* \brief Lightweight utility library
*/
/*
* Copyright (c) 2024 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* This file is part of LwUTIL - Lightweight utility library.
*
* Author: Tilen MAJERLE <tilen@majerle.eu>
* Version: v1.3.0
*/
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "lwutil.h"
/**
* \brief Makes ascii char array from `unsigned 8-bit` value
* \param[in] hex: Hexadecimal data to be converted
* \param[out] ascii: Minimum `3-bytes` long array to write value to
*/
void
lwutil_u8_to_2asciis(uint8_t hex, char* ascii) {
for (uint8_t idx = 2U; idx != 0; --idx) {
uint8_t aux = (uint8_t)(hex >> (4U * (idx - 1U))) & 0x0FU;
aux = (aux <= 9U) ? (aux + 0x30U) : (aux + 0x57U);
ascii[2U - idx] = (char)aux;
}
ascii[2U] = '\0';
}
/**
* \brief Makes ascii char array from `unsigned 16-bit` value
* \param[in] hex: Hexadecimal data to be converted
* \param[out] ascii: Minimum `5-bytes` long array to write value to
*/
void
lwutil_u16_to_4asciis(uint16_t hex, char* ascii) {
for (uint8_t idx = 4U; idx != 0; --idx) {
uint8_t aux = (uint8_t)(hex >> (4U * (idx - 1U))) & 0x0FU;
aux = (aux <= 9U) ? (aux + 0x30U) : (aux + 0x57U);
ascii[4U - idx] = (char)aux;
}
ascii[4] = '\0';
}
/**
* \brief Makes ascii char array from `unsigned 32-bit` value
* \param[in] hex: Hexadecimal data to be converted
* \param[out] ascii: Minimum `9-bytes` long array to write value to
*/
void
lwutil_u32_to_8asciis(uint32_t hex, char* ascii) {
for (uint8_t idx = 8U; idx != 0; --idx) {
uint8_t aux = (uint8_t)(hex >> (4U * (idx - 1U))) & 0x0FU;
aux = (aux <= 9U) ? (aux + 0x30U) : (aux + 0x57U);
ascii[8U - idx] = (char)aux;
}
ascii[8] = '\0';
}
/**
* \brief Load variable length integer from the byte stream to the variable.
*
* Variable length integers (short varints) are stored with continuation bit (bit 7, 0x80),
* which tells us if there is a next byte to be used as part of the number sequence.
*
* 32-bit integer can be stored with anything between `1` and `5` bytes.
*
* \param ptr: Array pointer to load data from
* \param ptr_len: Input array length
* \param val_out: Pointer to variable to write result value
* \return Number of bytes written (stored). `0` in case of an error.
*/
uint8_t
lwutil_ld_u32_varint(const void* ptr, size_t ptr_len, uint32_t* val_out) {
size_t cnt = 0;
uint32_t val = 0;
const uint8_t* p_data = ptr;
uint8_t byt;
if (ptr == NULL || ptr_len == 0 || val_out == NULL) {
return 0;
}
do {
byt = *p_data++;
val |= ((uint32_t)(byt & 0x7FU)) << (cnt * 7U);
++cnt;
} while (--ptr_len > 0 && (byt & 0x80U) > 0);
/* Check memory length */
if ((byt & 0x80U) > 0) {
val = 0;
cnt = 0;
}
*val_out = val;
return cnt;
}
/**
* \brief Store an integer into variable length byte sequence array.
*
* Variable length integers (short varints) are stored with continuation bit (bit 7, 0x80),
* which tells us if there is a next byte to be used as part of the number sequence.
*
* 32-bit integer can be stored with anything between `1` and `5` bytes.
*
* \param val: Value to encode into byte sequence
* \param ptr: Array to write output result
* \param ptr_len: Length of an input array
* \return Number of bytes written (stored). `0` in case of an error.
*/
uint8_t
lwutil_st_u32_varint(uint32_t val, void* ptr, size_t ptr_len) {
uint8_t* p_data = ptr;
size_t cnt = 0;
if (ptr == NULL || ptr_len == 0) {
return 0;
}
do {
*p_data++ = (val & 0x7FU) | (val > 0x7FU ? 0x80U : 0x00U);
val >>= 7U;
++cnt;
} while (--ptr_len > 0 && val > 0);
/* Memory check */
if (val > 0) {
cnt = 0;
}
return cnt;
}

362
common/LwUtil/lwutil.h Normal file
View File

@ -0,0 +1,362 @@
/*
* @Author : stark1898y 1658608470@qq.com
* @Date : 2024-06-14 13:25:23
* @LastEditors : stark1898y 1658608470@qq.com
* @LastEditTime : 2024-06-14 13:25:40
* @FilePath : \wuxi_alarm_ch32v303rct6_rtt\common\LwUtil\lwutil.h
* @Description :
*
* Copyright (c) 2024 by yzy, All Rights Reserved.
*/
/**
* \file lwutil.h
* \brief Lightweight utility library
*/
/*
* Copyright (c) 2024 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* This file is part of LwUTIL - Lightweight utility library.
*
* Author: Tilen MAJERLE <tilen@majerle.eu>
* Version: v1.3.0
*/
#ifndef LWUTIL_HDR_H
#define LWUTIL_HDR_H
#include <stdint.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* \defgroup LWUTIL Lightweight utility
* \brief Lightweight utility
* \{
*/
/**
* \brief Get size of statically allocated array
* Array must be declared in a form of `type var_name[element_count]`
*
* \param[in] x: Object to get array size of
* \return Number of elements in array (`element_count`)
*/
#define LWUTIL_ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0]))
/**
* \brief Get size of statically allocated array
* Array must be declared in a form of `type var_name[element_count]`
*
* \note This is an alias of \ref LWUTIL_ARRAYSIZE
*
* \param[in] x: Object to get array size of
* \return Number of elements in array (`element_count`)
*/
#define LWUTIL_ASZ(x) LWUTIL_ARRAYSIZE(x)
/**
* \brief Get larger value out of 2 different inputs
* \param[in] x: First input
* \param[in] y: Second input
* \return Larger of both inputs
*/
#define LWUTIL_MAX(x, y) ((x) > (y) ? (x) : (y))
/**
* \brief Get smaller value out of 2 different inputs
* \param[in] x: First input
* \param[in] y: Second input
* \return Smaller of both inputs
*/
#define LWUTIL_MIN(x, y) ((x) < (y) ? (x) : (y))
/**
* \brief Constrains an input number within a range
* \param[in] x: Number to constrain
* \param[in] a: Minimum allowed number
* \param[in] b: Maximum allowed number
*
* \return `x` if `a < x < b`
* \return `a` if `x <= a`
* \return `b` if `x >= b`
*
* \note Function does not check if `a < b`. This must be handled by the user.
* \note This is implemented as macro and return data type depends on the input number types.
*/
#define LWUTIL_CONSTRAIN(x, a, b) LWUTIL_MIN(LWUTIL_MAX((x), (a)), (b))
/**
* \brief Maps the input number with the min and max range to the map of the output min and max range
*
* Mathematical calculation is:
*
* (x - in_min) * (out_max - out_min)
* y = ------------------------------------ + out_min
* (in_max - in_min)
*
* \note Data types depend on the user inputs. If high precision is required,
* user can cast the input variables to appropriate type (double or float),
* or use integer types if decimal precision is not required.
*
* \note Input data is not constrained between out min and out max values.
* This may sometimes be useful for the application.
* Use \ref LWUTIL_CONSTRAIN macro to constrain the value
*
* \param[in] x: Input value to map
* \param[in] in_min: Minimum value to map from (input boundary)
* \param[in] in_max: Maximum value to map from (input boundary)
* \param[in] out_min: Minimum value to map to (output boundary)
* \param[in] out_max: Maximum value to map to (output boundary)
* \return Mapped value
*/
#define LWUTIL_MAP(x, in_min, in_max, out_min, out_max) \
(((x) - (in_min)) * ((out_max) - (out_min)) / ((in_max) - (in_min)) + (out_min))
/**
* \brief Get absolute value of the input
* Returns always-positive value of the input.
*
* \note Special care must be taken when input variable holds
* minimum value available for given signed integer type (char, int, long, ...).
*
* Making absolute value of such input means longer output data type requirement.
*
* Consider writing result of this function to unsigned type of same integer width.
* For example, minimum `signed char` value is `-128` while its absolute value (`128`)
* requires variable type of either `unsigned char` or minimum signed 16-bit (or more).
*
* \param[in] x: Input value
* \return Absolute value of the input value
*/
#define LWUTIL_ABS(x) ((x) < 0 ? -(x) : (x))
/**
* \brief Unused variable to avoid compilation warning if declared but not used
* \param[in] x: Input variable to declare unused
*/
#define LWUTIL_UNUSED(x) (void)(x)
/**
* \brief Dereference the pointer and assign the value to it,
* but only if ptr is not NULL.
*
* \note It is fully up to user to handle to correct variable and data types
*
* \param[in] ptr: Pointer to check and assign to
* \param[in] value: Value to assign
*/
#define LWUTIL_SET_VALUE_IF_PTR_NOT_NULL(ptr, value) \
do { \
if ((ptr) != NULL) { \
*(ptr) = (value); \
} \
} while (0)
#define LWUTIL_CONCAT_BASE(x, y) x##y
#define LWUTIL_CONCAT(s0, s1) LWUTIL_CONCAT_BASE(s0, s1)
/**
* \brief Compile time assert to validate specific expression.
* Compilation will fail if expression evaluated to zero
* \note Can only be used with the integer types
*
* \param[in] exp: Expression to test. It must be compile-time evaluation
* \param[in] random_variable_name: Meaningful variable name to be used.
* Can be whatever until it is valid variable name
*/
#define LWUTIL_COMPILE_TIME_ASSERT(exp, random_variable_name) \
typedef char LWUTIL_CONCAT2(random_variable_name, __LINE__)[!(exp) ? -1 : 1];
/**
* \brief Check if all bits in the `bit_mask` are set in the input value
* \note Can only be used with the integer types
*
* \param[in] val: Value to check for bits in
* \param[in] bit_mask: Bit mask to check in value
* \return `1` if all bits are set, `0` otherwise
*/
#define lwutil_bits_is_set_all(val, bit_mask) (((val) & (bit_mask)) == (bit_mask))
/**
* \brief Check if any of the `bit_mask` bits is set in the input value
* \note Can only be used with the integer types
*
* \param[in] val: Value to check for bits in
* \param[in] bit_mask: Bit mask to check in value
* \return `1` if any bit is set, `0` otherwise
*/
#define lwutil_bits_is_set_any(val, bit_mask) (((val) & (bit_mask)) != 0)
/**
* \brief Set bit mask in the input value
* \note Can only be used with the integer types
*
* \param[in] val: Value to set bits in.
* Original input is not modified. It is pass-by-value.
* \param[in] bit_mask: Bit mask indicating which bits to set
* \return New value with bitwise OR between input value and bit mask
*/
#define lwutil_bits_set(val, bit_mask) ((val) | (bit_mask))
/**
* \brief Clear bit mask in the input value
* \note Can only be used with the integer types
*
* \param[in] val: Value to clear bits in.
* Original input is not modified. It is pass-by-value.
* \param[in] bit_mask: Bit mask indicating which bits to clear
* \return New value with bitwise AND and negated bit_mask between input value and bit mask
* Value has bits cleared in the bit_mask set
*/
#define lwutil_bits_clear(val, bit_mask) ((val) & ~(bit_mask))
/**
* \brief Toggle bit mask in the input value
* \note Can only be used with the integer types
*
* \param[in] val: Value to toggle bits in.
* Original input is not modified. It is pass-by-value.
* \param[in] bit_mask: Bit mask indicating which bits to toggle
* \return New value with bitwise AND and negated bit_mask between input value and bit mask
* Value has bits cleared in the bit_mask set
*/
#define lwutil_bits_toggle(val, bit_mask) ((val) ^ (bit_mask))
/**
* \brief Store `16-bit` value to bytes array in little-endian format
* \param[in] val: Value to write to output array
* \param[out] ptr: Minimum `2-bytes` long output array to write value to
*/
static inline void
lwutil_st_u16_le(uint16_t val, void* ptr) {
uint8_t* p = (uint8_t*)ptr;
p[0] = (uint8_t)((val >> 0) & 0xFF);
p[1] = (uint8_t)((val >> 8) & 0xFF);
}
/**
* \brief Store `32-bit` value to bytes array in little-endian format
* \param[in] val: Value to write to output array
* \param[out] ptr: Minimum `4-bytes` long output array to write value to
*/
static inline void
lwutil_st_u32_le(uint32_t val, void* ptr) {
uint8_t* p = (uint8_t*)ptr;
p[0] = (uint8_t)((val >> 0) & 0xFF);
p[1] = (uint8_t)((val >> 8) & 0xFF);
p[2] = (uint8_t)((val >> 16) & 0xFF);
p[3] = (uint8_t)((val >> 24) & 0xFF);
}
/**
* \brief Load `16-bit` value from bytes array in little-endian format
* \param[in] ptr: Minimum `2-bytes` long input array to extract bytes from
* \return `16-bit` value extracted from input array
*/
static inline uint16_t
lwutil_ld_u16_le(const void* ptr) {
const uint8_t* p = (const uint8_t*)ptr;
return p[1] << 8 | p[0];
}
/**
* \brief Load `32-bit` value from bytes array in little-endian format
* \param[in] ptr: Minimum `2-bytes` long input array to extract bytes from
* \return `32-bit` value extracted from input array
*/
static inline uint32_t
lwutil_ld_u32_le(const void* ptr) {
const uint8_t* p = (const uint8_t*)ptr;
return p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0];
}
/**
* \brief Store `16-bit` value to bytes array in big-endian format
* \param[in] val: Value to write to output array
* \param[out] ptr: Minimum `2-bytes` long output array to write value to
*/
static inline void
lwutil_st_u16_be(uint16_t val, void* ptr) {
uint8_t* p = (uint8_t*)ptr;
p[0] = (uint8_t)((val >> 8) & 0xFF);
p[1] = (uint8_t)((val >> 0) & 0xFF);
}
/**
* \brief Store `32-bit` value to bytes array in big-endian format
* \param[in] val: Value to write to output array
* \param[out] ptr: Minimum `4-bytes` long output array to write value to
*/
static inline void
lwutil_st_u32_be(uint32_t val, void* ptr) {
uint8_t* p = (uint8_t*)ptr;
p[0] = (uint8_t)((val >> 24) & 0xFF);
p[1] = (uint8_t)((val >> 16) & 0xFF);
p[2] = (uint8_t)((val >> 8) & 0xFF);
p[3] = (uint8_t)((val >> 0) & 0xFF);
}
/**
* \brief Load `16-bit` value from bytes array in big-endian format
* \param[in] ptr: Minimum `2-bytes` long input array to extract bytes from
* \return `16-bit` value extracted from input array
*/
static inline uint16_t
lwutil_ld_u16_be(const void* ptr) {
const uint8_t* p = (const uint8_t*)ptr;
return p[0] << 8 | p[1];
}
/**
* \brief Load `32-bit` value from bytes array in big-endian format
* \param[in] ptr: Minimum `4-bytes` long input array to extract bytes from
* \return `32-bit` value extracted from input array
*/
static inline uint32_t
lwutil_ld_u32_be(const void* ptr) {
const uint8_t* p = (const uint8_t*)ptr;
return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
}
void lwutil_u8_to_2asciis(uint8_t hex, char* ascii);
void lwutil_u16_to_4asciis(uint16_t hex, char* ascii);
void lwutil_u32_to_8asciis(uint32_t hex, char* ascii);
uint8_t lwutil_ld_u32_varint(const void* ptr, size_t ptr_len, uint32_t* val_out);
uint8_t lwutil_st_u32_varint(uint32_t val, void* ptr, size_t ptr_len);
/**
* \}
*/
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* LWUTIL_HDR_H */

View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

10
common/letter-shell-master/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
CMakeFiles
cmake_install.cmake
CMakeCache.txt
demo/x86-gcc/Makefile
demo/x86-gcc/LetterShell
demo/x86-gcc/LetterShell.map
demo/x86-gcc/compile_commands.json
demo/x86-gcc/.cache/
.vscode
build

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Letter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,655 @@
# letter shell 3.x
![version](https://img.shields.io/badge/version-3.2.0-brightgreen.svg)
![standard](https://img.shields.io/badge/standard-c99-brightgreen.svg)
![build](https://img.shields.io/badge/build-2023.04.15-brightgreen.svg)
![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)
一个功能强大的嵌入式shell
![shell_info.png](doc/img/shell_info.png)
- [letter shell 3.x](#letter-shell-3x)
- [简介](#简介)
- [功能](#功能)
- [移植说明](#移植说明)
- [使用方式](#使用方式)
- [函数定义](#函数定义)
- [main函数形式](#main函数形式)
- [普通C函数形式](#普通c函数形式)
- [变量使用](#变量使用)
- [在函数中获取当前shell对象](#在函数中获取当前shell对象)
- [执行未导出函数](#执行未导出函数)
- [命令定义](#命令定义)
- [定义方式](#定义方式)
- [定义宏说明](#定义宏说明)
- [命令属性字段说明](#命令属性字段说明)
- [代理函数和代理参数解析](#代理函数和代理参数解析)
- [函数签名](#函数签名)
- [自定义类型解析](#自定义类型解析)
- [权限系统说明](#权限系统说明)
- [锁说明](#锁说明)
- [伴生对象](#伴生对象)
- [尾行模式](#尾行模式)
- [建议终端软件](#建议终端软件)
- [命令遍历工具](#命令遍历工具)
- [x86 demo](#x86-demo)
## 简介
[letter shell](https://github.com/NevermindZZT/letter-shell)是一个C语言编写的可以嵌入在程序中的嵌入式shell主要面向嵌入式设备以C语言函数为运行单位可以通过命令行调用运行程序中的函数
相对2.x版本letter shell 3.x增加了用户管理权限管理以及对文件系统的初步支持
此外3.x版本修改了命令格式和定义2.x版本的工程需要经过简单的修改才能完成迁移
若只需要使用基础功能,可以使用[letter shell 2.x](https://github.com/NevermindZZT/letter-shell/tree/shell2.x)版本
使用说明可参考[Letter shell 3.0 全新出发](https://nevermindzzt.github.io/2020/01/19/Letter%20shell%203.0%E5%85%A8%E6%96%B0%E5%87%BA%E5%8F%91/)
如果从3.0版本迁移到3.1以上版本请注意3.1版本对读写函数原型的修改
## 功能
- 命令自动补全
- 快捷键功能定义
- 命令权限管理
- 用户管理
- 变量支持
- 代理函数和参数代理解析
## 移植说明
1. 定义shell对象
```C
Shell shell;
```
2. 定义shell读写函数
对于使用letter shell 3.0版本,读写函数原型如下:
```C
/**
* @brief shell读取数据函数原型
*
* @param char shell读取的字符
*
* @return char 0 读取数据成功
* @return char -1 读取数据失败
*/
typedef signed char (*shellRead)(char *);
/**
* @brief shell写数据函数原型
*
* @param const char 需写的字符
*/
typedef void (*shellWrite)(const char);
```
对于使用letter shell 3.1版本,为了优化效率,修改了读写函数原型,如下:
```C
/**
* @brief shell读取数据函数原型
*
* @param data shell读取的字符
* @param len 请求读取的字符数量
*
* @return unsigned short 实际读取到的字符数量
*/
typedef unsigned short (*shellRead)(char *data, unsigned short len);
/**
* @brief shell写数据函数原型
*
* @param data 需写的字符数据
* @param len 需要写入的字符数
*
* @return unsigned short 实际写入的字符数量
*/
typedef unsigned short (*shellWrite)(char *data, unsigned short len);
```
3. 申请一片缓冲区
```C
char shellBuffer[512];
```
4. 调用shellInit进行初始化
```C
shell.read = shellRead;
shell.write = shellWrite;
shellInit(&shell, shellBuffer, 512);
```
5. 调用(建立)shell任务
对于运行在操作系统的情况,建立`shellTask`任务(确保sell_cfg.h中的配置无误)任务参数为shell对象
```C
OsTaskCreate(shellTask, &shell, ...);
```
对于裸机环境,在主循环中调用`shellTask`,或者在接收到数据时,调用`shellHandler`
6. 说明
- 对于中断方式使用shell不用定义`shell->read`,但需要在中断中调用`shellHandler`
- 对于使用操作系统的情况,使能`SHEHLL_TASK_WHILE`宏然后创建shellTask任务
7. 其他配置
- 定义宏`SHELL_GET_TICK()`为获取系统tick函数使能tab双击操作用户长帮助补全
8. 配置宏
shell_cfg.h文件中包含了所有用于配置shell的宏在使用前需要根据需要进行配置
建议采用 overlay 的方式,新建一个头文件,例如 `shell_cfg_user.h`,然后定义编译宏 `SHELL_CFG_USER="shell_cfg_user.h"`,在这个头文件中添加需要修改的配置即可
| 宏 | 意义 |
| --------------------------- | ------------------------------ |
| SHELL_TASK_WHILE | 是否使用默认shell任务while循环 |
| SHELL_USING_CMD_EXPORT | 是否使用命令导出方式 |
| SHELL_USING_COMPANION | 是否使用shell伴生对象功能 |
| SHELL_SUPPORT_END_LINE | 是否支持shell尾行模式 |
| SHELL_HELP_LIST_USER | 是否在输入命令列表中列出用户 |
| SHELL_HELP_LIST_VAR | 是否在输入命令列表中列出变量 |
| SHELL_HELP_LIST_KEY | 是否在输入命令列表中列出按键 |
| SHELL_ENTER_LF | 使用LF作为命令行回车触发 |
| SHELL_ENTER_CR | 使用CR作为命令行回车触发 |
| SHELL_ENTER_CRLF | 使用CRLF作为命令行回车触发 |
| SHELL_EXEC_UNDEF_FUNC | 使用执行未导出函数的功能 |
| SHELL_COMMAND_MAX_LENGTH | shell命令最大长度 |
| SHELL_PARAMETER_MAX_NUMBER | shell命令参数最大数量 |
| SHELL_HISTORY_MAX_NUMBER | 历史命令记录数量 |
| SHELL_DOUBLE_CLICK_TIME | 双击间隔(ms) |
| SHELL_QUICK_HELP | 快速帮助 |
| SHELL_MAX_NUMBER | 管理的最大shell数量 |
| SHELL_GET_TICK() | 获取系统时间(ms) |
| SHELL_USING_LOCK | 是否使用锁 |
| SHELL_MALLOC(size) | 内存分配函数(shell本身不需要) |
| SHELL_FREE(obj) | 内存释放函数(shell本身不需要) |
| SHELL_SHOW_INFO | 是否显示shell信息 |
| SHELL_CLS_WHEN_LOGIN | 是否在登录后清除命令行 |
| SHELL_DEFAULT_USER | shell默认用户 |
| SHELL_DEFAULT_USER_PASSWORD | 默认用户密码 |
| SHELL_LOCK_TIMEOUT | shell自动锁定超时 |
| SHELL_USING_FUNC_SIGNATURE | 使用函数签名 |
## 使用方式
### 函数定义
letter shell 3.x同时支持两种形式的函数定义方式形如main函数定义的`func(int argc, char *agrv[])`以及形如普通C函数的定义`func(int i, char *str, ...)`,两种函数定义方式适用于不同的场景
#### main函数形式
使用此方式,一个函数定义的例子如下:
```C
int func(int argc, char *agrv[])
{
printf("%dparameter(s)\r\n", argc);
for (char i = 1; i < argc; i++)
{
printf("%s\r\n", argv[i]);
}
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), func, func, test);
```
终端调用
```sh
letter:/$ func "hello world"
2 parameter(s)
hello world
```
#### 普通C函数形式
使用此方式shell会自动对参数进行转化处理目前支持二进制八进制十进制十六进制整形字符字符串的自动处理如果需要其他类型的参数请使用代理参数解析的方式(参考[代理函数和代理参数解析](#代理函数和代理参数解析)),或者使用字符串的方式作为参数,自行进行处理,例子如下:
```C
int func(int i, char ch, char *str)
{
printf("input int: %d, char: %c, string: %s\r\n", i, ch, str);
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), func, func, test);
```
终端调用
```sh
letter:/$ func 666 'A' "hello world"
input int: 666, char: A, string: hello world
```
### 变量使用
letter shell 3.x支持导出变量通过命令行查看设置以及使用变量的值
- 导出变量
变量导出使用`SHELL_EXPORT_VAR`宏,支持整形(char, short, int),字符串,指针以及节点变量,变量导出需要使用引用的方式,如果不允许对变量进行修改,在属性中添加`SHELL_CMD_READ_ONLY`
```C
int varInt = 0;
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_INT), varInt, &varInt, test);
char str[] = "test string";
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_STRING), varStr, str, test);
Log log;
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_POINT), log, &log, test);
```
- 查看变量
在命令行直接输入导出的变量名即可查看变量当前的值
```sh
letter:/$ varInt
varInt = 0, 0x00000000
letter:/$ varStr
varStr = "test string"
```
- 修改变量
使用`setVar`命令修改变量的值,对于字符串型变量,请确认字符串有分配足够的空间,指针类型的变量不可修改
```sh
letter:/$ setVar varInt 45678
varInt = 45678, 0x0000b26e
letter:/$ setVar varStr "hello"
varStr = "hello"
```
- 使用变量
letter shell 3.x的变量可以在命令中作为参数传递对于需要传递结构体引用到命令中的场景特别适用使用`$`+变量名的方式传递
```sh
letter:/$ shellPrint $shell "hello world\r\n"
hello world
```
### 在函数中获取当前shell对象
letter shell采取一个静态数组对定义的多个shell进行管理shell数量可以修改宏`SHELL_MAX_NUMBER`定义(为了不使用动态内存分配,此处通过数据进行管理)从而在shell执行的函数中可以调用`shellGetCurrent()`获得当前活动的shell对象从而可以实现某一个函数在不同的shell对象中发生不同的行为也可以通过这种方式获得shell对象后调用`shellWriteString(shell, string)`进行shell的输出
### 执行未导出函数
letter shell支持通过函数地址直接执行函数可以方便执行那些没有导出但是又临时需要使用的函数使用命令`exec [addr] [args]`执行,使用此功能需要开启`SHELL_EXEC_UNDEF_FUNC`宏,注意,由于直接操作函数地址执行,如果给进的地址有误,可能引起程序崩溃
函数的地址可以通过编译生成的文件查找比如说对于keil可以在`.map`文件中查找到每个函数的地址,但是要注意有些平台可能需要要对地址做进一步处理,比如说对于 arm 平台,如果使用的是 Thumb 指令集,那么需要将地址的最低位置 1比如说`shellClear`函数地址为`0x08028620`,则通过`exec`执行应为`exec 0x08028621`
其他编译器查找函数地址的方式和地址偏移的处理,请参考各编译器手册
## 命令定义
letter shell 3.x将可执行的函数命令定义用户定义按键定义以及变量定义统一归为命令定义使用相同的结构储存查找和执行
### 定义方式
letter shell 支持使用命令导出方式和命令表方式进行命令的添加,定义,通过宏```SHELL_USING_CMD_EXPORT```控制
命令导出方式支持keilIAR以及GCC
1. 命令导出方式
letter shell 支持在函数体外部,采用定义常量的方式定义命令,例如`SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE (SHELL_TYPE_CMD_MAIN)|SHELL_CMD_DISABLE_RETURN,help, shellHelp, show command info\r\nhelp [cmd]);`
对于使用keil进行编译需要在keil的target option中增加--keep shellCommand*,防止定义的命令被优化掉
使用GCC编译时需要在ld文件中的只读数据区(建议)添加:
```ld
_shell_command_start = .;
KEEP (*(shellCommand))
_shell_command_end = .;
```
2. 命令表方式
- 当使用其他暂时不支持使用命令导出方式的编译器时,需要在`shell_cmd_list.c`文件的命令表中添加
```C
const SHELL_CommandTypeDef shellDefaultCommandList[] =
{
SHELL_CMD_ITEM(
SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN)|SHELL_CMD_DISABLE_RETURN,
help, shellHelp, show command info\r\nhelp [cmd]),
};
```
### 定义宏说明
letter shell 3.x对可执行命令按键用户以及变量分别提供了一个宏用于进行命令定义
1. 可执行命令定义
使用宏`SHELL_EXPORT_CMD`定义可执行命令,定义如下
```C
/**
* @brief shell 命令定义
*
* @param _attr 命令属性
* @param _name 命令名
* @param _func 命令函数
* @param _desc 命令描述
*/
#define SHELL_EXPORT_CMD(_attr, _name, _func, _desc) \
const char shellCmd##_name[] = #_name; \
const char shellDesc##_name[] = #_desc; \
SHELL_USED const ShellCommand \
shellCommand##_name SHELL_SECTION("shellCommand") = \
{ \
.attr.value = _attr, \
.data.cmd.name = shellCmd##_name, \
.data.cmd.function = (int (*)())_func, \
.data.cmd.desc = shellDesc##_name \
}
```
2. 变量定义
使用宏`SHELL_EXPORT_VAR`定义变量,定义如下
```C
/**
* @brief shell 变量定义
*
* @param _attr 变量属性
* @param _name 变量名
* @param _value 变量值
* @param _desc 变量描述
*/
#define SHELL_EXPORT_VAR(_attr, _name, _value, _desc) \
const char shellCmd##_name[] = #_name; \
const char shellDesc##_name[] = #_desc; \
SHELL_USED const ShellCommand \
shellVar##_name SHELL_SECTION("shellCommand") = \
{ \
.attr.value = _attr, \
.data.var.name = shellCmd##_name, \
.data.var.value = (void *)_value, \
.data.var.desc = shellDesc##_name \
}
```
变量定义时,`_value`应该是变量的引用,如果变量不允许修改,则需要在增加`SHELL_CMD_READ_ONLY`属性
3. 用户定义
使用宏`SHELL_EXPORT_USER`定义用户,定义如下
```C
/**
* @brief shell 用户定义
*
* @param _attr 用户属性
* @param _name 用户名
* @param _password 用户密码
* @param _desc 用户描述
*/
#define SHELL_EXPORT_USER(_attr, _name, _password, _desc) \
const char shellCmd##_name[] = #_name; \
const char shellPassword##_name[] = #_password; \
const char shellDesc##_name[] = #_desc; \
SHELL_USED const ShellCommand \
shellUser##_name SHELL_SECTION("shellCommand") = \
{ \
.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_USER), \
.data.user.name = shellCmd##_name, \
.data.user.password = shellPassword##_name, \
.data.user.desc = shellDesc##_name \
}
```
4. 按键定义
使用宏`SHELL_EXPORT_KEY`定义按键,定义如下
```C
/**
* @brief shell 按键定义
*
* @param _attr 按键属性
* @param _value 按键键值
* @param _func 按键函数
* @param _desc 按键描述
*/
#define SHELL_EXPORT_KEY(_attr, _value, _func, _desc) \
const char shellDesc##_value[] = #_desc; \
SHELL_USED const ShellCommand \
shellKey##_value SHELL_SECTION("shellCommand") = \
{ \
.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_KEY), \
.data.key.value = _value, \
.data.key.function = (void (*)(Shell *))_func, \
.data.key.desc = shellDesc##_value \
}
```
按键键值为在终端输入按键会发送的字符串序列以大端模式表示比如在SecureCRT中断按下Tab键会发送0x0B则这个按键的键值为0x0B000000如果按下方向上会依次发送0x1B, 0x5B, 0x41, 则这个键的键值为0x1B5B4100
### 命令属性字段说明
在命令定义中,有一个`attr`字段,表示该命令的属性,具体定义为
```C
union
{
struct
{
unsigned char permission : 8; /**< command权限 */
ShellCommandType type : 4; /**< command类型 */
unsigned char enableUnchecked : 1; /**< 在未校验密码的情况下可用 */
unsigned char readOnly : 1; /**< 只读 */
unsigned char reserve : 1; /**< 保留 */
unsigned char paramNum : 4; /**< 参数数量 */
} attrs;
int value;
} attr;
```
在定义命令时,需要给定这些值,可以通过宏`SHELL_CMD_PERMISSION(permission)`, `SHELL_CMD_TYPE(type)`, `SHELL_CMD_ENABLE_UNCHECKED`, `SHELL_CMD_DISABLE_RETURN`, `SHELL_CMD_READ_ONLY`, `SHELL_CMD_PARAM_NUM(num)`快速声明
## 代理函数和代理参数解析
letter shell 3.x原生支持将整数字符字符串参数以及在某些情况下的浮点参数直接传递给执行命令的函数一般情况下这几种参数类型完全可以满足调试需要然而在某些情况下用户确实需要传递其他类型的参数此时可以选择将命令定义成main函数形式使用字符串传递参数然后自行对参数进行解析除此之外letter shell还提供了代理函数的机制可以对任意类型的参数进行自定义解析
关于代理函数的实现原理和具体使用示例,可以参考[letter-shell代理函数解析](https://nevermindzzt.github.io/2020/04/17/letter-shell%E4%BB%A3%E7%90%86%E5%87%BD%E6%95%B0%E8%A7%A3%E6%9E%90/)
使用代理函数,用户需要自定义代理参数解析器,即一个将基本参数(整数,字符,字符串参数)转换成目标类型参数的函数或者宏letter shell默认实现了浮点类型的参数解析器`SHELL_PARAM_FLOAT(x)`
然后,使用代理函数命令导出宏定义命令,比如需要需要传递多个浮点参数的函数,如下
```C
void test(int a, float b, int c, float d)
{
printf("%d, %f, %d, %f \r\n", a, b, c, d);
}
SHELL_EXPORT_CMD_AGENCY(SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC),
test, test, test,
p1, SHELL_PARAM_FLOAT(p2), p3, SHELL_PARAM_FLOAT(p4));
```
相比常规的命令导出代理函数命令导出前4个参数和常规形式的命令导出一致之后的参数即传递至目标函数的参数letter shell默认实现的代理函数定义支持最多7个参数p1~p7对于不需要代理参数解析的参数只需要对应写入`px(x为1~7)`即可,比如上方示例的`p1`和`p3`,而需要代理参数解析的参数,则需要使用对应的参数解析器,比如上方示例的`p2`和`p4`
## 函数签名
letter shell 3.2.x 之后,引入了函数签名的概念,以便于参数自动解析
之前的版本里,如果声明的命令是 `SHELL_TYPE_CMD_FUNC`shell 会自动进行参数的转换,但是参数转换后的类型是猜出来的,无法保证转换后的数据类型是正确的,一旦猜错了,就容易导致程序挂掉
由此,借鉴 Java 等语言的函数签名新版也引入了函数签名的概念在声明命令时可以给定最终执行命令的函数的签名shell 根据这个签名进行参数转换,使用此功能时,需要打开宏 `SHELL_USING_FUNC_SIGNATURE`
函数签名是一个字符串,通过这个字符串声明表达函数的参数类型,返回值不声明,比如一个函数`int func(int a, char *b, char c)`,它的函数签名就是 `isc`
基本类型的参数签名定义如下:
| 类型 | 签名 |
| -------------------- | ---- |
| char(字符) | c |
| int/short/char(数字) | i |
| char * (字符串) | s |
| pointer | p |
声明命令时,在最后添加一个参数 `.data.cmd.signature = "isc"` 即可,比如:
```c
void shellFuncSignatureTest(int a, char *b, char c)
{
printf("a = %d, b = %s, c = %c\r\n", a, b, c);
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC),
funcSignatureTest, shellFuncSignatureTest, test function signature, .data.cmd.signature = "isc");
```
### 自定义类型解析
由于函数签名的引用,我们就可以使用函数签名描述任何参数,对应的,在参数类型已知的情况下,也可以定义对应的参数解析器进行参数解析,自定义的参数类型签名需要以 `L` 开头,以 `;` 结尾,比如说定义一个 `TestStruct` 结构体类型为 `LTestStruct;`,那么接收这个结构体为参数的函数就可以通过这个类型签名定义函数签名,并导出命令
```c
typedef struct {
int a;
char *b;
} TestStruct;
void shellParamParserTest(int a, TestStruct *data, char *c)
{
printf("a = %d, data->a = %d, data->b = %s, c = %s\r\n", a, data->a, data->b, c);
}
SHELL_EXPORT_CMD_SIGN(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC),
paramParserTest, shellParamParserTest, test function signature and param parser, iLTestStruct;s);
```
同时,我们需要对自定义的类型定义解析器,使用 `SHELL_EXPORT_PARAM_PARSER`
```c
int testStructParser(char *string, void **param)
{
TestStruct *data = malloc(sizeof(TestStruct));
data->b = malloc(16);
if (sscanf(string, "%d %s", &(data->a), data->b) == 2)
{
*param = (void *)data;
return 0;
}
return -1;
}
int testStructClener(void *param)
{
TestStruct *data = (TestStruct *)param;
free(data->b);
free(data);
return 0;
}
SHELL_EXPORT_PARAM_PARSER(0, LTestStruct;, testStructParser, testStructClener);
```
`SHELL_EXPORT_PARAM_PARSER` 接收四个参数,第一个参数表示属性,这里一般填 0 皆可,第二个参数就是解析器对应的类型签名,第三个参数是解析器函数,第四个参数是清理函数,清理函数在参数解析失败或者命令执行完毕后会被调用,一般用于清理解析器分配的内存,如果不需要清理函数,填 `NULL` 即可
解析器函数接收两个参数,第一个参数是输入的字符串,也就是命令行输入的参数,第二个参数是解析后的参数,解析成功后,需要将解析后的参数赋值给第二个参数,解析成功返回 0解析失败返回 -1
清理函数接收一个参数,就是解析器函数解析得到的结果
## 权限系统说明
letter shell 3.x的权限管理同用户定义紧密相关letter shell 3.x使用8个bit位表示命令权限当用户和命令的权限按位与为真或者命令权限为0时表示该用户拥有此命令的权限可以调用该命令
## 锁说明
letter shell 3.1增加了shell锁主要目的是为了防止shell输出和其他输入(比如说日志)对终端的竞争导致输出混乱的现象如果使用场景中没有出现终端输出混乱的情况可以不使用shell锁
注意: 请使用支持嵌套的锁
1. 使能宏并实现锁
使能`SHELL_USING_LOCK`宏实现shell上锁和解锁函数函数原型如下
```c
/**
* @brief shell上锁
*
* @param struct shell_def shell对象
*
* @return 0
*/
typedef int (*shellLock)(struct shell_def *);
/**
* @brief shell解锁
*
* @param struct shell_def shell对象
*
* @return 0
*/
typedef int (*shellLock)(struct shell_def *);
```
2. 使用锁
在可能产生终端竞争的地方加上shell锁比如如果调用`shellPrint`进行格式化输出
```C
SHELL_LOCK(shell);
shellPrint(shell, ...);
SHELL_UNLOCK(shell);
```
3. 注意
- 不要在shell命令中调用shell锁除非实现的shell锁为可嵌套的锁
## 伴生对象
letter shell 3.0.3版本引入了伴生对象的概念,通过宏`SHELL_USING_COMPANION`开启或者关闭若使用伴生对象的功能需要同时将shell_companion.c文件加入到工程中伴生对象可以用于需要将某个对象同shell关联的场景比如说通过快捷键控制shell终端对应的日志打印对象
一般情况下,使用`shellCompanionAdd`将伴生对象同shell对象进行关联之后可以在shell操作中通过`shellCompanionGet`获取相应的伴生对象以达到在不同的shell中操作不同对象的目的
## 尾行模式
letter shell 3.0.4版本新增了尾行模式适用于需要在shell所使用的交互终端同时输入其他信息(比如说日志)时防止其他信息的输出导致shell交互体验极差的情况使用时使能宏`SHELL_SUPPORT_END_LINE`,然后对于其他需要使用终端输入信息的地方,调用`shellWriteEndLine`接口将信息输入,此时,调用`shellWriteEndLine`进行输入的内容将会插入到命令行上方终端会一直保持shell命令行位于最后一行
使用letter shell尾行模式结合[log](./extensions/log/readme.md)日志输出的效果如下:
![end line mode](doc/img/shell_end_line_mode.gif)
## 建议终端软件
- 对于基于串口移植letter shell建议使用secureCRT软件letter shell中的相关按键映射都是按照secureCRT进行设计的使用其他串口软件时可能需要修改键值
## 命令遍历工具
letter shell 3.x提供了一个用于遍历工程中命令导出的工具位于tools/shellTools.py需要python3环境运行可以列出工程中所有使用`SHELL_EXPORT_XXX`导出的命令名以及位置结合VS Code可以直接进行跳转
```sh
python shellTools.py project
```
注意shellTools会遍历指定目录中所有文件所以当工程中文件较多时速度会比较慢建议只用于遍历用户模块的目录
## x86 demo
letter shell 3.x提供了一个x86的demo可以直接编译运行其中包含了一条按键键值测试命令可以测试按键键值用于快捷键的定义编译运行方法如下
```sh
cd demo/x86-gcc/
cmake .
make
./LetterShell
```

View File

@ -0,0 +1,315 @@
/**
* @file log.c
* @author Letter (nevermindzzt@gmail.com)
* @brief log
* @version 1.0.0
* @date 2020-07-30
*
* @copyright (c) 2020 Letter
*
*/
#include "log.h"
#include "stdio.h"
#include "stdarg.h"
#include "shell.h"
#if LOG_USING_COLOR == 1
#define memPrintHead CSI(31) \
" Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F" \
CSI(39) \
"\r\n"
#define memPrintAddr CSI(31)"0x%08X: "CSI(39)
#else
#define memPrintHead " Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n"
#define memPrintAddr "0x%08X: "
#endif
Log *logList[LOG_MAX_NUMBER] = {0};
static char logBuffer[LOG_BUFFER_SIZE];
#if LOG_USING_LOCK == 1
/**
* @brief log对象
* @param log log对象
*/
static void logLock(Log *log)
{
if (log == LOG_ALL_OBJ)
{
for (short i = 0; i < LOG_MAX_NUMBER; i++)
{
if (logList[i] && logList[i]->active)
{
if (logList[i]->lock)
{
LOG_LOCK(logList[i]);
}
}
}
}
else if (log->lock)
{
LOG_LOCK(log);
}
}
/**
* @brief log对象
* @param log log对象
*/
static void logUnlock(Log *log)
{
if (log == LOG_ALL_OBJ)
{
for (short i = 0; i < LOG_MAX_NUMBER; i++)
{
if (logList[i] && logList[i]->active)
{
if (logList[i]->unlock)
{
LOG_UNLOCK(logList[i]);
}
}
}
}
else if (log->unlock)
{
LOG_UNLOCK(log);
}
}
#endif /* LOG_USING_LOCK == 1 */
/**
* @brief log对象
*
* @param log log对象
*/
void logRegister(Log *log, Shell *shell)
{
if (shell)
{
log->shell = shell;
#if SHELL_USING_COMPANION == 1
shellCompanionAdd(shell, SHELL_COMPANION_ID_LOG, log);
#endif
}
for (short i = 0; i < LOG_MAX_NUMBER; i++)
{
if (logList[i] == 0)
{
logList[i] = log;
return;
}
}
}
/**
* @brief log对象
*
* @param log log对象
*/
void logUnRegister(Log *log)
{
for (short i = 0; i < LOG_MAX_NUMBER; i++)
{
if (logList[i] == log)
{
logList[i] = 0;
return;
}
}
}
/**
* @brief log日志级别
*
* @param log log对象
* @param level
*/
void logSetLevel(Log *log, LogLevel level)
{
logAssert(log, return);
log->level = level;
}
#if SHELL_USING_COMPANION == 1
SHELL_EXPORT_CMD_AGENCY(
SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN,
logSetLevel, logSetLevel, set log level\r\n logSetLevel [level],
(void *)shellCompanionGet(shellGetCurrent(), SHELL_COMPANION_ID_LOG), p1);
#else
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC),
logSetLevel, logSetLevel, set log level\r\n logSetLevel [log] [level]);
#endif /** SHELL_USING_COMPANION == 1 */
/**
* @brief log写buffer
*
* @param log log对象
* @param level
* @param buffer buffer
* @param len buffer长度
*/
static void logWriteBuffer(Log *log, LogLevel level, char *buffer, short len)
{
#if LOG_USING_LOCK == 1
logLock(log);
#endif /* LOG_USING_LOCK == 1 */
if (log == LOG_ALL_OBJ)
{
for (short i = 0; i < LOG_MAX_NUMBER; i++)
{
if (logList[i]
&& logList[i]->active
&& logList[i]->level >= level)
{
logList[i]->write(logBuffer, len);
}
}
}
else if (log && log->active && log->level >= level)
{
log->write(logBuffer, len);
}
#if LOG_USING_LOCK == 1
logUnlock(log);
#endif /* LOG_USING_LOCK == 1 */
}
/**
* @brief log格式化写入数据
*
* @param log log对象
* @param level log级别
* @param fmt
* @param ...
*/
void logWrite(Log *log, LogLevel level, const char *fmt, ...)
{
va_list vargs;
int len;
#if LOG_USING_LOCK == 1
logLock(log);
#endif /* LOG_USING_LOCK == 1 */
va_start(vargs, fmt);
len = vsnprintf(logBuffer, LOG_BUFFER_SIZE - 1, fmt, vargs);
va_end(vargs);
if (len > LOG_BUFFER_SIZE)
{
len = LOG_BUFFER_SIZE;
}
logWriteBuffer(log, level, logBuffer, len);
#if LOG_USING_LOCK == 1
logUnlock(log);
#endif /* LOG_USING_LOCK == 1 */
}
/**
* @brief 16
*
* @param log log对象
* @param level
* @param base
* @param length
*/
void logHexDump(Log *log, LogLevel level, void *base, unsigned int length)
{
unsigned char *address;
unsigned int len;
unsigned int printLen = 0;
if (length == 0 || (log != LOG_ALL_OBJ && log->level < level))
{
return;
}
#if LOG_USING_LOCK == 1
logLock(log);
#endif /* LOG_USING_LOCK == 1 */
len = snprintf(logBuffer, LOG_BUFFER_SIZE - 1, "memory of 0x%08X, size: %d:\r\n%s",
(unsigned int)base, length, memPrintHead);
logWriteBuffer(log, level, logBuffer, len);
len = length;
address = (unsigned char *)((unsigned int)base & (~0x0000000F));
length += (unsigned int)base - (unsigned int)address;
length = (length + 15) & (~0x0000000F);
while (length)
{
printLen += sprintf(logBuffer + printLen, memPrintAddr, (unsigned int)address);
for (int i = 0; i < 16; i++)
{
if ((unsigned int)(address + i) < (unsigned int)base
|| (unsigned int)(address + i) >= (unsigned int)base + len)
{
logBuffer[printLen ++] = ' ';
logBuffer[printLen ++] = ' ';
logBuffer[printLen ++] = ' ';
}
else
{
printLen += sprintf(logBuffer + printLen, "%02X ", *(address + i));
}
}
logBuffer[printLen ++] = '|';
logBuffer[printLen ++] = ' ';
for (int i = 0; i < 16; i++)
{
if ((unsigned int)(address + i) < (unsigned int)base
|| (unsigned int)(address + i) >= (unsigned int)base + len)
{
logBuffer[printLen ++] = ' ';
}
else
{
if (*(address + i) >= 32 && *(address + i) <= 126)
{
printLen += sprintf(logBuffer + printLen, "%c", *(address + i));
}
else
{
logBuffer[printLen ++] = '.';
}
}
}
logBuffer[printLen ++] = ' ';
logBuffer[printLen ++] = '|';
logBuffer[printLen ++] = '\r';
logBuffer[printLen ++] = '\n';
logWriteBuffer(log, level, logBuffer, printLen);
address += 16;
length -= 16;
printLen = 0;
}
#if LOG_USING_LOCK == 1
logUnlock(log);
#endif /* LOG_USING_LOCK == 1 */
}
#if SHELL_USING_COMPANION == 1
SHELL_EXPORT_CMD_AGENCY(
SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN,
hexdump, logHexDump, hex dump\r\n hexdump [base] [len],
(void *)shellCompanionGet(shellGetCurrent(), SHELL_COMPANION_ID_LOG), LOG_NONE, (void *)p1, (unsigned int)p2);
#else
SHELL_EXPORT_CMD(
SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN,
hexdump, logHexDump, hex dump\r\n hexdump [log] [level] [base] [len]);
#endif /** SHELL_USING_COMPANION == 1 */
#if SHELL_USING_COMPANION == 1
void logSwitchLevel(Shell *shell)
{
Log *log = shellCompanionGet(shell, SHELL_COMPANION_ID_LOG);
SHELL_ASSERT(log, return);
log->level = (LogLevel)(log->level >= LOG_ALL ? LOG_NONE : (log->level + 1));
logPrintln("set log level: %d", log->level);
}
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0), 0x04000000, logSwitchLevel, switch log level);
#endif /** SHELL_USING_COMPANION == 1 */

View File

@ -0,0 +1,225 @@
/**
* @file log.h
* @author Letter (nevermindzzt@gmail.com)
* @brief log
* @version 1.0.0
* @date 2020-07-30
*
* @copyright (c) 2020 Letter
*
*/
#ifndef __LOG_H__
#define __LOG_H__
#ifdef __cplusplus
extern "C" {
#endif /**< defined __cplusplus */
#include "shell.h"
#include "bsp_tim.h"
#define LOG_VERSION "1.0.1"
#define SHELL_COMPANION_ID_LOG -2
#define LOG_USING_LOCK 0
#define LOG_BUFFER_SIZE 512 /**< log输出缓冲大小 */
#define LOG_USING_COLOR 1 /**< 是否使用颜色 */
#define LOG_MAX_NUMBER 10 /**< 允许注册的最大log对象数量 */
#define LOG_AUTO_TAG 1 /**< 是否自动添加TAG */
#define LOG_END "\r\n" /**< log信息结尾 */
#define LOG_TIME_STAMP BSP_Get_Tick() /**< 设置获取系统时间戳 */
#ifndef LOG_TAG
#define LOG_TAG __FUNCTION__ /**< 自定添加的TAG */
#endif
#ifndef LOG_ENABLE
#define LOG_ENABLE 1 /**< 使能log */
#endif
#if LOG_USING_LOCK == 1
#define LOG_LOCK(log) log->lock(log)
#define LOG_UNLOCK(log) log->unlock(log)
#else
#define LOG_LOCK(log)
#define LOG_UNLOCK(log)
#endif /* LOG_USING_LOCK == 1 */
#define LOG_ALL_OBJ ((Log *)-1) /**< 所有已注册的log对象 */
/**
*
*/
#define CSI_BLACK 30 /**< 黑色 */
#define CSI_RED 31 /**< 红色 */
#define CSI_GREEN 32 /**< 绿色 */
#define CSI_YELLOW 33 /**< 黄色 */
#define CSI_BLUE 34 /**< 蓝色 */
#define CSI_FUCHSIN 35 /**< 品红 */
#define CSI_CYAN 36 /**< 青色 */
#define CSI_WHITE 37 /**< 白色 */
#define CSI_BLACK_L 90 /**< 亮黑 */
#define CSI_RED_L 91 /**< 亮红 */
#define CSI_GREEN_L 92 /**< 亮绿 */
#define CSI_YELLOW_L 93 /**< 亮黄 */
#define CSI_BLUE_L 94 /**< 亮蓝 */
#define CSI_FUCHSIN_L 95 /**< 亮品红 */
#define CSI_CYAN_L 96 /**< 亮青 */
#define CSI_WHITE_L 97 /**< 亮白 */
#define CSI_DEFAULT 39 /**< 默认 */
#define CSI(code) "\033[" #code "m" /**< ANSI CSI指令 */
/**
* log级别字符()
*/
#if LOG_USING_COLOR == 1
#define ERROR_TEXT CSI(31) "E(%d) %s:" CSI(39) /**< 错误标签 */
#define WARNING_TEXT CSI(33) "W(%d) %s:" CSI(39) /**< 警告标签 */
#define INFO_TEXT CSI(32) "I(%d) %s:" CSI(39) /**< 信息标签 */
#define DEBUG_TEXT CSI(34) "D(%d) %s:" CSI(39) /**< 调试标签 */
#define VERBOSE_TEXT CSI(36) "V(%d) %s:" CSI(39) /**< 冗余信息标签 */
#else
#define ERROR_TEXT "E(%d) %s:"
#define WARNING_TEXT "W(%d) %s:"
#define INFO_TEXT "I(%d) %s:"
#define DEBUG_TEXT "D(%d) %s:"
#define VERBOSE_TEXT "V(%d) %s:"
#endif
/**
* @brief
*
*/
typedef enum
{
LOG_NONE = 0, /**< 无级别 */
LOG_ERROR = 1, /**< 错误 */
LOG_WRANING = 2, /**< 警告 */
LOG_INFO = 3, /**< 消息 */
LOG_DEBUG = 4, /**< 调试 */
LOG_VERBOSE = 5, /**< 冗余 */
LOG_ALL = 6, /**< 所有日志 */
} LogLevel;
/**
* @brief log对象定义
*
*/
typedef struct log_def
{
void (*write)(char *, short shot); /**< 写buffer */
char active; /**< 是否激活 */
LogLevel level; /**< 日志级别 */
#if LOG_USING_LOCK == 1
int (*lock)(struct log_def *); /**< log 加锁 */
int (*unlock)(struct log_def *); /**< log 解锁 */
#endif /** LOG_USING_LOCK == 1 */
Shell *shell; /**< 关联shell对象 */
} Log;
/**
* @brief log打印()
*
* @param fmt
* @param ...
*/
#define logPrintln(format, ...) \
logWrite(LOG_ALL_OBJ, LOG_NONE, format "\r\n", ##__VA_ARGS__)
/**
* @brief
*
* @param text
* @param level
* @param fmt
* @param ...
*/
#define logFormat(text, level, fmt, ...) \
if (LOG_ENABLE) {\
logWrite(LOG_ALL_OBJ, level, text " " fmt "" LOG_END, \
LOG_TIME_STAMP, LOG_TAG, ##__VA_ARGS__); }
/**
* @brief log输出
*
* @param fmt
* @param ...
*/
#define logError(fmt, ...) \
logFormat(ERROR_TEXT, LOG_ERROR, fmt, ##__VA_ARGS__)
/**
* @brief log输出
*
* @param fmt
* @param ...
*/
#define logWarning(fmt, ...) \
logFormat(WARNING_TEXT, LOG_WRANING, fmt, ##__VA_ARGS__)
/**
* @brief log输出
*
* @param fmt
* @param ...
*/
#define logInfo(fmt, ...) \
logFormat(INFO_TEXT, LOG_INFO, fmt, ##__VA_ARGS__)
/**
* @brief log输出
*
* @param fmt
* @param ...
*/
#define logDebug(fmt, ...) \
logFormat(DEBUG_TEXT, LOG_DEBUG, fmt, ##__VA_ARGS__)
/**
* @brief log输出
*
* @param fmt
* @param ...
*/
#define logVerbose(fmt, ...) \
logFormat(VERBOSE_TEXT, LOG_VERBOSE, fmt, ##__VA_ARGS__)
/**
* @brief
*
* @param expr
* @param action
*/
#define logAssert(expr, action) \
if (!(expr)) { \
logError("\"" #expr "\" assert failed at file: %s, line: %d", __FILE__, __LINE__); \
action; \
}
/**
* @brief 16
*
* @param base
* @param length
*/
#define logHexDumpAll(base, length) \
logHexDump(LOG_ALL_OBJ, LOG_ALL, base, length)
void logRegister(Log *log, Shell *shell);
void logUnRegister(Log *log);
void logSetLevel(Log *log, LogLevel level);
void logWrite(Log *log, LogLevel level, const char *fmt, ...);
void logHexDump(Log *log, LogLevel level, void *base, unsigned int length);
#ifdef __cplusplus
}
#endif /**< defined __cplusplus */
#endif

View File

@ -0,0 +1,215 @@
# log
![version](https://img.shields.io/badge/version-1.0.1-brightgreen.svg)
![standard](https://img.shields.io/badge/standard-c99-brightgreen.svg)
![build](https://img.shields.io/badge/build-2020.08.02-brightgreen.svg)
![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)
嵌入式日志打印工具
![preview](../../doc/img/log_preview.png)
- [log](#log)
- [简介](#简介)
- [使用](#使用)
- [配置](#配置)
- [API](#api)
- [logPrintln](#logprintln)
- [logError](#logerror)
- [logWarning](#logwarning)
- [logInfo](#loginfo)
- [logDebug](#logdebug)
- [logVerbose](#logverbose)
- [logAssert](#logassert)
- [logRegister](#logregister)
- [logSetLevel](#logsetlevel)
- [logHexDump](#loghexdump)
- [结合letter shell尾行模式](#结合letter-shell尾行模式)
- [其他用法](#其他用法)
- [单独控制某个文件日志](#单独控制某个文件日志)
## 简介
log是一个用于嵌入式系统的日志打印工具可以为日志定义不同的级别然后设置日志工具的打印级别可以进行日志打印的控制
此外log通过letter shell的伴生对象功能可以和letter shell结合实现log和shell的绑定等功能
## 使用
1. 实现log写buffer函数
```C
void uartLogWrite(char *buffer, short len)
{
serialTransmit(&debugSerial, (uint8_t *)buffer, len, 0x100);
}
```
2. 定义log对象
```C
Log uartLog = {
.write = uartLogWrite,
.active = true,
.level = LOG_DEBUG
};
```
3. 注册log对象
```C
logRegister(&uartLog, NULL);
```
## 配置
通过修改log.h文件中的宏可以对log工具进行配置
| 宏 | 意义 |
| --------------- | ------------------------- |
| LOG_BUFFER_SIZE | log输出缓冲大小 |
| LOG_USING_COLOR | 是否使用颜色 |
| LOG_MAX_NUMBER | 允许注册的最大log对象数量 |
| LOG_AUTO_TAG | 是否自动添加TAG |
| LOG_END | log信息结尾 |
| LOG_TAG | 自定添加的TAG |
| LOG_TIME_STAMP | 设置获取系统时间戳 |
## API
以下是log工具部分API的说明
### logPrintln
宏声明,用于一般的打印输出
```C
#define logPrintln(format, ...)
```
- 参数
- `format` 输出格式
- `...` 可变参数
### logError
宏声明,错误日志级别输出
```C
#define logError(fmt, ...)
```
- 参数
- `fmt` 输出格式
- `...` 可变参数
### logWarning
宏声明,警告日志级别输出,函数原型及参数说明参考`logError`
### logInfo
宏声明,信息日志级别输出,函数原型及参数说明参考`logError`
### logDebug
宏声明,调试日志级别输出,函数原型及参数说明参考`logError`
### logVerbose
宏声明,冗余日志级别输出,函数原型及参数说明参考`logError`
### logAssert
宏声明,断言
```C
#define logAssert(expr, action)
```
- 参数
- `expr` 表达式
- `action` 断言失败执行操作
### logRegister
注册log对象
```C
void logRegister(Log *log, Shell *shell)
```
- 参数
- `log` log对象
- `shell` 关联的shell对象
### logSetLevel
设置日志级别
```C
void logSetLevel(Log *log, LogLevel level)
```
- 参数
- `log` log对象
- `level` 日志级别
### logHexDump
数据16进制打印
```C
void logHexDump(Log *log, LogLevel level, void *base, unsigned int length)
```
- 参数
- `log` log对象
- `level` 日志级别
- `base` 数据基址
- `length` 数据长度
## 结合letter shell尾行模式
log工具可以结合letter shell的尾行模式实现log和shell共用一个终端但不影响shell交互体验
1. 使用`shellWriteEndLine`事项log写buffer函数
```C
void uartLogWrite(char *buffer, short len)
{
if (uartLog.shell)
{
shellWriteEndLine(uartLog.shell, buffer, len);
}
}
```
2. 定义log对象
```C
Log uartLog = {
.write = uartLogWrite,
.active = true,
.level = LOG_DEBUG
};
```
3. 注册log对象
```C
logRegister(&uartLog, &shell);
```
## 其他用法
### 单独控制某个文件日志
log工具可以单独对某个文件的日志进行打印控制使用时在对应的.c文件中加入
```C
#undef LOG_ENABLE
#define LOG_ENABLE 1
```
即可单独控制某个文件的日志开关

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,557 @@
/**
* @file shell.h
* @author Letter (NevermindZZT@gmail.com)
* @brief letter shell
* @version 3.0.0
* @date 2019-12-30
*
* @copyright (c) 2020 Letter
*
*/
#ifndef __SHELL_H__
#define __SHELL_H__
#include "shell_cfg.h"
#define SHELL_VERSION "3.2.1" /**< 版本号 */
/**
* @brief shell
*
* @param expr
* @param action
*/
#define SHELL_ASSERT(expr, action) \
if (!(expr)) { \
action; \
}
#if SHELL_USING_LOCK == 1
#define SHELL_LOCK(shell) shell->lock(shell)
#define SHELL_UNLOCK(shell) shell->unlock(shell)
#else
#define SHELL_LOCK(shell)
#define SHELL_UNLOCK(shell)
#endif /** SHELL_USING_LOCK == 1 */
/**
* @brief shell
*
* @param permission
*/
#define SHELL_CMD_PERMISSION(permission) \
(permission & 0x000000FF)
/**
* @brief shell
*
* @param type
*/
#define SHELL_CMD_TYPE(type) \
((type & 0x0000000F) << 8)
/**
* @brief 使使
*/
#define SHELL_CMD_ENABLE_UNCHECKED \
(1 << 12)
/**
* @brief
*/
#define SHELL_CMD_DISABLE_RETURN \
(1 << 13)
/**
* @brief ()
*/
#define SHELL_CMD_READ_ONLY \
(1 << 14)
/**
* @brief
*/
#define SHELL_CMD_PARAM_NUM(num) \
((num & 0x0000000F)) << 16
#ifndef SHELL_SECTION
#if defined(__CC_ARM) || defined(__CLANG_ARM)
#define SHELL_SECTION(x) __attribute__((section(x), aligned(1)))
#elif defined (__IAR_SYSTEMS_ICC__)
#define SHELL_SECTION(x) @ x
#elif defined(__GNUC__)
#define SHELL_SECTION(x) __attribute__((section(x), aligned(1)))
#else
#define SHELL_SECTION(x)
#endif
#endif
#ifndef SHELL_USED
#if defined(__CC_ARM) || defined(__CLANG_ARM)
#define SHELL_USED __attribute__((used))
#elif defined (__IAR_SYSTEMS_ICC__)
#define SHELL_USED __root
#elif defined(__GNUC__)
#define SHELL_USED __attribute__((used))
#else
#define SHELL_USED
#endif
#endif
/**
* @brief shell float型参数转换
*/
#define SHELL_PARAM_FLOAT(x) (*(float *)(&x))
/**
* @brief shell
*/
#define SHELL_AGENCY_FUNC_NAME(_func) agency##_func
/**
* @brief shell代理函数定义
*
* @param _func
* @param ...
*/
#define SHELL_AGENCY_FUNC(_func, ...) \
void SHELL_AGENCY_FUNC_NAME(_func)(int p1, int p2, int p3, int p4, int p5, int p6, int p7) \
{ _func(__VA_ARGS__); }
#if SHELL_USING_CMD_EXPORT == 1
/**
* @brief shell
*
* @param _attr
* @param _name
* @param _func
* @param _desc
* @param ...
*/
#define SHELL_EXPORT_CMD(_attr, _name, _func, _desc, ...) \
const char shellCmd##_name[] = #_name; \
const char shellDesc##_name[] = #_desc; \
SHELL_USED const ShellCommand \
shellCommand##_name SHELL_SECTION("shellCommand") = \
{ \
.attr.value = _attr, \
.data.cmd.name = shellCmd##_name, \
.data.cmd.function = (int (*)())_func, \
.data.cmd.desc = shellDesc##_name, \
##__VA_ARGS__ \
}
#if SHELL_USING_FUNC_SIGNATURE == 1
/**
* @brief shell
*
* @param _attr
* @param _name
* @param _func
* @param _desc
* @param _sign
*/
#define SHELL_EXPORT_CMD_SIGN(_attr, _name, _func, _desc, _sign) \
const char shellCmd##_name[] = #_name; \
const char shellDesc##_name[] = #_desc; \
const char shellSign##_name[] = #_sign; \
SHELL_USED const ShellCommand \
shellCommand##_name SHELL_SECTION("shellCommand") = \
{ \
.attr.value = _attr, \
.data.cmd.name = shellCmd##_name, \
.data.cmd.function = (int (*)())_func, \
.data.cmd.desc = shellDesc##_name, \
.data.cmd.signature = shellSign##_name \
}
#endif /** SHELL_USING_FUNC_SIGNATURE == 1 */
/**
* @brief shell
*
* @param _attr
* @param _name
* @param _func
* @param _desc
* @param ...
*/
#define SHELL_EXPORT_CMD_AGENCY(_attr, _name, _func, _desc, ...) \
SHELL_AGENCY_FUNC(_func, ##__VA_ARGS__) \
SHELL_EXPORT_CMD(_attr, _name, SHELL_AGENCY_FUNC_NAME(_func), _desc)
/**
* @brief shell
*
* @param _attr
* @param _name
* @param _value
* @param _desc
*/
#define SHELL_EXPORT_VAR(_attr, _name, _value, _desc) \
const char shellCmd##_name[] = #_name; \
const char shellDesc##_name[] = #_desc; \
SHELL_USED const ShellCommand \
shellVar##_name SHELL_SECTION("shellCommand") = \
{ \
.attr.value = _attr, \
.data.var.name = shellCmd##_name, \
.data.var.value = (void *)_value, \
.data.var.desc = shellDesc##_name \
}
/**
* @brief shell
*
* @param _attr
* @param _name
* @param _password
* @param _desc
*/
#define SHELL_EXPORT_USER(_attr, _name, _password, _desc) \
const char shellCmd##_name[] = #_name; \
const char shellPassword##_name[] = #_password; \
const char shellDesc##_name[] = #_desc; \
SHELL_USED const ShellCommand \
shellUser##_name SHELL_SECTION("shellCommand") = \
{ \
.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_USER), \
.data.user.name = shellCmd##_name, \
.data.user.password = shellPassword##_name, \
.data.user.desc = shellDesc##_name \
}
/**
* @brief shell
*
* @param _attr
* @param _value
* @param _func
* @param _desc
*/
#define SHELL_EXPORT_KEY(_attr, _value, _func, _desc) \
const char shellDesc##_value[] = #_desc; \
SHELL_USED const ShellCommand \
shellKey##_value SHELL_SECTION("shellCommand") = \
{ \
.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_KEY), \
.data.key.value = _value, \
.data.key.function = (void (*)(Shell *))_func, \
.data.key.desc = shellDesc##_value \
}
/**
* @brief shell
*
* @param _attr
* @param _value
* @param _func
* @param _desc
* @param ...
*/
#define SHELL_EXPORT_KEY_AGENCY(_attr, _value, _func, _desc, ...) \
SHELL_AGENCY_FUNC(_func, ##__VA_ARGS__) \
SHELL_EXPORT_KEY(_attr, _value, SHELL_AGENCY_FUNC_NAME(_func), _desc)
#if SHELL_USING_FUNC_SIGNATURE == 1
/**
* @brief shell
*
* @param _attr
* @param _type
* @param _parser
* @param _cleaner
*/
#define SHELL_EXPORT_PARAM_PARSER(_attr, _type, _parser, _cleaner) \
const char shellDesc##_parser[] = #_type; \
SHELL_USED const ShellCommand \
shellCommand##_parser SHELL_SECTION("shellCommand") = \
{ \
.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_PARAM_PARSER), \
.data.paramParser.type = shellDesc##_parser, \
.data.paramParser.parser = (int (*)(char *, void **))_parser, \
.data.paramParser.cleaner = (int (*)(void *))_cleaner \
}
#endif
#else
/**
* @brief shell item定义
*
* @param _attr
* @param _name
* @param _func
* @param _desc
*/
#define SHELL_CMD_ITEM(_attr, _name, _func, _desc) \
{ \
.attr.value = _attr, \
.data.cmd.name = #_name, \
.data.cmd.function = (int (*)())_func, \
.data.cmd.desc = #_desc \
}
/**
* @brief shell item定义
*
* @param _attr
* @param _name
* @param _value
* @param _desc
*/
#define SHELL_VAR_ITEM(_attr, _name, _value, _desc) \
{ \
.attr.value = _attr, \
.data.var.name = #_name, \
.data.var.value = (void *)_value, \
.data.var.desc = #_desc \
}
/**
* @brief shell item定义
*
* @param _attr
* @param _name
* @param _password
* @param _desc
*/
#define SHELL_USER_ITEM(_attr, _name, _password, _desc) \
{ \
.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_USER), \
.data.user.name = #_name, \
.data.user.password = #_password, \
.data.user.desc = #_desc \
}
/**
* @brief shell item定义
*
* @param _attr
* @param _value
* @param _func
* @param _desc
*/
#define SHELL_KEY_ITEM(_attr, _value, _func, _desc) \
{ \
.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_KEY), \
.data.key.value = _value, \
.data.key.function = (void (*)(Shell *))_func, \
.data.key.desc = #_desc \
}
#if SHELL_USING_FUNC_SIGNATURE == 1
/**
* @brief shell item定义
*
* @param _attr
* @param _type
* @param _parser
* @param _cleaner
*/
#define SHELL_PARAM_PARSER_ITEM(_attr, _type, _parser, _cleaner) \
{ \
.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_PARAM_PARSER), \
.data.paramParser.type = #_type, \
.data.paramParser.parser = (int (*)(char *, void **))_parser, \
.data.paramParser.cleaner = (int (*)(void *))_cleaner \
}
#endif /** SHELL_USING_FUNC_SIGNATURE == 1 */
#define SHELL_EXPORT_CMD(_attr, _name, _func, _desc)
#if SHELL_USING_FUNC_SIGNATURE == 1
#define SHELL_EXPORT_CMD_SIGN(_attr, _name, _func, _desc, _sign)
#endif /** SHELL_USING_FUNC_SIGNATURE == 1 */
#define SHELL_EXPORT_CMD_AGENCY(_attr, _name, _func, _desc, ...)
#define SHELL_EXPORT_VAR(_attr, _name, _value, _desc)
#define SHELL_EXPORT_USER(_attr, _name, _password, _desc)
#define SHELL_EXPORT_KEY(_attr, _value, _func, _desc)
#define SHELL_EXPORT_KEY_AGENCY(_attr, _name, _func, _desc, ...)
#if SHELL_USING_FUNC_SIGNATURE == 1
#define SHELL_EXPORT_PARAM_PARSER(_attr, _type, _parser, _cleaner)
#endif /** SHELL_USING_FUNC_SIGNATURE == 1 */
#endif /** SHELL_USING_CMD_EXPORT == 1 */
/**
* @brief shell command类型
*/
typedef enum
{
SHELL_TYPE_CMD_MAIN = 0, /**< main形式命令 */
SHELL_TYPE_CMD_FUNC, /**< C函数形式命令 */
SHELL_TYPE_VAR_INT, /**< int型变量 */
SHELL_TYPE_VAR_SHORT, /**< short型变量 */
SHELL_TYPE_VAR_CHAR, /**< char型变量 */
SHELL_TYPE_VAR_STRING, /**< string型变量 */
SHELL_TYPE_VAR_POINT, /**< 指针型变量 */
SHELL_TYPE_VAR_NODE, /**< 节点变量 */
SHELL_TYPE_USER, /**< 用户 */
SHELL_TYPE_KEY, /**< 按键 */
#if SHELL_USING_FUNC_SIGNATURE == 1
SHELL_TYPE_PARAM_PARSER, /**< 参数解析器 */
#endif
} ShellCommandType;
/**
* @brief Shell定义
*/
typedef struct shell_def
{
struct
{
const struct shell_command *user; /**< 当前用户 */
int activeTime; /**< shell激活时间 */
char *path; /**< 当前shell路径 */
#if SHELL_USING_COMPANION == 1
struct shell_companion_object *companions; /**< 伴生对象 */
#endif
#if SHELL_KEEP_RETURN_VALUE == 1
int retVal; /**< 返回值 */
#endif
} info;
struct
{
unsigned short length; /**< 输入数据长度 */
unsigned short cursor; /**< 当前光标位置 */
char *buffer; /**< 输入缓冲 */
char *param[SHELL_PARAMETER_MAX_NUMBER]; /**< 参数 */
unsigned short bufferSize; /**< 输入缓冲大小 */
unsigned short paramCount; /**< 参数数量 */
int keyValue; /**< 输入按键键值 */
} parser;
#if SHELL_HISTORY_MAX_NUMBER > 0
struct
{
char *item[SHELL_HISTORY_MAX_NUMBER]; /**< 历史记录 */
unsigned short number; /**< 历史记录数 */
unsigned short record; /**< 当前记录位置 */
signed short offset; /**< 当前历史记录偏移 */
} history;
#endif /** SHELL_HISTORY_MAX_NUMBER > 0 */
struct
{
void *base; /**< 命令表基址 */
unsigned short count; /**< 命令数量 */
} commandList;
struct
{
unsigned char isChecked : 1; /**< 密码校验通过 */
unsigned char isActive : 1; /**< 当前活动Shell */
unsigned char tabFlag_t : 1; /**< tab标志 */
} status;
signed short (*read)(char *, unsigned short); /**< shell读函数 */
signed short (*write)(char *, unsigned short); /**< shell写函数 */
#if SHELL_USING_LOCK == 1
int (*lock)(struct shell_def *); /**< shell 加锁 */
int (*unlock)(struct shell_def *); /**< shell 解锁 */
#endif
} Shell;
/**
* @brief shell command定义
*/
typedef struct shell_command
{
union
{
struct
{
unsigned char permission : 8; /**< command权限 */
ShellCommandType type : 4; /**< command类型 */
unsigned char enableUnchecked : 1; /**< 在未校验密码的情况下可用 */
unsigned char disableReturn : 1; /**< 禁用返回值输出 */
unsigned char readOnly : 1; /**< 只读 */
unsigned char reserve : 1; /**< 保留 */
unsigned char paramNum : 4; /**< 参数数量 */
} attrs;
int value;
} attr; /**< 属性 */
union
{
struct
{
const char *name; /**< 命令名 */
int (*function)(); /**< 命令执行函数 */
const char *desc; /**< 命令描述 */
#if SHELL_USING_FUNC_SIGNATURE == 1
const char *signature; /**< 函数签名 */
#endif
} cmd; /**< 命令定义 */
struct
{
const char *name; /**< 变量名 */
void *value; /**< 变量值 */
const char *desc; /**< 变量描述 */
} var; /**< 变量定义 */
struct
{
const char *name; /**< 用户名 */
const char *password; /**< 用户密码 */
const char *desc; /**< 用户描述 */
} user; /**< 用户定义 */
struct
{
int value; /**< 按键键值 */
void (*function)(Shell *); /**< 按键执行函数 */
const char *desc; /**< 按键描述 */
} key; /**< 按键定义 */
#if SHELL_USING_FUNC_SIGNATURE == 1
struct
{
const char *type; /**< 参数类型 */
int (*parser)(char *, void **); /**< 解析函数 */
int (*cleaner)(void *); /**< 清理器 */
} paramParser; /**< 参数解析器 */
#endif
} data;
} ShellCommand;
/**
* @brief shell节点变量属性
*/
typedef struct
{
void *var; /**< 变量引用 */
int (*get)(); /**< 变量get方法 */
int (*set)(); /**< 变量set方法 */
} ShellNodeVarAttr;
#define shellSetPath(_shell, _path) (_shell)->info.path = _path
#define shellGetPath(_shell) ((_shell)->info.path)
#define shellDeInit(shell) shellRemove(shell)
void shellInit(Shell *shell, char *buffer, unsigned short size);
void shellRemove(Shell *shell);
unsigned short shellWriteString(Shell *shell, const char *string);
void shellPrint(Shell *shell, const char *fmt, ...);
void shellScan(Shell *shell, char *fmt, ...);
Shell* shellGetCurrent(void);
void shellHandler(Shell *shell, char data);
void shellWriteEndLine(Shell *shell, char *buffer, int len);
void shellTask(void *param);
int shellRun(Shell *shell, const char *cmd);
#if SHELL_USING_COMPANION == 1
/**
* @brief shell伴生对象定义
*/
typedef struct shell_companion_object
{
int id; /**< 伴生对象ID */
void *obj; /**< 伴生对象 */
struct shell_companion_object *next; /**< 下一个伴生对象 */
} ShellCompanionObj;
signed char shellCompanionAdd(Shell *shell, int id, void *object);
signed char shellCompanionDel(Shell *shell, int id);
void *shellCompanionGet(Shell *shell, int id);
#endif
#endif

View File

@ -0,0 +1,262 @@
/**
* @file shell_cfg.h
* @author Letter (nevermindzzt@gmail.com)
* @brief shell config
* @version 3.0.0
* @date 2019-12-31
*
* @copyright (c) 2019 Letter
*
*/
#ifndef __SHELL_CFG_H__
#define __SHELL_CFG_H__
#include "bsp_tim.h"
#ifdef SHELL_CFG_USER
#include SHELL_CFG_USER
#endif
#ifndef SHELL_TASK_WHILE
/**
* @brief 使shell任务while循环
* 使`shellTask()`使shell
* 使`shellTask()`
*/
#define SHELL_TASK_WHILE 1
#endif /** SHELL_TASK_WHILE */
#ifndef SHELL_USING_CMD_EXPORT
/**
* @brief 使
* 使使`SHELL_EXPORT_CMD()`
* shell命令使
*/
#define SHELL_USING_CMD_EXPORT 1
#endif /** SHELL_USING_CMD_EXPORT */
#ifndef SHELL_USING_COMPANION
/**
* @brief 使shell伴生对象
* ()使
*/
#define SHELL_USING_COMPANION 1
#endif /** SHELL_USING_COMPANION */
#ifndef SHELL_SUPPORT_END_LINE
/**
* @brief shell尾行模式
*/
#define SHELL_SUPPORT_END_LINE 1
#endif /** SHELL_SUPPORT_END_LINE */
#ifndef SHELL_HELP_LIST_USER
/**
* @brief
*/
#define SHELL_HELP_LIST_USER 1
#endif /** SHELL_HELP_LIST_USER */
#ifndef SHELL_HELP_LIST_VAR
/**
* @brief
*/
#define SHELL_HELP_LIST_VAR 0
#endif /** SHELL_HELP_LIST_VAR */
#ifndef SHELL_HELP_LIST_KEY
/**
* @brief
*/
#define SHELL_HELP_LIST_KEY 0
#endif /** SHELL_HELP_LIST_KEY */
#ifndef SHELL_HELP_SHOW_PERMISSION
/**
* @brief
*/
#define SHELL_HELP_SHOW_PERMISSION 1
#endif /** SHELL_HELP_SHOW_PERMISSION */
#ifndef SHELL_ENTER_LF
/**
* @brief 使LF作为命令行回车触发
* SHELL_ENTER_CR同时开启
*/
#define SHELL_ENTER_LF 1
#endif /** SHELL_ENTER_LF */
#ifndef SHELL_ENTER_CR
/**
* @brief 使CR作为命令行回车触发
* SHELL_ENTER_LF同时开启
*/
#define SHELL_ENTER_CR 1
#endif /** SHELL_ENTER_CR */
#ifndef SHELL_ENTER_CRLF
/**
* @brief 使CRLF作为命令行回车触发
* SHELL_ENTER_LF或SHELL_ENTER_CR同时开启
*/
#define SHELL_ENTER_CRLF 0
#endif /** SHELL_ENTER_CRLF */
#ifndef SHELL_EXEC_UNDEF_FUNC
/**
* @brief 使
* `exec [addr] [args]`
* @attention
*/
#define SHELL_EXEC_UNDEF_FUNC 0
#endif /** SHELL_EXEC_UNDEF_FUNC */
#ifndef SHELL_PARAMETER_MAX_NUMBER
/**
* @brief shell命令参数最大数量
* 16使
*/
#define SHELL_PARAMETER_MAX_NUMBER 15
#endif /** SHELL_PARAMETER_MAX_NUMBER */
#ifndef SHELL_HISTORY_MAX_NUMBER
/**
* @brief
*/
#define SHELL_HISTORY_MAX_NUMBER 5
#endif /** SHELL_HISTORY_MAX_NUMBER */
#ifndef SHELL_DOUBLE_CLICK_TIME
/**
* @brief (ms)
* 使`SHELL_LONG_HELP`tab补全help的时间间隔
*/
#define SHELL_DOUBLE_CLICK_TIME 200
#endif /** SHELL_DOUBLE_CLICK_TIME */
#ifndef SHELL_QUICK_HELP
/**
* @brief
* tab的场景使tab不会对命令进行help补全
*/
#define SHELL_QUICK_HELP 1
#endif /** SHELL_QUICK_HELP */
#ifndef SHELL_KEEP_RETURN_VALUE
/**
* @brief
* `RETVAL`
* `SHELL_CMD_DISABLE_RETURN``RETVAL`
*/
#define SHELL_KEEP_RETURN_VALUE 0
#endif /** SHELL_KEEP_RETURN_VALUE */
#ifndef SHELL_MAX_NUMBER
/**
* @brief shell数量
*/
#define SHELL_MAX_NUMBER 30
#endif /** SHELL_MAX_NUMBER */
#ifndef SHELL_PRINT_BUFFER
/**
* @brief shell格式化输出的缓冲大小
* 0使shell格式化输出
*/
#define SHELL_PRINT_BUFFER 128
#endif /** SHELL_PRINT_BUFFER */
#ifndef SHELL_SCAN_BUFFER
/**
* @brief shell格式化输入的缓冲大小
* 0使shell格式化输入
* @note shell格式化输入会阻塞shellTask, 使
*/
#define SHELL_SCAN_BUFFER 0
#endif /** SHELL_SCAN_BUFFER */
#ifndef SHELL_GET_TICK
/**
* @brief (ms)
* Tick`HAL_GetTick()`
* @note 使tab补全命令help使shell超时锁定
*/
#define SHELL_GET_TICK() BSP_Get_Tick()
#endif /** SHELL_GET_TICK */
#ifndef SHELL_USING_LOCK
/**
* @brief 使
* @note 使shell锁时
*/
#define SHELL_USING_LOCK 0
#endif /** SHELL_USING_LOCK */
#ifndef SHELL_MALLOC
/**
* @brief shell内存分配
* shell本身不需要此接口使shell伴生对象
*/
#define SHELL_MALLOC(size) 0
#endif /** SHELL_MALLOC */
#ifndef SHELL_FREE
/**
* @brief shell内存释放
* shell本身不需要此接口使shell伴生对象
*/
#define SHELL_FREE(obj)
#endif /** SHELL_FREE */
#ifndef SHELL_SHOW_INFO
/**
* @brief shell信息
*/
#define SHELL_SHOW_INFO 0
#endif /** SHELL_SHOW_INFO */
#ifndef SHELL_CLS_WHEN_LOGIN
/**
* @brief
*/
#define SHELL_CLS_WHEN_LOGIN 1
#endif /** SHELL_CLS_WHEN_LOGIN */
#ifndef SHELL_DEFAULT_USER
/**
* @brief shell默认用户
*/
#define SHELL_DEFAULT_USER "ch585"
#endif /** SHELL_DEFAULT_USER */
#ifndef SHELL_DEFAULT_USER_PASSWORD
/**
* @brief shell默认用户密码
* ""
*/
#define SHELL_DEFAULT_USER_PASSWORD ""
#endif /** SHELL_DEFAULT_USER_PASSWORD */
#ifndef SHELL_LOCK_TIMEOUT
/**
* @brief shell自动锁定超时
* shell当前用户密码有效的时候生效shell
* 0`SHELL_GET_TICK()`
* @note 使`SHELL_GET_TICK()`
*/
#define SHELL_LOCK_TIMEOUT 0 * 60 * 1000
#endif /** SHELL_LOCK_TIMEOUT */
#ifndef SHELL_USING_FUNC_SIGNATURE
/**
* @brief 使
* 使shell
*
*/
#define SHELL_USING_FUNC_SIGNATURE 1
#endif /** SHELL_USING_FUNC_SIGNATURE */
#endif

View File

@ -0,0 +1,107 @@
/**
* @file shell_cmd_list.c
* @author Letter (NevermindZZT@gmail.com)
* @brief shell cmd list
* @version 3.0.0
* @date 2020-01-17
*
* @copyright (c) 2020 Letter
*
*/
#include "shell.h"
#if SHELL_USING_CMD_EXPORT != 1
extern int shellSetVar(char *name, int value);
extern void shellUp(Shell *shell);
extern void shellDown(Shell *shell);
extern void shellRight(Shell *shell);
extern void shellLeft(Shell *shell);
extern void shellTab(Shell *shell);
extern void shellBackspace(Shell *shell);
extern void shellDelete(Shell *shell);
extern void shellEnter(Shell *shell);
extern void shellHelp(int argc, char *argv[]);
extern void shellUsers(void);
extern void shellCmds(void);
extern void shellVars(void);
extern void shellKeys(void);
extern void shellClear(void);
#if SHELL_EXEC_UNDEF_FUNC == 1
extern int shellExecute(int argc, char *argv[]);
#endif
SHELL_AGENCY_FUNC(shellRun, shellGetCurrent(), (const char *)p1);
/**
* @brief shell命令表
*
*/
const ShellCommand shellCommandList[] =
{
{.attr.value=SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_USER),
.data.user.name = SHELL_DEFAULT_USER,
.data.user.password = SHELL_DEFAULT_USER_PASSWORD,
.data.user.desc = "default user"},
SHELL_CMD_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC),
setVar, shellSetVar, set var),
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0), 0x1B5B4100, shellUp, up),
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0), 0x1B5B4200, shellDown, down),
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_ENABLE_UNCHECKED,
0x1B5B4300, shellRight, right),
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_ENABLE_UNCHECKED,
0x1B5B4400, shellLeft, left),
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0), 0x09000000, shellTab, tab),
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_ENABLE_UNCHECKED,
0x08000000, shellBackspace, backspace),
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_ENABLE_UNCHECKED,
0x7F000000, shellDelete, delete),
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_ENABLE_UNCHECKED,
0x1B5B337E, shellDelete, delete),
#if SHELL_ENTER_LF == 1
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_ENABLE_UNCHECKED,
0x0A000000, shellEnter, enter),
#endif
#if SHELL_ENTER_CR == 1
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_ENABLE_UNCHECKED,
0x0D000000, shellEnter, enter),
#endif
#if SHELL_ENTER_CRLF == 1
SHELL_KEY_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_ENABLE_UNCHECKED,
0x0D0A0000, shellEnter, enter),
#endif
SHELL_CMD_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN)|SHELL_CMD_DISABLE_RETURN,
help, shellHelp, show command info\r\nhelp [cmd]),
SHELL_CMD_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN,
users, shellUsers, list all user),
SHELL_CMD_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN,
cmds, shellCmds, list all cmd),
SHELL_CMD_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN,
vars, shellVars, list all var),
SHELL_CMD_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN,
keys, shellKeys, list all key),
SHELL_CMD_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN,
clear, shellClear, clear console),
SHELL_CMD_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN,
sh, SHELL_AGENCY_FUNC_NAME(shellRun), run command directly),
#if SHELL_EXEC_UNDEF_FUNC == 1
SHELL_CMD_ITEM(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN)|SHELL_CMD_DISABLE_RETURN,
exec, shellExecute, execute function undefined),
#endif
};
/**
* @brief shell命令表大小
*
*/
const unsigned short shellCommandCount
= sizeof(shellCommandList) / sizeof(ShellCommand);
#endif

View File

@ -0,0 +1,87 @@
/**
* @file shell_companion.c
* @author Letter (nevermindzzt@gmail.com)
* @brief shell companion object support
* @version 3.0.3
* @date 2020-07-22
*
* @copyright (c) 2020 Letter
*
*/
#include "shell.h"
#if SHELL_USING_COMPANION == 1
/**
* @brief shell添加伴生对象
*
* @param shell shell对象
* @param id ID
* @param object
* @return signed char 0 -1
*/
signed char shellCompanionAdd(Shell *shell, int id, void *object)
{
ShellCompanionObj *companions = shell->info.companions;
ShellCompanionObj *node = SHELL_MALLOC(sizeof(ShellCompanionObj));
SHELL_ASSERT(node, return -1);
node->id = id;
node->obj = object;
node->next = companions;
shell->info.companions = node;
return 0;
}
/**
* @brief shell删除伴生对象
*
* @param shell shell对象
* @param id ID
* @return signed char 0 -1
*/
signed char shellCompanionDel(Shell *shell, int id)
{
ShellCompanionObj *companions = shell->info.companions;
ShellCompanionObj *front = companions;
while (companions)
{
if (companions->id == id)
{
if (companions == shell->info.companions && !(companions->next))
{
shell->info.companions = (void *)0;
}
else
{
front->next = companions->next;
}
SHELL_FREE(companions);
return 0;
}
front = companions;
companions = companions->next;
}
return -1;
}
/**
* @brief shell获取伴生对象
*
* @param shell shell对象
* @param id ID
* @return void* NULL
*/
void *shellCompanionGet(Shell *shell, int id)
{
SHELL_ASSERT(shell, return (void *)0);
ShellCompanionObj *companions = shell->info.companions;
while (companions)
{
if (companions->id == id)
{
return companions->obj;
}
companions = companions->next;
}
return (void *)0;
}
#endif /** SHELL_USING_COMPANION == 1 */

View File

@ -0,0 +1,658 @@
/**
* @file shell_ext.c
* @author Letter (NevermindZZT@gmail.com)
* @brief shell extensions
* @version 3.0.0
* @date 2019-12-31
*
* @copyright (c) 2019 Letter
*
*/
#include "shell_cfg.h"
#include "shell.h"
#include "shell_ext.h"
#include "string.h"
extern ShellCommand* shellSeekCommand(Shell *shell,
const char *cmd,
ShellCommand *base,
unsigned short compareLength);
extern int shellGetVarValue(Shell *shell, ShellCommand *command);
#if SHELL_USING_FUNC_SIGNATURE == 1
/**
* @brief
*
* @param signature
* @param index
* @param type
*
* @return int
*/
static int shellGetNextParamType(const char *signature, int index, char *type)
{
const char *p = signature + index;
if (*p == 'L')
{
while (*p != ';' && *p != 0)
{
*type++ = *p++;
index++;
}
*type++ = *p++;
index++;
}
else if (*p != 0)
{
*type++ = *p;
index++;
}
*type = '\0';
return index;
}
static int shellGetParamNumExcept(const char *signature)
{
int num = 0;
const char *p = signature;
while (*p)
{
if (*p == 'L')
{
while (*p != ';' && *p != 0)
{
p++;
}
p++;
}
else
{
p++;
}
num++;
}
return num;
}
#endif
/**
* @brief
*
* @param string
* @return ShellNumType
*/
static ShellNumType shellExtNumType(char *string)
{
char *p = string;
ShellNumType type = NUM_TYPE_DEC;
if ((*p == '0') && ((*(p + 1) == 'x') || (*(p + 1) == 'X')))
{
type = NUM_TYPE_HEX;
}
else if ((*p == '0') && ((*(p + 1) == 'b') || (*(p + 1) == 'B')))
{
type = NUM_TYPE_BIN;
}
else if (*p == '0')
{
type = NUM_TYPE_OCT;
}
while (*p++)
{
if (*p == '.' && *(p + 1) != 0)
{
type = NUM_TYPE_FLOAT;
break;
}
}
return type;
}
/**
* @brief
*
* @param code
* @return char
*/
static char shellExtToNum(char code)
{
if ((code >= '0') && (code <= '9'))
{
return code -'0';
}
else if ((code >= 'a') && (code <= 'f'))
{
return code - 'a' + 10;
}
else if ((code >= 'A') && (code <= 'F'))
{
return code - 'A' + 10;
}
else
{
return 0;
}
}
/**
* @brief
*
* @param string
* @return char
*/
static char shellExtParseChar(char *string)
{
char *p = (*string == '\'') ? (string + 1) : string;
char value = 0;
if (*p == '\\')
{
switch (*(p + 1))
{
case 'b':
value = '\b';
break;
case 'r':
value = '\r';
break;
case 'n':
value = '\n';
break;
case 't':
value = '\t';
break;
case '0':
value = 0;
break;
default:
value = *(p + 1);
break;
}
}
else
{
value = *p;
}
return value;
}
/**
* @brief
*
* @param string
* @return char*
*/
static char* shellExtParseString(char *string)
{
char *p = string;
unsigned short index = 0;
if (*string == '\"')
{
p = ++string;
}
while (*p)
{
if (*p == '\\')
{
*(string + index) = shellExtParseChar(p - 1);
p++;
}
else if (*p == '\"')
{
*(string + index) = 0;
}
else
{
*(string + index) = *p;
}
p++;
index ++;
}
*(string + index) = 0;
return string;
}
/**
* @brief
*
* @param string
* @return unsigned int
*/
static unsigned int shellExtParseNumber(char *string)
{
ShellNumType type = NUM_TYPE_DEC;
char radix = 10;
char *p = string;
char offset = 0;
signed char sign = 1;
unsigned int valueInt = 0;
float valueFloat = 0.0;
unsigned int devide = 0;
if (*string == '-')
{
sign = -1;
}
type = shellExtNumType(string + ((sign == -1) ? 1 : 0));
switch ((char)type)
{
case NUM_TYPE_HEX:
radix = 16;
offset = 2;
break;
case NUM_TYPE_OCT:
radix = 8;
offset = 1;
break;
case NUM_TYPE_BIN:
radix = 2;
offset = 2;
break;
default:
break;
}
p = string + offset + ((sign == -1) ? 1 : 0);
while (*p)
{
if (*p == '.')
{
devide = 1;
p++;
continue;
}
valueInt = valueInt * radix + shellExtToNum(*p);
devide *= 10;
p++;
}
if (type == NUM_TYPE_FLOAT && devide != 0)
{
valueFloat = (float)valueInt / devide * sign;
return *(unsigned int *)(&valueFloat);
}
else
{
return valueInt * sign;
}
}
/**
* @brief
*
* @param shell shell对象
* @param var
* @return unsigned int
*/
static unsigned int shellExtParseVar(Shell *shell, char *var)
{
ShellCommand *command = shellSeekCommand(shell,
var + 1,
shell->commandList.base,
0);
if (command)
{
return shellGetVarValue(shell, command);
}
else
{
return 0;
}
}
/**
* @brief
*
* @param shell shell对象
* @param string
* @param type
* @param result
*
* @return int 0 --1
*/
int shellExtParsePara(Shell *shell, char *string, char *type, unsigned int *result)
{
if (type == NULL || (*string == '$' && *(string + 1)))
{
if (*string == '\'' && *(string + 1))
{
*result = (unsigned int)shellExtParseChar(string);
return 0;
}
else if (*string == '-' || (*string >= '0' && *string <= '9'))
{
*result = (unsigned int)shellExtParseNumber(string);
return 0;
}
else if (*string == '$' && *(string + 1))
{
*result = shellExtParseVar(shell, string);
return 0;
}
else if (*string)
{
*result = (unsigned int)shellExtParseString(string);
return 0;
}
}
#if SHELL_USING_FUNC_SIGNATURE == 1
else
{
if (strcmp("c", type) == 0)
{
*result = (unsigned int)shellExtParseChar(string);
return 0;
}
else if (strcmp("i", type) == 0
|| strcmp("f", type) == 0
|| strcmp("p", type) == 0)
{
*result = (unsigned int)shellExtParseNumber(string);
return 0;
}
else if (strcmp("s", type) == 0)
{
*result = (unsigned int)shellExtParseString(string);
return 0;
}
else
{
ShellCommand *command = shellSeekCommand(shell,
type,
shell->commandList.base,
0);
if (command != NULL)
{
void *param;
if (command->data.paramParser.parser(shellExtParseString(string), &param) == 0)
{
*result = (unsigned int)param;
return 0;
}
else
{
shellWriteString(shell, "Parse param for type: ");
shellWriteString(shell, type);
shellWriteString(shell, " failed\r\n");
return -1;
}
}
else
{
shellWriteString(shell, "Can't find the param parser for type: ");
shellWriteString(shell, type);
shellWriteString(shell, "\r\n");
return -1;
}
}
}
#endif /** SHELL_USING_FUNC_SIGNATURE == 1 */
return -1;
}
#if SHELL_USING_FUNC_SIGNATURE == 1
/**
* @brief
*
* @param shell shell
* @param type
* @param param
*
* @return int 0 -1
*/
int shellExtCleanerPara(Shell *shell, char *type, unsigned int param)
{
if (type == NULL)
{
return 0;
}
else
{
if (strcmp("c", type) == 0
|| strcmp("i", type) == 0
|| strcmp("f", type) == 0
|| strcmp("p", type) == 0
|| strcmp("s", type) == 0)
{
return 0;
}
else
{
ShellCommand *command = shellSeekCommand(shell,
type,
shell->commandList.base,
0);
if (command != NULL && command->data.paramParser.cleaner != NULL)
{
return command->data.paramParser.cleaner((void *)param);
}
}
}
return -1;
}
#endif /** SHELL_USING_FUNC_SIGNATURE == 1 */
/**
* @brief
*
* @param shell shell对象
* @param command
* @param argc
* @param argv
* @return int
*/
int shellExtRun(Shell *shell, ShellCommand *command, int argc, char *argv[])
{
int ret = 0;
unsigned int params[SHELL_PARAMETER_MAX_NUMBER] = {0};
int paramNum = command->attr.attrs.paramNum > (argc - 1) ?
command->attr.attrs.paramNum : (argc - 1);
#if SHELL_USING_FUNC_SIGNATURE == 1
char type[16];
int index = 0;
if (command->data.cmd.signature != NULL)
{
int except = shellGetParamNumExcept(command->data.cmd.signature);
if (except != argc - 1)
{
shellWriteString(shell, "Parameters number incorrect\r\n");
return -1;
}
}
#endif
for (int i = 0; i < argc - 1; i++)
{
#if SHELL_USING_FUNC_SIGNATURE == 1
if (command->data.cmd.signature != NULL) {
index = shellGetNextParamType(command->data.cmd.signature, index, type);
if (shellExtParsePara(shell, argv[i + 1], type, &params[i]) != 0)
{
return -1;
}
}
else
#endif /** SHELL_USING_FUNC_SIGNATURE == 1 */
{
if (shellExtParsePara(shell, argv[i + 1], NULL, &params[i]) != 0)
{
return -1;
}
}
}
switch (paramNum)
{
#if SHELL_PARAMETER_MAX_NUMBER >= 1
case 0:
ret = command->data.cmd.function();
break;
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 1 */
#if SHELL_PARAMETER_MAX_NUMBER >= 2
case 1:
{
int (*func)(int) = command->data.cmd.function;
ret = func(params[0]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 2 */
#if SHELL_PARAMETER_MAX_NUMBER >= 3
case 2:
{
int (*func)(int, int) = command->data.cmd.function;
ret = func(params[0], params[1]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 3 */
#if SHELL_PARAMETER_MAX_NUMBER >= 4
case 3:
{
int (*func)(int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 4 */
#if SHELL_PARAMETER_MAX_NUMBER >= 5
case 4:
{
int (*func)(int, int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 5 */
#if SHELL_PARAMETER_MAX_NUMBER >= 6
case 5:
{
int (*func)(int, int, int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 6 */
#if SHELL_PARAMETER_MAX_NUMBER >= 7
case 6:
{
int (*func)(int, int, int, int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4], params[5]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 7 */
#if SHELL_PARAMETER_MAX_NUMBER >= 8
case 7:
{
int (*func)(int, int, int, int, int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4], params[5], params[6]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 8 */
#if SHELL_PARAMETER_MAX_NUMBER >= 9
case 8:
{
int (*func)(int, int, int, int, int, int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 9 */
#if SHELL_PARAMETER_MAX_NUMBER >= 10
case 9:
{
int (*func)(int, int, int, int, int, int, int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7],
params[8]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 10 */
#if SHELL_PARAMETER_MAX_NUMBER >= 11
case 10:
{
int (*func)(int, int, int, int, int, int, int, int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7],
params[8], params[9]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 11 */
#if SHELL_PARAMETER_MAX_NUMBER >= 12
case 11:
{
int (*func)(int, int, int, int, int, int, int, int, int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7],
params[8], params[9], params[10]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 12 */
#if SHELL_PARAMETER_MAX_NUMBER >= 13
case 12:
{
int (*func)(int, int, int, int, int, int, int, int, int, int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7],
params[8], params[9], params[10], params[11]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 13 */
#if SHELL_PARAMETER_MAX_NUMBER >= 14
case 13:
{
int (*func)(int, int, int, int, int, int, int, int, int, int, int, int, int) = command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7],
params[8], params[9], params[10], params[11], params[12]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 14 */
#if SHELL_PARAMETER_MAX_NUMBER >= 15
case 14:
{
int (*func)(int, int, int, int, int, int, int, int, int, int, int, int, int, int)
= command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7],
params[8], params[9], params[10], params[11], params[12], params[13]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 15 */
#if SHELL_PARAMETER_MAX_NUMBER >= 16
case 15:
{
int (*func)(int, int, int, int, int, int, int, int, int, int, int, int, int, int, int)
= command->data.cmd.function;
ret = func(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7],
params[8], params[9], params[10], params[11], params[12], params[13], params[14]);
break;
}
#endif /** SHELL_PARAMETER_MAX_NUMBER >= 16 */
default:
ret = -1;
break;
}
#if SHELL_USING_FUNC_SIGNATURE == 1
if (command->data.cmd.signature != NULL) {
index = 0;
for (int i = 0; i < argc - 1; i++)
{
index = shellGetNextParamType(command->data.cmd.signature, index, type);
shellExtCleanerPara(shell, type, params[i]);
}
}
#endif /** SHELL_USING_FUNC_SIGNATURE == 1 */
return ret;
}

View File

@ -0,0 +1,36 @@
/**
* @file shell_ext.h
* @author Letter (NevermindZZT@gmail.com)
* @brief shell extensions
* @version 3.0.0
* @date 2019-12-31
*
* @copyright (c) 2019 Letter
*
*/
#ifndef __SHELL_EXT_H__
#define __SHELL_EXT_H__
#include "shell.h"
/**
* @brief
*
*/
typedef enum
{
NUM_TYPE_DEC, /**< 十进制整型 */
NUM_TYPE_BIN, /**< 二进制整型 */
NUM_TYPE_OCT, /**< 八进制整型 */
NUM_TYPE_HEX, /**< 十六进制整型 */
NUM_TYPE_FLOAT /**< 浮点型 */
} ShellNumType;
int shellExtParsePara(Shell *shell, char *string, char *type, unsigned int *result);
#if SHELL_USING_FUNC_SIGNATURE == 1
int shellExtCleanerPara(Shell *shell, char *type, unsigned int param);
#endif /** SHELL_USING_FUNC_SIGNATURE == 1 */
int shellExtRun(Shell *shell, ShellCommand *command, int argc, char *argv[]);
#endif

View File

@ -0,0 +1,77 @@
/**
* @file shell_port.c
* @author Letter (NevermindZZT@gmail.com)
* @brief
* @version 0.1
* @date 2019-02-22
*
* @copyright (c) 2019 Letter
*
*/
#include "shell.h"
#include "bsp_uart.h"
#include "log.h"
#include "stdbool.h"
#define SHELL_UART USART1
#define SHELL_BUF_LENGTH 1024
Shell shell;
char shellBuffer[SHELL_BUF_LENGTH];
void Uart_Log_Write(char *buffer, short len);
// 定义log对象
Log uartLog = {
.write = Uart_Log_Write,
.active = LOG_ENABLE,
.level = LOG_ALL
};
/**
* @brief shell写
*
* @param data
* @param len
*
* @return unsigned short
*/
signed short userShellWrite(char *data, unsigned short len)
{
BSP_Uart3_Send_Data(data, len);
return len;
}
// 实现log写buffer函数
void Uart_Log_Write(char *buffer, short len)
{
if (uartLog.shell)
{
//
// log工具可以结合letter shell的尾行模式实现log和shell共用一个终端但不影响shell交互体验
shellWriteEndLine(uartLog.shell, buffer, len);
}
// UART1_SendString((uint8_t *)buffer, len);
}
/**
* @brief shell初始化
*
*/
void userShellInit(void)
{
shell.write = userShellWrite;
shellInit(&shell, shellBuffer, SHELL_BUF_LENGTH);
logRegister(&uartLog, &shell);
}

View File

@ -0,0 +1,20 @@
/**
* @file shell_port.h
* @author Letter (NevermindZZT@gmail.com)
* @brief
* @version 0.1
* @date 2019-02-22
*
* @copyright (c) 2019 Letter
*
*/
#ifndef __SHELL_PORT_H__
#define __SHELL_PORT_H__
#include "shell.h"
extern Shell shell;
void userShellInit(void);
#endif

646
common/lwrb/lwrb.c Normal file
View File

@ -0,0 +1,646 @@
/**
* \file lwrb.c
* \brief Lightweight ring buffer
*/
/*
* Copyright (c) 2023 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* This file is part of LwRB - Lightweight ring buffer library.
*
* Author: Tilen MAJERLE <tilen@majerle.eu>
* Version: v3.0.0
*/
#include "lwrb.h"
/* Memory set and copy functions */
#define BUF_MEMSET memset
#define BUF_MEMCPY memcpy
#define BUF_IS_VALID(b) ((b) != NULL && (b)->buff != NULL && (b)->size > 0)
#define BUF_MIN(x, y) ((x) < (y) ? (x) : (y))
#define BUF_MAX(x, y) ((x) > (y) ? (x) : (y))
#define BUF_SEND_EVT(b, type, bp) \
do { \
if ((b)->evt_fn != NULL) { \
(b)->evt_fn((void*)(b), (type), (bp)); \
} \
} while (0)
/* Optional atomic opeartions */
#ifdef LWRB_DISABLE_ATOMIC
#define LWRB_INIT(var, val) (var) = (val)
#define LWRB_LOAD(var, type) (var)
#define LWRB_STORE(var, val, type) (var) = (val)
#else
#define LWRB_INIT(var, val) atomic_init(&(var), (val))
#define LWRB_LOAD(var, type) atomic_load_explicit(&(var), (type))
#define LWRB_STORE(var, val, type) atomic_store_explicit(&(var), (val), (type))
#endif
/**
* \brief Initialize buffer handle to default values with size and buffer data array
* \param[in] buff: Ring buffer instance
* \param[in] buffdata: Pointer to memory to use as buffer data
* \param[in] size: Size of `buffdata` in units of bytes
* Maximum number of bytes buffer can hold is `size - 1`
* \return `1` on success, `0` otherwise
*/
uint8_t
lwrb_init(lwrb_t* buff, void* buffdata, lwrb_sz_t size) {
if (buff == NULL || buffdata == NULL || size == 0) {
return 0;
}
buff->evt_fn = NULL;
buff->size = size;
buff->buff = buffdata;
LWRB_INIT(buff->w, 0);
LWRB_INIT(buff->r, 0);
return 1;
}
/**
* \brief Check if buff is initialized and ready to use
* \param[in] buff: Ring buffer instance
* \return `1` if ready, `0` otherwise
*/
uint8_t
lwrb_is_ready(lwrb_t* buff) {
return BUF_IS_VALID(buff);
}
/**
* \brief Free buffer memory
* \note Since implementation does not use dynamic allocation,
* it just sets buffer handle to `NULL`
* \param[in] buff: Ring buffer instance
*/
void
lwrb_free(lwrb_t* buff) {
if (BUF_IS_VALID(buff)) {
buff->buff = NULL;
}
}
/**
* \brief Set event function callback for different buffer operations
* \param[in] buff: Ring buffer instance
* \param[in] evt_fn: Callback function
*/
void
lwrb_set_evt_fn(lwrb_t* buff, lwrb_evt_fn evt_fn) {
if (BUF_IS_VALID(buff)) {
buff->evt_fn = evt_fn;
}
}
/**
* \brief Write data to buffer.
* Copies data from `data` array to buffer and marks buffer as full for maximum `btw` number of bytes
*
* \param[in] buff: Ring buffer instance
* \param[in] data: Pointer to data to write into buffer
* \param[in] btw: Number of bytes to write
* \return Number of bytes written to buffer.
* When returned value is less than `btw`, there was no enough memory available
* to copy full data array.
*/
lwrb_sz_t
lwrb_write(lwrb_t* buff, const void* data, lwrb_sz_t btw) {
lwrb_sz_t written = 0;
if (lwrb_write_ex(buff, data, btw, &written, 0)) {
return written;
}
return 0;
}
/**
* \brief Write extended functionality
*
* \param buff: Ring buffer instance
* \param data: Pointer to data to write into buffer
* \param btw: Number of bytes to write
* \param bw: Output pointer to write number of bytes written
* \param Flag_ts: Optional Flag_ts.
* \ref LWRB_Flag_t_WRITE_ALL: Request to write all data (up to btw).
* Will early return if no memory available
* \return `1` if write operation OK, `0` otherwise
*/
uint8_t
lwrb_write_ex(lwrb_t* buff, const void* data, lwrb_sz_t btw, lwrb_sz_t* bw, uint16_t Flag_ts) {
lwrb_sz_t tocopy, free, buff_w_ptr;
const uint8_t* d = data;
if (!BUF_IS_VALID(buff) || data == NULL || btw == 0) {
return 0;
}
/* Calculate maximum number of bytes available to write */
free = lwrb_get_free(buff);
/* If no memory, or if user wants to write ALL data but no enough space, exit early */
if (free == 0 || (free < btw && (Flag_ts & LWRB_Flag_t_WRITE_ALL)))
{
return 0;
}
btw = BUF_MIN(free, btw);
buff_w_ptr = LWRB_LOAD(buff->w, memory_order_acquire);
/* Step 1: Write data to linear part of buffer */
tocopy = BUF_MIN(buff->size - buff_w_ptr, btw);
BUF_MEMCPY(&buff->buff[buff_w_ptr], d, tocopy);
buff_w_ptr += tocopy;
btw -= tocopy;
/* Step 2: Write data to beginning of buffer (overflow part) */
if (btw > 0) {
BUF_MEMCPY(buff->buff, &d[tocopy], btw);
buff_w_ptr = btw;
}
/* Step 3: Check end of buffer */
if (buff_w_ptr >= buff->size) {
buff_w_ptr = 0;
}
/*
* Write final value to the actual running variable.
* This is to ensure no read operation can access intermediate data
*/
LWRB_STORE(buff->w, buff_w_ptr, memory_order_release);
BUF_SEND_EVT(buff, LWRB_EVT_WRITE, tocopy + btw);
if (bw != NULL) {
*bw = tocopy + btw;
}
return 1;
}
/**
* \brief Read data from buffer.
* Copies data from buffer to `data` array and marks buffer as free for maximum `btr` number of bytes
*
* \param[in] buff: Ring buffer instance
* \param[out] data: Pointer to output memory to copy buffer data to
* \param[in] btr: Number of bytes to read
* \return Number of bytes read and copied to data array
*/
lwrb_sz_t
lwrb_read(lwrb_t* buff, void* data, lwrb_sz_t btr) {
lwrb_sz_t read = 0;
if (lwrb_read_ex(buff, data, btr, &read, 0)) {
return read;
}
return 0;
}
/**
* \brief Write extended functionality
*
* \param buff: Ring buffer instance
* \param data: Pointer to memory to write read data from buffer
* \param btr: Number of bytes to read
* \param br: Output pointer to write number of bytes read
* \param Flag_ts: Optional Flag_ts
* \ref LWRB_Flag_t_READ_ALL: Request to read all data (up to btr).
* Will early return if no enough bytes in the buffer
* \return `1` if read operation OK, `0` otherwise
*/
uint8_t
lwrb_read_ex(lwrb_t* buff, void* data, lwrb_sz_t btr, lwrb_sz_t* br, uint16_t Flag_ts) {
lwrb_sz_t tocopy, full, buff_r_ptr;
uint8_t* d = data;
if (!BUF_IS_VALID(buff) || data == NULL || btr == 0) {
return 0;
}
/* Calculate maximum number of bytes available to read */
full = lwrb_get_full(buff);
if (full == 0 || (full < btr && (Flag_ts & LWRB_Flag_t_READ_ALL))) {
return 0;
}
btr = BUF_MIN(full, btr);
buff_r_ptr = LWRB_LOAD(buff->r, memory_order_acquire);
/* Step 1: Read data from linear part of buffer */
tocopy = BUF_MIN(buff->size - buff_r_ptr, btr);
BUF_MEMCPY(d, &buff->buff[buff_r_ptr], tocopy);
buff_r_ptr += tocopy;
btr -= tocopy;
/* Step 2: Read data from beginning of buffer (overflow part) */
if (btr > 0) {
BUF_MEMCPY(&d[tocopy], buff->buff, btr);
buff_r_ptr = btr;
}
/* Step 3: Check end of buffer */
if (buff_r_ptr >= buff->size) {
buff_r_ptr = 0;
}
/*
* Write final value to the actual running variable.
* This is to ensure no write operation can access intermediate data
*/
LWRB_STORE(buff->r, buff_r_ptr, memory_order_release);
BUF_SEND_EVT(buff, LWRB_EVT_READ, tocopy + btr);
if (br != NULL) {
*br = tocopy + btr;
}
return 1;
}
/**
* \brief Read from buffer without changing read pointer (peek only)
* \param[in] buff: Ring buffer instance
* \param[in] skip_count: Number of bytes to skip before reading data
* \param[out] data: Pointer to output memory to copy buffer data to
* \param[in] btp: Number of bytes to peek
* \return Number of bytes peeked and written to output array
*/
lwrb_sz_t
lwrb_peek(const lwrb_t* buff, lwrb_sz_t skip_count, void* data, lwrb_sz_t btp) {
lwrb_sz_t full, tocopy, r;
uint8_t* d = data;
if (!BUF_IS_VALID(buff) || data == NULL || btp == 0) {
return 0;
}
/*
* Calculate maximum number of bytes available to read
* and check if we can even fit to it
*/
full = lwrb_get_full(buff);
if (skip_count >= full) {
return 0;
}
r = LWRB_LOAD(buff->r, memory_order_relaxed);
r += skip_count;
full -= skip_count;
if (r >= buff->size) {
r -= buff->size;
}
/* Check maximum number of bytes available to read after skip */
btp = BUF_MIN(full, btp);
if (btp == 0) {
return 0;
}
/* Step 1: Read data from linear part of buffer */
tocopy = BUF_MIN(buff->size - r, btp);
BUF_MEMCPY(d, &buff->buff[r], tocopy);
btp -= tocopy;
/* Step 2: Read data from beginning of buffer (overflow part) */
if (btp > 0) {
BUF_MEMCPY(&d[tocopy], buff->buff, btp);
}
return tocopy + btp;
}
/**
* \brief Get available size in buffer for write operation
* \param[in] buff: Ring buffer instance
* \return Number of free bytes in memory
*/
lwrb_sz_t
lwrb_get_free(const lwrb_t* buff) {
lwrb_sz_t size, w, r;
if (!BUF_IS_VALID(buff)) {
return 0;
}
/*
* Copy buffer pointers to local variables with atomic access.
*
* To ensure thread safety (only when in single-entry, single-exit FIFO mode use case),
* it is important to write buffer r and w values to local w and r variables.
*
* Local variables will ensure below if statements will always use the same value,
* even if buff->w or buff->r get changed during interrupt processing.
*
* They may change during load operation, important is that
* they do not change during if-elseif-else operations following these assignments.
*
* lwrb_get_free is only called for write purpose, and when in FIFO mode, then:
* - buff->w pointer will not change by another process/interrupt because we are in write mode just now
* - buff->r pointer may change by another process. If it gets changed after buff->r has been loaded to local variable,
* buffer will see "free size" less than it actually is. This is not a problem, application can
* always try again to write more data to remaining free memory that was read just during copy operation
*/
w = LWRB_LOAD(buff->w, memory_order_relaxed);
r = LWRB_LOAD(buff->r, memory_order_relaxed);
if (w == r) {
size = buff->size;
} else if (r > w) {
size = r - w;
} else {
size = buff->size - (w - r);
}
/* Buffer free size is always 1 less than actual size */
return size - 1;
}
/**
* \brief Get number of bytes currently available in buffer
* \param[in] buff: Ring buffer instance
* \return Number of bytes ready to be read
*/
lwrb_sz_t
lwrb_get_full(const lwrb_t* buff) {
lwrb_sz_t size, w, r;
if (!BUF_IS_VALID(buff)) {
return 0;
}
/*
* Copy buffer pointers to local variables.
*
* To ensure thread safety (only when in single-entry, single-exit FIFO mode use case),
* it is important to write buffer r and w values to local w and r variables.
*
* Local variables will ensure below if statements will always use the same value,
* even if buff->w or buff->r get changed during interrupt processing.
*
* They may change during load operation, important is that
* they do not change during if-elseif-else operations following these assignments.
*
* lwrb_get_full is only called for read purpose, and when in FIFO mode, then:
* - buff->r pointer will not change by another process/interrupt because we are in read mode just now
* - buff->w pointer may change by another process. If it gets changed after buff->w has been loaded to local variable,
* buffer will see "full size" less than it really is. This is not a problem, application can
* always try again to read more data from remaining full memory that was written just during copy operation
*/
w = LWRB_LOAD(buff->w, memory_order_relaxed);
r = LWRB_LOAD(buff->r, memory_order_relaxed);
if (w == r) {
size = 0;
} else if (w > r) {
size = w - r;
} else {
size = buff->size - (r - w);
}
return size;
}
/**
* \brief Resets buffer to default values. Buffer size is not modified
* \note This function is not thread safe.
* When used, application must ensure there is no active read/write operation
* \param[in] buff: Ring buffer instance
*/
void
lwrb_reset(lwrb_t* buff) {
if (BUF_IS_VALID(buff)) {
LWRB_STORE(buff->w, 0, memory_order_release);
LWRB_STORE(buff->r, 0, memory_order_release);
BUF_SEND_EVT(buff, LWRB_EVT_RESET, 0);
}
}
/**
* \brief Get linear address for buffer for fast read
* \param[in] buff: Ring buffer instance
* \return Linear buffer start address
*/
void*
lwrb_get_linear_block_read_address(const lwrb_t* buff) {
if (!BUF_IS_VALID(buff)) {
return NULL;
}
return &buff->buff[buff->r];
}
/**
* \brief Get length of linear block address before it overflows for read operation
* \param[in] buff: Ring buffer instance
* \return Linear buffer size in units of bytes for read operation
*/
lwrb_sz_t
lwrb_get_linear_block_read_length(const lwrb_t* buff) {
lwrb_sz_t len, w, r;
if (!BUF_IS_VALID(buff)) {
return 0;
}
/*
* Use temporary values in case they are changed during operations.
* See lwrb_buff_free or lwrb_buff_full functions for more information why this is OK.
*/
w = LWRB_LOAD(buff->w, memory_order_relaxed);
r = LWRB_LOAD(buff->r, memory_order_relaxed);
if (w > r) {
len = w - r;
} else if (r > w) {
len = buff->size - r;
} else {
len = 0;
}
return len;
}
/**
* \brief Skip (ignore; advance read pointer) buffer data
* Marks data as read in the buffer and increases free memory for up to `len` bytes
*
* \note Useful at the end of streaming transfer such as DMA
* \param[in] buff: Ring buffer instance
* \param[in] len: Number of bytes to skip and mark as read
* \return Number of bytes skipped
*/
lwrb_sz_t
lwrb_skip(lwrb_t* buff, lwrb_sz_t len) {
lwrb_sz_t full, r;
if (!BUF_IS_VALID(buff) || len == 0) {
return 0;
}
full = lwrb_get_full(buff);
len = BUF_MIN(len, full);
r = LWRB_LOAD(buff->r, memory_order_acquire);
r += len;
if (r >= buff->size) {
r -= buff->size;
}
LWRB_STORE(buff->r, r, memory_order_release);
BUF_SEND_EVT(buff, LWRB_EVT_READ, len);
return len;
}
/**
* \brief Get linear address for buffer for fast read
* \param[in] buff: Ring buffer instance
* \return Linear buffer start address
*/
void*
lwrb_get_linear_block_write_address(const lwrb_t* buff) {
if (!BUF_IS_VALID(buff)) {
return NULL;
}
return &buff->buff[buff->w];
}
/**
* \brief Get length of linear block address before it overflows for write operation
* \param[in] buff: Ring buffer instance
* \return Linear buffer size in units of bytes for write operation
*/
lwrb_sz_t
lwrb_get_linear_block_write_length(const lwrb_t* buff) {
lwrb_sz_t len, w, r;
if (!BUF_IS_VALID(buff)) {
return 0;
}
/*
* Use temporary values in case they are changed during operations.
* See lwrb_buff_free or lwrb_buff_full functions for more information why this is OK.
*/
w = LWRB_LOAD(buff->w, memory_order_relaxed);
r = LWRB_LOAD(buff->r, memory_order_relaxed);
if (w >= r) {
len = buff->size - w;
/*
* When read pointer is 0,
* maximal length is one less as if too many bytes
* are written, buffer would be considered empty again (r == w)
*/
if (r == 0) {
/*
* Cannot overflow:
* - If r is not 0, statement does not get called
* - buff->size cannot be 0 and if r is 0, len is greater 0
*/
--len;
}
} else {
len = r - w - 1;
}
return len;
}
/**
* \brief Advance write pointer in the buffer.
* Similar to skip function but modifies write pointer instead of read
*
* \note Useful when hardware is writing to buffer and application needs to increase number
* of bytes written to buffer by hardware
* \param[in] buff: Ring buffer instance
* \param[in] len: Number of bytes to advance
* \return Number of bytes advanced for write operation
*/
lwrb_sz_t
lwrb_advance(lwrb_t* buff, lwrb_sz_t len) {
lwrb_sz_t free, w;
if (!BUF_IS_VALID(buff) || len == 0) {
return 0;
}
/* Use local variables before writing back to main structure */
free = lwrb_get_free(buff);
len = BUF_MIN(len, free);
w = LWRB_LOAD(buff->w, memory_order_acquire);
w += len;
if (w >= buff->size) {
w -= buff->size;
}
LWRB_STORE(buff->w, w, memory_order_release);
BUF_SEND_EVT(buff, LWRB_EVT_WRITE, len);
return len;
}
/**
* \brief Searches for a *needle* in an array, starting from given offset.
*
* \note This function is not thread-safe.
*
* \param buff: Ring buffer to search for needle in
* \param bts: Constant byte array sequence to search for in a buffer
* \param len: Length of the \arg bts array
* \param start_offset: Start offset in the buffer
* \param found_idx: Pointer to variable to write index in array where bts has been found
* Must not be set to `NULL`
* \return `1` if \arg bts found, `0` otherwise
*/
uint8_t
lwrb_find(const lwrb_t* buff, const void* bts, lwrb_sz_t len, lwrb_sz_t start_offset, lwrb_sz_t* found_idx) {
lwrb_sz_t full, r, max_x;
uint8_t found = 0;
const uint8_t* needle = bts;
if (!BUF_IS_VALID(buff) || needle == NULL || len == 0 || found_idx == NULL) {
return 0;
}
*found_idx = 0;
full = lwrb_get_full(buff);
/* Verify initial conditions */
if (full < (len + start_offset)) {
return 0;
}
/* Max number of for loops is buff_full - input_len - start_offset of buffer length */
max_x = full - len;
for (lwrb_sz_t skip_x = start_offset; !found && skip_x <= max_x; ++skip_x) {
found = 1; /* Found by default */
/* Prepare the starting point for reading */
r = buff->r + skip_x;
if (r >= buff->size) {
r -= buff->size;
}
/* Search in the buffer */
for (lwrb_sz_t i = 0; i < len; ++i) {
if (buff->buff[r] != needle[i]) {
found = 0;
break;
}
if (++r >= buff->size) {
r = 0;
}
}
if (found) {
*found_idx = skip_x;
}
}
return found;
}

157
common/lwrb/lwrb.h Normal file
View File

@ -0,0 +1,157 @@
/**
* \file lwrb.h
* \brief LwRB - Lightweight ring buffer
*/
/*
* Copyright (c) 2023 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* This file is part of LwRB - Lightweight ring buffer library.
*
* Author: Tilen MAJERLE <tilen@majerle.eu>
* Version: v3.0.0-rc1
*/
#ifndef LWRB_HDR_H
#define LWRB_HDR_H
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* \defgroup LWRB Lightweight ring buffer manager
* \brief Lightweight ring buffer manager
* \{
*/
#if !defined(LWRB_DISABLE_ATOMIC) || __DOXYGEN__
// #include <stdatomic.h>
#define LWRB_DISABLE_ATOMIC
#ifdef LWRB_DISABLE_ATOMIC
typedef unsigned long lwrb_sz_atomic_t;
#else
#include <stdatomic.h>
typedef atomic_ulong lwrb_sz_atomic_t;
#endif
/**
* \brief Atomic type for size variable.
* Default value is set to be `unsigned 32-bits` type
*/
//typedef atomic_ulong lwrb_sz_atomic_t;
/**
* \brief Size variable for all library operations.
* Default value is set to be `unsigned 32-bits` type
*/
typedef unsigned long lwrb_sz_t;
#else
typedef unsigned long lwrb_sz_atomic_t;
typedef unsigned long lwrb_sz_t;
#endif
/**
* \brief Event type for buffer operations
*/
typedef enum {
LWRB_EVT_READ, /*!< Read event */
LWRB_EVT_WRITE, /*!< Write event */
LWRB_EVT_RESET, /*!< Reset event */
} lwrb_evt_type_t;
/**
* \brief Buffer structure forward declaration
*/
struct lwrb;
/**
* \brief Event callback function type
* \param[in] buff: Buffer handle for event
* \param[in] evt: Event type
* \param[in] bp: Number of bytes written or read (when used), depends on event type
*/
typedef void (*lwrb_evt_fn)(struct lwrb* buff, lwrb_evt_type_t evt, lwrb_sz_t bp);
/* List of Flag_ts */
#define LWRB_Flag_t_READ_ALL ((uint16_t)0x0001)
#define LWRB_Flag_t_WRITE_ALL ((uint16_t)0x0001)
/**
* \brief Buffer structure
*/
typedef struct lwrb {
uint8_t* buff; /*!< Pointer to buffer data. Buffer is considered initialized when `buff != NULL` and `size > 0` */
lwrb_sz_t size; /*!< Size of buffer data. Size of actual buffer is `1` byte less than value holds */
lwrb_sz_atomic_t r; /*!< Next read pointer. Buffer is considered empty when `r == w` and full when `w == r - 1` */
lwrb_sz_atomic_t w; /*!< Next write pointer. Buffer is considered empty when `r == w` and full when `w == r - 1` */
lwrb_evt_fn evt_fn; /*!< Pointer to event callback function */
} lwrb_t;
uint8_t lwrb_init(lwrb_t* buff, void* buffdata, lwrb_sz_t size);
uint8_t lwrb_is_ready(lwrb_t* buff);
void lwrb_free(lwrb_t* buff);
void lwrb_reset(lwrb_t* buff);
void lwrb_set_evt_fn(lwrb_t* buff, lwrb_evt_fn fn);
/* Read/Write functions */
lwrb_sz_t lwrb_write(lwrb_t* buff, const void* data, lwrb_sz_t btw);
lwrb_sz_t lwrb_read(lwrb_t* buff, void* data, lwrb_sz_t btr);
lwrb_sz_t lwrb_peek(const lwrb_t* buff, lwrb_sz_t skip_count, void* data, lwrb_sz_t btp);
/* Extended read/write functions */
uint8_t lwrb_write_ex(lwrb_t* buff, const void* data, lwrb_sz_t btw, lwrb_sz_t* bw, uint16_t Flag_ts);
uint8_t lwrb_read_ex(lwrb_t* buff, void* data, lwrb_sz_t btr, lwrb_sz_t* br, uint16_t Flag_ts);
/* Buffer size information */
lwrb_sz_t lwrb_get_free(const lwrb_t* buff);
lwrb_sz_t lwrb_get_full(const lwrb_t* buff);
/* Read data block management */
void* lwrb_get_linear_block_read_address(const lwrb_t* buff);
lwrb_sz_t lwrb_get_linear_block_read_length(const lwrb_t* buff);
lwrb_sz_t lwrb_skip(lwrb_t* buff, lwrb_sz_t len);
/* Write data block management */
void* lwrb_get_linear_block_write_address(const lwrb_t* buff);
lwrb_sz_t lwrb_get_linear_block_write_length(const lwrb_t* buff);
lwrb_sz_t lwrb_advance(lwrb_t* buff, lwrb_sz_t len);
/* Search in buffer */
uint8_t lwrb_find(const lwrb_t* buff, const void* bts, lwrb_sz_t len, lwrb_sz_t start_offset, lwrb_sz_t* found_idx);
lwrb_sz_t lwrb_overwrite(lwrb_t* buff, const void* data, lwrb_sz_t btw);
lwrb_sz_t lwrb_move(lwrb_t* dest, lwrb_t* src);
/**
* \}
*/
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* LWRB_HDR_H */