Module BlueRNG de STM

Étude du réseau Bluetooth : j'ai implanté un module Bluetooth Low Energy de chez STMElectronics sur le circuit imprimé du Timer (Minuteur). Le module STxxx communique via une interface série SPI. La liaison s'effectue de la manière suivante :

MOSI 0x0A, 0x00, 0x00, 0x00, 0x00
MISO 0x02, 0x7F, 0x00, 0x0A, 0x00

Le byte 0x0A indique que l’on veut transmettre des informations. Les quatre bytes suivants sont des dummy. Le module répond par un byte 0x02 pour indiquer qu’il est prêt, suivi de deux bytes indiquant le nombre d’octets que l’on peut envoyer au module, dans cet exemple 128 (0x007F). Les deux bytes suivants représentent le nombre d’octets contenus dans le buffer de réception du module, dans ce cas (0x000A) 10 octets.

Structure d’une commande

Lorsque l’on envoie une commande HCI ou ACI au module Bluetooth, le premier byte indique le type de paquet. Il en existe cinq types.
0x01HCI_COMMAND_PKT
0x02HCI_ACLDATA_PKT
0x03HCI_SCODATA_PKT
0x04HCI_EVENT_PKT
0xFFHCI_VENDOR_PKT

Ensuite, nous avons 2 bytes pour l’OpCode. Celui-ci est divisé en deux parties : le Groupe (OGF), représenté par les 6 bits de poids fort, et la Commande (OCF), par les 10 bits restants. Cela est suivi d’un byte représentant le nombre de paramètres de la commande, et enfin les paramètres.

OpCode
Parameter Total Length
Parameter 0
OGF
OCF
Parameter 1
Parameter 2
....
Parameter N-1
Parameter N

L’OpCode est transmis en mode LITTLE ENDIAN, c’est-à-dire l'octet de poids faible pour le premier byte suivi de l'octet de poids fort pour le second byte. Exemple : pour un OGF = 0x04 (Information paramètres) et un OCF = 0x01 (hci_read_local_version_information), la combinaison des deux 0001’0000’0000’0001 = 0x1001. L’OpCode sera donc transmis en 0x01, 0x10. Ci-dessous les différents types de groupes OGF.

Table 1. OGF value
Group name OGF value
Link control commands 0x01
Link policy commands 0x02
Controller and baseband commands 0x03
Information parameters 0x04
Status parameters 0x05
Testing commands 0x06
LE controller commands 0x08
Vendor specific commands 0x3F
Pour connaître l'OCF, consultez le document du fournisseur ST pour les commandes HCI et les commandes ACI. Ci-dessous, une petite partie de la liste.
Table 2. HCI commands
Item Command OGF OCF OpCode
1 HCI_Disconnect 0x01 0x06 0x0406
2 HCI_Read_Remote_Version_Information 0x01 0x1D 0x041D
3 HCI_Set_Event_Mask 0x03 0x01 0x0C01
4 HCI_Reset 0x03 0x03 0x0C03
5 HCI_Read_Transmit_Power_Level 0x03 0x2D 0x0C2D
6 HCI_Read_Local_Version_Information 0x04 0x01 0x1001
7 HCI_Read_Local_Supported_Commands 0x04 0x02 0x1002

Exemple: HCI_READ_LOCAL_VERSION_INFORMATION

Cette commande hci n’a pas de paramètre.

MOSI 0x0A 0x00 0x00 0x00 0x00 0x01 0x01 0x10 0x00
MISO 0x02 0x7F 0x00 0x00 0x00 0xFF 0xFF 0xFF 0xFF

En bleu, l’en-tête (write) ; en rouge, HCI_COMMAND_PKT (Packet) ; en vert, l’OpCode (Little Endian) ; et en violet, le nombre total de paramètres en bytes. Il est parfois nécessaire d’envoyer plusieurs fois l’en-tête tant que le module ne répond pas par 0x02 pour indiquer qu’il est prêt et que le byte suivant est égal à zéro, ce qui représente la taille du buffer de réception.

Réponse à la commande ci-dessus.

MOSI 0x0B 0x00 0x00 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
MISO 0x02 0x7F 0x00 0x0F 0x00 0x04 0x0E 0x0C 0x01 0x01 0x10
0x00 0x07 0x07 0x31 0x07 0x30 0x00 0x23 0x00

Le module Bluetooth répond qu’il est prêt et qu’il a 0x0F (15) bytes dans son buffer. Après les cinq bytes d’en-tête, les trois suivants 0x04, 0x0E, 0x0C représentent : 0x04 indique qu’il s’agit d’un paquet de type Event, le byte suivant 0x0E indique le type d’Event (voir la liste ci-dessous), et le dernier 0x0C représente le nombre d’octets qui suivent.

On retrouve dans la réponse le HCI_COMMAND_PKT et l’OpCode de la commande hci_read_local_version_information en vert. Les derniers bytes fournissent les informations suivantes :

Parameter Size Description
Status 1 Error Code
HCI Version 1 See Bluetooth assigned numbers
HCI Revision 2 Revision of the current HCI in the BR/EDR controller
LMP Version 1 Version of the current LMP or PAL in the controller.
Manufacture 2 Manufacturer name of the BR/EDR controller.
LMP SubVersion 2 Subversion of the current LMP or PAL in the controller. This value is implementation dependent.

Exemple: ACI_GATT_ADD_SERVICE

Cette commande comporte plusieurs paramètres :

MOSI 0A 00 00 00 00 01 02 FD 13 02 1B C5 D5 A5 02 00 B4 9A E1 11
3A CF 80 6E 36 02 01 07
MISO 02 7F 00 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF

On reconnaît l’en-tête d’écriture suivi du HCI_COMMAND_PKT (0x01) et de l’OpCode (0xFD02). En violet, c’est le nombre de paramètres en octets ; dans cet exemple, 19 octets (0x13).

Réponse à la commande aci ci-dessus.

MOSI 0x0B 0x00 0x00 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
0xFF 0xFF
MISO 0x02 0x7F 0x00 0x09 0x00 0x04 0x0E 0x06 0x01 0x02 0xFD 0x00
0x0C 0x00

Dans la réponse, on reconnaît le HCI_EVENT_PKT (0x04) suivi du type (0x0E) et le nombre d’octets de paramètres (0x06). Ensuite, on retrouve l’OpCode de la commande ACI (0xFD02).

Service Handle. Lorsque ce service est ajouté, un descripteur est attribué par le serveur pour ce service. Le serveur alloue également une plage de descripteurs pour ce service à partir de serviceHandle à "serviceHandle + max_attr_records - 1". br> Ci-dessous la liste des types d’événements.

Table 3. HCI events
Item Event Event code Sub event code
1 Evt_Disconn_Complete 0x05 -
2 Evt_Encrypt_Change 0x08 -
3 Evt_Read_Remote_Version_Complete 0x0C -
4 Evt_Cmd_Complete 0x0E -
5 Evt_Cmd_Status 0x0F -
6 Evt_Hardware_Error 0x10 -
7 Evt_Num_Comp_Pkts 0x13 -
8 Evt_Data_Buffer_Overflow 0x1A -
9 Evt_Encryption_Key_Refresh_Complete 0x30 -
10 Evt_LE_Conn_Complete 0x3E 0x01
11 Evt_LE_Advertising_Report 0x3E 0x02
12 Evt_LE_Conn_Update_Complete 0x3E 0x03
13 Evt_LE_Read_Remote_Used_Features_Complete 0x3E 0x04
14 Evt_LE_LTK_Request 0x3E 0x05

Liste chainée

Le module Bluetooth reçoit des paquets de données, comme dans le système Ethernet. Ces paquets doivent être assemblés et ils n’arrivent pas forcément dans l’ordre. Pour résoudre ce problème, il faut utiliser une liste chaînée. (La liste chaînée ne fait pas partie de cette étude !)

Phase d'initialisation et boucle principale de l'application.

Les principales étapes suivantes sont requises pour configurer correctement les périphériques BlueNRG-1 et BlueNRG-2.

Configurez l'adresse publique du périphérique BLE (si l'adresse publique est utilisée):

				        aci_hal_write_config_data()
			        
Initialisation de la couche GATT BLE :

				        aci_gatt_init()
			        
Définissez les fonctionnalités d'E/S de sécurité et les exigences d'authentification appropriées (si la sécurité BLE est utilisée) :

				        aci_gap_set_io_capability()
                        aci_gap_set_authentication_requirement()
			        
Définir les services et caractéristiques requis, ainsi que les descripteurs de caractéristiques si le périphérique est un serveur GATT :

				        aci_gatt_add_service()
                        aci_gatt_add_char()
                        aci_gatt_add_char_desc()
			        
Principe de fonctionnement du Bluetooth LE

Maintenant que nous sommes en mesure de communiquer avec le module BlueNRG, il est important de comprendre le principe de fonctionnement du protocole de communication.

Les adresses suivantes sont prises en charge par les périphériques BlueNRG-1 et BlueNRG-2 :

Les adresses MAC publiques (adresses de 6 octets, soit 48 bits) identifient de manière unique un périphérique BLE et sont définies par l'Institut des ingénieurs électriciens et électroniciens (IEEE).

Les trois premiers octets de l'adresse publique identifient l'entreprise qui a émis l'identifiant et sont connus sous le nom d'identificateur unique d'organisation (OUI). Un identificateur unique d'organisation (OUI) est un numéro de 24 bits acheté auprès de l'IEEE.

Cet identifiant permet de réserver un bloc d’adresses publiques possibles (jusqu’à 2^24, provenant des 3 octets restants de l’adresse publique) à l’usage exclusif de l’entreprise possédant un OUI spécifique.

Si l'utilisateur souhaite programmer son adresse MAC personnalisée, il doit la stocker dans un emplacement Flash spécifique du périphérique, réservé uniquement au stockage de l'adresse MAC. Ensuite, lors de la mise sous tension du périphérique, il doit programmer cette adresse dans la radio en appelant une API spécifique de la pile BLE.

La commande API BLE pour définir l'adresse MAC est la suivante :


		    aci_hal_write_config_data()
		
Cette commande doit être envoyée aux périphériques BlueNRG avant le démarrage de toute opération BLE (après l'appel à l'API BlueNRG_Stack_Initialization pour l'initialisation de la pile BLE). L'exemple de pseudocode suivant montre comment définir une adresse publique :

		    uint8_t bdaddr[] = {0x12, 0x34, 0x00, 0xE1, 0x80, 0x02};
            ret = aci_hal_write_config_data(CONFIG_DATA_PUBADDR_OFFSET, 
								CONFIG_DATA_PUBADDR_LEN,
								bdaddr);
		

Initialisation de la couche GAP

Le Generic Access Profile (GAP) définit les rôles des composants maîtres et esclaves, comme les messages de signalement ou la découverte du réseau. Il y a deux méthodes de signalement dans le GAP :

Compréhension des messages Bluetooth Low Energy

Alors que ces deux types de messages ont le même format composé de 31 octets de données, seuls les messages de signalement sont obligatoires et envoyés à intervalles réguliers (plus la période est longue, plus la consommation est faible).

Lorsqu'ils les reçoivent, les autres composants peuvent demander l’émission d’un message de réponse au scan (scan response packet) avec d’éventuelles données supplémentaires.

La diffusion de messages de signalement personnalisés est la méthode utilisée par l’iBeacon et la norme Eddystone. À chaque fois qu’une connexion est établie avec un composant esclave, les liaisons se font grâce à un service GATT avec ses caractéristiques. Les messages de signalement cessent tant que la connexion n’est pas terminée.
La couche GAP introduit 4 rôles :

Le broadcaster s'oppose au scanner, le peripheral s'oppose au central. Ce qu'il est fondamental de comprendre avec le Bluetooth Low Energy, c'est qu'il y a deux façons pour que deux appareils communiquent ensemble.
Le mode dit connecté, ou le mode advertising.

C'est ainsi que l'on fait le lien avec les deux types d'événements et les deux types de canaux radio introduits précédemment.

En mode advertising

, un appareil émet des trames sur les 3 canaux d'advertising et ces trames sont accessibles à tout autre appareil à l'écoute sur ces 3 canaux.

En mode connecté

, un lien est établi entre deux appareils et eux seuls peuvent alors communiquer ensemble sur des canaux connus négociés ; le lien peut éventuellement être authentifié et encrypté pour accroître la sécurité.

Le rôle de Broadcaster est destiné à des applications qui ne font qu'émettre. Les appareils supportant ce rôle envoient des événements d'advertising pour diffuser des données. Ce rôle ne supporte pas le mode connecté.

Le rôle de Scanner est dédié à des applications qui ne font que recevoir. Il s'agit du rôle dual de l'Observer. Un appareil basé sur ce rôle reçoit des données diffusées lors d'événements d'advertising. Ce rôle ne supporte pas le mode connecté.

Le rôle Peripheral est destiné à des appareils qui supportent une (ou plusieurs) connexions et sont moins complexes et plus contraints qu'un appareil avec le rôle Central. Un appareil avec un rôle Peripheral a besoin d'un Controller esclave (par opposition à maître).

Le rôle Central supporte plusieurs connexions avec différents appareils Peripheral ; un tel appareil est l'initiateur des connexions et a besoin d'un Controller maître. Il est doté de fonctionnalités plus complexes et plus coûteuses que le Peripheral.

Initialisation de la couche GATT


e Generic Attribute Profile définit comment le BLE transfère les données entre maîtres et esclaves dans les deux sens.
Il définit des profiles qui regroupent un ensemble de services. Chaque service a des caractéristiques qui correspondent aux données.

Les modes de fonctionnement peuvent changer quand on passe du GAP au GATT. Le GATT dispose de deux modes de fonctionnement : client ou serveur. Au risque de faire vieux jeu, les périphériques esclaves sont appelés « serveurs GATT » et les maîtres (plus puissants) sont des « clients GATT ».
Souvenez-vous : le serveur détient les données, le client veut les données. Toutes les connexions sont initiées par les clients.

À chaque nouvelle connexion, un client peut demander une liste des services fournis par le serveur. Le maître détient en effet en permanence une liste des services (pas forcément exhaustive) complétée grâce aux messages de signalement.

Le plus souvent, l'esclave est un serveur GATT et le maître est client GATT. Autrement dit, le serveur expose à un client des données, appelées attributes.
Les attributes, tels que définis par le protocole ATT, sont formatés soit en services, soit en characteristics.

Un service peut contenir une collection de characteristics.

La valeur incluse dans le characteristic est la valeur que le client cherche en pratique à récupérer, comme par exemple le nombre de battements cardiaques relevés par un capteur placé près du cœur.

client server
Initialisation du module ST

		// Lecture de la version du logiciel du module (cette étape n’est pas nécessaire)
    HCI_Read_Local_Version_Information();

    // Remise à zéro du module
    HCI_Reset();

    // Après le reset, il est important d'écrire la configuration de l'adresse BDADDR
    ACI_hal_write_config_data();

    // Initialisation de la couche GATT
    ACI_gatt_init();

    // Initialisation de la couche GAP en tant que périphérique esclave
    ACI_Gap_Init_slave_peripheral();

    // Il est crucial de commencer par l'initialisation de la couche GATT !
    // Lors de l’initialisation de la couche GAP, le rôle du module est défini dans les paramètres.
    // Une fois le rôle défini, on obtient un handle pour le service et un handle pour les paramètres
    // que l’on doit passer à la fonction.
    ACI_gatt_update_char_value(...);

    // Spécification des exigences d'authentification.
    Dans l’exemple donné, il utilise la fonction :
    ACI_gap_set_auth_requirement();

    // Il existe également une autre fonction ACI_gap_set_authentication_requirement() ?
    ACI_gap_set_authentication_requirement();
	

Si toutes ces étapes se sont bien déroulées, on peut afficher "SERVER : BLE Stack initialized !!. Avant de détailler les services et les caractéristiques, il est maintenant nécessaire de créer une fonction permettant au GATT Client de découvrir le GATT Server nouvellement créé. Les périphériques esclaves ont deux modes de fonctionnement : en mode diffusion ou en mode liaison directe avec un composant maître.
Cette fonction vise à désactiver la réponse au scan (/* disable scan response */).

Pour désactiver la réponse au scanHCI_le_set_scan_resp_data(...)

            /* Pour que le périphérique soit détectable par le maître, il faut faire appel à la fonction : */
            ACI_gap_set_discoverable(...)
        

Connexion

Une fois que l'appareil utilisé en tant que serveur GATT est rendu détectable, il devient visible pour le dispositif client GATT, permettant ainsi l'établissement d'une connexion BLE. Le dispositif client GATT utilise la commande GAP ACI suivante pour se connecter au serveur GATT en mode "Advertisement".

Command name Parameters Return
Aci_Gap_Create_Connection (0xFC9C) Scan_Interval
Scan_Window
Peer_Address_Type
Peer_Address
Own_Address_Type
Conn_Interval_Min
Conn_Interval_Max
Conn_Latency
Supervision_Timeout
Conn_Len_Min
Conn_Len_Max
Status

exemple:


            ACI_gap_create_connection(0x4000, 0x4000, 
                          PUBLIC_ADDR, bdaddr,
                          PUBLIC_ADDR, 9, 9, 0, 60, 1000, 1000);
    

La variable « bdAddr » est l’adresse du périphérique client GATT correspondant.
Lors de l'établissement de la connexion, le client retourne un événement avec les paramètres suivants : (EVT_LE_CONN_COMPLETE).


7.1 LE Connection Complete Event

Event Event Code Event parameters
LE Connection Complete 0x3E Subevent_Code,
Status,
Connection_Handle,
Role,
Peer_Address_Type,
Peer_Address,
Conn_Interval,
Conn_Latency,
Supervision_Timeout,
Master_Clock_Accuracy

MOSI 0x0B 0x00 0x00 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
MISO 0x04 0x3e 0x13 0x01 0x00 0x01 0x08 0x01 0x01 0x4D 0xBE
0x1E 0x68 0xE7 0x44 0x27 0x00 0x00 0x00 0xD0 0x07 0x05

Parameter Value Comment
LE Connection Complete 0x01
Status Success 0x00
Handle 0x0801 = 2049
Role Master 0x01 // 0x02 = Slave
Peer address type 0x01 // Public
Peer address 0x44, 0xE7, 0x68, 0x1E, 0xBE, 0x4D
Connection interval 0x0027
Connection latency 0x0000
Supervision timeout 0x07D0
Master clock accuracy 0x05

Voici la réponse du client ci-dessus ! La variable status est 0x00, indiquant que la connexion avec le serveur a réussi. Une fois établie, le client peut lire les caractéristiques des services offerts par le serveur.

Création d'un service et des caractéristiques

Création d'un service sur le serveur : la fonction aci_gatt_add_serv(...)

Table 219. ACI_GATT_ADD_SERVICE input parameters

Parameter Size Description Possible values
Service_UUID_Type 1 UUID type: 0x01 = 16 bits UUID while 0x02 = 128 bits UUID -
Service_UUID 2 or 16 16-bit UUID or 128-bit UUID -
Service_Type 1 Service type • 0x01: Primary service
• 0x02: Secondary service
Max_Attribute_Records 1 Maximum number of attribute records that can be added to this service -

Attention le nombre d'attribue et de trois par caractéristique et 1 pour le service !!


Aci_gatt_add_service(UUID_TYPE_128, uuid, PRIMARY_SERVICE, 10, timeServHandle);
	
Dans l'exemple ci-dessus il y a 3 caractéristiques et 1 service (time).

Modification d'une caractéristique.

Lecture d'une caractéristique

Sur le dispositif client génère l'évènement suivant pour lire une caractérisitique d'un service du serveur.

MOSI 0x0B 0x00 0x00 0x00 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF
0xFF
MISO 0x04 0xff 0x09 0x14 0x0c 0x01 0x08 0x0e 0x00 0x02 0x00
0x00

Field Size Value
The event code oft //ecode 2 bytes 0x0c14
Handle of the connection 2 bytes 0x0801
The handle of the attribute 2 bytes 0x000e
Length of the data to follow 1 byte 0x02
Contains offset read n bytes 0x0000

Sur le périphérique serveur GATT, l’API suivante doit être appelée pour la mise à jour la valeur caractéristique :
            
    Aci_gatt_update_char_value(chatServHandle, TXCharHandle, 0, len, (tHalUint8 *) data);
            
        
Command name Parameters Return
Serv_Handle
Char_Handle
Aci_Gatt_Update_Char_Value (0xFD06) Val_Offset Status
Char_Value_Length
Char_Value

Ecriture d'une caractéristique

Sur le dispositif client GATT, l’API suivante doit être invoquée pour écrire dans une caractéristique :
        
Aci_gatt_write_without_response(connection_handle, RX_HANDLE + 1, len, (tHalUint8 *) data);
        
    
Command name Parameters Return
Connection_handle
Aci_Gatt_Write_Without_Response (0xFD23) AttrHandle Status
ValLen
AttrVal

Ici, 'data' représente la valeur de l'attribut pointé par le handle RX_HANDLE contenu dans le handle de connexion 'connection_handle'. 'connection_handle' est le descripteur renvoyé lors de la création de la connexion, spécifié comme paramètre dans EVT_LE_CONN_COMPLETE.