跳转至

0804——telink SDK二次开发 主设备向从设备写数据踩坑

这是走步机手控开发的支线任务,因为BLE固件用的还是厂家的AT固件,很不灵活。所以从2025.5起我就陆续开始研究Telink TLSR8258的SDK了,总的来说难度比较大。

一是TLSR8是不支持调试开发的,意味着我只能用串口打印数据来调试,二是烧录工具,开发工具简直是一坨,好在我的耐心慢慢是培养起来了,慢慢磨。总体感受就是,吃了一坨——茅塞顿开,柳暗花明——吃了依托,这样反复,好在总能隔段时间有一些正反馈,否则我真的是要放弃了。

  1. 购买开发板,因为公司只有烧录器,供应商提供的也只是模组,所以要学习只能自己买了个便宜的开发板AiThinker TB-02,TB-02 智能照明模块是一款基于TLSR8250F512芯片设计的符合BT 5.0低功耗蓝牙模块。

    这块开发板其实帮助了我非常多,首先是使用tc32-elf-gcc编译器+makefile,编译AiThinker的二开源码。让我初识了makefile。虽然直接还没学会makefile,但有个开始总是好的。
    
    第二AiThinker的SDK开发代码是开源的。在github可以直接拉取,写了很大有帮助的Demo,其中最有帮助的是AT指令Demo,和串口printf示例。
    
    初学者第一次接触BLE模块,必然是绕不开AT指令的,理解固件背后如何处理串口的AT指令,以及执行哪些动作,就不用毫无重点地看代码了,比如需要知道如何根据MAC连接从设备,直接查看AT+SCAN部分的指令代码即可。另外由于TLSR8是无法在线调试的,UART+printf的移植是重中之中。这直接决定了你的开发效率。
    
    由于AiThinker的二开代码都包含这些,所以我在较短的时间内,就上手了TLASR8的开发,帮我打开了SDK开发的大门。
    
  2. 转战Telink SDK,由于AiThinker的代码也是基于Telink SDK开发的,而如果我想进一步使用更丰富的功能,例如主机从机一体开发,就必须使用Telink SDK,所以转回Telink IDE+DBT烧录工具开发是无法避免的。目前主要用的是4.0.1.x主线SDK,不过最新的已经是4.0.2.x了

  3. 在Telink SDK中,使用UART+DMA,并且实现printf作为Debug和交互接口,大大提高开发效率。

    在主线SDK中,有很多写好的通用组件 vendor / common,在只要在app_config.h中定义好相关宏定义就可以调用。这里关于UART的初始化我们调用。。。,DMA接收和处理需要我们自己实现。。。

UART+DMA 与 printf 实现调试接口

UART+DMA驱动与printf实现,参考了Ai-Thinker-Open/Telink_825X_SDK

调用common_dbg.c中的low_power_uart_debug_init,用户只需要在app_config.h中添加宏定义#define UART_LOW_POWER_DEBUG_EN 1

  • uart底层驱动
//common_dbg.c
//主要在于添加了接收DMA与设置irq_mask中断请求
#if (UART_LOW_POWER_DEBUG_EN)
    #if(MCU_CORE_TYPE == MCU_CORE_825x || MCU_CORE_TYPE == MCU_CORE_827x)
        #define UART_BAUDRATE                   115200  //修改波特率
        #define UART_TRANS_BUFF_LEN             64      //设置发送缓存大小

        __attribute__((aligned(4))) unsigned char uart_trans_buff[UART_TRANS_BUFF_LEN] = {0x0c,0x00,0x00,0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc};

        int lp_uart_init = 0;  //attention: can not be retention data !!!
        void low_power_uart_debug_init(void)
        {

            reg_dma_uart_tx_size = UART_TRANS_BUFF_LEN >> 4;//set DMA TX buffer size.
            //uart_gpio_set(UART_TX_PB1, UART_RX_PB7);// uart tx/rx pin set
            uart_gpio_set(UART_TX_PB1, UART_RX_PC3);// uart tx/rx pin set
            uart_reset();  //uart module power-on again.
            uart_init_baudrate(UART_BAUDRATE, CLOCK_SYS_CLOCK_HZ, PARITY_NONE, STOP_BIT_ONE);
            uart_dma_enable(1, 1);  //uart data in hardware buffer moved by dma, so we need enable them first
            irq_set_mask(FLD_IRQ_DMA_EN);
            dma_chn_irq_enable(FLD_DMA_CHN_UART_TX | FLD_DMA_CHN_UART_RX, 1);       //uart Tx dma irq enable
            lp_uart_init = 1;
        }
    #endif
#endif
  • uart应用

//app_uart.c
#include "tl_common.h"
#include "drivers.h"
#include "stack/ble/ble.h"
#include "app_config.h"
#include "app_att.h"
//设置发送buffer和接收buffer fifo
#define UART_DATA_LEN           12+8*16    //data max  (UART_DATA_LEN+4) must 16 byte aligned
#define UART_RXFIFO_NUM         8

typedef struct{
    unsigned int dma_len;        // dma len must be 4 byte
    unsigned char data[UART_DATA_LEN];
}uart_data_t;

uart_data_t trans_buff = {0, {0,} };

_attribute_data_retention_  u8          uart_rx_fifo_b[UART_DATA_LEN * UART_RXFIFO_NUM] = {0};
_attribute_data_retention_  my_fifo_t   uart_rx_fifo = {
                                                UART_DATA_LEN,
                                                UART_RXFIFO_NUM,
                                                0,
                                                0,
                                                uart_rx_fifo_b,};

//dma 发数据实现
void at_print(char * str)//判空截止
{
    while(uart_tx_is_busy());
    trans_buff.dma_len = 0;

    while(*str)
    {
        trans_buff.data[trans_buff.dma_len] = *str++;
        trans_buff.dma_len += 1;

        if(trans_buff.dma_len == UART_DATA_LEN)
        {
            while(uart_tx_is_busy());
            uart_dma_send((unsigned char*)&trans_buff);
            while(uart_tx_is_busy());
            trans_buff.dma_len = 0;
        }
    }

    if(trans_buff.dma_len)
    {
        while(uart_tx_is_busy());
        uart_dma_send((unsigned char*)&trans_buff);
    }
}

void at_send(char * data, u32 len)//长度截止
{
    while(len > UART_DATA_LEN)
    {
        while(uart_tx_is_busy());
        memcpy(trans_buff.data, data,  UART_DATA_LEN);
        data += UART_DATA_LEN;
        len -= UART_DATA_LEN;

        trans_buff.dma_len = UART_DATA_LEN;
        uart_dma_send((unsigned char*)&trans_buff);
    }

    if(len > 0)
    {
        while(uart_tx_is_busy());
        memcpy(trans_buff.data, data,  len);
        trans_buff.dma_len = len;

        uart_dma_send((unsigned char*)&trans_buff);
    }
}

//实现puts
void puts(char *s) { at_print(s); }
////////////////////////////////////////////////////////////
//uart接收缓存设置
void uart_recbuff_dma_config(void)
{
    uart_recbuff_init( (unsigned short *)my_fifo_wptr(&uart_rx_fifo), UART_DATA_LEN);
}
//uart中断处理函数
void app_uart_irq_proc(void)
{
    unsigned char uart_dma_irqsrc;
    //1. UART irq
    uart_dma_irqsrc = dma_chn_irq_status_get();///in function,interrupt flag have already been cleared,so need not to clear DMA interrupt flag here

    if(uart_dma_irqsrc & FLD_DMA_CHN_UART_RX)
    {
        //Offset dma writes Pointers and addresses
        u8* w = my_fifo_wptr(&uart_rx_fifo);
        if((w[0]!=0) || (w[1]!=0))
        {
            my_fifo_next(&uart_rx_fifo);
            u8* p = my_fifo_wptr(&uart_rx_fifo);
            reg_dma_uart_rx_addr = (u16)((u32)p);  //switch uart RX dma address
        }

        dma_chn_irq_status_clr(FLD_DMA_CHN_UART_RX);
    }

    if(uart_dma_irqsrc & FLD_DMA_CHN_UART_TX)
    {
        dma_chn_irq_status_clr(FLD_DMA_CHN_UART_TX);
    }
}
- printf应用

// application/print/u_printf.c
// 新增My_printf
int My_printf(const char *format, ...) {
    static char my_printf_buff[1024] = { 0 };
    char *out = &my_printf_buff[0];

    extern void puts(char* s);

    va_list args;
    va_start(args, format );
    print(&out, format, args);
    puts(my_printf_buff);
}

// application/print/u_printf.h
// printf 定位到 My_printf
#define printf          My_printf
  • 初始化调用与uart数据处理
#if(UART_LOW_POWER_DEBUG_EN)
    uart_recbuff_dma_config();
    low_power_uart_debug_init();
#endif

//中断函数调用
_attribute_ram_code_ void irq_handler(void)
{
    app_uart_irq_proc();
    blc_sdk_irq_handler ();
}

//tx输入处理,在该函数中处理数据指针p即可
void app_uart_loop()
{
    if(data = my_fifo_get(&uart_rx_fifo)) 
    {
        p = (uart_data_t *)data;
        //...
        //...
        //...
        my_fifo_pop(&uart_rx_fifo);
    }
}

handle不对 导致主机给从机发数据失效总结

Telink论坛——有人知道tlsr8258在多链接的时候,与指定的从机发送数据 具体要准备那些参数和步骤吗?

按照以下步骤实现与对端设备的通讯:

  1. 连接建立与句柄记录

    在连接完成事件回调中,记录系统分配的连接句柄(connHandle)。该句柄是后续所有操作的基础,用于唯一标识一个 BLE 连接.

  2. 服务发现(SDP)流程

连接建立后,需通过 SDP 协议发现对端设备的服务(Service)和特性(Characteristic),从而获取您需要操作的特性的handle值。每个特性都有唯一的 UUID 和对应的 handle。

  1. 数据传输 使用blc_gatt_pushWriteCommand或blc_gatt_pushWriteRequest函数发送数据,这两个函数均需传入connHandle和目标handle值。

在了解到上述之前,我一直认为AiThinker的ATDemo代码中,主机给从机发数据blc_gatt_pushWriteComand是有问题的代码,因为我在较长的一段时间里,反复尝试不同差异,让blc_gatt_pushWriteComand给从机发数据,都没有成功。

导致我认为数据包根本没发到从机,直到我使用了nRF52840 sniffer抓包从机后,看见从机确实收到了这个写数据的动作,但是确有一个ATT_OP_ERROR_RSP(0x01)的ATT Err Response.

我再改变主机,使用手机作为主机给从机发数据,因为这是可以成功接收的,我需要看看我写的主机和手机主机发数据有什么区别,导致了失败。改变主机后,我再次抓包从机,发现没有ATT Err Response,唯一的差异是handle号不一样,我写的主机发送的handle号是0x34,手机主机发送的handle号是0x18.

得到了这个发现,我手动记录可行的handle号,再使用blc_gatt_pushWriteCommand(conn_dev_list[0].conn_handle, 0x18, data, data_len),发现已经可以成功发数据到从机。

这次的实验引发了我的一些思考,如下:

  1. 猜测AiThinker的ATDemo代码是没问题的,然而它需要主从蓝牙设备的代码都是Demo代码,这样它们的service handle是一致的,这样就可以互通了。而我做实验时,从机的固件并不是AT Demo,writecommand指定的handle不对。所以接收到了,但会有一个ATT Err Response。

    也就是主从设备固件都是自己开发的代码时,这个问题就不会存在了,因为uuid,service handle都是可知的,可控的。保持一致就行了。

  2. 如果想像手机一样,连接任何设备都可以正常收发数据,就必须遵循以上的对端设备通讯过程,也就是通过SDP协议获取对端设备的handle。在调用blc_gatt_pushWriteCommand时,attHandle传入该获取的handle值。

SDP获取对端设备服务和特性步骤过程