1 控制台
rtthread的finsh控制台使用起来非常友好,类似于linux的控制台,在我们需要调试理想的传感器阈值,理想的PID、又或者是想验证某个API功能时,控制台可以帮助我们节省很多试错成本
2 测试环境
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;} 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]); ({ \ __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); 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"); 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控制台获取本次总结的所有代码