Mise en œuvre d'une communication LoRa point-à-point avec STM8 et SX1278

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.

  1. 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     │
└─────────────────────┘                    └─────────────────────┘

  1. 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;
}

  1. 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)
  1. Débogage et Validation

4.1. Procédure de Test

Des étapes systématiques sont essentielles pour assurer le bon fonctionnement du système :

  1. Vérification Matérielle : S'assurer que le module SX1278 est correctement alimenté en 3.3V.
  2. 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.
  3. 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.
  4. 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.
  1. 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
}

Étiquettes: STM8 SX1278 LoRA Microcontrôleur Communication radio

Publié le 23 octobre à 05h36