0804——telink SDK二次开发 主设备向从设备写数据踩坑
这是走步机手控开发的支线任务,因为BLE固件用的还是厂家的AT固件,很不灵活。所以从2025.5起我就陆续开始研究Telink TLSR8258的SDK了,总的来说难度比较大。
一是TLSR8是不支持调试开发的,意味着我只能用串口打印数据来调试,二是烧录工具,开发工具简直是一坨,好在我的耐心慢慢是培养起来了,慢慢磨。总体感受就是,吃了一坨——茅塞顿开,柳暗花明——吃了依托,这样反复,好在总能隔段时间有一些正反馈,否则我真的是要放弃了。
Telink SDK开发心路历程
-
购买开发板,因为公司只有烧录器,供应商提供的也只是模组,所以要学习只能自己买了个便宜的开发板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开发的大门。
-
转战Telink SDK,由于AiThinker的代码也是基于Telink SDK开发的,而如果我想进一步使用更丰富的功能,例如主机从机一体开发,就必须使用Telink SDK,所以转回Telink IDE+DBT烧录工具开发是无法避免的。目前主要用的是4.0.1.x主线SDK,不过最新的已经是4.0.2.x了
-
在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);
}
}
// 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在多链接的时候,与指定的从机发送数据 具体要准备那些参数和步骤吗?
按照以下步骤实现与对端设备的通讯:
-
连接建立与句柄记录
在连接完成事件回调中,记录系统分配的连接句柄(connHandle)。该句柄是后续所有操作的基础,用于唯一标识一个 BLE 连接.
-
服务发现(SDP)流程
连接建立后,需通过 SDP 协议发现对端设备的服务(Service)和特性(Characteristic),从而获取您需要操作的特性的handle值。每个特性都有唯一的 UUID 和对应的 handle。
- 数据传输 使用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)
,发现已经可以成功发数据到从机。
这次的实验引发了我的一些思考,如下:
-
猜测AiThinker的ATDemo代码是没问题的,然而它需要主从蓝牙设备的代码都是Demo代码,这样它们的service handle是一致的,这样就可以互通了。而我做实验时,从机的固件并不是AT Demo,writecommand指定的handle不对。所以接收到了,但会有一个ATT Err Response。
也就是主从设备固件都是自己开发的代码时,这个问题就不会存在了,因为uuid,service handle都是可知的,可控的。保持一致就行了。
-
如果想像手机一样,连接任何设备都可以正常收发数据,就必须遵循以上的对端设备通讯过程,也就是通过SDP协议获取对端设备的handle。在调用
blc_gatt_pushWriteCommand
时,attHandle传入该获取的handle值。