1 at指令
官方的at固件是不开源的,指令解析和功能实现被封装成静态库了,这套at指令可以很方便的控制芯片,满足一些基本的功能需求,比如at+mqtt,at+web服务器等,今天记录一下如何实现这样一套at指令,这套指令完全可以复用到其他的主控上,复用到未来的项目上。
2 串口部分
2.1 参数配置
uart_config_t g_uart_config = { .baud_rate = config_baud_uart_default, .data_bits = uart_data_8_bits, .parity = uart_parity_disable, .stop_bits = uart_stop_bits_1, .flow_ctrl = uart_hw_flowctrl_disable};uart_param_config(ex_uart_num, &g_uart_config);2.2 串口任务
#define rd_buf_size (2048)static queuehandle_t uart0_queue;xtaskcreate(uart_event_task, uart_event_task, 2048, &transport_config, 15, null);2.3 接收中断处理
static void uart_rx_intr_handler_default(void *param){ uart_obj_t *p_uart = (uart_obj_t *) param; uint8_t uart_num = p_uart->uart_num; uart_dev_t *uart_reg = uart[uart_num]; int rx_fifo_len = uart_reg->status.rxfifo_cnt; uint8_t buf_idx = 0; uint32_t uart_intr_status = uart[uart_num]->int_st.val; uart_event_t uart_event; basetype_t task_woken = 0; while (uart_intr_status != 0x0) { uart_select_notif_t notify = uart_select_error_notif; buf_idx = 0; uart_event.type = uart_event_max; if (uart_intr_status & uart_txfifo_empty_int_st_m) { uart_clear_intr_status(uart_num, uart_txfifo_empty_int_clr_m); uart_disable_intr_mask(uart_num, uart_txfifo_empty_int_ena_m); // tx semaphore will only be used when tx_buf_size is zero. if (p_uart->tx_waiting_fifo == true && p_uart->tx_buf_size == 0) { p_uart->tx_waiting_fifo = false; xsemaphoregivefromisr(p_uart->tx_fifo_sem, &task_woken); if (task_woken == pdtrue) { portyield_from_isr(); } } else { // we don't use tx ring buffer, because the size is zero. if (p_uart->tx_buf_size == 0) { continue; } int tx_fifo_rem = uart_fifo_len - uart[uart_num]->status.txfifo_cnt; bool en_tx_flg = false; // we need to put a loop here, in case all the buffer items are very short. // that would cause a watch_dog reset because empty interrupt happens so often. // although this is a loop in isr, this loop will execute at most 128 turns. while (tx_fifo_rem) { if (p_uart->tx_len_tot == 0 || p_uart->tx_ptr == null || p_uart->tx_len_cur == 0) { size_t size; p_uart->tx_head = (uart_tx_data_t *) xringbufferreceivefromisr(p_uart->tx_ring_buf, &size); if (p_uart->tx_head) { // the first item is the data description // get the first item to get the data information if (p_uart->tx_len_tot == 0) { p_uart->tx_ptr = null; p_uart->tx_len_tot = p_uart->tx_head->tx_data.size; // we have saved the data description from the 1st item, return buffer. vringbufferreturnitemfromisr(p_uart->tx_ring_buf, p_uart->tx_head, &task_woken); if (task_woken == pdtrue) { portyield_from_isr(); } } else if (p_uart->tx_ptr == null) { // update the tx item pointer, we will need this to return item to buffer. p_uart->tx_ptr = (uint8_t *) p_uart->tx_head; en_tx_flg = true; p_uart->tx_len_cur = size; } } else { // can not get data from ring buffer, return; break; } } if (p_uart->tx_len_tot > 0 && p_uart->tx_ptr && p_uart->tx_len_cur > 0) { // to fill the tx fifo. int send_len = p_uart->tx_len_cur > tx_fifo_rem ? tx_fifo_rem : p_uart->tx_len_cur; for (buf_idx = 0; buf_idx fifo.rw_byte = *(p_uart->tx_ptr++) & 0xff; } p_uart->tx_len_tot -= send_len; p_uart->tx_len_cur -= send_len; tx_fifo_rem -= send_len; if (p_uart->tx_len_cur == 0) { // return item to ring buffer. vringbufferreturnitemfromisr(p_uart->tx_ring_buf, p_uart->tx_head, &task_woken); if (task_woken == pdtrue) { portyield_from_isr(); } p_uart->tx_head = null; p_uart->tx_ptr = null; } if (p_uart->tx_len_tot == 0) { if (tx_fifo_rem == 0) { en_tx_flg = true; } else{ en_tx_flg = false; } xsemaphoregivefromisr(p_uart->tx_done_sem, &task_woken); if (task_woken == pdtrue) { portyield_from_isr(); } } else { en_tx_flg = true; } } } if (en_tx_flg) { uart_clear_intr_status(uart_num, uart_txfifo_empty_int_clr_m); uart_enable_intr_mask(uart_num, uart_txfifo_empty_int_ena_m); } } } else if ((uart_intr_status & uart_rxfifo_tout_int_st_m) || (uart_intr_status & uart_rxfifo_full_int_st_m) ) { rx_fifo_len = uart_reg->status.rxfifo_cnt; if (p_uart->rx_buffer_full_flg == false) { // we have to read out all data in rx fifo to clear the interrupt signal while (buf_idx rx_data_buf[buf_idx++] = uart_reg->fifo.rw_byte; } // get the buffer from the fifo // after copying the data from fifo ,clear intr_status uart_clear_intr_status(uart_num, uart_rxfifo_tout_int_clr_m | uart_rxfifo_full_int_clr_m); uart_event.type = uart_data; uart_event.size = rx_fifo_len; p_uart->rx_stash_len = rx_fifo_len; // if we fail to push data to ring buffer, we will have to stash the data, and send next time. // mainly for applications that uses flow control or small ring buffer. if (pdfalse == xringbuffersendfromisr(p_uart->rx_ring_buf, p_uart->rx_data_buf, p_uart->rx_stash_len, &task_woken)) { uart_disable_intr_mask(uart_num, uart_rxfifo_tout_int_ena_m | uart_rxfifo_full_int_ena_m); uart_event.type = uart_buffer_full; p_uart->rx_buffer_full_flg = true; } else { p_uart->rx_buffered_len += p_uart->rx_stash_len; } notify = uart_select_read_notif; if (task_woken == pdtrue) { portyield_from_isr(); } } else { uart_disable_intr_mask(uart_num, uart_rxfifo_full_int_ena_m | uart_rxfifo_tout_int_ena_m); uart_clear_intr_status(uart_num, uart_rxfifo_full_int_clr_m | uart_rxfifo_tout_int_clr_m); } } else if (uart_intr_status & uart_rxfifo_ovf_int_st_m) { // when fifo overflows, we reset the fifo. uart_reset_rx_fifo(uart_num); uart_reg->int_clr.rxfifo_ovf = 1; uart_event.type = uart_fifo_ovf; notify = uart_select_error_notif; } else if (uart_intr_status & uart_frm_err_int_st_m) { uart_reg->int_clr.frm_err = 1; uart_event.type = uart_frame_err; notify = uart_select_error_notif; } else if (uart_intr_status & uart_parity_err_int_st_m) { uart_reg->int_clr.parity_err = 1; uart_event.type = uart_parity_err; notify = uart_select_error_notif; } else { uart_reg->int_clr.val = uart_intr_status; // simply clear all other intr status uart_event.type = uart_event_max; notify = uart_select_error_notif; }#ifdef config_using_esp_vfs if (uart_event.type != uart_event_max && p_uart->uart_select_notif_callback) { p_uart->uart_select_notif_callback(uart_num, notify, &task_woken); if (task_woken == pdtrue) { portyield_from_isr(); } }#else (void)notify;#endif if (uart_event.type != uart_event_max && p_uart->xqueueuart) { if (pdfalse == xqueuesendfromisr(p_uart->xqueueuart, (void *)&uart_event, &task_woken)) { esp_early_logv(uart_tag, uart event queue full); } if (task_woken == pdtrue) { portyield_from_isr(); } } uart_intr_status = uart_reg->int_st.val; }}2.4 at指令解析和数据透传
void uart_event_task(void *pvparameters){ uart_event_t event; uart_driver_install(ex_uart_num, 2048, 2048, 100, &uart0_queue, 0); if (pvparameters) { get_config = pvparameters; } /*at command say hello */ uart_write_bytes(ex_uart_num, (const char *) config_at_hello, strlen(config_at_hello)); uint8_t *dtmp = (uint8_t *) malloc(rd_buf_size); for (;;) { // waiting for uart event. if (xqueuereceive(uart0_queue, (void *)&event, (portticktype)portmax_delay)) { bzero(dtmp, rd_buf_size); switch (event.type) { // event of uart receving data // we'd better handler data event fast, there would be much more data events than // other types of events. if we take too much time on data event, the queue might be full. case uart_data: uart_read_bytes(ex_uart_num, dtmp, event.size, portmax_delay); if (event.size > 0) { if((dtmp[0] == 'a') && (dtmp[1] == 't') && (dtmp[event.size-2] == 0x0d) && (dtmp[event.size-1] == 0x0a)) { uint8_t m = mstrlen((char *)dtmp); uint8_t ret_parse = at_cmd_parse(dtmp, m); 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_logi(tag, error parse =[%d],ret_parse); esp_at_response_result(esp_at_result_code_error); } } else { //透传代码... } // note: only one character was read even the buffer contains more. the other characters will // be read one-by-one by subsequent calls to select() which will then return immediately // without timeout. } break; // event of hw fifo overflow detected case uart_fifo_ovf: esp_logi(tag, hw fifo overflow); // if fifo overflow happened, you should consider adding flow control for your application. // the isr has already reset the rx fifo, // as an example, we directly flush the rx buffer here in order to read more data. uart_flush_input(ex_uart_num); xqueuereset(uart0_queue); break; // event of uart ring buffer full case uart_buffer_full: esp_logi(tag, ring buffer full); // if buffer full happened, you should consider encreasing your buffer size // as an example, we directly flush the rx buffer here in order to read more data. uart_flush_input(ex_uart_num); xqueuereset(uart0_queue); break; case uart_parity_err: esp_logi(tag, uart parity error); break; // event of uart frame error case uart_frame_err: esp_logi(tag, uart frame error); break; // others default: esp_logi(tag, uart event type: %d, event.type); break; } } } free(dtmp); dtmp = null; vtaskdelete(null);}at指令解析const esp_at_cmd_struct at_cmd_func[] ={ {+rst,null,null,at_cmd_reset}, {+gmr,null,null,at_cmd_version}, {+restore,null,null,at_cmd_restore}, {+en,at_get_wifien,at_config_wifien}, {+uart_def,at_querycmduartdef,at_setupcmduartdef}, {+mode,at_get_workmode,at_config_workmode}, {+ap,at_get_apinfo,at_config_ap}, };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
以填空的形式,实现at指令typedef struct{ char *at_name; /*!< at command name */ uint8_t (*at_get)(char *cmd_name); /*!< query command function pointer */ uint8_t (*at_set)(uint8_t para_num); /*!< setup command function pointer */ uint8_t (*at_exe)(char *cmd_name);} esp_at_cmd_struct;以ap和gmr指令为例,at_name为”+ap”,完整指令是at+ap+回车字符,at_get为at_get_apinfo,at_set为at_config_ap
/* at指令表 */const esp_at_cmd_struct at_cmd_func[] ={ {+gmr,null,null,at_cmd_version}, {+ap,at_get_apinfo,at_config_ap},};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, mstrlen((char *)buffer_tx)); snprintf((char *)buffer_tx, 64, at version: %s\\n, config_version_at); esp_at_port_write_data(buffer_tx, mstrlen((char *)buffer_tx)); snprintf((char *)buffer_tx, 64, compile time: %s %s\\n, __date__, __time__); esp_at_port_write_data(buffer_tx, mstrlen((char *)buffer_tx)); return esp_at_result_code_ok;}static uint8_t at_get_apinfo(char *cmd_name){ uint8_t buffer[128]; snprintf((char*)buffer,sizeof(buffer) - 1,%s:ssid=%s&psk=%s&enc=%d&hide=%d&ip=%s&gw=%s&masknet=%s&\\r\\n, cmd_name,g_ap_config.ap.ssid,g_ap_config.ap.password,g_ap_config.ap.authmode,g_ap_config.ap.ssid_hidden, set_ip_ap,set_gateway_ap,set_netmask_ap); esp_at_port_write_data(buffer,mstrlen((char*)buffer)); return esp_at_result_code_ok;}char param_info[128]={0};static uint8_t at_config_ap(uint8_t para_num){ uint8_t buf_len = 0; uint8_t find_parameters = 0; uint8_t find_different = 0; if (1 != para_num) { return esp_at_result_code_error; } if (!str_is_notblank(revbuf[0])) { return esp_at_result_code_error; } esp_logi(tag, {%s}\\n,revbuf[0]); buf_len = strlen(revbuf[0]); if (buf_len < (8+2)) { esp_logi(tag, {%d}\\n,buf_len); return esp_at_result_code_error; } char* new_buf = revbuf[0]; if (!new_buf) { return esp_at_result_code_error; } if ((new_buf[0] != '\\') || (new_buf[buf_len-1-2] != '\\')) { return esp_at_result_code_error; } if (new_buf[buf_len-1-2-1] != '&') { return esp_at_result_code_error; } new_buf += 1; if (httpd_query_key_value(new_buf, ssid, param_info, sizeof(param_info)) == esp_ok) { //check ssid if (is_valid_wifi_ssid(param_info)) { uint8_t check_len = strlen(param_info); if (memcmp(g_ap_config.ap.ssid,(uint8_t *)param_info,check_len) || (check_len != strlen((char *)g_ap_config.ap.ssid))) { sprintf((char *)g_ap_config.ap.ssid,%s,param_info); user_nvs_setkey(ap_name,param_info); find_different++; } find_parameters++; } } if (httpd_query_key_value(new_buf, psk, param_info, sizeof(param_info)) == esp_ok) { //check password if (is_valid_wifi_password(param_info)) { uint8_t check_len = strlen(param_info); if (memcmp(g_ap_config.ap.password,(uint8_t *)param_info,check_len) || (check_len != strlen((char *)g_ap_config.ap.password))) { sprintf((char *)g_ap_config.ap.password,%s,param_info); user_nvs_setkey(ap_paswd,param_info); find_different++; } find_parameters++; } } if (httpd_query_key_value(new_buf, enc, param_info, sizeof(param_info)) == esp_ok) { //check enc if (1 == strlen(param_info)) { uint8_t get_val = (uint8_t)atoi(param_info); if (is_valid_enc(get_val)) { if (g_ap_config.ap.authmode != get_val) { g_ap_config.ap.authmode = get_val; user_nvs_setkey(ap_enc_mode,param_info); find_different++; } find_parameters++; } } } if (httpd_query_key_value(new_buf, hide, param_info, sizeof(param_info)) == esp_ok) { //check hide if (1 == strlen(param_info)) { uint8_t get_val = (uint8_t)atoi(param_info); if (get_val<=1) { if (g_ap_config.ap.ssid_hidden != get_val) { g_ap_config.ap.ssid_hidden = get_val; user_nvs_setkey(ap_hide,param_info); find_different++; } find_parameters++; } } } if (httpd_query_key_value(new_buf, ip, param_info, sizeof(param_info)) == esp_ok) { //check ip if (is_valid_ip(param_info)) { uint8_t check_len = strlen(param_info); if (memcmp((uint8_t *)set_ip_ap,(uint8_t *)param_info,length_of_array(set_ip_ap)) || (check_len != strlen(set_ip_ap))) { sprintf(set_ip_ap,%s,param_info); user_nvs_setkey(ip_ap,param_info); find_different++; } find_parameters++; } } if (httpd_query_key_value(new_buf, gw, param_info, sizeof(param_info)) == esp_ok) { //check gataway if (is_valid_ip(param_info)) { uint8_t check_len = strlen(param_info); if (memcmp((uint8_t *)set_gateway_ap,(uint8_t *)param_info,length_of_array(set_gateway_ap)) || (check_len != strlen(set_gateway_ap))) { sprintf(set_gateway_ap,%s,param_info); user_nvs_setkey(gateway_ap,param_info); find_different++; } find_parameters++; } } if (httpd_query_key_value(new_buf, masknet, param_info, sizeof(param_info)) == esp_ok) { //check netmask if (is_valid_ip(param_info)) { uint8_t check_len = strlen(param_info); if (memcmp((uint8_t *)set_netmask_ap,(uint8_t *)param_info,length_of_array(set_netmask_ap)) || (check_len != strlen(set_netmask_ap))) { sprintf(set_netmask_ap,%s,param_info); user_nvs_setkey(netmask_ap,param_info); find_different++; } find_parameters++; } } if (find_parameters) { user_nvs_close(); if (find_different) { //reconfig ap wifi info notify_reset_task(0); g_reconfig_ip(wt_ap); at_wifi_reconnect(g_set_work_mode); uint8_t send_work_mode = g_set_work_mode; if (wifi_mode_sta != g_set_work_mode) { send_work_mode = 0; } update_json_str(send_work_mode); } return esp_at_result_code_ok; } return esp_at_result_code_error;}/* helper function to get a url query tag from a query string of the type param1=val1¶m2=val2 */esp_err_t httpd_query_key_value(const char *qry_str, const char *key, char *val, size_t val_size){ if (qry_str == null || key == null || val == null) { return esp_err_invalid_arg; } const char *qry_ptr = qry_str; const size_t buf_len = val_size; while (strlen(qry_ptr)) { /* search for the '=' character. else, it would mean * that the parameter is invalid */ const char *val_ptr = strchr(qry_ptr, '='); if (!val_ptr) { break; } size_t offset = val_ptr - qry_ptr; /* if the key, does not match, continue searching. * compare lengths first as key from url is not * null terminated (has '=' in the end) */ if ((offset != strlen(key)) || (strncasecmp(qry_ptr, key, offset))) { /* get the name=val string. multiple name=value pairs * are separated by '&' */ qry_ptr = strchr(val_ptr, '&'); if (!qry_ptr) { break; } qry_ptr++; continue; } /* locate start of next query */ qry_ptr = strchr(++val_ptr, '&'); /* or this could be the last query, in which * case get to the end of query string */ if (!qry_ptr) { qry_ptr = val_ptr + strlen(val_ptr); } /* update value length, including one byte for null */ val_size = qry_ptr - val_ptr + 1; /* copy value to the caller's buffer. */ strlcpy(val, val_ptr, min(val_size, buf_len)); /* if buffer length is smaller than needed, return truncation error */ if (buf_len < val_size) { return esp_err_httpd_result_trunc; } return esp_ok; } esp_logd(tag, log_fmt(key %s not found), key); return esp_err_not_found;}3 at指令测试
上电后默认会返回at ready
3.1 发送gmr指令
at+gmr返回
sdk version: fe6604a-dirtyat version: v0.1compile time: april 16, 2023 20:28:00at ok3.2 发送ap指令
at+ap=ssid=test_wifi&psk=01234567&dhcp=1&连接路由器wifi,名称为test_wifi,密码为01234567,使能动态获取ip
返回
at ok4 字符串处理相关api
uint8_t str_is_notblank(char *p_str){ if (!p_str) { return 0; } return (mstrlen(p_str)?1:0);}#include string.hstatic uint8_t str_start_with(char* src, char* str){ if (strlen(src) < strlen(str)) { return false; } for (int i = 0; i < strlen(str); i++) { if (src[i] != str[i]) { return false; } } return true;}/** * @description: 是否以指定子字符串结尾 * @param {src} 待比较的字符串 * @param {str} 指定的子字符串 * @return {*} true/false */static uint8_t str_end_with(char* src, char* str){ if (strlen(src) < strlen(str)) { return false; } char* ptr = src+(strlen(src)-strlen(str)); for (int i = 0; i < strlen(str); i++) { if (ptr[i] != str[i]) { return false; } } return true;}uint8_t split(char *src,const char *separator,char **dest){ char *pnext; uint8_t get_cnt = 0; if (src == 0 || mstrlen(src) == 0) return; if (separator == 0 || mstrlen(separator) == 0) return; pnext = (char *)strtok(src,separator); while(pnext != 0) { *dest++ = pnext; pnext = (char *)strtok(0,separator); get_cnt ++; } return get_cnt;}5 跨平台通用at指令
在新的硬件回来之后,可以使用at指令来测试基本的外设功能,比如指定pin来操作gpio、指定adc来获取传感器数据、指定spi来读取flash数据等,无论是什么mcu,无论是什么项目都可以通过这样的at指令来帮助我们更快地测试硬件。
bug记录
esp8266中在使用sprintf时,如果超出了给定数组的长度,并不会引起崩溃,而是会改变某个变量的数值。
char test_buf[8];sprintf(test_buf,(set=%s),hello world);测试时发现有些变量的数值被改变了
特斯拉陷入舆论风波 “货不对板”有消费欺诈之嫌
美国科技股泡沫破灭?中概股集体走软 苹果1天蒸发400亿
防爆无人值守称重系统的详细说明
如何制作一个基于Arduino的微处理器3相逆变器电路?
华为两款新机入网 含折叠屏新机
ESP8266官方AT指令的实现方法
哪一款无线充电宝质量最好,好用的无线充电宝推荐
美国独立评级机构Weiss Ratings发表了加密货币的评级结果
下一代整车级架构解决方案—区域控制器
数控车床编程技巧和循环指令应用
基于 Realtek RTL8221B 2.5G ETHERNET TRANSCEIVER WiFi6路由器 AX5400M 设计方案
瑞萨电子宣布和黑莓共同开发出针对瑞萨电子 R-Car 软件包
CNC加工中心换刀故障分析及处理
2021年全球IT行业发展趋势的七个预测
无线通信技术发展趋势探讨
4K智能电视将进入全面普及阶段
DC电源模块如何定制呢?
多级放大电路3种耦合方式的详细分析
解析隔离与非隔离LED驱动电源
今天的人工智能技术将如何带来明天的进步