AT命令组件

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

#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

/*************************************************移植到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

/**
 * 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中断中调用即可。回调函数如下所示:

/* 由静态函数改为全局函数 */
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;
}

调用示例如下:

/* 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)

举例:

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文件中创建了以下任务:

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 连接成功

请参考以下链接

RT-Thread AT 组件

RT-Thread API参考手册

转载说明

转载文章请保留以下链接:

资源下载链接1: https://gitee.com/sharkisyou/at_client_with_rtos

文章转载链接: https://blog.csdn.net/qq_41430785/article/details/133416610?spm=1001.2014.3001.5501

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值