Cet article décrit la mise en place d'un système de communication radio LoRa en mode point-à-point, en utilisant un microcontrôleur STM8 et un module émetteur-récepteur SX1278. Il aborde les aspects liés aux interconnexions matérielles, à l'architecture logicielle, aux pilotes de périphériques, au protocole de communication, ainsi qu'aux stratégies de débogage et d'optimisation des performances.
- Interconnexions Matérielles et Architecture Système
1.1. Tableau de Connexion Matérielle
Les broches du module LoRa SX1278 doivent être correctement connectées au microcontrôleur STM8S103F3 pour établir la communication SPI et les signaux de contrôle.
| Module SX1278 | Broche STM8S103F3 | Description |
|---|---|---|
| VCC | 3.3V | Alimentation électrique (le SX1278 est un composant 3.3V) |
| GND | GND | Masse commune |
| SCK | PC5 (SPI_SCK) | Horloge SPI |
| MISO | PC7 (SPI_MISO) | Données du module vers le microcontrôleur |
| MOSI | PC6 (SPI_MOSI) | Données du microcontrôleur vers le module |
| NSS | PC4 (SPI_NSS) | Signal de sélection d'esclave SPI |
| RST | PD0 | Broche de réinitialisation du module LoRa |
| DIO0 | PD1 | Broche d'interruption (pour fin de réception/transmission) |
1.2. Architecture du Système
L'architecture repose sur une liaison SPI entre le microcontrôleur STM8 et le module LoRa SX1278. Le débogage est assuré par une interface série USB-TTL, tandis que la communication sans fil s'établit entre deux dispositifs équipés de modules SX1278.
┌─────────────────────┐ Liaison SPI ┌─────────────────────┐
│ Microcontrôleur │◄─────────────────►│ Module LoRa │
│ STM8S103 │ │ SX1278 │
└───────────┬─────────┘ └───────────┬─────────┘
│ │
│ Débogage via │ Transmission
│ liaison série │ radio
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Outil de débogage │ │ Autre dispositif │
│ USB-TTL │ │ LoRa SX1278 │
└─────────────────────┘ └─────────────────────┘
- Implémentation Logicielle
2.1. Fichier d'en-tête du pilote LoRa (lora_module.h)
Ce fichier définit les adresses des registres du SX1278, les constantes pour les modes d'opération, les masques d'interruption, les fréquences standard, et la structure de configuration LoRa. Il déclare également les variables globales et les prototypes des fonctions du pilote.
#ifndef _LORA_MODULE_H_
#define _LORA_MODULE_H_
#include "stm8s.h"
// Définitions des registres du SX1278
#define LORA_REG_FIFO 0x00
#define LORA_REG_OP_MODE 0x01
#define LORA_REG_FRF_MSB 0x06
#define LORA_REG_FRF_MID 0x07
#define LORA_REG_FRF_LSB 0x08
#define LORA_REG_PA_CONFIG 0x09
#define LORA_REG_LNA 0x0C
#define LORA_REG_FIFO_ADDR_PTR 0x0D
#define LORA_REG_FIFO_TX_BASE_ADDR 0x0E
#define LORA_REG_FIFO_RX_BASE_ADDR 0x0F
#define LORA_REG_FIFO_RX_CURRENT_ADDR 0x10
#define LORA_REG_IRQ_FLAGS 0x12
#define LORA_REG_RX_NB_BYTES 0x13
#define LORA_REG_MODEM_CONFIG1 0x1D
#define LORA_REG_MODEM_CONFIG2 0x1E
#define LORA_REG_SYMB_TIMEOUT_LSB 0x1F
#define LORA_REG_PREAMBLE_MSB 0x20
#define LORA_REG_PREAMBLE_LSB 0x21
#define LORA_REG_PAYLOAD_LENGTH 0x22
#define LORA_REG_HOP_PERIOD 0x24
#define LORA_REG_IRQ_FLAGS_MASK 0x11
#define LORA_REG_DIO_MAPPING1 0x40
#define LORA_REG_VERSION 0x42
// Modes d'opération
#define LORA_OP_MODE_EXTENDED_RANGE 0x80
#define LORA_OP_MODE_SLEEP 0x00
#define LORA_OP_MODE_STANDBY 0x01
#define LORA_OP_MODE_TRANSMIT 0x03
#define LORA_OP_MODE_RX_CONTINUOUS 0x05
#define LORA_OP_MODE_RX_SINGLE 0x06
// Masques des drapeaux d'interruption
#define LORA_IRQ_TX_DONE_MASK 0x08
#define LORA_IRQ_RX_DONE_MASK 0x40
#define LORA_IRQ_PAYLOAD_CRC_ERROR_MASK 0x20
// Fréquences courantes (en Hz)
#define LORA_FREQ_433_MHZ 433000000
#define LORA_FREQ_868_MHZ 868000000
#define LORA_FREQ_915_MHZ 915000000
// Structure de configuration LoRa
typedef struct {
uint32_t freqHz; // Fréquence en Hz
uint8_t sfValue; // Facteur d'étalement (6-12)
uint8_t bwIndex; // Bande passante (0: 125kHz, 1: 250kHz, 2: 500kHz)
uint8_t crRatio; // Taux de codage (1-4, pour 4/5 à 4/8)
uint8_t txPower_dBm; // Puissance d'émission (0-15 pour 2-17dBm sur PA_BOOST)
uint8_t preambleLen; // Longueur du préambule
uint8_t implicitMode; // Mode d'en-tête implicite (0: explicite, 1: implicite)
uint8_t crcEnabled; // Vérification CRC (0: désactivé, 1: activé)
} LoRaConfiguration_t;
// Variables globales de gestion des événements LoRa
extern volatile uint8_t g_lora_rx_done;
extern volatile uint8_t g_lora_tx_done;
extern uint8_t g_lora_rx_buffer[256];
extern uint8_t g_lora_rx_data_len;
// Prototypes des fonctions du pilote LoRa
void LORA_Module_Initialize(void);
void LORA_Module_Configure(LoRaConfiguration_t *config);
void LORA_Module_SendFrame(uint8_t *data, uint8_t length);
void LORA_Module_StartReception(void);
void LORA_Module_ReadReceivedFrame(void);
void LORA_Module_SleepMode(void);
void LORA_Module_StandbyMode(void);
uint8_t LORA_Module_GetVersion(void);
void LORA_Module_SetFrequency(uint32_t frequency);
void LORA_Module_SetOutputPower(uint8_t power);
#endif /* _LORA_MODULE_H_ */
2.2. Pilote SPI (spi_driver.c)
Le pilote SPI gère l'initialisation du périphérique SPI du STM8 et les fonctions d'échange de données. Il inclut également la gestion du signal Chip Select (CS) logicielle.
#include "spi_driver.h"
// Macro pour l'écriture de bit (facultatif, pour plus de clarté)
#define SET_BIT(REG, BIT) ((REG) |= (1U << (BIT)))
#define CLEAR_BIT(REG, BIT) ((REG) &= ~(1U << (BIT)))
/**
* @brief Initialise le périphérique SPI du STM8.
* Configure les broches GPIO pour SPI et le module SPI en mode maître.
*/
void SPI_Peripheral_Init(void)
{
// Configuration des broches GPIO pour SPI :
// PC4: NSS (géré logiciellement)
// PC5: SCK
// PC6: MOSI
// PC7: MISO
// PC4 comme sortie Push-Pull pour NSS
GPIOC->DDR |= (1U << 4); // Mode sortie
GPIOC->CR1 |= (1U << 4); // Push-Pull
GPIOC->CR2 |= (1U << 4); // Vitesse rapide
// PC5 (SCK) comme sortie Push-Pull (Maître)
GPIOC->DDR |= (1U << 5);
GPIOC->CR1 |= (1U << 5);
GPIOC->CR2 |= (1U << 5);
// PC6 (MOSI) comme sortie Push-Pull (Maître)
GPIOC->DDR |= (1U << 6);
GPIOC->CR1 |= (1U << 6);
GPIOC->CR2 |= (1U << 6);
// PC7 (MISO) comme entrée Pull-up (Esclave)
GPIOC->DDR &= ~(1U << 7); // Mode entrée
GPIOC->CR1 |= (1U << 7); // Pull-up
// Réinitialisation des registres de contrôle SPI
SPI->CR1 = 0x00;
SPI->CR2 = 0x00;
// Configuration SPI en mode maître
// Baud Rate : f_master / 2 (CPOL=0, CPHA=0 par défaut ou à configurer si nécessaire)
SPI->CR1 |= (1U << SPI_CR1_MSTR_POS) | (1U << SPI_CR1_BR_0_POS); // Maître, Diviseur /2
// Gestion logicielle du NSS
SPI->CR1 |= (1U << SPI_CR1_SSM_POS);
// MSB en premier
CLEAR_BIT(SPI->CR1, SPI_CR1_LSBFIRST_POS);
// Configuration CR2 : mode bidirectionnel (full-duplex)
SPI->CR2 |= (1U << SPI_CR2_BDM_POS) | (1U << SPI_CR2_BDOE_POS); // Mode bidirectionnel, sortie activée
// Activer le périphérique SPI
SET_BIT(SPI->CR1, SPI_CR1_SPE_POS);
}
/**
* @brief Transfère un octet via SPI (envoie et reçoit simultanément).
* @param byte_to_send : L'octet à envoyer.
* @retval L'octet reçu du périphérique.
*/
uint8_t SPI_ExchangeByte(uint8_t byte_to_send)
{
// Attendre que le buffer de transmission soit vide (TXE flag)
while (!(SPI->SR & (1U << SPI_SR_TXE_POS)));
// Écrire l'octet à envoyer dans le registre de données
SPI->DR = byte_to_send;
// Attendre que la réception soit complète (RXNE flag)
while (!(SPI->SR & (1U << SPI_SR_RXNE_POS)));
// Lire et retourner l'octet reçu
return SPI->DR;
}
/**
* @brief Contrôle la broche Chip Select (NSS) du SPI.
* @param select_state : 0 pour sélectionner (actif bas), 1 pour désélectionner (actif haut).
*/
void SPI_ChipSelect(uint8_t select_state)
{
if (select_state == 0) // Sélectionner
GPIOC->ODR &= ~(1U << 4); // Mettre NSS à l'état bas
else // Désélectionner
GPIOC->ODR |= (1U << 4); // Mettre NSS à l'état haut
}
2.3. Pilote SX1278 (lora_module.c)
Ce fichier contient l'implémentation complète du pilote pour le module SX1278. Il gère l'initialisation du module, la configuration des paramètres LoRa, l'envoi et la réception de paquets, ainsi que les transitions entre les différents modes de fonctionnement.
#include "lora_module.h"
#include "spi_driver.h"
#include "system_delays.h" // Fonctions de délai (ex: Delay_ms)
#include <stdio.h> // Pour printf
// Variables globales définies dans l'en-tête
volatile uint8_t g_lora_rx_done = 0;
volatile uint8_t g_lora_tx_done = 0;
uint8_t g_lora_rx_buffer[256];
uint8_t g_lora_rx_data_len = 0;
// Fonctions privées du module LoRa
static void lora_write_register(uint8_t address, uint8_t data_val);
static uint8_t lora_read_register(uint8_t address);
static void lora_write_fifo_burst(uint8_t *data_buf, uint8_t length);
static void lora_read_fifo_burst(uint8_t *data_buf, uint8_t length);
static void lora_set_operation_mode(uint8_t mode);
/**
* @brief Écrit une valeur dans un registre du module LoRa via SPI.
* @param address : Adresse du registre à écrire.
* @param data_val : Valeur à écrire.
*/
static void lora_write_register(uint8_t address, uint8_t data_val)
{
SPI_ChipSelect(0); // Activer NSS
SPI_ExchangeByte(address | 0x80); // Bit 7 à '1' pour l'opération d'écriture
SPI_ExchangeByte(data_val);
SPI_ChipSelect(1); // Désactiver NSS
}
/**
* @brief Lit une valeur depuis un registre du module LoRa via SPI.
* @param address : Adresse du registre à lire.
* @retval Valeur lue du registre.
*/
static uint8_t lora_read_register(uint8_t address)
{
uint8_t received_data;
SPI_ChipSelect(0); // Activer NSS
SPI_ExchangeByte(address & 0x7F); // Bit 7 à '0' pour l'opération de lecture
received_data = SPI_ExchangeByte(0x00); // Envoyer un octet fictif pour recevoir la donnée
SPI_ChipSelect(1); // Désactiver NSS
return received_data;
}
/**
* @brief Écrit un bloc de données dans le FIFO du module LoRa.
* @param data_buf : Pointeur vers le buffer de données à écrire.
* @param length : Longueur des données à écrire.
*/
static void lora_write_fifo_burst(uint8_t *data_buf, uint8_t length)
{
SPI_ChipSelect(0); // Activer NSS
SPI_ExchangeByte(LORA_REG_FIFO | 0x80); // Accès au registre FIFO en écriture
for (uint8_t i = 0; i < length; i++)
{
SPI_ExchangeByte(data_buf[i]);
}
SPI_ChipSelect(1); // Désactiver NSS
}
/**
* @brief Lit un bloc de données depuis le FIFO du module LoRa.
* @param data_buf : Pointeur vers le buffer où stocker les données lues.
* @param length : Longueur des données à lire.
*/
static void lora_read_fifo_burst(uint8_t *data_buf, uint8_t length)
{
SPI_ChipSelect(0); // Activer NSS
SPI_ExchangeByte(LORA_REG_FIFO & 0x7F); // Accès au registre FIFO en lecture
for (uint8_t i = 0; i < length; i++)
{
data_buf[i] = SPI_ExchangeByte(0x00); // Envoyer octet fictif et lire
}
SPI_ChipSelect(1); // Désactiver NSS
}
/**
* @brief Définit le mode d'opération du module LoRa.
* @param mode : Mode souhaité (ex: LORA_OP_MODE_SLEEP, LORA_OP_MODE_STANDBY).
*/
static void lora_set_operation_mode(uint8_t mode)
{
lora_write_register(LORA_REG_OP_MODE, mode | LORA_OP_MODE_EXTENDED_RANGE);
Wait_ms(10); // Délai pour la stabilisation du mode
}
/**
* @brief Initialise le module LoRa SX1278.
* Effectue une réinitialisation matérielle, configure les GPIO et les registres LoRa initiaux.
*/
void LORA_Module_Initialize(void)
{
// Réinitialisation matérielle du SX1278 via PD0
GPIOD->DDR |= (1U << 0); // PD0 comme sortie
GPIOD->CR1 |= (1U << 0); // Push-Pull
GPIOD->ODR &= ~(1U << 0); // Broche RST à l'état bas
Wait_ms(10);
GPIOD->ODR |= (1U << 0); // Relâcher RST (état haut)
Wait_ms(10);
// Configuration de DIO0 (PD1) comme entrée pour les interruptions
GPIOD->DDR &= ~(1U << 1); // PD1 comme entrée
GPIOD->CR1 |= (1U << 1); // Entrée Pull-up
// Vérification de la version du module
uint8_t module_version = LORA_Module_GetVersion();
printf("Version du module SX1278: 0x%02X\r\n", module_version);
// Entrer en mode LoRa (passage par Sleep puis Standby)
lora_set_operation_mode(LORA_OP_MODE_SLEEP);
lora_write_register(LORA_REG_OP_MODE, LORA_OP_MODE_EXTENDED_RANGE | LORA_OP_MODE_STANDBY);
// Réinitialiser les adresses de base FIFO
lora_write_register(LORA_REG_FIFO_TX_BASE_ADDR, 0x00);
lora_write_register(LORA_REG_FIFO_RX_BASE_ADDR, 0x00);
// Configuration LoRa par défaut
LoRaConfiguration_t default_params = {
.freqHz = LORA_FREQ_433_MHZ,
.sfValue = 7,
.bwIndex = 0, // 125kHz
.crRatio = 1, // 4/5
.txPower_dBm = 15, // 17dBm avec PA_BOOST
.preambleLen = 8,
.implicitMode = 0,
.crcEnabled = 1
};
LORA_Module_Configure(&default_params);
// Mapper DIO0 aux interruptions RxDone/TxDone
lora_write_register(LORA_REG_DIO_MAPPING1, 0x00);
// Effacer tous les drapeaux d'interruption
lora_write_register(LORA_REG_IRQ_FLAGS, 0xFF);
printf("Initialisation du SX1278 terminee !\r\n");
}
/**
* @brief Applique une configuration LoRa au module.
* @param config : Pointeur vers la structure de configuration LoRaConfiguration_t.
*/
void LORA_Module_Configure(LoRaConfiguration_t *config)
{
lora_set_operation_mode(LORA_OP_MODE_STANDBY); // Passer en Standby avant configuration
LORA_Module_SetFrequency(config->freqHz);
LORA_Module_SetOutputPower(config->txPower_dBm);
// Configuration Modem 1: Bande passante, Taux de codage, En-tête implicite
uint8_t modem_config_reg1 = (config->bwIndex << 4) | (config->crRatio << 1) | (config->implicitMode & 0x01);
// Configuration Modem 2: Facteur d'étalement, CRC activé, Timeout Symbole (MSB)
uint8_t modem_config_reg2 = (config->sfValue << 4) | (config->crcEnabled << 2) | 0x03; // Symbol timeout MSB est 0x03 par defaut
lora_write_register(LORA_REG_MODEM_CONFIG1, modem_config_reg1);
lora_write_register(LORA_REG_MODEM_CONFIG2, modem_config_reg2);
// Longueur du préambule
lora_write_register(LORA_REG_PREAMBLE_MSB, config->preambleLen >> 8);
lora_write_register(LORA_REG_PREAMBLE_LSB, config->preambleLen & 0xFF);
// Timeout Symbole LSB (exemple: 100 symboles)
lora_write_register(LORA_REG_SYMB_TIMEOUT_LSB, 0x64);
lora_set_operation_mode(LORA_OP_MODE_STANDBY); // Revenir en Standby
}
/**
* @brief Définit la fréquence de transmission/réception du module LoRa.
* @param frequency : Fréquence souhaitée en Hz.
*/
void LORA_Module_SetFrequency(uint32_t frequency)
{
// Calcul de la valeur du registre Frf (Frf = Freq * 2^19 / 32MHz)
uint64_t frf_value = ((uint64_t)frequency << 19) / 32000000UL;
lora_write_register(LORA_REG_FRF_MSB, (uint8_t)(frf_value >> 16));
lora_write_register(LORA_REG_FRF_MID, (uint8_t)(frf_value >> 8));
lora_write_register(LORA_REG_FRF_LSB, (uint8_t)(frf_value));
}
/**
* @brief Définit la puissance d'émission du module LoRa.
* @param power : Niveau de puissance (0-15 pour PA_BOOST).
*/
void LORA_Module_SetOutputPower(uint8_t power)
{
if (power > 15) power = 15;
lora_write_register(LORA_REG_PA_CONFIG, 0x80 | power); // Activer PA_BOOST
}
/**
* @brief Envoie un paquet de données via le module LoRa.
* @param data : Pointeur vers le buffer de données à envoyer.
* @param length : Longueur du paquet.
*/
void LORA_Module_SendFrame(uint8_t *data, uint8_t length)
{
lora_set_operation_mode(LORA_OP_MODE_STANDBY);
// Définir le pointeur FIFO au début de la zone de transmission
lora_write_register(LORA_REG_FIFO_ADDR_PTR, 0x00);
// Écrire les données dans le FIFO
lora_write_fifo_burst(data, length);
// Définir la longueur de la charge utile (payload)
lora_write_register(LORA_REG_PAYLOAD_LENGTH, length);
// Effacer les drapeaux d'interruption avant la transmission
lora_write_register(LORA_REG_IRQ_FLAGS, 0xFF);
// Passer en mode transmission
lora_set_operation_mode(LORA_OP_MODE_TRANSMIT);
g_lora_tx_done = 0; // Réinitialiser le drapeau de transmission
}
/**
* @brief Met le module LoRa en mode réception continue.
*/
void LORA_Module_StartReception(void)
{
lora_set_operation_mode(LORA_OP_MODE_RX_CONTINUOUS);
// Effacer les drapeaux d'interruption avant la réception
lora_write_register(LORA_REG_IRQ_FLAGS, 0xFF);
g_lora_rx_done = 0; // Réinitialiser le drapeau de réception
}
/**
* @brief Lit les données reçues depuis le FIFO du module LoRa.
*/
void LORA_Module_ReadReceivedFrame(void)
{
// Obtenir l'adresse de début du paquet reçu dans le FIFO
uint8_t rx_fifo_start_addr = lora_read_register(LORA_REG_FIFO_RX_CURRENT_ADDR);
lora_write_register(LORA_REG_FIFO_ADDR_PTR, rx_fifo_start_addr);
// Obtenir la longueur du paquet reçu
g_lora_rx_data_len = lora_read_register(LORA_REG_RX_NB_BYTES);
// Lire les données si la longueur est valide
if (g_lora_rx_data_len > 0 && g_lora_rx_data_len <= 256)
{
lora_read_fifo_burst(g_lora_rx_buffer, g_lora_rx_data_len);
}
}
/**
* @brief Met le module LoRa en mode sommeil (faible consommation).
*/
void LORA_Module_SleepMode(void)
{
lora_set_operation_mode(LORA_OP_MODE_SLEEP);
}
/**
* @brief Met le module LoRa en mode veille (faible consommation, mais plus rapide pour TX/RX).
*/
void LORA_Module_StandbyMode(void)
{
lora_set_operation_mode(LORA_OP_MODE_STANDBY);
}
/**
* @brief Lit le numéro de version du module SX1278.
* @retval Numéro de version du silicium.
*/
uint8_t LORA_Module_GetVersion(void)
{
return lora_read_register(LORA_REG_VERSION);
}
2.4. Programme Principal (main.c)
Le programme principal orchestre l'initialisation du système, la gestion des communications LoRa, et implémente une logique simple d'envoi et de réception de messages avec accusé de réception.
#include "stm8s.h"
#include "lora_module.h"
#include "spi_driver.h"
#include "system_delays.h"
#include "uart_comm.h"
#include <stdio.h>
// Identifiants des dispositifs pour la communication point-à-point
#define NODE_IDENTIFIER 0x0A // Adresse de ce dispositif
#define TARGET_IDENTIFIER 0x0B // Adresse du dispositif cible
// Structure de données pour un paquet applicatif LoRa
typedef struct {
uint8_t destination_id; // Identifiant du destinataire
uint8_t source_id; // Identifiant de l'expéditeur
uint8_t sequence_counter; // Numéro de séquence du paquet
uint8_t command_type; // Code de commande
uint8_t data_payload_length; // Longueur des données utiles
uint8_t data_payload_buffer[32]; // Buffer pour les données utiles
} LoRaDataPacket_t;
volatile uint8_t g_periodic_send_timer = 0; // Compteur pour l'envoi périodique
uint8_t g_packet_sequence = 0; // Compteur de séquence des paquets envoyés
/**
* @brief Construit un paquet de données LoRa.
* @param packet_ptr : Pointeur vers la structure LoRaDataPacket_t à remplir.
* @param cmd_code : Code de commande à inclure.
* @param data_buf : Pointeur vers les données utiles.
* @param data_len : Longueur des données utiles.
*/
void FormulateLoRaPacket(LoRaDataPacket_t *packet_ptr, uint8_t cmd_code, uint8_t *data_buf, uint8_t data_len)
{
packet_ptr->destination_id = TARGET_IDENTIFIER;
packet_ptr->source_id = NODE_IDENTIFIER;
packet_ptr->sequence_counter = g_packet_sequence++;
packet_ptr->command_type = cmd_code;
packet_ptr->data_payload_length = data_len;
for (uint8_t i = 0; i < data_len; i++)
{
packet_ptr->data_payload_buffer[i] = data_buf[i];
}
}
/**
* @brief Déconstruit un paquet LoRa reçu et vérifie son destinataire.
* @param packet_ptr : Pointeur vers la structure LoRaDataPacket_t à remplir.
* @param rx_data_buf : Pointeur vers le buffer de données brutes reçues.
* @param rx_total_len : Longueur totale des données reçues.
* @retval 1 si le paquet est destiné à ce dispositif, 0 sinon.
*/
uint8_t DeconstructLoRaPacket(LoRaDataPacket_t *packet_ptr, uint8_t *rx_data_buf, uint8_t rx_total_len)
{
if (rx_total_len < 5) return 0; // Un paquet doit avoir au moins 5 octets d'en-tête
packet_ptr->destination_id = rx_data_buf[0];
packet_ptr->source_id = rx_data_buf[1];
packet_ptr->sequence_counter = rx_data_buf[2];
packet_ptr->command_type = rx_data_buf[3];
packet_ptr->data_payload_length = rx_data_buf[4];
if (packet_ptr->data_payload_length > 32) packet_ptr->data_payload_length = 32; // Limiter la taille du payload
for (uint8_t i = 0; i < packet_ptr->data_payload_length; i++)
{
packet_ptr->data_payload_buffer[i] = rx_data_buf[5 + i];
}
// Vérifier si le paquet est adressé à notre dispositif
return (packet_ptr->destination_id == NODE_IDENTIFIER);
}
/**
* @brief Transmet un message applicatif LoRa.
* @param cmd_code : Code de commande.
* @param data_buf : Pointeur vers les données utiles.
* @param data_len : Longueur des données utiles.
*/
void TransmitAppMessage(uint8_t cmd_code, uint8_t *data_buf, uint8_t data_len)
{
LoRaDataPacket_t app_packet;
FormulateLoRaPacket(&app_packet, cmd_code, data_buf, data_len);
// Préparer le buffer pour l'envoi physique
uint8_t physical_tx_buffer[64];
uint8_t current_tx_len = 0;
physical_tx_buffer[current_tx_len++] = app_packet.destination_id;
physical_tx_buffer[current_tx_len++] = app_packet.source_id;
physical_tx_buffer[current_tx_len++] = app_packet.sequence_counter;
physical_tx_buffer[current_tx_len++] = app_packet.command_type;
physical_tx_buffer[current_tx_len++] = app_packet.data_payload_length;
for (uint8_t i = 0; i < app_packet.data_payload_length; i++)
{
physical_tx_buffer[current_tx_len++] = app_packet.data_payload_buffer[i];
}
// Envoi du paquet via le module LoRa
printf("Envoi de paquet vers 0x%02X, Seq: %d\r\n",
app_packet.destination_id, app_packet.sequence_counter);
LORA_Module_SendFrame(physical_tx_buffer, current_tx_len);
}
void main(void)
{
// Initialisation des périphériques système
SystemInit(); // Initialisation bas niveau du STM8
SysTick_Timer_Configure(); // Initialisation du délai basé sur Timer4
USART_Initialize(9600); // Initialisation de l'UART à 9600 bps
SPI_Peripheral_Init(); // Initialisation du SPI
printf("Systeme point-a-point LoRa STM8 + SX1278\r\n");
// Initialisation du module LoRa
LORA_Module_Initialize();
// Démarrer la réception continue
LORA_Module_StartReception();
uint8_t sensor_data_counter = 0;
LoRaDataPacket_t received_app_packet;
enableInterrupts(); // Activer les interruptions globales
while (1)
{
// Vérifier si un paquet a été reçu
if (g_lora_rx_done)
{
g_lora_rx_done = 0; // Effacer le drapeau
LORA_Module_ReadReceivedFrame(); // Lire les données du FIFO
if (DeconstructLoRaPacket(&received_app_packet, g_lora_rx_buffer, g_lora_rx_data_len))
{
printf("Paquet recu de 0x%02X, Cmd: 0x%02X, Seq: %d, DataLen: %d\r\n",
received_app_packet.source_id, received_app_packet.command_type,
received_app_packet.sequence_counter, received_app_packet.data_payload_length);
// Envoyer un accusé de réception (ACK)
uint8_t ack_payload[1] = {0xAC}; // ACK code
TransmitAppMessage(0x80 | received_app_packet.command_type, ack_payload, 1);
}
// Relancer le mode réception après traitement
LORA_Module_StartReception();
}
// Envoi périodique de données (toutes les 5 secondes)
if (g_periodic_send_timer == 0) // Le compteur est décrémenté par l'ISR du Timer4
{
g_periodic_send_timer = 50; // Réinitialiser le compteur à 50 (50 * 100ms = 5s)
uint8_t current_sensor_values[4];
current_sensor_values[0] = sensor_data_counter++; // Compteur pour les tests
current_sensor_values[1] = 23; // Température factice
current_sensor_values[2] = 55; // Humidité factice
current_sensor_values[3] = 1010; // Pression factice
printf("Envoi de donnees capteur...\r\n");
TransmitAppMessage(0x01, current_sensor_values, 4); // Commande 0x01 pour données capteur
}
Wait_ms(100); // Délai de 100 ms
g_periodic_send_timer--; // Décrémenter manuellement ici si le timer n'est pas ISR basé
}
}
// Handler d'interruption pour le port D (où DIO0 est connecté)
INTERRUPT_HANDLER(EXTI_PORTD_IRQHandler, 6)
{
// Vérifier si l'interruption provient de la broche DIO0 (PD1)
if (GPIOD->IDR & (1U << 1))
{
uint8_t lora_interrupt_flags = lora_read_register(LORA_REG_IRQ_FLAGS);
if (lora_interrupt_flags & LORA_IRQ_RX_DONE_MASK)
{
g_lora_rx_done = 1; // Marquer la réception comme terminée
printf("RX Terminee !\r\n");
}
if (lora_interrupt_flags & LORA_IRQ_TX_DONE_MASK)
{
g_lora_tx_done = 1; // Marquer la transmission comme terminée
printf("TX Terminee !\r\n");
LORA_Module_StartReception(); // Repasser en mode réception après TX
}
// Effacer tous les drapeaux d'interruption du module LoRa
lora_write_register(LORA_REG_IRQ_FLAGS, 0xFF);
}
// Note: GPIOD->IDR peut être utilisé pour vérifier quelle broche a déclenché l'IT.
// Pour des interruptions externes, il faut aussi effacer le drapeau du controleur d'interruption.
}
2.5. Fonctions de Délai (system_delays.c)
Ce module fournit des fonctions de délai précises basées sur le Timer 4 du STM8, configuré pour générer une interruption toutes les millisecondes.
#include "system_delays.h"
static volatile uint32_t s_delay_countdown_ms = 0;
/**
* @brief Configure le Timer 4 pour générer une interruption toutes les 1ms.
* Utilisé pour les fonctions de délai.
*/
void SysTick_Timer_Configure(void)
{
// Configuration du Timer4 pour une interruption de 1ms
// Fréquence du CPU STM8 est généralement 16MHz.
// Pour 1ms, on a besoin de 16000 cycles.
// Prescaler = 128 -> (16MHz / 128) = 125kHz
// Auto-Reload Register (ARR) = 125 -> (125kHz / 125) = 1kHz (1ms)
TIM4->PSCR = 0x07; // Prescaler = 128 (2^7)
TIM4->ARR = 125 - 1; // Valeur ARR = 124 (0x7C) pour 125 cycles
TIM4->IER |= TIM4_IER_UIE; // Activer l'interruption de mise à jour
TIM4->CR1 |= TIM4_CR1_CEN; // Démarrer le compteur
// Initialiser le compteur de délai global si besoin
s_delay_countdown_ms = 0;
}
/**
* @brief Attend pendant un nombre spécifié de millisecondes.
* @param milliseconds : Nombre de millisecondes à attendre.
*/
void Wait_ms(uint32_t milliseconds)
{
s_delay_countdown_ms = milliseconds;
while (s_delay_countdown_ms != 0); // Attendre que le compteur atteigne zéro
}
/**
* @brief Fonction de décrémentation du compteur de délai, appelée par l'ISR du Timer.
*/
void Decrement_Delay_Counter(void)
{
if (s_delay_countdown_ms != 0)
{
s_delay_countdown_ms--;
}
}
// Handler d'interruption pour le Timer 4
INTERRUPT_HANDLER(TIM4_UPD_OVF_TRG_IRQHandler, 23) // Numéro d'IT pour TIM4 Update/Overflow/Trigger
{
Decrement_Delay_Counter(); // Décrémenter le compteur de délai
TIM4->SR1 &= ~TIM4_SR1_UIF; // Effacer le drapeau d'interruption de mise à jour
}
2.6. Pilote UART (uart_comm.c)
Le pilote UART fournit les fonctions nécessaires pour initialiser le périphérique UART1 du STM8 et pour transmettre des caractères ou des chaînes de caractères, notamment pour les messages de débogage via printf.
#include "uart_comm.h"
#include "stm8s.h"
/**
* @brief Initialise l'UART1 du STM8 à la vitesse de transmission spécifiée.
* @param baud_rate : Vitesse en bauds (ex: 9600, 115200).
*/
void USART_Initialize(uint32_t baud_rate)
{
// Configuration des broches GPIO pour UART1:
// PD5: TX (Transmission)
// PD6: RX (Réception)
// PD5 comme sortie Push-Pull, Vitesse rapide
GPIOD->DDR |= (1U << 5);
GPIOD->CR1 |= (1U << 5);
GPIOD->CR2 |= (1U << 5);
// PD6 comme entrée flottante (ou Pull-up si pas de pull-up externe)
GPIOD->DDR &= ~(1U << 6); // Mode entrée
GPIOD->CR1 &= ~(1U << 6); // Entrée flottante
// Désactiver l'UART pendant la configuration
UART1->CR1 = 0x00;
UART1->CR2 = 0x00;
// Calcul des registres de baud rate pour 16MHz F_CPU
// Baud Rate = F_CPU / (UART_DIV * 8)
// Pour 9600 bps: UART_DIV = 16000000 / (9600 * 8) = 208.33
// BRR1 = 208 / 16 = 13 (0x0D)
// BRR2 = 208 % 16 = 0 (avec bits 3:0), 208 & 0xF0 | (208 & 0x0F)
// En utilisant la table de ST pour 16MHz, 9600bps -> BRR2 = 0x01, BRR1 = 0x68
// BRR1 = 0x68 pour 9600 bps (16MHz clock)
// BRR2 = 0x01
UART1->BRR2 = 0x01; // Bits 7:4 de UART_DIV_M, Bits 3:0 pour UART_DIV_F
UART1->BRR1 = 0x68; // Bits 7:0 de UART_DIV_M
// Configuration UART: 8 bits de données, pas de parité
UART1->CR1 &= ~UART1_CR1_PCEN; // Pas de parité
UART1->CR1 &= ~UART1_CR1_M; // 8 bits de données
// Activer l'émetteur (TE) et le récepteur (RE)
UART1->CR2 |= (UART1_CR2_TEN | UART1_CR2_REN);
}
/**
* @brief Envoie un seul caractère via UART. Bloquant.
* @param character : Le caractère à envoyer.
*/
void USART_SendCharacter(char character)
{
// Attendre que le registre de données de transmission soit vide
while (!(UART1->SR & UART1_SR_TXE));
// Écrire le caractère dans le registre de données
UART1->DR = character;
}
/**
* @brief Envoie une chaîne de caractères via UART. Bloquant.
* @param string_ptr : Pointeur vers la chaîne de caractères à envoyer.
*/
void USART_SendString_Blocking(char *string_ptr)
{
while (*string_ptr)
{
USART_SendCharacter(*string_ptr++);
}
}
// Redirection de putchar pour permettre l'utilisation de printf
int putchar(int ch)
{
USART_SendCharacter((char)ch);
return ch;
}
- Conception du Protocole de Communication
Afin de structurer les échanges d'informations au-delà de la simple transmission d'octets, un protocole de communication est défini pour les paquets applicatifs.
3.1. Format des Paquets de Données
Chaque paquet applicatif LoRa suit une structure prédéfinie pour faciliter l'identification et le routage des messages entre les dispositifs.
| Identifiant Destinataire (1 Octet) | Identifiant Expéditeur (1 Octet) | Numéro de Séquence (1 Octet) | Code de Commande (1 Octet) | Longueur des Données Utiles (1 Octet) | Données Utiles (N Octets) |
- Identifiant Destinataire (1B) : L'adresse du dispositif qui doit recevoir le paquet.
- Identifiant Expéditeur (1B) : L'adresse du dispositif ayant envoyé le paquet.
- Numéro de Séquence (1B) : Un compteur incrémental pour chaque paquet émis par un expéditeur, utile pour détecter les paquets perdus ou dupliqués.
- Code de Commande (1B) : Définit le type de message (ex: données de capteur, commande de contrôle).
- Longueur des Données Utiles (1B) : Le nombre d'octets dans la section des données utiles.
- Données Utiles (N octets) : La charge utile réelle du message.
3.2. Définition des Codes de Commande
Pour standardiser les interactions, les codes de commande suivants sont proposés :
| Code de Commande | Description |
|---|---|
| 0x01 | Rapport de données de capteurs |
| 0x02 | Commande de contrôle à distance |
| 0x03 | Commande de configuration du dispositif |
| 0x80 | Indicateur d'accusé de réception (utilisé par OR avec le code de commande original) |
- Débogage et Validation
4.1. Procédure de Test
Des étapes systématiques sont essentielles pour assurer le bon fonctionnement du système :
- Vérification Matérielle : S'assurer que le module SX1278 est correctement alimenté en 3.3V.
- Test de Communication SPI : Lire le registre de version du SX1278 (adresse 0x42), la valeur attendue est 0x12. Si la lecture échoue, c'est que la liaison SPI n'est pas fonctionnelle.
- Test d'Émission Radio : Utiliser un analyseur de spectre pour vérifier la présence d'un signal radio à la fréquence configurée (ex: 433MHz) lors de l'envoi d'un paquet.
- Test de Communication Bidirectionnelle : Configurer deux dispositifs pour qu'ils s'envoient mutuellement des données, vérifiant ainsi la capacité d'émission et de réception.
4.2. Problèmes Courants et Solutions
| Symptôme | Cause probable | Solution |
|---|---|---|
| Impossible de lire le numéro de version (0x12) | Échec de la communication SPI | Vérifier les connexions des broches SPI (SCK, MISO, MOSI, NSS) et la séquence de pilotage (NSS). |
| Aucun signal radio émis | Configuration de fréquence incorrecte ou puissance d'émission faible | S'assurer que les registres de fréquence sont correctement configurés et que la puissance est suffisante (PA_BOOST activé). |
| Aucune donnée reçue | Incohérence des paramètres LoRa entre les dispositifs | Vérifier que le facteur d'étalement (SF), la bande passante (BW) et le taux de codage (CR) sont identiques sur les deux dispositifs. |
| Portée de communication limitée | Antenne non adaptée ou environnement défavorable | Utiliser une antenne 433MHz ou 868MHz (selon la fréquence utilisée) avec une impédance de 50Ω. Éviter les obstacles métalliques. |
- Optimisation des Performances
5.1. Optimisation des Paramètres LoRa
Le choix des paramètres LoRa (facteur d'étalement, bande passante, taux de codage) a un impact direct sur la portée, le débit et la robustesse de la communication.
| Scénario d'Application | Facteur d'Étalement (SF) | Bande Passante (BW) | Taux de Codage (CR) | Portée Estimée |
|---|---|---|---|---|
| Courte portée en intérieur | SF7 | 125kHz | 4/5 | Jusqu'à 500m |
| Portée moyenne en extérieur | SF9 | 125kHz | 4/5 | Jusqu'à 2km |
| Longue distance (faible débit) | SF12 | 125kHz | 4/8 | Plus de 5km |
5.2. Optimisation de la Consommation d'Énergie
Pour les applications alimentées par batterie, la gestion de la consommation est cruciale. Le module LoRa et le microcontrôleur peuvent être mis en mode faible consommation.
// Entrer en mode faible consommation pour le système
void EnterSystemLowPowerMode(void)
{
LORA_Module_SleepMode(); // Le module LoRa entre en mode sommeil
// Le microcontrôleur STM8 entre en mode Stop (consommation minimale)
// Les périphériques sont arrêtés, le RAM est retenu.
// La sortie de ce mode se fait par une interruption externe.
PWR->CSR2 |= PWR_CSR2_FWU; // Optionnel: Wake-up Fast (selon la série STM8)
__asm__("halt"); // Instruction HALT pour entrer en mode Stop
}