- 在學習rtthread的過程中發現rthhread的控制台組件也可以支持帶參數的命令寫法。其實官網文檔寫得很詳細了,但是還是記錄一下。
不帶參數的命令寫法
- 不帶參數的命令寫法十分簡單,就是寫一個普通的函數
- 再利用這個宏導出
MSH_CMD_EXPORT(name, desc);
自己的demo節選,作為示例
void TEXT(void)
{
const rt_uint8_t Buffer[] = "hello world\r\n";
rt_uint8_t len = rt_strlen((const char*)Buffer);
rt_device_write(Project_uart_Device,0,Buffer,len);
}
MSH_CMD_EXPORT(TEXT, RT-Thread TEXT sample);
- 在命令行里輸入TEXT\r\n就會觸發這個函數。關鍵是這個宏定義的實現
帶參數的命令的寫法
- 帶參數的命令寫法其實就類似main函數的參數一樣(int argc,char **argv)
下面這個代碼效果是查詢或設置一個藍牙設備的設備名稱的代碼,當沒參數時,是查詢藍牙設備名稱,帶參數,就是修改藍牙設備名稱
static void AT_NAME(int argc, char **argv){//設置/查詢設備名稱
if(argc < 2){
const rt_uint8_t Buffer[] = "AT+NAME\r\n";
rt_uint8_t len = rt_strlen((const char*)Buffer);
rt_device_write(Project_uart_Device,0,Buffer,len);
}else if(argc > 2){
rt_kprintf("Only one parameter can be entered\r\n");
}else{
char Buffer[] = "AT+NAME";
char* NewBuf = strcat(Buffer,argv[1]);
NewBuf = strcat(NewBuf,"\r\n");
rt_uint8_t len = rt_strlen((const char*)NewBuf);
rt_device_write(Project_uart_Device,0,NewBuf,len);
}
}
MSH_CMD_EXPORT(AT_NAME, AT_NAME sample: AT_NAME <drivers_name>);
- 電腦終端輸入命令給MCU,MCU再把信息解析然后發送到藍牙設備
- argv[1]直接就可以被拿來用了,參數與參數之間以空格分開,十分方便
嘗試分析MSH_CMD_EXPORT宏定義源碼
1.
#define MSH_CMD_EXPORT(command, desc) FINSH_FUNCTION_EXPORT_CMD(command, __cmd_##command, desc)
//嵌套一層宏定義,把兩個參數變成3個參數,command用##與__cmd_連接起來,那么它的第二參數就變成__cmd_command
2.
#pragma section("FSymTab$f",read)
#define FINSH_FUNCTION_EXPORT_CMD(name, cmd, desc) \
const char __fsym_##cmd##_name[] = #cmd; \
const char __fsym_##cmd##_desc[] = #desc; \
__declspec(allocate("FSymTab$f")) \
const struct finsh_syscall __fsym_##cmd = \
{ \
__fsym_##cmd##_name, \
__fsym_##cmd##_desc, \
(syscall_func)&name \
};
//進入這層,可以看見宏定義的數據被放進一個叫finsh_syscall(系統調用)的結構體里。對象名字就是_fsym_cmd_command
//結構體的第一個成員是字符串首地址,因為是const char類型的數組(宏定義'#'代表讓后面修飾的東西變成字符串)
//結構體的第二個成員也是字符串
//結構體的第三個成員是一個指向函數的指針,指向name函數,也就是名字為name的函數的地址
3.
typedef long (*syscall_func)(void);
/* system call table */
struct finsh_syscall
{
const char* name; /* the name of system call */
#if defined(FINSH_USING_DESCRIPTION) && defined(FINSH_USING_SYMTAB)
const char* desc; /* description of system call */
#endif
syscall_func func; /* the function address of system call */
};
extern struct finsh_syscall *_syscall_table_begin, *_syscall_table_end;
/* find out system call, which should be implemented in user program */
struct finsh_syscall* finsh_syscall_lookup(const char* name);
//進入下一層,可以看到第一行typedef long (*syscall_func)(void),這個就是一個函數指針。
//接下來是結構體主體的定義,不得不說rtthread無論是文檔還是注釋都太適合國人學習了
//緊接着可以看到引用了兩個指向結構體的指針(指針定義在symbol.c),從命名就可以看出一個指針指向表的開始,一個指針指向表的尾部。應該就是個鏈表結構了(好像不是。。)
//再接着是一個函數的定義,它的注釋寫着這個函數的作用是遍歷鏈表,然后利用函數指針調用相應的函數(但是在源碼中沒有找到實現)
- 接下來跳到shell.c的函數---finsh_system_init開始
...
unsigned int *ptr_begin, *ptr_end;
ptr_begin = (unsigned int *)&__fsym_begin;
ptr_begin += (sizeof(struct finsh_syscall) / sizeof(unsigned int));
while (*ptr_begin == 0) ptr_begin ++;
ptr_end = (unsigned int *) &__fsym_end;
ptr_end --;
while (*ptr_end == 0) ptr_end --;
finsh_system_function_init(ptr_begin, ptr_end);
...
- 光看這里很迷茫,接着看__fsym_begin和__fsym_end是什么東西
#pragma section("FSymTab$a", read)
const char __fsym_begin_name[] = "__start";
const char __fsym_begin_desc[] = "begin of finsh";
__declspec(allocate("FSymTab$a")) const struct finsh_syscall __fsym_begin =
{
__fsym_begin_name,
__fsym_begin_desc,
NULL
};
#pragma section("FSymTab$z", read)
const char __fsym_end_name[] = "__end";
const char __fsym_end_desc[] = "end of finsh";
__declspec(allocate("FSymTab$z")) const struct finsh_syscall __fsym_end =
{
__fsym_end_name,
__fsym_end_desc,
NULL
};
//然后又引出了問題,這里的關鍵其實是#pragma section("xxxx",read)以及__declspec(allocate("xxxx"))這兩個宏定義
//#pragma section("xxxx",read),創建一個段,段名是第一個參數,read表示可讀
//__declspec(allocate("xxxx"))是一個修飾,用這個這個修飾可以把變量放進"xxxx"段中
//接下來回到2.中去,可以看到結構體前面有個__declspec(allocate("FSymTab$f"))修飾
//留意到FSymTab$a表示start,FSymTab$z表示end,而真正的結構體被存到FSymTab$f段中
//a-z表示一頭一尾,這個我猜測應該是可以保證f段是在內存a段之后,z段之前的
//回過頭來看上一part,答案似乎呼之欲出,ptr_begin, ptr_end兩根指針都可以分別指向表頭和表尾了。
//得到兩根指針后,被傳進finsh_system_function_init()中。
- finsh_system_function_init
struct finsh_syscall *_syscall_table_begin = NULL;
struct finsh_syscall *_syscall_table_end = NULL;
void finsh_system_function_init(const void *begin, const void *end)
{
_syscall_table_begin = (struct finsh_syscall *) begin;
_syscall_table_end = (struct finsh_syscall *) end;
}
//這個函數就是把這兩根指針分別賦值給_syscall_table_begin和_syscall_table_end
- 得到這兩根指針,就可以為遍歷提供起始節點和結束兩個邊界信息啦。但是還沒結束。。
...
struct finsh_syscall *index;
...
for (index = _syscall_table_begin; index < _syscall_table_end; FINSH_NEXT_SYSCALL(index))
...
- 可以看到還有一個宏定義FINSH_NEXT_SYSCALL()需要探究的,否則條件不足,不知道段之間的空間是否連續的,估計和編譯器有關
#if defined(_MSC_VER) || (defined(__GNUC__) && defined(__x86_64__))
struct finsh_syscall* finsh_syscall_next(struct finsh_syscall* call);
struct finsh_sysvar* finsh_sysvar_next(struct finsh_sysvar* call);
#define FINSH_NEXT_SYSCALL(index) index=finsh_syscall_next(index)
#define FINSH_NEXT_SYSVAR(index) index=finsh_sysvar_next(index)
#else
#define FINSH_NEXT_SYSCALL(index) index++
#define FINSH_NEXT_SYSVAR(index) index++
#endif
- 可以看到,在_MSC_VER,GUNC,x86_64環境下,是需要有一定規則來遍歷地址的,但是我在源碼中沒有找到finsh_syscall_next(index)的實現
- 所以,那應該空間是連續的了。直接就是index++。
- ok,這樣在rtthread接收到串口消息的時候就可以通過遍歷這個地址空間中的name,來識別相應的命令,適配成功,就可以利用結構體的函數指針,調用相應的動作了
最后,在編譯后生成的map文件中查找,終於找到了答案。
- 以下是自己寫的demo里的自定義命令,可以看到,所有的rtthread的finsh命令都在一個名為FSymTab段里了
FSymTab$$Base 0x0800f52c Number 0 commandanalyze.o(FSymTab)
__fsym___cmd_AT 0x0800f52c Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_VERSION 0x0800f538 Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_RESET 0x0800f544 Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_LADDR 0x0800f550 Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_NAME 0x0800f55c Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_PIN 0x0800f568 Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_BAUD 0x0800f574 Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_SLEEP 0x0800f580 Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_ROLE 0x0800f58c Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_INQ 0x0800f598 Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_SHOW 0x0800f5a4 Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_AT_CONN 0x0800f5b0 Data 12 commandanalyze.o(FSymTab)
__fsym___cmd_show_temp 0x0800f5bc Data 12 humiture.o(FSymTab)
__fsym___cmd_tick_now 0x0800f5c8 Data 12 main.o(FSymTab)
__fsym___cmd_w_hello_eeprom 0x0800f5d4 Data 12 eeprom.o(FSymTab)
__fsym___cmd_r_hello_eeprom 0x0800f5e0 Data 12 eeprom.o(FSymTab)
__fsym_list_mem 0x0800f5ec Data 12 mem.o(FSymTab)
__fsym_pinMode 0x0800f5f8 Data 12 pin.o(FSymTab)
__fsym_pinWrite 0x0800f604 Data 12 pin.o(FSymTab)
__fsym_pinRead 0x0800f610 Data 12 pin.o(FSymTab)
__fsym_hello 0x0800f61c Data 12 cmd.o(FSymTab)
__fsym_version 0x0800f628 Data 12 cmd.o(FSymTab)
__fsym___cmd_version 0x0800f634 Data 12 cmd.o(FSymTab)
__fsym_list_thread 0x0800f640 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_thread 0x0800f64c Data 12 cmd.o(FSymTab)
__fsym_list_sem 0x0800f658 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_sem 0x0800f664 Data 12 cmd.o(FSymTab)
__fsym_list_event 0x0800f670 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_event 0x0800f67c Data 12 cmd.o(FSymTab)
__fsym_list_mutex 0x0800f688 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_mutex 0x0800f694 Data 12 cmd.o(FSymTab)
__fsym_list_mailbox 0x0800f6a0 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_mailbox 0x0800f6ac Data 12 cmd.o(FSymTab)
__fsym_list_msgqueue 0x0800f6b8 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_msgqueue 0x0800f6c4 Data 12 cmd.o(FSymTab)
__fsym_list_memheap 0x0800f6d0 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_memheap 0x0800f6dc Data 12 cmd.o(FSymTab)
__fsym_list_mempool 0x0800f6e8 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_mempool 0x0800f6f4 Data 12 cmd.o(FSymTab)
__fsym_list_timer 0x0800f700 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_timer 0x0800f70c Data 12 cmd.o(FSymTab)
__fsym_list_device 0x0800f718 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_device 0x0800f724 Data 12 cmd.o(FSymTab)
__fsym_list 0x0800f730 Data 12 cmd.o(FSymTab)
__fsym___cmd_help 0x0800f73c Data 12 msh.o(FSymTab)
FSymTab$$Limit 0x0800f748 Number 0 msh.o(FSymTab)
總結
- 又學到了不錯的東西
