我主要想使用其中组件AT框架,所以对官方例程进行个人笔记。因为是第一次看代码,水平也有限没看到哪里记到哪里,在完成最后我会做一个总结。
下载源码后打开例子工程
肯定是从主函数进行看
main.c
#include "mcu_init.h"
#include "cmsis_os.h"
#define APPLICATION_TASK_STK_SIZE 4096
extern void application_entry(void *arg);
osThreadDef(application_entry, osPriorityNormal, 1, APPLICATION_TASK_STK_SIZE);
__weak void application_entry(void *arg)
{
while (1) {
printf("This is a demo task,please use your task entry!\r\n");
tos_task_delay(1000);
}
}
int main(void)
{
board_init();
printf("Welcome to TencentOS tiny\r\n");
osKernelInitialize(); // TOS Tiny kernel initialize
osThreadCreate(osThread(application_entry), NULL); // Create TOS Tiny task
osKernelStart(); // Start TOS Tiny
}
可以看到就是一个任务然后启动运行,但是!!前面有弱定义,这个是例子工程是tcp模组连接的,肯定不是主函数的这个任务,别的地方肯定是有定义。
搜索过后从 tcp_through_module.c 找到
#define USE_ESP8266
void application_entry(void *arg)
{
#ifdef USE_ESP8266
esp8266_sal_init(HAL_UART_PORT_0);
esp8266_join_ap("SheldonDai", "srnr6x9xbhmb0");
#endif
#ifdef USE_SIM800A
sim800a_power_on();
sim800a_sal_init(HAL_UART_PORT_2);
#endif
socket_id_0 = tos_sal_module_connect("39.108.190.129", "8080", TOS_SAL_PROTO_TCP);
if (socket_id_0 == -1) {
printf("TCP0 connect failed\r\n");
} else {
printf("TCP0 connect success! fd: %d\n", socket_id_0);
}
socket_id_1 = tos_sal_module_connect("39.108.190.129", "8001", TOS_SAL_PROTO_TCP);
if (socket_id_1 == -1) {
printf("TCP1 connect failed\r\n");
} else {
printf("TCP1 connect success! fd: %d\n", socket_id_1);
}
osThreadCreate(osThread(tcp_test0), NULL);
osThreadCreate(osThread(tcp_test1), NULL);
}
esp8266_sal_init(HAL_UART_PORT_0);看名字我猜到这应该是把esp8266封装抽象了
官方也有说明,可以想到sal下面就是AT框架了,所以 HAL_UART_PORT_0 是什么!
在 tos_hal_uart.c 文件中找到了
__API__ int tos_hal_uart_init(hal_uart_t *uart, hal_uart_port_t port)
{
if (!uart) {
return -1;
}
if (port == HAL_UART_PORT_0) {
uart->private_uart = &hlpuart1;
MX_LPUART1_UART_Init();
} else if (port == HAL_UART_PORT_1) {
uart->private_uart = &huart1;
MX_USART1_UART_Init();
} else if (port == HAL_UART_PORT_2) {
uart->private_uart = &huart2;
MX_USART2_UART_Init();
} else if (port == HAL_UART_PORT_3) {
uart->private_uart = &huart3;
MX_USART3_UART_Init();
}
return 0;
}
这里把串口抽象了,HAL_UART_PORT_0 与设备硬件的串口lpuart1映射到了一起,接着初始化串口,这里波特率之类的肯定是从cube mx配置好,这里只调用初始化串口的接口。
现在可以看看sal下AT框架到底是什么样子了
at_event_t esp8266_at_event[] = {
{ "+IPD,", esp8266_incoming_data_process },
};
sal_module_t sal_module_esp8266 = {
.init = esp8266_init,
.connect = esp8266_connect,
.send = esp8266_send,
.recv_timeout = esp8266_recv_timeout,
.recv = esp8266_recv,
.sendto = esp8266_sendto,
.recvfrom = esp8266_recvfrom,
.recvfrom_timeout = esp8266_recvfrom_timeout,
.close = esp8266_close,
.parse_domain = esp8266_parse_domain,
};
int esp8266_sal_init(hal_uart_port_t uart_port)
{
if (tos_at_init(uart_port, esp8266_at_event,
sizeof(esp8266_at_event) / sizeof(esp8266_at_event[0])) != 0) {
return -1;
}
if (tos_sal_module_register(&sal_module_esp8266) != 0) {
return -1;
}
if (tos_sal_module_init() != 0) {
return -1;
}
return 0;
}
tos_at.c中tos_at_init
__API__ int tos_at_init(hal_uart_port_t uart_port, at_event_t *event_table, size_t event_table_size)
{
void *buffer = K_NULL;
memset(AT_AGENT, 0, sizeof(at_agent_t));
at_event_table_set(event_table, event_table_size);
at_channel_init();
at_timer_init(&AT_AGENT->timer);
buffer = tos_mmheap_alloc(AT_UART_RX_FIFO_BUFFER_SIZE);
if (!buffer) {
return -1;
}
AT_AGENT->uart_rx_fifo_buffer = (uint8_t *)buffer;
tos_chr_fifo_create(&AT_AGENT->uart_rx_fifo, buffer, AT_UART_RX_FIFO_BUFFER_SIZE);
buffer = tos_mmheap_alloc(AT_CMD_BUFFER_SIZE);
if (!buffer) {
goto errout0;
}
AT_AGENT->cmd_buf = (char *)buffer;
if (tos_mutex_create(&AT_AGENT->cmd_buf_lock) != K_ERR_NONE) {
goto errout1;
}
if (at_recv_cache_init() != 0) {
goto errout2;
}
if (tos_sem_create(&AT_AGENT->uart_rx_sem, (k_sem_cnt_t)0u) != K_ERR_NONE) {
goto errout3;
}
if (tos_mutex_create(&AT_AGENT->uart_rx_lock) != K_ERR_NONE) {
goto errout4;
}
if (tos_mutex_create(&AT_AGENT->uart_tx_lock) != K_ERR_NONE) {
goto errout5;
}
if (tos_task_create(&AT_AGENT->parser, "at_parser", at_parser,
K_NULL, AT_PARSER_TASK_PRIO, at_parser_task_stack,
AT_PARSER_TASK_STACK_SIZE, 0) != K_ERR_NONE) {
goto errout6;
}
if (tos_hal_uart_init(&AT_AGENT->uart, uart_port) != 0) {
goto errout7;
}
if (tos_mutex_create(&AT_AGENT->global_lock) != K_ERR_NONE) {
goto errout8;
}
return 0;
errout8:
tos_hal_uart_deinit(&AT_AGENT->uart);
errout7:
tos_task_destroy(&AT_AGENT->parser);
errout6:
tos_mutex_destroy(&AT_AGENT->uart_tx_lock);
errout5:
tos_mutex_destroy(&AT_AGENT->uart_rx_lock);
errout4:
tos_sem_destroy(&AT_AGENT->uart_rx_sem);
errout3:
at_recv_cache_deinit();
errout2:
tos_mutex_destroy(&AT_AGENT->cmd_buf_lock);
errout1:
tos_mmheap_free(AT_AGENT->cmd_buf);
AT_AGENT->cmd_buf = K_NULL;
errout0:
tos_mmheap_free(AT_AGENT->uart_rx_fifo_buffer);
AT_AGENT->uart_rx_fifo_buffer = K_NULL;
tos_chr_fifo_destroy(&AT_AGENT->uart_rx_fifo);
return -1;
}
这么长,难搞哦!memset(AT_AGENT, 0, sizeof(at_agent_t))这个是把at_agent_t清空,这个是句柄,句柄就是像把手一样,抓到手柄,各种东西都在上面。
typedef struct at_agent_st {
at_data_channel_t data_channel[AT_DATA_CHANNEL_NUM];
at_event_t *event_table;
size_t event_table_size;
at_echo_t *echo;
k_task_t parser;
at_cache_t recv_cache;
at_timer_t timer;
k_mutex_t global_lock;
char *cmd_buf;
k_mutex_t cmd_buf_lock;
hal_uart_t uart;
k_mutex_t uart_tx_lock;
k_mutex_t uart_rx_lock;
k_sem_t uart_rx_sem;
k_chr_fifo_t uart_rx_fifo;
uint8_t *uart_rx_fifo_buffer;
} at_agent_t;
#define AT_AGENT ((at_agent_t *)(&at_agent))
因为清空了,里面参数需要重新赋值
typedef struct at_data_channel_st {
uint8_t is_free;
k_chr_fifo_t rx_fifo;
uint8_t *rx_fifo_buffer;
k_mutex_t rx_lock;
at_channel_status_t status;
const char *remote_ip;
const char *remote_port;
} at_data_channel_t;
__STATIC__ void at_channel_init(void)
{
int i = 0;
for (i = 0; i < AT_DATA_CHANNEL_NUM; ++i) {
memset(&AT_AGENT->data_channel[i], 0, sizeof(at_data_channel_t));
AT_AGENT->data_channel[i].is_free = K_TRUE;
AT_AGENT->data_channel[i].status = AT_CHANNEL_STATUS_HANGING;
}
}
到这里我猜到他的意图
- 里面基本参数初始化
- 申请动态内存,存储待发送的AT指令和接收的数据
- 创建rx信号量,创建收发互斥锁,以及at_parser 任务
- 串口初始化
- 创建全局互斥锁
at_parser任务代码
__STATIC__ void at_parser(void *arg)
{
at_echo_t *at_echo = K_NULL;
at_event_t *at_event = K_NULL;
at_cache_t *recv_cache = K_NULL;
at_parse_status_t at_parse_status;
recv_cache = &AT_AGENT->recv_cache;
while (K_TRUE) {
at_parse_status = at_uart_line_parse();
if (at_parse_status == AT_PARSE_STATUS_OVERFLOW) {
// TODO: fix me
continue;
}
if (at_parse_status == AT_PARSE_STATUS_EVENT) {
at_event = at_get_event();
if (at_event && at_event->event_callback) {
at_event->event_callback();
}
continue;
}
at_echo = AT_AGENT->echo;
if (!at_echo) {
continue;
}
if (at_parse_status == AT_PARSE_STATUS_EXPECT) {
at_echo->status = AT_ECHO_STATUS_EXPECT;
if (at_echo->__is_expecting) {
tos_sem_post(&at_echo->__expect_notify);
}
} else if (at_parse_status == AT_PARSE_STATUS_NEWLINE &&
at_echo->status == AT_ECHO_STATUS_NONE) {
at_echo_status_set(at_echo);
}
if (at_echo->buffer) {
at_echo_buffer_copy(recv_cache, at_echo);
}
printf("--->%s\n", recv_cache->buffer);
}
}
看名字任务开始了行解析,马上就尝试分析一下
__STATIC__ at_parse_status_t at_uart_line_parse(void)
{
size_t curr_len = 0;
uint8_t data, last_data = 0;
at_cache_t *recv_cache = K_NULL;
recv_cache = &AT_AGENT->recv_cache;
recv_cache->recv_len = 0;
memset(recv_cache->buffer, 0, recv_cache->buffer_size);
while (K_TRUE) {
if (at_uart_getchar(&data, TOS_TIME_FOREVER) != 0) {
continue;
}
if (data == '\0') {
continue;
}
if (curr_len < recv_cache->buffer_size) {
recv_cache->buffer[curr_len++] = data;
recv_cache->recv_len = curr_len;
} else {
recv_cache->buffer[recv_cache->buffer_size - 1] = '\0';
return AT_PARSE_STATUS_OVERFLOW;
}
if (at_get_event() != K_NULL) {
return AT_PARSE_STATUS_EVENT;
}
if (at_is_echo_expect()) {
return AT_PARSE_STATUS_EXPECT;
}
if (data == '\n' && last_data == '\r') { // 0xd 0xa
curr_len -= 1;
recv_cache->buffer[curr_len - 1] = '\n';
recv_cache->recv_len = curr_len;
if (curr_len == 1) { // only a blank newline, ignore
last_data = 0;
curr_len = 0;
recv_cache->recv_len = 0;
continue;
}
return AT_PARSE_STATUS_NEWLINE;
}
last_data = data;
}
}
这个是接收数据,假如达到最大长度,此时把缓存最后一位补0,因为是字符串需要’\0’结束符(软件设计就是为了达不到这个啊!)。
然后进行UTC主动事件上报的检查,就是比如通信模块在你没发指令情况下发送给你数据,这个数据一般是比较重要的数据,所以需要解析(我自己写代码时候,这个地方是真的难搞,马上看看Tencent怎么搞的,之前ali看都看不懂,还有rtt让人头疼)
__STATIC__ at_event_t *at_event_do_get(char *buffer, size_t buffer_len)
{
int i = 0;
at_event_t *event_table = K_NULL, *event = K_NULL;
size_t event_table_size = 0, event_len;
event_table = AT_AGENT->event_table;
event_table_size = AT_AGENT->event_table_size;
for (i = 0; i < event_table_size; ++i) {
event = &event_table[i];
event_len = strlen(event->event_header);
if (buffer_len < event_len) {
continue;
}
if (strncmp(event->event_header, buffer, event_len) == 0) {
return event;
}
}
return K_NULL;
}
__STATIC__ at_event_t *at_get_event(void)
{
char *buffer;
size_t buffer_len;
at_cache_t *at_cache = K_NULL;
at_cache = &AT_AGENT->recv_cache;
buffer = (char *)at_cache->buffer;
buffer_len = at_cache->recv_len;
return at_event_do_get(buffer, buffer_len);
}
他这里是把接收的数据全部缓冲,然后和咱们设定的部分进行比对,出现这个字符时候返回事件状态,这样看这部分难处理的就是数据缓存。
__STATIC__ int at_is_echo_expect(void)
{
char *recv_buffer, *expect;
size_t recv_buffer_len, expect_len;
at_echo_t *at_echo = K_NULL;
at_cache_t *at_cache = K_NULL;
at_echo = AT_AGENT->echo;
if (!at_echo || !at_echo->echo_expect) {
return 0;
}
at_cache = &AT_AGENT->recv_cache;
recv_buffer = (char *)at_cache->buffer;
recv_buffer_len = at_cache->recv_len;
expect = at_echo->echo_expect;
expect_len = strlen(expect);
if (recv_buffer_len < expect_len) {
return 0;
}
if (strncmp(expect, recv_buffer, expect_len) == 0) {
return 1;
}
return 0;
}
接下来的是我们发送的AT指令,响应的数据部分解析,上面代码就是,这和上面主动上报实现基本一致,我怎么没想到。
接着部分就是判断新行,就是只解析一行,仅当刚开始接收直接就是换行时继续接收,并把开始换行符去掉。
at_uart_line_parse 小结
返回状态有三种
- 达到最大缓存数据长度
- UTC主动上报和AT指令回复
- 一行结束
脉络清晰了,可是怎么接收数据的!从模组来的数据怎么传到os里面的!!!根本没从上面找到
从文档上找到这个说明(官方根本没没提AT部分,这是我从这些os找了很久,Tencent是唯一一个带了AT框架文档还不是从官方文档中找到的)
接收数据是从中断假如os部分接收数据的,中断是开启才能进入到中断的,我猜一定是有地方开启中断接收。
void MX_LPUART1_UART_Init(void)
{
hlpuart1.Instance = LPUART1;
hlpuart1.Init.BaudRate = 115200;
hlpuart1.Init.WordLength = UART_WORDLENGTH_8B;
hlpuart1.Init.StopBits = UART_STOPBITS_1;
hlpuart1.Init.Parity = UART_PARITY_NONE;
hlpuart1.Init.Mode = UART_MODE_TX_RX;
hlpuart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
hlpuart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
hlpuart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&hlpuart1) != HAL_OK)
{
Error_Handler();
}
HAL_UART_Receive_IT(&hlpuart1, &data, 1);
}
果然,是在硬件串口初始化开启中断接收的。
at_parser 任务代码 接收新行后进行新行返回状态的判断,把这些状态以及数据传给上层。
现在咱们回到esp8266_sal_init解析
- 首先是AT框架和esp8266绑定,事件上报等标识
- sal注册模块(这里应该就是对上层应用来说调用的api了,应该是统一的)
- sal模组初始化(函数指针实际使用 esp8266_init() )
可以从sal看到一些端倪,这些函数应该是统一的,移植时候这里不用动,下面改改(废话,现在问题就是下面怎么搞啊)
static int esp8266_init(void)
{
printf("Init ESP8266 ...\n" );
if (esp8266_restore() != 0) {
printf("esp8266 restore FAILED\n");
return -1;
}
if (esp8266_echo_close() != 0) {
printf("esp8266 echo close FAILED\n");
return -1;
}
if (esp8266_net_mode_set(SAL_NET_MODE_STA) != 0) {
printf("esp8266 net mode set FAILED\n");
return -1;
}
if (esp8266_send_mode_set(SAL_SEND_MODE_NORMAL) != 0) {
printf("esp8266 send mode set FAILED\n");
return -1;
}
#if TOS_CFG_MODULE_SINGLE_LINK_EN > 0u
if (esp8266_multilink_set(SAL_MULTILINK_STATE_DISABLE) != 0) {
printf("esp8266 multilink set FAILED\n");
return -1;
}
#else
if (esp8266_multilink_set(SAL_MULTILINK_STATE_ENABLE) != 0) {
printf("esp8266 multilink set FAILED\n");
return -1;
}
#endif
printf("Init ESP8266 Done\n" );
return 0;
}
按顺序解析一下,先restore
static int esp8266_restore(void)
{
int try = 0;
at_echo_t echo;
tos_at_echo_create(&echo, NULL, 0, NULL);
while (try++ < 10) {
tos_at_cmd_exec(&echo, 3000, "AT+RESTORE\r\n");
if (echo.status == AT_ECHO_STATUS_OK) {
return 0;
}
}
return -1;
}
一看就猜到这里做的事情
关键字 腾讯云大学大咖分享 | 腾讯物联网操作系统TencentOS tiny技术架构及开发案例讲解 就可找到文章
到此,函数**esp8266_sal_init()**解析完毕。
初始化完毕之后就是连接ap,然后是tcp连接发送数据了
连接AP,esp8266_join_ap(“SheldonDai”, “srnr6x9xbhmb0”);
int esp8266_join_ap(const char *ssid, const char *pwd)
{
int try = 0;
at_echo_t echo;
tos_at_echo_create(&echo, NULL, 0, "OK");
while (try++ < 10) {
tos_at_cmd_exec_until(&echo, 15000, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, pwd);
if (echo.status == AT_ECHO_STATUS_EXPECT) {
return 0;
}
}
return -1;
}
现在有些理解AT框架的使用了
__STATIC__ int at_cmd_do_exec(const char *format, va_list args)
{
size_t cmd_len = 0;
if (tos_mutex_pend(&AT_AGENT->cmd_buf_lock) != K_ERR_NONE) {
return -1;
}
cmd_len = vsnprintf(AT_AGENT->cmd_buf, AT_CMD_BUFFER_SIZE, format, args);
printf("AT CMD:\n%s\n", AT_AGENT->cmd_buf);
at_uart_send((uint8_t *)AT_AGENT->cmd_buf, cmd_len, 0xFFFF);
tos_mutex_post(&AT_AGENT->cmd_buf_lock);
return 0;
}
__API__ int tos_at_cmd_exec_until(at_echo_t *echo, uint32_t timeout, const char *cmd, ...)
{
int ret = 0;
va_list args;
if (!echo || !echo->echo_expect) {
return -1;
}
if (tos_sem_create(&echo->__expect_notify, 0) != K_ERR_NONE) {
return -1;
}
echo->__is_expecting = K_TRUE;
at_echo_attach(echo);
va_start(args, cmd);
ret = at_cmd_do_exec(cmd, args);
va_end(args);
if (ret != 0) {
AT_AGENT->echo = K_NULL;
return -1;
}
if (tos_sem_pend(&echo->__expect_notify, tos_millisec2tick(timeout)) != K_ERR_NONE) {
ret = -1;
}
tos_sem_destroy(&echo->__expect_notify);
AT_AGENT->echo = K_NULL;
return ret;
}
这里函数有…代表格式化数据,跟printf函数差不多
然后发送接收,其他的都差不多