TencentOS-thiny esp8266B组件部分笔记

我主要想使用其中组件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封装抽象了
TencentOS-thiny esp8266B组件部分笔记_第1张图片
官方也有说明,可以想到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;
    }
}

到这里我猜到他的意图

  1. 里面基本参数初始化
  2. 申请动态内存,存储待发送的AT指令和接收的数据
  3. 创建rx信号量,创建收发互斥锁,以及at_parser 任务
  4. 串口初始化
  5. 创建全局互斥锁

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 小结

返回状态有三种

  1. 达到最大缓存数据长度
  2. UTC主动上报和AT指令回复
  3. 一行结束
    脉络清晰了,可是怎么接收数据的!从模组来的数据怎么传到os里面的!!!根本没从上面找到
    TencentOS-thiny esp8266B组件部分笔记_第2张图片
    从文档上找到这个说明(官方根本没没提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解析

  1. 首先是AT框架和esp8266绑定,事件上报等标识
  2. sal注册模块(这里应该就是对上层应用来说调用的api了,应该是统一的)
  3. sal模组初始化(函数指针实际使用 esp8266_init() )
    可以从sal看到一些端倪,这些函数应该是统一的,移植时候这里不用动,下面改改(废话,现在问题就是下面怎么搞啊)
    TencentOS-thiny esp8266B组件部分笔记_第3张图片
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-thiny esp8266B组件部分笔记_第4张图片
关键字 腾讯云大学大咖分享 | 腾讯物联网操作系统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函数差不多
然后发送接收,其他的都差不多
TencentOS-thiny esp8266B组件部分笔记_第5张图片
TencentOS-thiny esp8266B组件部分笔记_第6张图片
TencentOS-thiny esp8266B组件部分笔记_第7张图片

你可能感兴趣的:(STM32,Tencentos)