# at_client_with_rtos **Repository Path**: eming/at_client_with_rtos ## Basic Information - **Project Name**: at_client_with_rtos - **Description**: 将RT-Thread 的AT组件移植到FreeRTOS中,若移植到其他操作系统也可以参照此项目 - **Primary Language**: C - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 9 - **Created**: 2024-03-29 - **Last Updated**: 2025-02-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # at_client_with_rtos ### 介绍 将RT-Thread 的AT组件移植到FreeRTOS中,若移植到其他操作系统也可以参照此项目 ### 软件架构 1. freertos(提供信号量、互斥量、创建线程) 2. at client(RT-Thread 的AT Client实现) 3. easylogger(为AT Client提供日志支持) 4. bsp_uart_fifo.c bsp_uart_fifo.h(安富莱电子的串口驱动,带有环形缓冲区) ### 安装教程 1. 复制at_client文件夹、easylogger文件夹、bsp_uart_fifo.c bsp_uart_fifo.h文件(这两个串口驱动文件可以不复制,由用户实现串口驱动,建议串口驱动最好带上环形缓冲区) 2. 先初始化串口驱动,再初始化easylogger,最后初始化at client,详细过程见bsp_init()函数。 3. 以下是为了适配freertos对原始文件所作出的修改 `at.h` ```C #define RT_EOK 0 #define RT_ERROR 1 #define RT_ETIMEOUT 2 #define RT_EFULL 3 #define RT_EEMPTY 4 #define RT_ENOMEM 5 #define RT_ENOSYS 6 #define RT_EBUSY 7 #define RT_EIO 8 #define RT_EINTR 9 #define RT_EINVAL 10 #define RT_TRUE 1 #define RT_FALSE 0 #define RT_NAME_MAX 16 #define RT_IPC_FLAG_PRIO 0x01 #define RT_IPC_FLAG_FIFO 0x00 #define RT_THREAD_PRIORITY_MAX configMAX_PRIORITIES #define RT_ASSERT(expression) do{if(!(expression))printf("expression:(%s) == 0\r\nfile:%s line:%d\r\n",#expression,__FILE__,__LINE__);}while(0) #define rt_memcpy memcpy #define rt_memset memset #define rt_strstr strstr #define rt_memcmp memcmp #define rt_snprintf snprintf #define rt_strlen strlen #define rt_strcmp strcmp #define rt_strncmp strncmp #define RT_WAITING_FOREVER portMAX_DELAY #define rt_weak __attribute__((weak)) #define rt_sem_take(Sem,timeout) xSemaphoreTake(Sem,timeout) #define rt_sem_release(Sem) do{if(xPortIsInsideInterrupt()){BaseType_t pxHigherPriorityTaskWoken;xSemaphoreGiveFromISR(Sem,&pxHigherPriorityTaskWoken);} else xSemaphoreGive(Sem);}while(0) #define rt_mutex_release(mutex) xSemaphoreGive(mutex) #define rt_mutex_take(mutex,timeout) xSemaphoreTake(mutex,timeout) #define rt_tick_from_millisecond pdMS_TO_TICKS #define rt_tick_get xTaskGetTickCount /* 根据实际需要选择是否修改以下宏定义 */ #define rt_calloc calloc #define rt_free free #define rt_realloc realloc #define rt_kprintf printf #define rt_device_t COM_PORT_E typedef size_t rt_size_t ; typedef size_t rt_size_t ; typedef int32_t rt_int32_t ; typedef uint32_t rt_uint32_t ; typedef TaskHandle_t rt_thread_t ; typedef SemaphoreHandle_t rt_sem_t ; typedef SemaphoreHandle_t rt_mutex_t ; typedef size_t rt_tick_t; typedef size_t rt_off_t; typedef int rt_bool_t; typedef uint8_t rt_uint8_t; typedef long rt_base_t; typedef rt_base_t rt_err_t; struct at_client { rt_device_t device; /* 自己添加的 */ char *client_name; at_status_t status; char end_sign; /* the current received one line data buffer */ char *recv_line_buf; /* The length of the currently received one line data */ rt_size_t recv_line_len; /* The maximum supported receive data length */ rt_size_t recv_bufsz; rt_sem_t rx_notice; rt_mutex_t lock; at_response_t resp; rt_sem_t resp_notice; at_resp_status_t resp_status; struct at_urc_table *urc_table; rt_size_t urc_table_size; rt_thread_t parser; //线程控制块 }; ``` `at_utils.c` ```C /*************************************************移植到freertos需要做的修改*******************************************************/ /** * 为了兼容 rt_device_read()函数自己定义的 * * @param dev 设备句柄 * @param pos 读取的偏移量 * @param buffer 用于保存读取数据的数据缓冲区 * @param size 缓冲区的大小 * * @return 成功返回实际读取的大小,如果是字符设备,返回大小以字节为单位,如果是块设备,返回的大小以块为单位;失败则返回0 */ rt_weak rt_size_t rt_device_read ( rt_device_t dev, rt_off_t pos, void * buffer, rt_size_t size ) { uint32_t rx_num = 0; for(uint32_t i = 0; i < size; i++){ if(comGetChar(dev - 1,buffer)) rx_num++; else goto exit; } exit: return rx_num; } /** * 为了兼容 rt_device_write()函数自己定义的 * * @param dev 设备句柄 * @param pos 写入的偏移量 * @param buffer 要写入设备的数据缓冲区 * @param size 写入数据的大小 * * @return 成功返回实际写入数据的大小,如果是字符设备,返回大小以字节为单位;如果是块设备,返回的大小以块为单位;失败则返回0。 */ rt_size_t rt_device_write (rt_device_t dev, rt_off_t pos, const void * buffer, rt_size_t size ) { comSendBuf(dev - 1,(uint8_t*)buffer,size); return size; } /*创建信号量 */ rt_sem_t rt_sem_create (const char * name, rt_uint32_t value, rt_uint8_t flag ) { return xSemaphoreCreateBinary(); } /*创建互斥量 */ rt_mutex_t rt_mutex_create (const char * name, rt_uint8_t flag ) { return xSemaphoreCreateMutex(); } /* 删除互斥量 */ rt_err_t rt_mutex_delete (rt_mutex_t mutex) { vSemaphoreDelete(mutex); return RT_EOK; } /* 删除信号量 */ rt_err_t rt_sem_delete ( rt_sem_t sem ) { vSemaphoreDelete(sem); return RT_EOK; } ``` 根据实际需要修改 rt_device_read() rt_device_write()函数 `at_client.c` ```C /** * AT client initialize. * 为了兼容安富莱的串口驱动,新增一个参数 _ucPort * @param dev_name AT client device name * @param recv_bufsz the maximum number of receive buffer length * @param _ucPort 为at客户端指定一个串口设备 * @return 0 : initialize success * -1 : initialize failed * -5 : no memory */ int at_client_init(const char *dev_name, rt_size_t recv_bufsz ,COM_PORT_E _ucPort) { int idx = 0; int result = RT_EOK; rt_err_t open_result = RT_EOK; at_client_t client = RT_NULL; RT_ASSERT(dev_name); RT_ASSERT(recv_bufsz > 0); if (at_client_get(dev_name) != RT_NULL) { return result; } for (idx = 0; idx < AT_CLIENT_NUM_MAX && at_client_table[idx].device; idx++); if (idx >= AT_CLIENT_NUM_MAX) { LOG_E("AT client initialize failed! Check the maximum number(%d) of AT client.", AT_CLIENT_NUM_MAX); result = -RT_EFULL; goto __exit; } client = &at_client_table[idx]; client->recv_bufsz = recv_bufsz; result = at_client_para_init(client); if (result != RT_EOK) { goto __exit; } /* find and open command device */ /* 为了兼容安富莱驱动 */ client->client_name = (char*)dev_name; client->device = _ucPort + 1; /* 加一是因为at_client_init会通过dev来遍历at_client_table,遍历语句for (idx = 0; idx < AT_CLIENT_NUM_MAX && at_client_table[idx].device; idx++); */ // client->device = rt_device_find(dev_name); // if (client->device) // { // /* using DMA mode first */ // open_result = rt_device_open(client->device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_DMA_RX); // /* using interrupt mode when DMA mode not supported */ // if (open_result == -RT_EIO) // { // open_result = rt_device_open(client->device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX); // } // RT_ASSERT(open_result == RT_EOK); // rt_device_set_rx_indicate(client->device, at_client_rx_ind); // } // else // { // LOG_E("AT client initialize failed! Not find the device(%s).", dev_name); // result = -RT_ERROR; // goto __exit; // } __exit: if (result == RT_EOK) { client->status = AT_STATUS_INITIALIZED; // rt_thread_startup(client->parser); LOG_I("AT client(V%s) on device %s initialize success.", AT_SW_VERSION, dev_name); } else { LOG_E("AT client(V%s) on device %s initialize failed(%d).", AT_SW_VERSION, dev_name, result); } return result; } ``` 根据实际需要修改at_client_init()函数,让at client和串口设备关联在一起,这里通过`client->device = _ucPort + 1;`语句,使client和安富莱串口驱动关联在一起。 串口驱动接收到字符需要通知线程来处理字符,at client组件已经为我们准备好了串口回调函数,我们只需要在串口接收中断或者DMA中断中调用即可。回调函数如下所示: ```C /* 由静态函数改为全局函数 */ rt_err_t at_client_rx_ind(rt_device_t dev, rt_size_t size) { int idx = 0; for (idx = 0; idx < AT_CLIENT_NUM_MAX; idx++) { if (at_client_table[idx].device == dev && size > 0) { rt_sem_release(at_client_table[idx].rx_notice); } } return RT_EOK; } ``` 调用示例如下: ```C /* AIR780E_ReciveNew是我的串口接收中断回调函数 */ void AIR780E_ReciveNew(uint8_t _byte) { at_client_rx_ind(COM1 + 1,1); /* 加一是因为at_client_init会通过dev来遍历at_client_table,遍历语句for (idx = 0; idx < AT_CLIENT_NUM_MAX && at_client_table[idx].device; idx++); */ } ``` 这里需要注意:由于需要在中断中使用freertos的API,所以中断的优先级需要小于等于`configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`(数值越小,优先级越高。即中断优先级的数值需要大于等于`configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`) 如果需要移植到其他操作系统,需要特别注意你使用的操作系统成功释放信号量是返回1还是返回0。(RT-Thread成功释放返回0,FreeRTOS成功释放返回1) 举例: ```C rt_size_t at_client_obj_recv(at_client_t client, char *buf, rt_size_t size, rt_int32_t timeout) { rt_size_t len = 0; RT_ASSERT(buf); if (client == RT_NULL) { LOG_E("input AT Client object is NULL, please create or get AT Client object!"); return 0; } while (1) { rt_size_t read_len; // rt_sem_control(client->rx_notice, RT_IPC_CMD_RESET, RT_NULL); read_len = rt_device_read(client->device, 0, buf + len, size); if(read_len > 0) { len += read_len; size -= read_len; if(size == 0) break; continue; } // if(rt_sem_take(client->rx_notice, rt_tick_from_millisecond(timeout)) != RT_EOK) //RT-Thread #define RT_EOK 0 if(rt_sem_take(client->rx_notice, rt_tick_from_millisecond(timeout)) != pdTRUE) //FreeRTOS #define pdTRUE 1 break; } #ifdef AT_PRINT_RAW_CMD at_print_raw_cmd("urc_recv", buf, len); #endif return len; } ``` 事实上,为了移植到freertos上所作出的修改不仅仅只是上面的那些修改,作出修改的地方一般都以注释的方式保留了原始代码。可以通过是否有注释来判断是否修改了源码。 ### 使用说明 example: 在`main.c`文件中创建了以下任务: ```C static void LED_Task (void* parameter) { if(at_client_wait_connect(10000)){ printf("连接失败\r\n"); }else{ printf("AT连接成功\r\n"); } while (1) { bsp_LedToggle(1); vTaskDelay(1000); /* 延时 500 个 tick */ } } ``` `at_client_wait_connect()`函数会在10秒内不断发送`AT\r\n`,直到接收字符串`OK\r\n` ![at 连接成功](./at_connect_img.png) 请参考以下链接 [RT-Thread AT 组件](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/at/at) [RT-Thread API参考手册](https://www.rt-thread.org/document/api/group___a_t.html) ### 转载说明 转载文章请保留以下链接: 资源下载链接1: 文章转载链接: