mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-01-18 19:21:54 +00:00
1261 lines
68 KiB
TeX
1261 lines
68 KiB
TeX
\documentclass[a4paper,titlepage,oneside,12pt]{amsart} %amsart
|
|
\usepackage{graphicx}
|
|
\usepackage{hyperref}
|
|
%\usepackage{geometry} % see geometry.pdf on how to lay out the page. There's lots.
|
|
\usepackage[margin=1.3in]{geometry}
|
|
\geometry{a4paper} % or letter or a5paper or ... etc
|
|
% \geometry{landscape} % rotated page geometry
|
|
\usepackage{ifthen}
|
|
|
|
% See the ``Article customise'' template for come common customisations
|
|
\usepackage[usenames,dvipsnames]{color}
|
|
|
|
\usepackage[table]{xcolor}
|
|
|
|
\definecolor{lightgray}{RGB}{245, 245, 245}
|
|
\definecolor{bkblue}{RGB}{18, 47, 76}
|
|
\definecolor{bklightblue}{RGB}{102, 131, 158}
|
|
\definecolor{mygreen}{rgb}{0,0.6,0}
|
|
\definecolor{orange}{RGB}{255,153,0}
|
|
|
|
\usepackage{opensans}
|
|
\usepackage{setspace}
|
|
\usepackage{booktabs} \newcommand{\ra}[1]{\renewcommand{\arraystretch}{#1}}
|
|
|
|
\usepackage{color}
|
|
|
|
% show todos
|
|
\newcommand{\todo}[1]{\colorbox{yellow}{#1}}
|
|
% ignore todos
|
|
% \newcommand{\todo}[1]{}
|
|
|
|
\newcommand{\toread}[1]{{\color{bklightblue} #1}}
|
|
|
|
\usepackage{listings}
|
|
\lstset{ %
|
|
float,
|
|
language=C, % choose the language of the code
|
|
basicstyle=\footnotesize, % the size of the fonts that are used for the code
|
|
%numbers=left, % where to put the line-numbers
|
|
%numberstyle=\footnotesize, % the size of the fonts that are used for the line-numbers
|
|
%stepnumber=1, % the step between two line-numbers. If it is 1 each line will be numbered
|
|
%numbersep=5pt, % how far the line-numbers are from the code
|
|
backgroundcolor=\color{lightgray}, % choose the background color. You must add \usepackage{color}
|
|
showspaces=false, % show spaces adding particular underscores
|
|
showstringspaces=false, % underline spaces within strings
|
|
showtabs=false, % show tabs within strings adding particular underscores
|
|
frame=single, % adds a frame around the code
|
|
framerule=0.2pt,
|
|
tabsize=2, % sets default tabsize to 2 spaces
|
|
captionpos=b, % sets the caption-position to bottom
|
|
breaklines=true, % sets automatic line breaking
|
|
breakatwhitespace=false, % sets if automatic breaks should only happen at whitespace
|
|
escapeinside={\%*}{*)}, % if you want to add a comment within your code
|
|
belowcaptionskip=5em,
|
|
belowskip=1em,
|
|
aboveskip=1.8em,
|
|
commentstyle=\itshape\color{mygreen},
|
|
keywordstyle=\bfseries\color{black},
|
|
identifierstyle=\color{black},
|
|
stringstyle=\color{blue},
|
|
morekeywords={*, timer_source_t, data_source_t, uint32_t, uint16_t, uint8_t, RUN_LOOP_TYPE, le_command_status_t, gatt_client_t,
|
|
le_characteristic_t, le_service_t, le_characteristic_descriptor_t, service_record_item_t, bd_addr_t, btstack_packet_handler_t,
|
|
hci_cmd_t, bt_control_t, remote_device_db_t, link_key_t, device_name_t, hci_transport_t, hci_uart_config_t, sdp_query_event_t,
|
|
sdp_query_complete_event_t, sdp_query_rfcomm_service_event_t, sdp_parser_event_t, sdp_parser_event_type_t,
|
|
sdp_parser_attribute_value_event_t, sdp_parser_complete_event_t, advertising_report_t, gc_state_t, le_service_event_t,
|
|
le_characteristic_event_t}
|
|
}
|
|
|
|
% Bluetopia & TI MSP430 + Stellaris
|
|
% http://processors.wiki.ti.com/index.php/CC256x_Bluetopia_Stack#Demos
|
|
|
|
% Setup MSP430+PAN1315
|
|
%
|
|
\newcommand{\versionNr}{1.3}
|
|
\newcommand{\authorMila}{Dr. sc. Milanka Ringwald}
|
|
\newcommand{\authorMatthias}{Dr. sc. Matthias Ringwald}
|
|
\newcommand{\bkContact}{\href{contact@bluekitchen-gmbh.com}{contact@bluekitchen-gmbh.com}}
|
|
\newcommand{\barWidth}{0.3cm}
|
|
\newcommand{\urlfoot}[2]{\href{#1}{{\color{blue} #2}}\footnote{#1}}
|
|
|
|
\makeatletter
|
|
\renewcommand{\maketitle}{
|
|
\begin{titlepage}
|
|
\fosfamily
|
|
\begin{center}
|
|
\begin{minipage}[b]{\textwidth}
|
|
\begin{minipage}[b]{.1\textwidth}
|
|
\color{bkblue}\rule{\barWidth{}}{22cm}
|
|
\end{minipage}
|
|
\hfill\begin{minipage}[b]{.8\textwidth}\begin{flushright}
|
|
{\color{bkblue}
|
|
% VERSION \versionNr{} \\
|
|
\today \\}
|
|
\vspace*{7.5cm}
|
|
\hfill\includegraphics[width=0.85\textwidth]{picts/bklogo.pdf}
|
|
\vspace*{1.5cm}
|
|
\begin{spacing}{2}
|
|
{\huge \color{bkblue} \@title} \\
|
|
{\Large \color{bklightblue} Including Quickstart Guide}
|
|
\end{spacing}
|
|
\vspace*{1.5cm}
|
|
{\color{bkblue}\large \authorMila \\
|
|
\large \authorMatthias \\
|
|
\large \bkContact\\ }
|
|
\end{flushright}\end{minipage}
|
|
\vfill
|
|
\begin{minipage}[b]{\textwidth}
|
|
\color{bklightblue}\rule{\barWidth{}}{\barWidth{}}
|
|
\end{minipage}
|
|
\end{minipage}
|
|
|
|
|
|
\end{center}
|
|
\end{titlepage}
|
|
}
|
|
\makeatother
|
|
|
|
\title[BTstack Manual] {BTstack Manual}
|
|
\author{Copyright \copyright 2012-2015 BlueKitchen GmbH}
|
|
|
|
%%% BEGIN DOCUMENT
|
|
|
|
\newcommand{\UserGuide}{\urlfoot{http://processors.wiki.ti.com/index.php/PAN1315EMK\_User\_Guide\#RF3\_Connector}{User Guide}{}}
|
|
\newcommand{\MSPGCCWiki}{\urlfoot{http://sourceforge.net/apps/mediawiki/mspgcc/index.php?title=MSPGCC\_Wiki}{MSPGCC Wiki}}
|
|
\newcommand{\GNUMake}{\urlfoot{http://gnuwin32.sourceforge.net/packages/make.htm}{GNU Make}}
|
|
\newcommand{\Python}{\urlfoot{http://www.python.org/getit/}{Python}}
|
|
\newcommand{\mspgcc}{\urlfoot{http://sourceforge.net/projects/mspgcc/files/Windows/mingw32/}{mspgcc}}
|
|
\newcommand{\BTSfile}{\urlfoot{http://processors.wiki.ti.com/index.php/CC256x\_Downloads}{BTS file}}
|
|
\newcommand{\MSPFlasher}{\urlfoot{http://processors.wiki.ti.com/index.php/MSP430\_Flasher\_-\_Command\_Line\_Programmer}{MSP430Flasher}}
|
|
\newcommand{\MSPDebug}{\urlfoot{http://mspdebug.sourceforge.net/}{MSPDebug}}
|
|
\newcommand{\BtstackGithub}{\urlfoot{https://github.com/bluekitchen/btstack/archive/master.zip}{BTstack's page}}
|
|
\newcommand{\gccarm}{\urlfoot{https://launchpad.net/gcc-arm-embedded}{arm-gcc}}
|
|
\newcommand{\OpenOCD}{\urlfoot{http://openocd.org}{OpenOCD}}
|
|
\newcommand{\mplabxc}{\urlfoot{http://www.microchip.com/pagehandler/en\_us/devtools/mplabxc/}{MPLAB XC}}
|
|
\newcommand{\PICkit}{\urlfoot{http://www.microchip.com/DevelopmentTools/ProductDetails.aspx?PartNO=pg164130}{PICkit 3}}
|
|
|
|
%level -1: part, 0: chapter, 1: section, etc.
|
|
\setcounter{tocdepth}{3}
|
|
|
|
\begin{document}
|
|
|
|
\maketitle
|
|
|
|
\tableofcontents
|
|
\pagebreak
|
|
|
|
|
|
Thanks for checking out BTstack! In this manual, we first provide the usual 'quick starter guide' before highlighting BTstack's main design choices and going into more details with a few examples. Finally, we outline the basic steps when integrating BTstack into existing single-threaded or even multi-threaded environments. The Revision History is shown in the Appendix \ref{appendix:revision_history} on page \pageref{appendix:revision_history}.
|
|
|
|
\section{Quick Start}
|
|
|
|
\subsection{General Tools}
|
|
On Unix-based systems, git, make, and Python are usually installed. If not, use the system's packet manager to install them.
|
|
|
|
On Windows, you need to install and configure mspgcc, GNU Make, Python, and optionally git manually:
|
|
\begin{itemize}
|
|
\item \GNUMake{} for Windows: Add its bin folder to the Windows Path in Environment Variables. The bin folder is where make.exe resides, and it's usually located in \path{C:\Program Files\GnuWin32\bin}.
|
|
\item \Python{} for Windows: Add Python installation folder to the Windows Path in Environment Variables.
|
|
\end{itemize}
|
|
|
|
Adding paths to the Windows Path variable:
|
|
\begin{itemize} \label{sec:windowsPath}
|
|
\item Go to: Control Panel$\rightarrow$System$\rightarrow$Advanced tab$\rightarrow$Environment Variables.
|
|
\item The top part contains a list of User variables.
|
|
\item Click on the Path variable and then click edit.
|
|
\item Go to the end of the line, then append the path to the list., for example, \path{C:\mspgcc\bin} for mspgcc.
|
|
\item Ensure that there is a semicolon before and after \path{C:\mspgcc\bin}.
|
|
\end{itemize}
|
|
|
|
\subsection{Getting BTstack from GitHub}
|
|
|
|
Use git to clone the latest version:
|
|
\begin{lstlisting}
|
|
git clone https://github.com/bluekitchen/btstack.git
|
|
\end{lstlisting}
|
|
Alternatively, you can download it as a ZIP archive from \BtstackGithub{}on GitHub.
|
|
|
|
\subsection{Compiling the examples and loading firmware}
|
|
This step is platform specific. To compile and run the examples, you need to download and install the platform specific toolchain and a flash tool. For TI's CC256x chipsets, you also need the correct init script, or "Service Pack" in TI nomenclature. Assuming that these are provided, go to \path{btstack/platforms/$PLATFORM$} folder in command prompt and run make. If all the paths are correct, it will generate several firmware files. These firmware files can be loaded onto the device using platform specific flash programmer. For the PIC32-Harmony platform, a project file for the MPLAB X IDE is provided, too.
|
|
|
|
\begin{table*}\centering
|
|
\caption{Overview of platform specific toolchains, programmers, and used chipsets.}
|
|
|
|
\resizebox{\textwidth}{!}{\begin{minipage}{\textwidth}
|
|
\rowcolors{1}{lightgray}{white}
|
|
|
|
\begin{tabular}{p{4cm}p{2cm}p{3cm}p{4cm} }
|
|
\toprule
|
|
\hiderowcolors Platform & Chipset & Toolchain & Programmer\\ \showrowcolors
|
|
\midrule
|
|
ez430-rf2560, msp-exp430f5438, msp430f5229lp & CC256x & \mspgcc{} &\MSPFlasher{}, \MSPDebug{} \\
|
|
stm32-f103rb-nucleo & CC256x & \gccarm{} & \OpenOCD{} \\
|
|
pic32-harmony & CSR8811 & \mplabxc{} & \PICkit{} \\ \hiderowcolors
|
|
\bottomrule
|
|
\label{table:platformCompiler}
|
|
\end{tabular}
|
|
|
|
\end{minipage} }
|
|
\end{table*}
|
|
|
|
\subsection{Run the Example}
|
|
|
|
As a first test, we recommend the SPP Counter example (see Section \ref{section:sppcounter}). During the startup, for TI chipsets, the init script is transferred, and the Bluetooth stack brought up. After that, the development board is discoverable as "BTstack SPP Counter" and provides a single virtual serial port. When you connect to it, you'll receive a counter value as text every second.
|
|
|
|
% The SPP Counter doesn't use the display to keep the memory footprint small.
|
|
% The HID demo has a fancier user interface - it uses a display to show the discovery process and connection establishment with a Bluetooth keyboard, as well as the text as you type.
|
|
|
|
\subsection{Platform specifics}
|
|
|
|
\input{quickstart_platforms}
|
|
|
|
|
|
\section{BTstack Architecture}
|
|
|
|
As well as any other communication stack, BTstack is a collection of state machines that interact with each other. There is one or more state machines for each protocol and service that it implements. The rest of the architecture follows these fundamental design guidelines:
|
|
|
|
\begin{itemize}
|
|
\item \emph{Single threaded design} - BTstack does not use or require multi-threading to handle data sources and timers. Instead, it uses a single run loop.
|
|
\item \emph{No blocking anywhere} - If Bluetooth processing is required, its result will be delivered as an event via registered packet handlers.
|
|
\item \emph{No artificially limited buffers/pools} - Incoming and outgoing data packets are not queued.
|
|
\item \emph{Statically bounded memory (optionally)} - The number of maximum connections/channels/services can be configured.
|
|
\end{itemize}
|
|
|
|
\begin{figure}[htbp] % figure placement: here, top, bottom, or page
|
|
\centering
|
|
\includegraphics[width=\textwidth]{picts/btstack-architecture.pdf}
|
|
\caption{BTstack-based single-threaded application. The Main Application contains the application logic, e.g., reading a sensor value and providing it via the Communication Logic as a SPP Server.
|
|
The Communication Logic is often modeled as a finite state machine with events and data coming from either the Main Application or from BTstack via registered packet handlers (PH).
|
|
BTstack's Run Loop is responsible for providing timers and processing incoming data.
|
|
}
|
|
|
|
\label{fig:BTstackArchitecture}
|
|
\end{figure}
|
|
|
|
Figure \ref{fig:BTstackArchitecture} shows the general architecture of a BTstack-based application that includes the BTstack run loop.
|
|
|
|
\subsection{Single threaded design}
|
|
BTstack does not use or require multi-threading. It uses a single run loop to handle data sources and timers. Data sources represent communication interfaces like an UART or an USB driver.
|
|
Timers are used by BTstack to implement various Bluetooth-related timeouts. For example, to disconnect a Bluetooth baseband channel without an active L2CAP channel after 20 seconds. They can also be used to handle periodic events. During a run loop cycle, the callback functions of all registered data sources are called. Then, the callback functions of timers that are ready are executed.
|
|
|
|
For adapting BTstack to multi-threaded environments, see Section \ref{section:multithreading}.
|
|
|
|
|
|
\subsection{No blocking anywhere}
|
|
|
|
Bluetooth logic is event-driven. Therefore, all BTstack functions are non-blocking, i.e., all functions that cannot return immediately implement an asynchronous pattern. If the arguments of a function are valid, the necessary commands are sent to the Bluetooth chipset and the function returns with a success value. The actual result is delivered later as an asynchronous event via registered packet handlers.
|
|
|
|
If a Bluetooth event triggers longer processing by the application, the processing should be split into smaller chunks. The packet handler could then schedule a timer that manages the sequential execution of the chunks.
|
|
|
|
\subsection{No artificially limited buffers/pools}
|
|
Incoming and outgoing data packets are not queued. BTstack delivers an incoming data packet to the application before it receives the next one from the Bluetooth chipset. Therefore, it relies on the link layer of the Bluetooth chipset to slow down the remote sender when needed.
|
|
|
|
Similarly, the application has to adapt its packet generation to the remote receiver for outgoing data.
|
|
L2CAP relies on ACL flow control between sender and receiver. If there are no free ACL buffers in the Bluetooth module, the application cannot send. For RFCOMM, the mandatory credit-based flow-control limits the data sending rate additionally. The application can only send an RFCOMM packet if it has RFCOMM credits.
|
|
|
|
\subsection{Statically bounded memory}
|
|
BTstack has to keep track of services and active connections on the various protocol layers. The number of maximum connections/channels/services can be configured. In addition, the non-persistent database for remote device names and link keys needs memory and can be be configured, too. These numbers determine the amount of static memory allocation.
|
|
|
|
|
|
\section{How to use BTstack}
|
|
BTstack implements a set of basic Bluetooth protocols. To make use of these to connect to other devices or to provide own services, BTstack has to be properly configured during application startup.
|
|
|
|
In the following, we provide an overview of the memory management, the run loop, and services that are necessary to setup BTstack. From the point when the run loop is executed, the application runs as a finite state machine, which processes events received from BTstack. BTstack groups events logically and provides them over packet handlers, of which an overview is provided here. Finally, we describe the RFCOMM credit-based flow-control, which may be necessary for resource-constraint devices. Complete examples for the MSP430 platforms will be presented in Chapter \ref{examples}.
|
|
|
|
\subsection{Memory configuration}
|
|
\label{section:memory_configuration}
|
|
|
|
The structs for services, active connections and remote devices can be allocated in two different manners:
|
|
\begin{itemize}
|
|
\item statically from an individual memory pool, whose maximal number of elements is defined in the config file. To initialize the static pools, you need to call \emph{btstack\_memory\_init} function. An example of memory configuration for a single SPP service with a minimal L2CAP MTU is shown in Listing \ref{memoryConfigurationSPP}.
|
|
\item dynamically using the \emph{malloc/free} functions, if HAVE\_MALLOC is defined in config file.
|
|
\end{itemize}
|
|
|
|
If both HAVE\_MALLOC and maximal size of a pool are defined in the config file, the statical allocation will take precedence. In case that both are omitted, an error will be raised.
|
|
|
|
The memory is set up by calling \emph{btstack\_memory\_init} function:
|
|
\begin{lstlisting}
|
|
btstack_memory_init();
|
|
\end{lstlisting}
|
|
|
|
|
|
\subsection{Run loop}
|
|
\label{section:run_loop}
|
|
|
|
BTstack uses a run loop to handle incoming data and to schedule work. The run loop handles events from two different types of sources: data sources and timers. Data sources represent communication interfaces like an UART or an USB driver. Timers are used by BTstack to implement various Bluetooth-related timeouts. They can also be used to handle periodic events.
|
|
|
|
Data sources and timers are represented by the \emph{data\_source\_t} and \emph{timer\_source\_t} structs respectively. Each of these structs contain a linked list node and a pointer to a callback function. All active timers and data sources are kept in link lists. While the list of data sources is unsorted, the timers are sorted by expiration timeout for efficient processing.
|
|
|
|
The complete run loop cycle looks like this: first, the callback function of all registered data sources are called in a round robin way. Then, the callback functions of timers that are ready are executed. Finally, it will be checked if another run loop iteration has been requested by an interrupt handler. If not, the run loop will put the MCU into sleep mode.
|
|
|
|
Incoming data over the UART, USB, or timer ticks will generate an interrupt and wake up the microcontroller. In order to avoid the situation where a data source becomes ready just before the run loop enters sleep mode, an interrupt-driven data source has to call the \emph{embedded\_trigger} function. The call to \emph{embedded\_trigger} sets an internal flag that is checked in the critical section just before entering sleep mode.
|
|
|
|
Timers are single shot: a timer will be removed from the timer list before its event handler callback is executed. If you need a periodic timer, you can re-register the same timer source in the callback function, as shown in Listing \ref{PeriodicTimerHandler}. Note that BTstack expects to get called periodically to keep its time, see Section \ref{section:timeAbstraction} for more on the tick hardware abstraction.
|
|
|
|
The Run loop API is provided in Appendix \ref{appendix:api_run_loop}. To enable the use of timers, make sure that you defined HAVE\_TICK in the config file.
|
|
|
|
In your code, you'll have to configure the run loop before you start it as shown in Listing \ref{RunLoopExecution}. The application can register data sources as well as timers, e.g., periodical sampling of sensors, or communication over the UART.
|
|
|
|
The run loop is set up by calling \emph{run\_loop\_init} function for embedded systems:
|
|
\begin{lstlisting}
|
|
run_loop_init(RUN_LOOP_EMBEDDED);
|
|
\end{lstlisting}
|
|
|
|
|
|
\begin{lstlisting}[float, caption=Periodic counter, label=PeriodicTimerHandler]
|
|
#define TIMER_PERIOD_MS 1000
|
|
timer_source_t periodic_timer;
|
|
|
|
void register_timer(timer_source_t *timer, uint16_t period){
|
|
run_loop_set_timer(timer, period);
|
|
run_loop_add_timer(timer);
|
|
}
|
|
|
|
void timer_handler(timer_source_t *ts){
|
|
// do something,
|
|
... e.g., increase counter,
|
|
|
|
// then re-register timer
|
|
register_timer(ts, TIMER_PERIOD_MS);
|
|
}
|
|
|
|
void timer_setup(){
|
|
// set one-shot timer
|
|
run_loop_set_timer_handler(&periodic_timer, &timer_handler);
|
|
register_timer(&periodic_timer, TIMER_PERIOD_MS);
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection{BTstack initialization}
|
|
\label{section:btstack_initialization}
|
|
To initialize BTstack you need to initialize the memory and the run loop as explained in Sections \ref{section:memory_configuration} and \ref{section:run_loop} respectively, then setup HCI and all needed higher level protocols.
|
|
|
|
The HCI initialization has to adapt BTstack to the used platform and requires four arguments. These are:
|
|
\begin{itemize}
|
|
\item \emph{Bluetooth hardware control}: The Bluetooth hardware control API can provide the HCI layer with a custom initialization script, a vendor-specific baud rate change command, and system power notifications. It is also used to control the power mode of the Bluetooth module, i.e., turning it on/off and putting to sleep. In addition, it provides an error handler \emph{hw\_error} that is called when a Hardware Error is reported by the Bluetooth module. The callback allows for persistent logging or signaling of this failure.
|
|
|
|
Overall, the struct \emph{bt\_control\_t} encapsulates common functionality that is not covered by the Bluetooth specification. As an example, the \emph{bt\_con-trol\_cc256x\_in-stance} function returns a pointer to a control struct suitable for the CC256x chipset.
|
|
\end{itemize}
|
|
|
|
\begin{lstlisting}
|
|
bt_control_t * control = bt_control_cc256x_instance();
|
|
\end{lstlisting}
|
|
|
|
\begin{itemize}
|
|
\item \emph{HCI Transport implementation}: On embedded systems, a Bluetooth module can be connected via USB or an UART port. BTstack implements two UART based protocols: HCI UART Transport Layer (H4) and H4 with eHCILL support, a lightweight low-power variant by Texas Instruments.
|
|
These are accessed by linking the appropriate file (\path{src/hci_transport_h4_dma.c} resp. \path{src/hci_transport_h4_ehcill_dma.c)} and then getting a pointer to HCI Transport implementation. For more information on adapting HCI Transport to different environments, see Section \ref{section:hci_transport}.
|
|
\end{itemize}
|
|
|
|
\begin{lstlisting}
|
|
hci_transport_t * transport = hci_transport_h4_dma_instance();
|
|
\end{lstlisting}
|
|
|
|
\begin{itemize}
|
|
\item \emph {HCI Transport configuration}: As the configuration of the UART used in the H4 transport interface are not standardized, it has to be provided by the main application to BTstack. In addition to the initial UART baud rate, the main baud rate can be specified. The HCI layer of BTstack will change the init baud rate to the main one after the basic setup of the Bluetooth module. A baud rate change has to be done in a coordinated way at both HCI and hardware level. First, the HCI command to change the baud rate is sent, then it is necessary to wait for the confirmation event from the Bluetooth module. Only now, can the UART baud rate changed. As an example, the CC256x has to be initialized at 115200 and can then be used at higher speeds.
|
|
\end{itemize}
|
|
|
|
\begin{lstlisting}
|
|
hci_uart_config_t* config = hci_uart_config_cc256x_instance();
|
|
\end{lstlisting}
|
|
|
|
\begin{itemize}
|
|
\item \emph {Persistent storage} - specifies where to persist data like link keys or remote device names. This commonly requires platform specific code to access the MCU's EEPROM of Flash storage. For the first steps, BTstack provides a (non) persistent store in memory. For more see Section \ref{section:persistent_storage}.
|
|
\end{itemize}
|
|
|
|
\begin{lstlisting}
|
|
remote_device_db_t * remote_db = &remote_device_db_memory;
|
|
\end{lstlisting}
|
|
|
|
|
|
After these are ready, HCI is initialized like this:
|
|
\begin{lstlisting}
|
|
hci_init(transport, config, control, remote_db);
|
|
\end{lstlisting}
|
|
|
|
The higher layers only rely on BTstack and are initialized by calling the respective \emph{*\_init} function. These init functions register themselves with the underlying layer. In addition, the application can register packet handlers to get events and data as explained in the following section.
|
|
|
|
|
|
\subsection{Services}
|
|
One important construct of BTstack is \emph{service}. A service represents a server side component that handles incoming connections. So far, BTstack provides L2CAP and RFCOMM services. An L2CAP service handles incoming connections for an L2CAP channel and is registered with its protocol service multiplexer ID (PSM). Similarly, an RFCOMM service handles incoming RFCOMM connections and is registered with the RFCOMM channel ID. Outgoing connections require no special registration, they are created by the application when needed.
|
|
|
|
\begin{lstlisting}[float, caption=Memory configuration for an SPP service with a minimal L2CAP MTU., label=memoryConfigurationSPP]
|
|
#define HCI_ACL_PAYLOAD_SIZE 52
|
|
#define MAX_SPP_CONNECTIONS 1
|
|
#define MAX_NO_HCI_CONNECTIONS MAX_SPP_CONNECTIONS
|
|
#define MAX_NO_L2CAP_SERVICES 2
|
|
#define MAX_NO_L2CAP_CHANNELS (1+MAX_SPP_CONNECTIONS)
|
|
#define MAX_NO_RFCOMM_MULTIPLEXERS MAX_SPP_CONNECTIONS
|
|
#define MAX_NO_RFCOMM_SERVICES 1
|
|
#define MAX_NO_RFCOMM_CHANNELS MAX_SPP_CONNECTIONS
|
|
#define MAX_NO_DB_MEM_DEVICE_NAMES 0
|
|
#define MAX_NO_DB_MEM_LINK_KEYS 3
|
|
#define MAX_NO_DB_MEM_SERVICES 1
|
|
\end{lstlisting}
|
|
|
|
\subsection{Where to get data - packet handlers}
|
|
\label{section:packetHandlers}
|
|
|
|
After the hardware and BTstack are set up, the run loop is entered. From now on everything is event driven. The application calls BTstack functions, which in turn may send commands to the Bluetooth module. The resulting events are delivered back to the application. Instead of writing a single callback handler for each possible event (as it is done in some other Bluetooth stacks), BTstack groups events logically and provides them over a single generic interface. Appendix \ref{appendix:api_events_and_errors} summarizes the parameters and event codes of L2CAP and RFCOMM events, as well as possible errors and the corresponding error codes.
|
|
|
|
Here is summarized list of packet handlers that an application might use:
|
|
\begin{itemize}
|
|
\item HCI packet handler - handles HCI and general BTstack events if L2CAP is not used (rare case).
|
|
\item L2CAP packet handler - handles HCI and general BTstack events.
|
|
\item L2CAP service packet handler - handles incoming L2CAP connections, i.e., channels initiated by the remote.
|
|
\item L2CAP channel packet handler - handles outgoing L2CAP connections, i.e., channels initiated internally.
|
|
\item RFCOMM packet handler - handles RFCOMM incoming/outgoing events and data.
|
|
\end{itemize}
|
|
|
|
\begin{table*}\centering
|
|
\caption{Functions for registering packet handlers}
|
|
\begin{tabular}{rl}\toprule
|
|
Packet Handler & Registering Function\\
|
|
\midrule
|
|
HCI packet handler & \emph{hci\_register\_packet\_handler}\\
|
|
L2CAP packet handler & \emph{l2cap\_register\_packet\_handler}\\
|
|
L2CAP service packet handler & \emph{l2cap\_register\_service\_internal}\\
|
|
L2CAP channel packet handler & \emph{l2cap\_create\_channel\_internal}\\
|
|
RFCOMM packet handler & \emph{rfcomm\_register\_packet\_handler}\\
|
|
\bottomrule
|
|
\label{table:registeringFunction}
|
|
\end{tabular}
|
|
\end{table*}
|
|
|
|
These handlers are registered with the functions listed in Table \ref{table:registeringFunction}.
|
|
|
|
HCI and general BTstack events are delivered to the packet handler specified by \emph{l2cap\_register\_packet\_handler} function, or \emph{hci\_register\_packet\_handler}, if L2CAP is not used. In L2CAP, BTstack discriminates incoming and outgoing connections, i.e., event and data packets are delivered to different packet handlers. Outgoing connections are used access remote services, incoming connections are used to provide services. For incoming connections, the packet handler specified by \emph{l2cap\_register\_service} is used. For outgoing connections, the handler provided by \emph{l2cap\_create\_channel\_internal} is used.
|
|
Currently, RFCOMM provides only a single packet handler specified by \emph{rfcomm\_register\_packet\_handler} for all RFCOMM connections, but this will be fixed in the next API overhaul.
|
|
|
|
The application can register a single shared packet handler for all protocols and services, or use separate packet handlers for each protocol layer and service. A shared packet handler is often used for stack initialization and connection management.
|
|
|
|
Separate packet handlers can be used for each L2CAP service and outgoing connection. For example, to connect with a Bluetooth HID keyboard, your application could use three packet handlers: one to handle HCI events during discovery of a keyboard registered by \emph{l2cap\_register\_packet\_handler}; one that will be registered to an outgoing L2CAP channel to connect to keyboard and to receive keyboard data registered by \emph{l2cap\_create\_channel\_internal}; after that keyboard can reconnect by itself. For this, you need to register L2CAP services for the HID Control and HID Interrupt PSMs using \emph{l2cap\_register\_service\_internal}. In this call, you'll also specify a packet handler to accept and receive keyboard data.
|
|
|
|
\newcommand{\BluetoothSpecification}{\urlfoot{https://www.bluetooth.org/Technical/Specifications/adopted.htm}{Bluetooth Specification}}
|
|
\newcommand{\BluetoothSpecificationURL}{\href{https://www.bluetooth.org/Technical/Specifications/adopted.htm}{\color{blue} Bluetooth Specification}}
|
|
|
|
\input{protocols_profiles}
|
|
|
|
\pagebreak
|
|
\section{Examples}
|
|
|
|
\label{examples}
|
|
The \path{MSP-EXP430F5438-CC256x} folder in BTstack repository currently includes the following examples for the MSP430F5438 Experimenter Board:
|
|
\begin{itemize}
|
|
\item UART example:
|
|
\begin{itemize}
|
|
\item \emph{led\_counter}: provides UART and timer interrupt without Bluetooth.
|
|
\end{itemize}
|
|
\item GAP example:
|
|
\begin{itemize}
|
|
\item \emph{gap\_inquiry}: uses GAP to discover surrounding Bluetooth devices and then requests their remote name.
|
|
\end{itemize}
|
|
\item SPP Server examples :
|
|
\begin{itemize}
|
|
\item \emph{spp\_counter}: provides a virtual serial port via SPP and a periodic timer over RFCOMM.
|
|
\item \emph{spp\_accel}: provides a virtual serial port via SPP. On connect, it sends the current accelerometer values as fast as possible.
|
|
\item \emph{spp\_flowcontrol}: provides a virtual serial port via SPP with manual RFCOMM credit management. Delayed processing of received data is simulated with the help of a periodic timer.
|
|
\end{itemize}
|
|
% \item SPP Client example: requires an SDP client to query SPP RFCOMM channel number. It is not yet provided by BTstack. Outgoing RFCOMM connections are fully supported otherwise.
|
|
\item HID Host example:
|
|
\begin{itemize}
|
|
\item \emph{hid\_demo}: on start, the device does a device discovery and connects to the first Bluetooth keyboard it finds, pairs, and allows to type on the little LCD screen.
|
|
% \item \emph{mouse}: on start, the device does a device discovery and connects to the first Bluetooth keyboard it finds, paris, and allows move cursor on the screen. It is not yet provided by BTstack, but it can be created by combining existing parts.
|
|
\end{itemize}
|
|
%\item HID Device examples:
|
|
% \begin{itemize}
|
|
% \item \emph{keyboard}: a keyboard device is simulated. Pressing the two buttons on the EXP430 board are mapped to Space and Enter. It is not yet provided by BTstack. %-- version for different board exists, needs to handle button IRQs
|
|
% \end{itemize}
|
|
\item Low Energy example:
|
|
\begin{itemize}
|
|
\item \emph{ble\_server}: provides a ready-to-run example for a test Peripheral device. It assumes that a PAN1323 or 1326 module with a CC2564 chipset is used.
|
|
\item \emph{gatt\_browser}: shows how to use the GATT Client API to discover primary services and their characteristics.
|
|
\end{itemize}
|
|
\item Dual mode example:
|
|
\begin{itemize}
|
|
\item \emph{spp\_and\_ble\_counter}: \todo{TODO}
|
|
\end{itemize}
|
|
|
|
\end{itemize}
|
|
|
|
In all examples the debug UART port is configured at 57600 bps speed.
|
|
|
|
\subsection{led\_counter: UART and timer interrupt without Bluetooth}
|
|
The example demonstrates how to setup hardware, initialize BTstack without Bluetooth, provide a periodic timer to toggle an LED and print number of toggles as a minimal BTstack test.
|
|
|
|
\subsubsection{Periodic Timer Setup}
|
|
$ $
|
|
\begin{lstlisting}[float, caption=Periodic counter, label=LEDToggler]
|
|
void heartbeat_handler(timer_source_t *ts){
|
|
// increment counter
|
|
char lineBuffer[30];
|
|
sprintf(lineBuffer, "BTstack counter %04u\n\r", ++counter);
|
|
printf(lineBuffer);
|
|
|
|
// toggle LED
|
|
LED_PORT_OUT = LED_PORT_OUT ^ LED_2;
|
|
|
|
// re-register timer
|
|
run_loop_register_timer(ts, HEARTBEAT_PERIOD_MS);
|
|
}
|
|
\end{lstlisting}
|
|
|
|
As timers in BTstack are single shot, the periodic counter is implemented by re-registering the \emph{timer\_source} in the \emph{heartbeat\_handler} callback function. The general setup is shown in Listing \ref{PeriodicTimerHandler}. Listing \ref{LEDToggler} shows \emph{heartbeat\_handler} adapted to periodically toggle an LED and print number of toggles.
|
|
|
|
|
|
\subsubsection{Turn On and Go}
|
|
|
|
Listing \ref{RunLoopExecution} shows how to setup and start the run loop. For hardware and BTstack setup, please check the source code.
|
|
|
|
\begin{lstlisting}[float, caption= Run loop execution., label=RunLoopExecution]
|
|
void timer_setup(){
|
|
// set one-shot timer
|
|
heartbeat.process = &timer_handler;
|
|
run_loop_register_timer(&heartbeat, HEARTBEAT_PERIOD_MS);
|
|
}
|
|
|
|
int main(void){
|
|
hw_setup();
|
|
btstack_setup();
|
|
timer_setup();
|
|
|
|
// go!
|
|
run_loop_execute();
|
|
|
|
return 0;
|
|
}
|
|
\end{lstlisting}
|
|
|
|
|
|
\subsection{gap\_inquiry: GAP Inquiry Example}
|
|
\label{example:GapInquiry}
|
|
|
|
The Generic Access Profile (GAP) defines how Bluetooth devices discover and establish a connection with each other. In this example, the application discovers surrounding Bluetooth devices and collects their Class of Device (CoD), page scan mode, clock offset, and RSSI. After that, the remote name of each device is requested. In the following section we outline the Bluetooth logic part, i.e., how the packet handler handles the asynchronous events and data packets.
|
|
|
|
\subsubsection{Bluetooth Logic}
|
|
The Bluetooth logic is implemented as a state machine within the packet handler. In this example, the following states are passed sequentially: INIT, W4\_INQUIRY\_MODE\_COMPLETE, and ACTIVE.
|
|
|
|
In INIT, the application enables the extended inquiry mode, which includes RSSI values, and transits to W4\_INQUIRY\_MODE\_COMPLETE state.
|
|
|
|
In W4\_INQUIRY\_MODE\_COMPLETE, after the inquiry mode was set, an inquiry scan is started, and the application transits to ACTIVE state.
|
|
|
|
IN ACTIVE, the following events are processed:
|
|
\begin{itemize}
|
|
\item Inquiry result event: the list of discovered devices is processed and the Class of Device (CoD), page scan mode, clock offset, and RSSI are stored in a table.
|
|
\item Inquiry complete event: the remote name is requested for devices without a fetched name. The state of a remote name can be one of the following: REMOTE\_NAME\_REQUEST, REMOTE\_NAME\_INQUIRED, or REMOTE\_NAME\_FETCHED.
|
|
\item Remote name cached event: prints cached remote names provided by BTstack - if persistent storage is provided.
|
|
\item Remote name request complete event: the remote name is stored in the table and the state is updated to REMOTE\_NAME\_FETCHED. The query of remote names is continued.
|
|
\end{itemize}
|
|
|
|
For more details please check Section \ref{section:DiscoverDevices} and the source code.
|
|
|
|
%*************************************
|
|
|
|
\begin{lstlisting}[float, caption=SPP service setup, label=SPPSetup]
|
|
void btstack_setup(void){
|
|
btstack_memory_init();
|
|
run_loop_init(RUN_LOOP_EMBEDDED);
|
|
|
|
// init HCI
|
|
hci_transport_t * transport = hci_transport_h4_dma_instance();
|
|
bt_control_t * control = bt_control_cc256x_instance();
|
|
hci_uart_config_t * config = hci_uart_config_cc256x_instance();
|
|
remote_device_db_t * remote_db = (remote_device_db_t *) &remote_device_db_memory;
|
|
hci_init(transport, config, control, remote_db);
|
|
hci_register_packet_handler(packet_handler);
|
|
|
|
// init L2CAP
|
|
l2cap_init();
|
|
l2cap_register_packet_handler(packet_handler);
|
|
|
|
// init RFCOMM
|
|
rfcomm_init();
|
|
rfcomm_register_packet_handler(packet_handler);
|
|
rfcomm_register_service_internal(NULL, rfcomm_channel_nr, 100);
|
|
|
|
// init SDP, create record for SPP and register with SDP
|
|
sdp_init();
|
|
memset(spp_service_buffer, 0, sizeof(spp_service_buffer));
|
|
service_record_item_t * service_record_item = (service_record_item_t *) spp_service_buffer;
|
|
sdp_create_spp_service( (uint8_t*) &service_record_item->service_record, 1, "SPP Counter");
|
|
sdp_register_service_internal(NULL, service_record_item);
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsection {spp\_counter: SPP Server - Heartbeat Counter over RFCOMM}
|
|
\label{section:sppcounter}
|
|
The Serial port profile (SPP) is widely used as it provides a serial port over Bluetooth. The SPP counter example demonstrates how to setup an SPP service, and provide a periodic timer over RFCOMM.
|
|
|
|
\subsubsection{SPP Service Setup}
|
|
|
|
SPP is based on RFCOMM, a Bluetooth protocol that emulates RS-232 serial ports. To access an RFCOMM serial port on a remote device, a client has to query its Service Discovery Protocol (SDP) server. The SDP response for an SPP service contains the RFCOMM channel number. To provide an SPP service, you need to initialize memory (Section \ref{section:memory_configuration}) and the run loop (Section \ref{section:run_loop}), setup HCI (Section \ref{section:btstack_initialization}) and L2CAP, then register an RFCOMM service and provide its RFCOMM channel number as part of the Protocol List attribute of the SDP record . Example code for SPP service setup is provided in Listing \ref{SPPSetup}. The SDP record created by $sdp\_create\_spp\_service$ consists of a basic SPP definition that uses provided RFCOMM channel ID and service name. For more details, please have a look at it in \path{include/btstack/sdp_util.c}. The SDP record is created on the fly in RAM and is deterministic. To preserve valuable RAM, the result can be stored as constant data inside the ROM.
|
|
|
|
|
|
\subsubsection{Periodic Timer Setup}
|
|
|
|
The heartbeat handler increases the real counter every second, as shown in Listing \ref{PeriodicCounter}. The general setup is shown in Listing \ref{PeriodicTimerHandler}.
|
|
|
|
\begin{lstlisting}[float, caption=Periodic counter, label=PeriodicCounter]
|
|
#define HEARTBEAT_PERIOD_MS 1000
|
|
|
|
void theartbeat_handler(timer_source_t *ts){
|
|
real_counter++;
|
|
// re-register timer
|
|
run_loop_register_timer(ts, HEARTBEAT_PERIOD_MS);
|
|
}
|
|
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Bluetooth logic}
|
|
The Bluetooth logic is implemented as a state machine within the packet handler, see Listing \ref{SppServerPacketHandler}. In this example, the following states are passed sequentially: INIT, W4\_CONNECTION, W4\_CHANNEL\_COMPLETE, and ACTIVE.
|
|
|
|
\begin{lstlisting}[float, caption=SPP Server - Heartbeat Counter over RFCOMM., label=SppServerPacketHandler]
|
|
void prepareData(void){
|
|
counter_to_send++;
|
|
}
|
|
|
|
void tryToSend(void){
|
|
// see Quick Recipe 5.4, Listing 8
|
|
}
|
|
|
|
void packet_handler (uint8_t packet_type, uint8_t *packet, uint16_t size){
|
|
...
|
|
switch(state){
|
|
case INIT:
|
|
if (packet[2] == HCI_STATE_WORKING) {
|
|
state = W4_CONNECTION;
|
|
}
|
|
break;
|
|
|
|
case W4_CONNECTION:
|
|
switch (event) {
|
|
case HCI_EVENT_PIN_CODE_REQUEST:
|
|
// see Quick Recipe 5.7
|
|
break;
|
|
case RFCOMM_EVENT_INCOMING_CONNECTION:
|
|
// see Quick Recipe 5.11
|
|
state = W4_CHANNEL_COMPLETE;
|
|
break;
|
|
}
|
|
|
|
case W4_CHANNEL_COMPLETE:
|
|
if (event != RFCOMM_EVENT_OPEN_CHANNEL_COMPLETE) break;
|
|
// see Quick Recipe 5.11
|
|
// state: W4_CONNECTION on failure, otherwise ACTIVE
|
|
break;
|
|
|
|
case ACTIVE:
|
|
// see Quick Recipe 5.4, Listing 9
|
|
// state: W4_CONNECTION on channel closed
|
|
break;
|
|
...
|
|
}
|
|
}
|
|
\end{lstlisting}
|
|
|
|
In INIT, upon successful startup of BTstack, the local Bluetooth name is set, and the state machine transits to W4\_CONNECTION.
|
|
|
|
The W4\_CONNECTION state handles authentication and accepts incoming RFCOMM connections. It uses a fixed PIN code "0000" for authentication. An incoming RFCOMM connection is accepted, and the state machine progresses to W4\_CHANNEL\_COMPLETE. More logic is need, if you want to handle connections from multiple clients. The incoming RFCOMM connection event contains the RFCOMM channel number used during the SPP setup phase and the newly assigned RFCOMM channel ID that is used by all BTstack commands and events.
|
|
|
|
In W4\_CHANNEL\_COMPLETE state, an error in the channel establishment fails (rare case, e.g., client crashes), the application returns to the W4\_CONNE-CTION state. On successful connection, the RFCOMM channel ID and MTU for this channel are made available to the heartbeat counter and the state machine transits to ACTIVE.
|
|
|
|
While in the ACTIVE state, the communication between client and the application takes place. In this example, the timer handler increases the real counter every second. The packet handler tries to send this information when an RFCOMM credit (RFCOMM\_EVENT\_CREDITS) or an HCI packet sent event (DAEMON\_EVENT\_HCI\_PACKET\_SENT) are received. These two events represent two orthogonal mechanisms that deal with flow control. A packet can only be sent when an RFCOMM credit is available and the internal BTstack outgoing packet buffer is free.
|
|
|
|
|
|
%*************************************
|
|
\subsection{spp\_accel: SPP Server - Accelerator Values}
|
|
In this example, the server tries to send the current accelerometer values. It does not use a periodic timer, instead, it sends the data as fast as possible.
|
|
|
|
|
|
%*************************************
|
|
\subsection {spp\_flowcontrol: SPP Server - Flow Control}
|
|
\label{example:spp_flow_control}
|
|
This example adds explicit flow control for incoming RFCOMM data to the SPP heartbeat counter example. We will highlight the changes compared to the SPP counter example.
|
|
|
|
\subsubsection{SPP Service Setup}
|
|
|
|
Listing \ref{explicitFlowControl} shows how to provide one initial credit during RFCOMM service initialization. Please note that providing a single credit effectively reduces the credit-based (sliding window) flow control to a stop-and-wait flow control that limits the data throughput substantially.
|
|
|
|
\begin{lstlisting}[float, caption= Heartbeat handler with manual credit management. , label=hbhManual]
|
|
void heartbeat_handler(struct timer *ts){
|
|
if (rfcomm_send_credit){
|
|
rfcomm_grant_credits(rfcomm_channel_id, 1);
|
|
rfcomm_send_credit = 0;
|
|
}
|
|
run_loop_register_timer(ts, HEARTBEAT_PERIOD_MS);
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{lstlisting}[float, caption= Packet handler with manual credit management. , label=phManual]
|
|
void packet_handler (void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
|
...
|
|
if (packet_type == RFCOMM_DATA_PACKET){
|
|
packet[size] = 0;
|
|
puts( (const char *) packet);
|
|
rfcomm_send_credit = 1;
|
|
return;
|
|
}
|
|
...
|
|
}
|
|
\end{lstlisting}
|
|
|
|
|
|
\subsubsection{Periodic Timer Setup}
|
|
|
|
Explicit credit management is recommended when received RFCOMM data cannot be processed immediately. In this example, delayed processing of received data is simulated with the help of a periodic timer as follows. When the packet handler receives a data packet, it does not provide a new credit, it sets a flag instead. If the flag is set, a new credit will be granted by the heartbeat handler, introducing a delay of up to 1 second. The heartbeat handler code is shown in Listing \ref{hbhManual}. The general setup is shown in Listing \ref{PeriodicTimerHandler}.
|
|
|
|
%*************************************
|
|
|
|
\subsection {ble\_server: LE Peripheral}
|
|
\label{example:ble_browser}
|
|
|
|
This example shows how to create an LE peripheral. The peripheral can be discovered by other devices and provides a GATT Server. The GATT server allows to discover the primary services, and read and write their characteristics. The BTstack setup code is written for a PAN1323 or 1326 module with a CC2564 chipset.
|
|
|
|
\begin{lstlisting}[float, caption=ATT Database ., label=code:lePeripheralDatabase]
|
|
PRIMARY_SERVICE, GAP_SERVICE
|
|
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "BTstack LE Peripheral"
|
|
CHARACTERISTIC, GAP_APPEARANCE, READ, 00 00
|
|
|
|
PRIMARY_SERVICE, GATT_SERVICE
|
|
CHARACTERISTIC, GATT_SERVICE_CHANGED, READ,
|
|
|
|
PRIMARY_SERVICE, FFF0
|
|
CHARACTERISTIC, FFF1, READ | WRITE | DYNAMIC,
|
|
CHARACTERISTIC, FFF2, READ | WRITE | DYNAMIC,
|
|
\end{lstlisting}
|
|
|
|
|
|
|
|
\begin{lstlisting}[float, caption= Setting up LE peripheral., label=code:lePeripheralSetup]
|
|
void setup(void){
|
|
...
|
|
// set up l2cap_le
|
|
l2cap_init();
|
|
|
|
// setup le device db
|
|
le_device_db_init();
|
|
|
|
// setup SM: Display only
|
|
sm_init();
|
|
sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY);
|
|
sm_set_authentication_requirements( SM_AUTHREQ_BONDING | SM_AUTHREQ_MITM_PROTECTION);
|
|
|
|
// setup ATT server
|
|
att_server_init(profile_data, NULL, att_write_callback);
|
|
att_server_register_packet_handler(app_packet_handler);
|
|
}
|
|
\end{lstlisting}
|
|
|
|
|
|
\begin{lstlisting}[float, caption= Read callback ., label=code:lePeripheralReadCallback]
|
|
uint16_t chr01_value_length = 0;
|
|
uint16_t max_chr01_value_length = 40;
|
|
char chr01_value[max_chr01_value_length];
|
|
char chr02_value = 0;
|
|
|
|
uint16_t get_read_att_value_len(uint16_t att_handle);
|
|
uint16_t get_write_att_value_len(uint16_t att_handle);
|
|
uint16_t get_bytes_to_copy(uint16_t value_len, uint16_t offset);
|
|
|
|
uint16_t att_read_callback(uint16_t con_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
|
|
printf("READ Callback, handle %04x\n", att_handle);
|
|
uint16_t value_len = get_att_read_value_len(att_handle);
|
|
if (!buffer) return value_len;
|
|
|
|
uint16_t bytes_to_copy = get_bytes_to_copy(value_len, offset);
|
|
if (!bytes_to_copy) return 0;
|
|
|
|
switch(att_handle){
|
|
case ATT_CHARACTERISTIC_FFF1_01_HANDLE:
|
|
memcpy(buffer, &chr01_value[offset], bytes_to_copy);
|
|
break;
|
|
case ATT_CHARACTERISTIC_FFF2_01_VALUE_HANDLE:
|
|
buffer[offset] = chr02_value;
|
|
break;
|
|
}
|
|
return bytes_to_copy;
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{lstlisting}[float, caption= Write callback ., label=code:lePeripheralWriteCallback]
|
|
int att_write_callback(uint16_t con_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){
|
|
printf("WRITE Callback, handle %04x\n", att_handle);
|
|
|
|
uint16_t value_len = get_write_att_value_len(att_handle);
|
|
uint16_t bytes_to_copy = get_bytes_to_copy(value_len, offset);
|
|
if (!bytes_to_copy) return ATT_ERROR_INVALID_OFFSET;
|
|
|
|
switch(att_handle){
|
|
case ATT_CHARACTERISTIC_FFF1_01_HANDLE:
|
|
buffer[buffer_size] = 0;
|
|
memcpy(&chr01_value[offset], buffer, bytes_to_copy);
|
|
chr01_value_length = bytes_to_copy + offset;
|
|
|
|
printf("New text: %s\n", buffer);
|
|
overwriteLine(7, (char*)buffer);
|
|
break;
|
|
case ATT_CHARACTERISTIC_FFF2_01_VALUE_HANDLE:
|
|
printf("New value: %u\n", buffer[offset]);
|
|
if (buffer[offset])
|
|
LED_PORT_OUT |= LED_2;
|
|
} else {
|
|
LED_PORT_OUT &= ~LED_2;
|
|
}
|
|
chr02_value = buffer[offset];
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{lstlisting}[float, caption= Write callback ., label=code:lePeripheralATTPacketHandler]
|
|
static void app_packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
|
if (packet_type != HCI_EVENT_PACKET) return;
|
|
bd_addr_t addr;
|
|
uint8_t adv_data[] = { 02, 01, 05, 03, 02, 0xf0, 0xff };
|
|
switch (packet[0]) {
|
|
case BTSTACK_EVENT_STATE:
|
|
// bt stack activated, get started - set local name
|
|
if (packet[2] == HCI_STATE_WORKING) {
|
|
printf("Working!\n");
|
|
hci_send_cmd(&hci_le_set_advertising_data, sizeof(adv_data), adv_data);
|
|
}
|
|
break;
|
|
case BTSTACK_EVENT_NR_CONNECTIONS_CHANGED:
|
|
if (packet[2]) {
|
|
overwriteLine(4, "CONNECTED");
|
|
} else {
|
|
overwriteLine(4, "NOT CONNECTED");
|
|
}
|
|
break;
|
|
case HCI_EVENT_DISCONNECTION_COMPLETE:
|
|
// restart advertising
|
|
hci_send_cmd(&hci_le_set_advertise_enable, 1);
|
|
break;
|
|
case HCI_EVENT_COMMAND_COMPLETE:
|
|
if (COMMAND_COMPLETE_EVENT(packet, hci_read_bd_addr)){
|
|
bt_flip_addr(addr, &packet[6]);
|
|
printf("BD ADDR: %s\n", bd_addr_to_str(addr));
|
|
break;
|
|
}
|
|
if(COMMAND_COMPLETE_EVENT(packet, hci_le_set_advertising_data)){
|
|
hci_send_cmd(&hci_le_set_scan_response_data,10,adv_data);
|
|
break;
|
|
}
|
|
if(COMMAND_COMPLETE_EVENT(packet, hci_le_set_scan_response_data)){
|
|
hci_send_cmd(&hci_le_set_advertise_enable, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
\end{lstlisting}
|
|
|
|
|
|
%*************************************
|
|
\subsection {gatt\_browser: GATT Client - Discovering primary services and their characteristics}
|
|
\label{example:gatt_browser}
|
|
This example shows how to use the GATT Client API to discover primary services and their characteristics of the first found device that is advertising its services.
|
|
|
|
The logic is divided between the HCI and GATT client packet handlers. The HCI packet handler with its state machine is responsible for finding and connecting to a remote device, and for starting the first GATT client query. Then, the GATT client packet handler receives all primary services and requests the characteristics of the last one to keep the example short.
|
|
|
|
\subsubsection{Setting up GATT client}
|
|
In setup phase, a GATT client must register the HCI and GATT client packet handlers, as shown in Listing \ref{code:gattClientSetup}. Additionally, the security manager can be setup, if signed writes, or encrypted or authenticated connection, are required to access the characteristics, as explained in Section \ref{subsection:smp}.
|
|
|
|
\begin{lstlisting}[float, caption= Setting up GATT client., label=code:gattClientSetup]
|
|
typedef enum {
|
|
IDLE,
|
|
W4_SCAN_RESULT,
|
|
W4_CONNECT,
|
|
W4_SERVICE_RESULT,
|
|
W4_CHARACTERISTIC_RESULT,
|
|
W4_DISCONNECT
|
|
} gc_state_t;
|
|
|
|
gc_state_t state = TC_IDLE;
|
|
uint16_t gc_id;
|
|
|
|
// Handles connect, disconnect, and advertising report events,
|
|
// starts the GATT client, and sends the first query.
|
|
void handle_hci_event(void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
|
|
|
// Handles GATT client query results, sends queries and the
|
|
// GAP disconnect command when the querying is done.
|
|
void handle_gatt_client_event(le_event_t * event);
|
|
|
|
void setup(void){
|
|
...
|
|
// Initialize L2CAP and register HCI event handler
|
|
l2cap_init();
|
|
l2cap_register_packet_handler(&handle_hci_event);
|
|
|
|
// Initialize GATT client and register handler for GATT client
|
|
// events
|
|
gatt_client_init();
|
|
gc_id = gatt_client_register_packet_handler(&handle_gatt_client_event);
|
|
|
|
// Optionally, setup security manager for signed writes or
|
|
// encrypted or authenticated connection
|
|
sm_init();
|
|
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Packet handlers}
|
|
The GATT browser goes sequentially through the states: IDLE, W4\_SCAN\_RESULT, W4\_CONNECT, W4\_SERVICE\_RESULT,
|
|
\\ W4\_CHARACTERISTIC\_RESULT, and W4\_DISCONNECT.
|
|
|
|
The W4\_SERVICE\_RESULT and W4\_CHARACTERISTIC\_RESULT states compose the state machine of the GATT client packet handler, as it reacts on GATT client events. The other states compose the state machine of the HCI packet handler.
|
|
|
|
In detail, the HCI packet handler has to start the scanning, to find the first advertising device, to stop scanning, to connect to and later to disconnect from it, to start the gatt client upon the connection is completed, and to send the first query - in this case the \emph{gatt\_client\_discover\_primary\_services} query is called, see Listing \ref{code:gattBrowserHCIPacketHandler}. A convenience function for filling advertising report struct from data packet is shown in Listing \ref{code:gattBrowserAdvReport}.
|
|
|
|
Query results and further queries are handled by the gatt client packet handler, as shown in Listing \ref{code:gattBrowserQueryHandler}. Here, upon receiving the primary services, the \emph{gatt\_client\_discover\_characteristics\_for\_service} query for the last received service is sent. After receiving the characteristics for the service, the \emph{gap\_disconnect} is called to terminate the connection. Upon disconnect, the HCI packet handler receives the disconnect complete event, and has to call \emph{gatt\_client\_stop} function to remove the disconnected device from the list of active GATT clients.
|
|
|
|
\begin{lstlisting}[float, caption= Advertising report handling., label=code:gattBrowserHCIPacketHandler]
|
|
void handle_hci_event(void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
|
if (packet_type != HCI_EVENT_PACKET) return;
|
|
advertising_report_t report;
|
|
|
|
uint8_t event = packet[0];
|
|
switch (event) {
|
|
case BTSTACK_EVENT_STATE:
|
|
// BTstack activated, get started
|
|
if (packet[2] != HCI_STATE_WORKING) break;
|
|
if (cmdline_addr_found){
|
|
printf("Trying to connect to %s\n", bd_addr_to_str(cmdline_addr));
|
|
state = TC_W4_CONNECT;
|
|
le_central_connect(cmdline_addr, 0);
|
|
break;
|
|
}
|
|
printf("BTstack activated, start scanning!\n");
|
|
state = TC_W4_SCAN_RESULT;
|
|
le_central_set_scan_parameters(0,0x0030, 0x0030);
|
|
le_central_start_scan();
|
|
break;
|
|
case GAP_LE_ADVERTISING_REPORT:
|
|
if (state != TC_W4_SCAN_RESULT) return;
|
|
fill_advertising_report_from_packet(&report, packet);
|
|
// stop scanning, and connect to the device
|
|
state = TC_W4_CONNECT;
|
|
le_central_stop_scan();
|
|
le_central_connect(report.address,report.address_type);
|
|
break;
|
|
case HCI_EVENT_LE_META:
|
|
// wait for connection complete
|
|
if (packet[2] != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) break;
|
|
if (state != TC_W4_CONNECT) return;
|
|
gc_handle = READ_BT_16(packet, 4);
|
|
// query primary services
|
|
state = TC_W4_SERVICE_RESULT;
|
|
gatt_client_discover_primary_services(gc_id, gc_handle);
|
|
break;
|
|
case HCI_EVENT_DISCONNECTION_COMPLETE:
|
|
printf("DISCONNECTED\n");
|
|
exit(0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{lstlisting}[float, caption=Convenience function for filling advertising report struct from data packet., label=code:gattBrowserAdvReport]
|
|
typedef struct advertising_report {
|
|
uint8_t type;
|
|
uint8_t event_type;
|
|
uint8_t address_type;
|
|
bd_addr_t address;
|
|
uint8_t rssi;
|
|
uint8_t length;
|
|
uint8_t * data;
|
|
} advertising_report_t;
|
|
|
|
void fill_advertising_report_from_packet(advertising_report_t * report, uint8_t *packet){
|
|
int pos = 2;
|
|
report->event_type = packet[pos++];
|
|
report->address_type = packet[pos++];
|
|
memcpy(report->address, &packet[pos], 6);
|
|
pos += 6;
|
|
report->rssi = packet[pos++];
|
|
report->length = packet[pos++];
|
|
report->data = &packet[pos];
|
|
pos += report->length;
|
|
dump_advertising_report(report);
|
|
|
|
bd_addr_t found_device_addr;
|
|
memcpy(found_device_addr, report->address, 6);
|
|
swapX(found_device_addr, report->address, 6);
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\begin{lstlisting}[float, caption=GATT client queries handling., label=code:gattBrowserQueryHandler]
|
|
void handle_gatt_client_event(le_event_t * event){
|
|
le_service_t service;
|
|
le_characteristic_t characteristic;
|
|
switch(state){
|
|
case TC_W4_SERVICE_RESULT:
|
|
switch(event->type){
|
|
case GATT_SERVICE_QUERY_RESULT:
|
|
service = ((le_service_event_t *) event)->service;
|
|
dump_service(&service);
|
|
services[service_count++] = service;
|
|
break;
|
|
case GATT_QUERY_COMPLETE:
|
|
state = TC_W4_CHARACTERISTIC_RESULT;
|
|
service_index = 0;
|
|
printf("\nCHARACTERISTIC for SERVICE ");
|
|
printUUID128(service.uuid128); printf("\n");
|
|
|
|
gatt_client_discover_characteristics_for_service(gc_id, gc_handle, &services[service_index]);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case TC_W4_CHARACTERISTIC_RESULT:
|
|
switch(event->type){
|
|
case GATT_CHARACTERISTIC_QUERY_RESULT:
|
|
characteristic = ((le_characteristic_event_t *) event)->characteristic;
|
|
dump_characteristic(&characteristic);
|
|
break;
|
|
case GATT_QUERY_COMPLETE:
|
|
if (service_index < service_count) {
|
|
state = TC_W4_CHARACTERISTIC_RESULT;
|
|
service = services[service_index++];
|
|
printf("\nCHARACTERISTIC for SERVICE ");
|
|
printUUID128(service.uuid128);
|
|
printf(", [0x%04x-0x%04x]\n", service.start_group_handle, service.end_group_handle);
|
|
gatt_client_discover_characteristics_for_service(gc_id, gc_handle, &service);
|
|
break;
|
|
}
|
|
state = TC_W4_DISCONNECT;
|
|
service_index = 0;
|
|
gap_disconnect(gc_handle);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
\end{lstlisting}
|
|
|
|
|
|
\include{example_spp_le_counter}
|
|
|
|
\subsection{sdp\_bnep\_query}
|
|
\label{subsection:panudemo}
|
|
|
|
% \section{Platforms}
|
|
|
|
\section{Porting to Other Platforms}
|
|
|
|
In this chapter, we highlight the BTstack components that need to be adjusted for different hardware platforms.
|
|
|
|
\subsection{Time Abstraction Layer}
|
|
\label{section:timeAbstraction}
|
|
BTstack requires a way to learn about passing time. \emph{run\_loop\_embedded.c} supports to different modes: system ticks or a system clock with millisecond resolution. BTstack's timing requirements are really low as only Bluetooth timeouts in the second range need to be handled.
|
|
|
|
\subsubsection{Tick Hardware Abstraction}
|
|
\label{ssection:tickAbstraction}
|
|
|
|
If your platform doesn't require a system clock or if you already have a system tick (as it is the default with CMSIS on ARM Cortex devices), you can use that to implement BTstack's time abstraction in \emph{include/btstack/hal\_tick.h>}.
|
|
|
|
For this, you need to define \emph{HAVE\_TICK} in \emph{btstack-config.h}:
|
|
\begin{lstlisting}
|
|
#define HAVE_TICK
|
|
\end{lstlisting}
|
|
|
|
Then, you need to implement the functions \emph{hal\_tick\_init} and \emph{hal\_tick\_set\_handler}, which will be called during the initialization of the run loop.
|
|
|
|
\begin{lstlisting}
|
|
void hal_tick_init(void);
|
|
void hal_tick_set_handler(void (*tick_handler)(void));
|
|
int hal_tick_get_tick_period_in_ms(void);
|
|
\end{lstlisting}
|
|
|
|
After BTstack calls \emph{hal\_tick\_init()} and \emph{hal\_tick\_set\_handler(tick\_handler)}, it expects that the \emph{tick\_handler} gets called every \emph{hal\_tick\_get\_tick\_period\_in\_ms()} ms.
|
|
|
|
\subsubsection{Time MS Hardware Abstraction}
|
|
\label{ssection:timeMSAbstraction}
|
|
If your platform already has a system clock or it is more convenient to provide such a clock, you can use the Time MS Hardware Abstraction in \emph{include/btstack/hal\_time\_ms.h}.
|
|
|
|
For this, you need to define \emph{HAVE\_TICK} in \emph{btstack-config.h}:
|
|
\begin{lstlisting}
|
|
#define HAVE_TIME_MS
|
|
\end{lstlisting}
|
|
|
|
Then, you need to implement the function \emph{hal\_time\_ms()}, which will be called from BTstack's run loop and when setting a timer for the future. It has to return the time in milliseconds.
|
|
|
|
\begin{lstlisting}
|
|
uint32_t hal_time_ms(void);
|
|
\end{lstlisting}
|
|
|
|
\subsection{Bluetooth Hardware Control API}
|
|
\label{section:bt_hw_control}
|
|
The Bluetooth hardware control API can provide the HCI layer with a custom initialization script, a vendor-specific baud rate change command, and system power notifications. It is also used to control the power mode of the Bluetooth module, i.e., turning it on/off and putting to sleep. In addition, it provides an error handler \emph{hw\_error} that is called when a Hardware Error is reported by the Bluetooth module. The callback allows for persistent logging or signaling of this failure. \todo{add recipe}
|
|
|
|
Overall, the struct \emph{bt\_control\_t} encapsulates common functionality that is not covered by the Bluetooth specification. As an example, the \emph{bt\_control\_cc256x\_in-stance} function returns a pointer to a control struct suitable for the CC256x chipset.
|
|
|
|
\subsection{HCI Transport Implementation}
|
|
\label{section:hci_transport}
|
|
On embedded systems, a Bluetooth module can be connected via USB or an UART port. BTstack implements two UART based protocols for carrying HCI commands, events and data between a host and a Bluetooth module: HCI UART Transport Layer (H4) and H4 with eHCILL support, a lightweight low-power variant by Texas Instruments.
|
|
|
|
\subsubsection{HCI UART Transport Layer (H4)}
|
|
\label{section:hciUART}
|
|
Most embedded UART interfaces operate on the byte level and generate a processor interrupt when a byte was received. In the interrupt handler, common UART drivers then place the received data in a ring buffer and set a flag for further processing or notify the higher-level code, i.e., in our case the Bluetooth stack.
|
|
|
|
Bluetooth communication is packet-based and a single packet may contain up to 1021 bytes. Calling a data received handler of the Bluetooth stack for every byte creates an unnecessary overhead. To avoid that, a Bluetooth packet can be read as multiple blocks where the amount of bytes to read is known in advance. Even better would be the use of on-chip DMA modules for these block reads, if available.
|
|
|
|
%During the BTstack will request up to three reads: first to get the packet type, second to get ACL or Event header, and last one to read the payload.
|
|
|
|
The BTstack UART Hardware Abstraction Layer API reflects this design approach and the underlying UART driver has to implement the following API:
|
|
|
|
\begin{lstlisting}
|
|
void hal_uart_dma_init(void);
|
|
void hal_uart_dma_set_block_received(void (*block_handler)(void));
|
|
void hal_uart_dma_set_block_sent(void (*block_handler)(void));
|
|
int hal_uart_dma_set_baud(uint32_t baud);
|
|
void hal_uart_dma_send_block(const uint8_t *buffer, uint16_t len);
|
|
void hal_uart_dma_receive_block(uint8_t *buffer, uint16_t len);
|
|
\end{lstlisting}
|
|
|
|
The main HCI H4 implementations for embedded system is \emph{hci\_h4\_transport-\_dma} function. This function calls the following sequence: \emph{hal\_uart\_dma\_init}, \emph{hal\_uart\_dma\_set\_block\_received} and \emph{hal\_uart\_dma\_set\_block\_sent} functions. \mbox{After} this sequence, the HCI layer will start packet processing by calling \emph{hal\_uart-\_dma\_receive\_block} function. The HAL implementation is responsible for reading the requested amount of bytes, stopping incoming data via the RTS line when the requested amount of data was received and has to call the handler. By this, the HAL implementation can stay generic, while requiring only three callbacks per HCI packet.
|
|
|
|
\subsubsection{H4 with eHCILL support}
|
|
With the standard H4 protocol interface, it is not possible for either the host nor the baseband controller to enter a sleep mode. Besides the official H5 protocol, various chip vendors came up with proprietary solutions to this. The eHCILL support by Texas Instruments allows both the host and the baseband controller to independently enter sleep mode without loosing their synchronization with the HCI H4 Transport Layer. In addition to the IRQ-driven block-wise RX and TX, eHCILL requires a callback for CTS interrupts.
|
|
|
|
\begin{lstlisting}
|
|
void hal_uart_dma_set_cts_irq_handler(void(*cts_irq_handler)(void));
|
|
void hal_uart_dma_set_sleep(uint8_t sleep);
|
|
\end{lstlisting}
|
|
|
|
|
|
\subsection{Persistent Storage API}
|
|
\label{section:persistent_storage}
|
|
|
|
On embedded systems there is no generic way to persist data like link keys or remote device names, as every type of a device has its own capabilities, particularities and limitations. The persistent storage API provides an interface to implement concrete drivers for a particular system. As an example and for testing purposes, BTstack provides the memory-only implementation \emph{remote\_device\_db\_memory}. An implementation has to conform to the interface in Listing \ref{persistentDB}.
|
|
\\
|
|
|
|
\begin{lstlisting}[float, caption=Persistent Storage Interface., label=persistentDB]
|
|
typedef struct {
|
|
// management
|
|
void (*open)();
|
|
void (*close)();
|
|
|
|
// link key
|
|
int (*get_link_key)(bd_addr_t bd_addr, link_key_t link_key);
|
|
void (*put_link_key)(bd_addr_t bd_addr, link_key_t key);
|
|
void (*delete_link_key)(bd_addr_t bd_addr);
|
|
|
|
// remote name
|
|
int (*get_name)(bd_addr_t bd_addr, device_name_t *device_name);
|
|
void(*put_name)(bd_addr_t bd_addr, device_name_t *device_name);
|
|
void(*delete_name)(bd_addr_t bd_addr);
|
|
} remote_device_db_t;
|
|
|
|
\end{lstlisting}
|
|
|
|
\section{Integrating with Existing Systems}
|
|
|
|
While the run loop provided by BTstack is sufficient for new designs, BTstack is often used with or added to existing projects. In this case, the run loop, data sources, and timers may need to be adapted. The following two sections provides a guideline for single and multi-threaded environments.
|
|
|
|
To simplify the discussion, we'll consider an application split into "Main \mbox{Application}", "Communication Logic", and "BTstack". The Communication Logic contains the packet handler (PH) that handles all asynchronous events and data packets from BTstack. The Main Application makes use of the Communication Logic for its Bluetooth communication.
|
|
|
|
\subsection{Adapting BTstack for Single-Threaded Environments}
|
|
\label{section:singlethreading}
|
|
|
|
In a single-threaded environment, all application components run on the same (single) thread and use direct function calls as shown in Figure \ref{fig:BTstackSingle}.
|
|
|
|
\begin{figure}[htbp] % figure placement: here, top, bottom, or page
|
|
\centering
|
|
\includegraphics[width=0.3\textwidth]{picts/singlethreading-btstack.pdf}
|
|
\caption{BTstack in single-threaded environment. }
|
|
\label{fig:BTstackSingle}
|
|
\end{figure}
|
|
|
|
BTstack provides a basic run loop that supports the concept of data sources and timers, which can be registered centrally. This works well when working with a small MCU and without an operating system.
|
|
To adapt to a basic operating system or a different scheduler, BTstack's run loop can be implemented based on the functions and mechanism of the existing system.
|
|
|
|
Currently, we have two examples for this:
|
|
\begin{itemize}
|
|
\item \emph{run\_loop\_cocoa.c} is an implementation for the CoreFoundation Framework used in OS X and iOS. All run loop functions are implemented in terms of CoreFoundation calls, data sources and timers are modeled as CFSockets and CFRunLoopTimer respectively.
|
|
\item \emph{run\_loop\_posix.c} is an implementation for POSIX compliant systems. The data sources are modeled as file descriptors and managed in a linked list. Then, the\emph{select} function is used to wait for the next file descriptor to become ready or timer to expire.
|
|
\end{itemize}
|
|
|
|
\subsection{Adapting BTstack for Multi-Threaded Environments}
|
|
\label{section:multithreading}
|
|
|
|
The basic execution model of BTstack is a general while loop. Aside from interrupt-driven UART and timers, everything happens in sequence.
|
|
When using BTstack in a multi-threaded environment, this assumption has to stay valid - at least with respect to BTstack. For this, there are two common options:
|
|
|
|
\begin{figure}[ht]
|
|
\begin{minipage}[b]{\linewidth}
|
|
\centering
|
|
\includegraphics[width=0.8\textwidth]{picts/multithreading-monolithic.pdf}
|
|
\caption{BTstack in multi-threaded environment - monolithic solution.}
|
|
\label{fig:BTstackMonolithic}
|
|
\vspace{0.8cm}
|
|
\end{minipage}
|
|
|
|
\begin{minipage}[b]{\linewidth}
|
|
\centering
|
|
\includegraphics[width=0.8\textwidth]{picts/multithreading-btdaemon.pdf}
|
|
\caption{BTstack in multi-threaded environment - solution with daemon.}
|
|
\label{fig:BTstackDaemon}
|
|
\end{minipage}
|
|
\end{figure}
|
|
|
|
\begin{itemize}
|
|
\item The Communication Logic is implemented on a dedicated BTstack thread, and the Main Application communicates with the BTstack thread via application-specific messages over an Interprocess Communication (IPC) as depicted in Figure \ref{fig:BTstackMonolithic}. This option results in less code and quick adaption.
|
|
\item BTstack must be extended to run standalone, i.e, as a Daemon, on a dedicated thread and the Main Application controls this daemon via BTstack extended HCI command over IPC - this is used for the non-embedded version of BTstack e.g., on the iPhone and it is depicted in Figure \ref{fig:BTstackDaemon}. This option requires more code but provides more flexibility.
|
|
\end{itemize}
|
|
|
|
|
|
\pagebreak
|
|
|
|
\appendix
|
|
\input{appendix_apis}
|
|
\input{api_events_and_errors}
|
|
|
|
\pagebreak
|
|
|
|
\section{Revision History}
|
|
\label{appendix:revision_history}
|
|
|
|
\begin{table}[!htbp]
|
|
\begin{tabular*}{\textwidth}{lp{3.5cm}p{8.5cm}}\toprule
|
|
Rev & Date & Comments\\
|
|
\midrule
|
|
1.x & April 3, 2015 & Added Time MS Hardware Abstraction.\\
|
|
1.3 & November 6, 2014 & Introducing GATT client and server. Work in progress.\\
|
|
1.2 & November 1, 2013 & Explained Secure Simple Pairing in "Pairing of devices".\\
|
|
1.1 & August 30, 2013 & Introduced SDP client. Updated Quick Recipe on "Query remote SDP service".\\
|
|
\bottomrule
|
|
\end{tabular*}
|
|
\end{table}
|
|
|
|
%\section {TODO}
|
|
%
|
|
%\section {Maybe Missing}
|
|
%\begin{itemize}
|
|
%\item profiles in BTstack (SPP)
|
|
%\item More examples: HID mouse host, HID mouse device, HID keyboard device, BLE Client, ..
|
|
%\end{itemize}
|
|
%
|
|
|
|
|
|
% \bibliographystyle{IEEEtran}
|
|
% \bibliography{btstack-manual}
|
|
|
|
% \section*{Bibliography}
|
|
|
|
%\textbf{P. W. Wachulak} received the degree${\ldots}$ \\[6pt]
|
|
%\textbf{M. C. Marconi} received the degree${\ldots}$ \\[6pt]
|
|
%\textbf{R. A. Bartels} received the degree${\ldots}$ \\[6pt]
|
|
%\textbf{C. S. Menoni} received the degree${\ldots}$ \\[6pt]
|
|
%\textbf{J. J. Rocca} received the degree${\ldots}$
|
|
|
|
|
|
\end{document} |