Logo Central

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.

figure ogf value

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.

figure ocf value

Exemple de la commande
HCI_READ_LOCAL_VERSION_INFORMATION

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

commande: hci read local version

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.

réponse: hci read local version

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 :

paramètres: hci read local version

EXEMPLE DE LA COMMANDE ACI_GATT_ADD_SERVICE

Cette commande comporte plusieurs paramètres :

paramètres: aci gatt add service

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.

réponse: aci gatt add service

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".
Ci-dessous la liste des types d’événements.

liste des events

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.

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


Le 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 */).

        
HCI_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".

fonction aci_gap_create_connexion()

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).

evt_le_conn_complete  binaire evt_le_conn_complete
LE Connection Complete 0x01
Status: Success 0x00
Handle: 0x0801 = 2049
Role: Master 0x01// « 0x02 = Slave »
Peer address type:0x00// 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(...)

fonction aci_gatt_add_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.

evt. read
The event code oft: 2 bytes : 0x0c14 //ecode
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);
            
        

fonction aci_gap_update_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);
        
    
fonction aci_gatt_write_without_response()

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.