ESP32-finsh
小陈学不停 2025-06-13

1 控制台

rtthread的finsh控制台使用起来非常友好,类似于linux的控制台,在我们需要调试理想的传感器阈值,理想的PID、又或者是想验证某个API功能时,控制台可以帮助我们节省很多试错成本

2 测试环境

3 实现目标及方法
3.1 目标
通过控制台实现AT指令运行器、ping指令、同步网络时间
3.2 方法
3.2.1 基本驱动首先要实现基本的串口驱动,
通过串口接收需要执行的命令,这个idf已经帮我们做好了
3.2.2 创建shell任务
主要是根据输入的数据回显或者输出应答字符
xTaskCreate((TaskFunction_t )finsh_thread_entry, (const char*    )"shell", (uint16_t )FINSH_THREAD_STACK_SIZE, (void*          )NULL, (UBaseType_t    )FINSH_THREAD_PRIORITY, (TaskHandle_t*  )&ShellTask_Handler);

3.2.3 编辑链接脚本,加入相关的section定义

在CPU will try to prefetch文本附近添加以下内容

 /* section information for finsh shell */ . = ALIGN(8); __fsymtab_start = .; KEEP(*(FSymTab)) __fsymtab_end = .; . = ALIGN(8); __vsymtab_start = .; KEEP(*(VSymTab)) __vsymtab_end = .;. = ALIGN(8);

3.2.4 添加自定义组件
把finsh作为一个自定义组件添加应用中

编辑cmake构建脚本

把shell.c复制到具体应用的目录下跟main一起编译,如果把shell也作为组件的部分,将无法运行自定义的传参程序
3.2.5 添加自定义传参程序
- AT指令运行器

static int run_at_cmd(int argc, char **argv){ char dtmp[256]={0};  if (2 == argc) {  sprintf(dtmp,"AT+%s\r\n",argv[1]); uint8_t get_bytes = strlen(dtmp);  if((dtmp[0] == 'A') && (dtmp[1] == 'T') && (dtmp[get_bytes-2] == 0x0D) && (dtmp[get_bytes-1] == 0x0A)) { uint8_t ret_parse = at_cmd_parse((uint8_t *)dtmp, get_bytes); rt_kprintf("ret_parse=%02x",ret_parse);  if ((ESP_AT_RESULT_CODE_OK == ret_parse) || (ESP_AT_RESULT_CODE_SEND_OK == ret_parse)) { esp_at_response_result(ESP_AT_RESULT_CODE_OK); } else if ((ESP_AT_RESULT_CODE_ERROR == ret_parse) || (ESP_AT_RESULT_CODE_SEND_FAIL == ret_parse) || (ESP_AT_RESULT_CODE_FAIL == ret_parse)) { esp_at_response_result(ESP_AT_RESULT_CODE_ERROR); } } }  return 0;}MSH_CMD_EXPORT_ALIAS(run_at_cmd,AT, AT [cmd]);

- ping指令

static int run_ping(int argc, char **argv){ if (2 == argc) {  ping_target(argv[1]); }  return 0;}MSH_CMD_EXPORT_ALIAS(run_ping,ping, ping ["baidu.com"]); static int stop_ping(int argc, char **argv){ if (1 == argc) {  esp_ping_stop(ping);  esp_ping_delete_session(ping); ping=NULL; }  return 0;}MSH_CMD_EXPORT_ALIAS(stop_ping,ping_stop, ping stop);

- 同步网络时间

static int sync_time(int argc, char **argv){ char timezone[32]={0}; if (2 == argc) {  sprintf(timezone,"%s",argv[1]);  struct timeval cur_time;  system_set_timezone(timezone);  gettimeofday(&cur_time, NULL);  rt_kprintf("Time: %.24s\n",ctime(&cur_time.tv_sec));  }  return 0;}MSH_CMD_EXPORT_ALIAS(sync_time,time,usage: time [timezone]("CST-8"));

4 测试程序
4.1 创建wifi任务

xTaskCreate(wifi_task, "wifi_task", 2048, NULL, 2, NULL);

4.2 初始化finsh控制台

finsh_system_init();

4.3 完整应用代码

void app_main(void){  nvs_save_init();  spi_config_t spi_config =  { .mosi_pin=4, .spi_high_level_data=0xf0, .spi_low_level_data=0xc0, .clock_speed_hz = 6*1000*1000, .host_id=SPI2_HOST, }; memcpy(&led_strip.config.spi_config,&spi_config,sizeof(spi_config_t));  uint8_t io_type=LED_STRIP_CTL_SPI;  led_strip_init(&led_strip,io_type,8);  led_strip_config_flash_ivt_ms(&led_strip,1000); led_strip_update_rgb(&led_strip,0xffffff); led_strip_set_state(&led_strip,LED_STRIP_STATE_BREATH);  debug_cmd_init();   xTaskCreate(wifi_task, "wifi_task", 2048, NULL, 2, NULL);   system_set_timezone("CST-8");  while (1) { led_strip_handle(&led_strip);  if (WS_CONNECTED == wifi_get_state()) { static uint32_t tick_sync_time = 0;  if ((xTaskGetTickCount() - tick_sync_time) >= pdMS_TO_TICKS(2000)) { tick_sync_time = xTaskGetTickCount(); if (rg_network_sync_time("cn.pool.ntp.org",0)) {  } } }  vTaskDelay(pdMS_TO_TICKS(10)); }} uint8_t at_cmd_reset(char *cmd_name){ esp_at_response_result(ESP_AT_RESULT_CODE_OK);  esp_restart();  return ESP_AT_RESULT_CODE_PROCESS_DONE;} uint8_t at_cmd_version(char *cmd_name){ uint8_t buffer_tx[64] = {0};  snprintf((char *)buffer_tx, 64, "SDK version: %s\r\n", esp_get_idf_version()); esp_at_port_write_data(buffer_tx, ut_strlen((char *)buffer_tx));  snprintf((char *)buffer_tx, 64, "compile time %s %s\n", __DATE__, __TIME__); esp_at_port_write_data(buffer_tx, ut_strlen((char *)buffer_tx));  return ESP_AT_RESULT_CODE_OK;}  extern led_strip_t led_strip; static uint8_t at_set_ledstrip(uint8_t para_num){ uint64_t get_val = 0; int32_t cnt = 0; uint8_t check_mode = 0;  esp_at_get_para_as_hex (cnt++,&get_val);  led_strip_set_state(&led_strip,(uint8_t)get_val); esp_at_get_para_as_hex (cnt++,&get_val);  led_strip_set_rgb(&led_strip,get_val);  return ESP_AT_RESULT_CODE_OK;} const esp_at_cmd_struct at_cmd_func[] ={ {"+RST",NULL,NULL,at_cmd_reset}, {"+GMR",NULL,NULL,at_cmd_version},  {"+RGB",NULL,at_set_ledstrip}, }; int16_t at_cmd_search(unsigned char *p, unsigned char len){ int16_t ret = -1; unsigned char *pstr; unsigned char i, n;  for(i=0; i { n = ut_strlen(at_cmd_func[i].at_name);  uint8_t get_res = memcmp((const char *)p, (const char *)at_cmd_func[i].at_name, n);  if(!get_res) { ret = i;  break; } }  return ret;}  /* AT指令解析 根据解析的结果返回AT+OK或者AT+ERROR*/uint8_t at_cmd_parse(uint8_t *p, uint8_t len){ uint8_t ret = ESP_AT_RESULT_CODE_ERROR; int16_t index = -1;  if(len < 4) { return ESP_AT_RESULT_CODE_ERROR; /* 不符合指令最小长度 */ }  if((p[0] == 'A') && (p[1] == 'T') && (p[len-2] == 0x0D) && (p[len-1] == 0x0A)) { if (p[2] == '+') { /* 执行指令解析 */  index = at_cmd_search(&p[2], len); /* 查找匹配的执行指令,0-已匹配,!0-未匹配 */  if (index >= 0) { /*查找到相应的指令后获取指令+TEST的长度,根据长度找到是?还是=*/  char *get_name = at_cmd_func[index].at_name;  if (ut_str_not_blank(get_name)) { uint8_t n = ut_strlen(get_name);  int8_t get_type = p[2+n]; /*去掉回车符*/  if (get_type == '=') { if (at_cmd_func[index].at_set) { PRINT_AT_CMD("at_set\n"); p[len-2]='\0'; p[len-1]='\0';  int8_t common_parameter_buffer[CONFIG_MAX_LEN_PARAMETER]={0};  sprintf((char *)common_parameter_buffer,"%s",&p[3+n]);  uint8_t get_para_num = ut_str_split((char *)common_parameter_buffer,",",revbuf);  if (get_para_num) { ret = at_cmd_func[index].at_set(get_para_num); } else { uint8_t get_para_num = ut_str_split((char *)common_parameter_buffer,"&",revbuf);  ret = at_cmd_func[index].at_set(get_para_num); } } } else if (get_type == '?') { if (at_cmd_func[index].at_get) {  ret = at_cmd_func[index].at_get(get_name); } } else if (get_type == '\r') { if (at_cmd_func[index].at_exe) { PRINT_AT_CMD("at_exe\n"); ret = at_cmd_func[index].at_exe(get_name); } } else { PRINT_AT_CMD("undefine type=%02x\r\n",get_type); } } } else { ret = ESP_AT_RESULT_CODE_FAIL; /* 未找到匹配的指令 */ } } } else {/* 格式不匹配 */  return ESP_AT_RESULT_CODE_ERROR; }  return ret;} #include "esp_ping.h"static void test_on_ping_success(esp_ping_handle_t hdl, void *args){ uint8_t ttl; uint16_t seqno; uint32_t elapsed_time, recv_len; ip_addr_t target_addr; esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); rt_kprintf("%" PRId32 "bytes from %s icmp_seq=%d ttl=%d time=%" PRId32 " ms\n", recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time);} static void test_on_ping_timeout(esp_ping_handle_t hdl, void *args){ uint16_t seqno; ip_addr_t target_addr; esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); rt_kprintf("From %s icmp_seq=%d timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno);} static void test_on_ping_end(esp_ping_handle_t hdl, void *args){ uint32_t transmitted; uint32_t received; uint32_t total_time_ms;  esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); rt_kprintf("%" PRId32 " packets transmitted, %" PRId32 " received, time %" PRId32 "ms\n", transmitted, received, total_time_ms);} static esp_ping_handle_t ping=NULL; static void ping_target(char *set_url){ ip_addr_t target_addr; struct addrinfo hint; struct addrinfo *res = NULL; memset(&hint, 0, sizeof(hint)); memset(&target_addr, 0, sizeof(target_addr)); /* convert URL to IP */ if (0 != getaddrinfo(set_url, NULL, &hint, &res)) { rt_kprintf("set_url[%s] invalid",set_url); return; }  rt_kprintf("ping %s",set_url);  struct in_addr addr4 = ((struct sockaddr_in *)(res->ai_addr))->sin_addr; inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4); freeaddrinfo(res);  esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG(); ping_config.timeout_ms = 2000; ping_config.target_addr = target_addr; ping_config.count = 0; // ping in infinite mode  /* set callback functions */ esp_ping_callbacks_t cbs = { .cb_args = NULL, .on_ping_success = test_on_ping_success, .on_ping_timeout = test_on_ping_timeout, .on_ping_end = test_on_ping_end };  if (NULL == ping) { esp_ping_new_session(&ping_config, &cbs, &ping); esp_ping_start(ping); }} static int run_ping(int argc, char **argv){ if (2 == argc) {  ping_target(argv[1]); }  return 0;}MSH_CMD_EXPORT_ALIAS(run_ping,ping, ping ["baidu.com"]); static int stop_ping(int argc, char **argv){ if (1 == argc) {  esp_ping_stop(ping);  esp_ping_delete_session(ping); ping=NULL; }  return 0;}MSH_CMD_EXPORT_ALIAS(stop_ping,ping_stop, ping stop); static int run_at_cmd(int argc, char **argv){ char dtmp[256]={0};  if (2 == argc) {  sprintf(dtmp,"AT+%s\r\n",argv[1]); uint8_t get_bytes = strlen(dtmp);  if((dtmp[0] == 'A') && (dtmp[1] == 'T') && (dtmp[get_bytes-2] == 0x0D) && (dtmp[get_bytes-1] == 0x0A)) { uint8_t ret_parse = at_cmd_parse((uint8_t *)dtmp, get_bytes); rt_kprintf("ret_parse=%02x",ret_parse);  if ((ESP_AT_RESULT_CODE_OK == ret_parse) || (ESP_AT_RESULT_CODE_SEND_OK == ret_parse)) { esp_at_response_result(ESP_AT_RESULT_CODE_OK); } else if ((ESP_AT_RESULT_CODE_ERROR == ret_parse) || (ESP_AT_RESULT_CODE_SEND_FAIL == ret_parse) || (ESP_AT_RESULT_CODE_FAIL == ret_parse)) { esp_at_response_result(ESP_AT_RESULT_CODE_ERROR); } } }  return 0;}MSH_CMD_EXPORT_ALIAS(run_at_cmd,AT, AT [cmd]); #define RG_MIN(a, b)            \ ({                          \ __typeof__(a) _a = (a); \ __typeof__(b) _b = (b); \ _a < _b ? _a : _b; \ }) void system_set_timezone(const char *tz){ setenv("TZ", tz, 1); tzset();} bool rg_network_sync_time(const char *host, int *str_timezone){  //SOCK_RAW SOCK_DGRAM int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); struct hostent *server = gethostbyname(host); struct sockaddr_in serv_addr = {}; struct timeval timeout = {3, 0}; struct timeval ntp_time = {0, 0}; struct timeval cur_time;  if (server == NULL) { rt_kprintf("Failed to resolve NTP server hostname\n"); return false; }  size_t addr_length = RG_MIN(server->h_length, sizeof(serv_addr.sin_addr.s_addr)); memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, addr_length); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(123);  uint32_t ntp_packet[12] = {0x0000001B, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // li, vn, mode.  setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); if (0 > connect(sockfd, (void *)&serv_addr, sizeof(serv_addr))) { rt_kprintf("Failed to connect\n");  return false; }  send(sockfd, &ntp_packet, sizeof(ntp_packet), 0);  if (recv(sockfd, &ntp_packet, sizeof(ntp_packet), 0) >= 0) { ntp_time.tv_sec = ntohl(ntp_packet[10]) - 2208988800UL; // DIFF_SEC_1900_1970; ntp_time.tv_usec = (((int64_t)ntohl(ntp_packet[11]) * 1000000) >> 32); long timezone = 8 * 3600; // 8小时  //system_set_timezone("CST-8");  gettimeofday(&cur_time, NULL); settimeofday(&ntp_time, NULL);  int64_t prev_millis = ((((int64_t)cur_time.tv_sec * 1000000) + cur_time.tv_usec) / 1000); int64_t now_millis = ((int64_t)ntp_time.tv_sec * 1000000 + ntp_time.tv_usec) / 1000; int ntp_time_delta = (now_millis - prev_millis);  #if 0 rt_kprintf("Received Time: %.24s, we were %dms %s\n", ctime(&ntp_time.tv_sec), abs((int)ntp_time_delta), ntp_time_delta < 0 ? "ahead" : "behind"); #endif  close(sockfd); return true; }  close(sockfd); return false;} static int sync_time(int argc, char **argv){ char timezone[32]={0}; if (2 == argc) {  sprintf(timezone,"%s",argv[1]);  struct timeval cur_time;  system_set_timezone(timezone);  gettimeofday(&cur_time, NULL);  rt_kprintf("Time: %.24s\n",ctime(&cur_time.tv_sec));  }  return 0;}MSH_CMD_EXPORT_ALIAS(sync_time,time,usage: time [timezone]("CST-8")); void debug_cmd_init(void){  finsh_system_init();}

5 实验效果

6 总结
除了实现自定义传参函数外,还可以结合文件系统实现一些自动脚本,在需要更改需求时,不需要重新编译固件,只通过简单的文本编辑就可以运行新的应用,类似于.bat文件,这样就降低了维护成本。
回复关键字esp-finsh控制台获取本次总结的所有代码


声明: 本文转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们及时删除。(联系我们,邮箱:evan.li@aspencore.com )
0
评论
  • 相关技术文库
  • C语言
  • 编程
  • 软件开发
  • 程序
下载排行榜
更多
评测报告
更多
广告