一、btstack源码文件结构
3rd-party: 第三方库,如md5加密,编码、解码等。
chipset: 支持的蓝牙控制器芯片组,如csr、cc256x、bcm等蓝牙芯片。
doc: btstack的说明文档。
example: 各种profile和service 的例程,如spp协议例程有spp_counter、spp_flowcontrol等。
platform: 支持btstack的操作系统或MCU架构,如freertos(嵌入式实时系统),embedded(裸机系统),posix(linux系统),windows等。
port: 完整项目启动(操作系统/MCU 与 芯片组组合),一个项目的入口就在这里;如windows-h4(在windows平台跑,使用uart传输的案例),stm32-f4discovery-cc256x(在stm32f4跑,使用uart传输,蓝牙芯片是cc256x的案例)。
src: 蓝牙协议栈的核心源码,如HCI、L2CAP、RFCOMM、SDP、SPP等。
test: 单元测试和PTS测试。
tool: 辅助工具。
下图是btstack源码一级目录截图。
二、单片机裸机运行的接口介绍
1、需要实现的函数
uint32_t hal_time_ms(void); 实现这个函数要返回一个系统时间戳,这个系统时间一般是每毫秒增加1,不然协议栈的某些层可能会因为超时问题导致错误。协议栈需要以这个为时间基准,做一些定时任务,如超时断开、超时重传等。
void hal_cpu_disable_irqs(void); 屏蔽cpu中断。
void hal_cpu_enable_irqs(void); 启动cpu中断。
void hal_cpu_enable_irqs_and_sleep(void); 启动cpu中断并且设置cpu休眠。
void hal_stdin_setup( void ( * handler)(char c) ); 设置一个函数,函数类型是void ( * handler)(char c),这个的用法还不太明白,不设写个空函数协议栈也能运行,TODO。
static void stdin_rx_complete(void); TODO,还不太明白这个函数的作用,先写个空函数放着,协议栈也能跑。
void hal_uart_dma_set_sleep(uint8_t sleep); TODO,还不太明白这个函数的作用,先写个空函数放着,协议栈也能跑。
void hal_uart_dma_init(void); 实现这个函数要硬件复位蓝牙模组(使用单片机GPIO产生一个复位信号),不同模组的复位电平不同,这个要看模组。
void hal_uart_dma_set_block_received( void ( * the_block_handler)(void)); 用一个函数指针 rx_done_handler 指向the_block_handler函数,即rx_done_handler = the_block_handler; the_block_handler是框架里的一个数据接收完成回调函数,当接收完蓝牙模组传给单片机的数据时需要调用这个函数,协议栈才知道数据接收完成,可以开始分析数据。实际就是调用了btstack_uart_block_embedded.c文件的 btstack_uart_block_received 函数。
void hal_uart_dma_set_block_sent( void ( * the_block_handler)(void)); 用一个函数指针 tx_done_handler 指向the_block_handler函数,即tx_done_handler = the_block_handler; the_block_handler是框架里的一个数据发送完成回调函数,当单片机发送完数据到蓝牙模组时需要调用这个函数,协议栈才知道数据发送完成,然后才能启动下一轮数据的发送。实际就是调用了btstack_uart_block_embedded.c文件的 btstack_uart_block_sent 函数。
void hal_uart_dma_set_csr_irq_handler( void ( * the_irq_handler)(void)); TODO,还不太明白这个函数的作用,先写个空函数放着,协议栈也能跑。
int hal_uart_dma_set_baud(uint32_t baud); 实现串口初始化,如果在自己的代码中已经调用了串口初始化函数,那这个函数可以空着,不过尽量还是在这里调用吧,毕竟这是框架的接口,代码比较规范,可读性也强点。
void hal_uart_dma_send_block(const uint8_t * data, uint16_t size); 这里需要实现串口发送数据功能,可以用DMA发送,也可以用循环加获取标志位的方式,用DMA发送就需要在对应的DMA通道中断函数里调用发送完成回调函数(*tx_done_handler)();,如果使用循环加获取标志位的方式就在循环发送完成时调用(*tx_done_handler)();。
void hal_uart_dma_receive_block(uint8_t * data, uint16_t size); 这里需要实现串口数据接收功能,注意需要将接收到的数据放在data所指的内存中,并且在接收完成时,调用 (*rx_done_handler)(); ,这里也可以使用DMA传输或中断传输,我是先设置DMA将串口数据放在一个缓存中,在串口中断里将缓存的数据放到环形数组里,然后在主循环里吧数据读到data所指的内存中,哈哈哈,搞得有哦点复杂了,没办法之前试的方法没成功,先将就着用等后面有空研究一下用DMA的方式直接将数据存到data中,并且能稳定的产生一个DMA中断(现在遇到一个问题就是有时候不会产生DMA中断)。
void hci_log_through_frontline(uint8_t packet_type, uint8_t in, uint8_t * packet, uint16_t len); 这里实现hci_log日志,发现有些btstack版本没有调用这个接口,如果没有的话就自己在hci_dump_embedded_stdout.c 文件的hci_dump_embedded_stdout_packet函数内调用一下,一般放在第一句。
hci_log_through_frontline(packet_type, in, packet, len);
int _write(int file, char * ptr, int len); 这里需要调用串口发送函数,与上面不同的是,这里发送的是普通日志信息,不是发给蓝牙模组的数据,也不是hci_log日志,其实就是在将printf函数重定向,使用串口输出;我这里使用的数gcc编译器所以重写的是int _write(int file, char * ptr, int len)函数,如果使用keil编译的话就需要重写 int fputc(int c, FILE * f)函数。
2、需要调用的函数
( * tx_done_handler)(); 在数据发送完成调用这个函数,在 hal_uart_dma_send_block 中有提到。
( * rx_done_handler)(); 在数据接收完成时调用这个函数,在 hal_uart_dma_receive_block 中有提到。
( * stdin_handler)(stdin_buffer[0]); TODO,还不太明白这个函数的作用。
stdin_rx_complete(); TODO,还不太明白这个函数的作用。
最后提一下,由于我手上刚好有stm32f103zet6开发板,CSR8311模组,所以我是参考网友的例程移植了一个在stm32f103裸机上运行的项目。目前项目放在gitee上,需要源码的先在评论区留个言,等后期有空上传到github。
gitee 项目下载