0% ont trouvé ce document utile (0 vote)
1 vues17 pages

Communication STM32 RaspberryPi UART

Transféré par

rouachedmaram
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
1 vues17 pages

Communication STM32 RaspberryPi UART

Transféré par

rouachedmaram
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd

Communication UART STM32 ↔ Raspberry Pi

Communication STM32F4 ↔ Raspberry


Pi 4
Guide de mise en œuvre de la liaison UART
Transport des données des services BCM • ACM • SEAT vers le tableau de bord

Projet de fin d’études — Calculateur embarqué multi-fonctions


Conception, protocole, code source complet et câblage

Page 1
Communication UART STM32 ↔ Raspberry Pi

Sommaire
1. Vue d’ensemble..................................................................................................................................... 3
1.1 Les briques logicielles..............................................................................................................................3
2. Principes de conception (à respecter absolument)...............................................................................3
2.1 Ne jamais mettre struct timespec dans la trame.....................................................................................3
2.2 Les autres règles appliquées....................................................................................................................3
3. Câblage matériel................................................................................................................................... 4
4. Configuration de l’UART........................................................................................................................ 4
4.1 Côté STM32 (STM32CubeMX)..................................................................................................................4
4.2 Côté Raspberry Pi.....................................................................................................................................4
5. Format de trame................................................................................................................................... 4
6. La charge utile : structures de données................................................................................................ 5
6.1 Instantané périodique — telemetry_msg_t.............................................................................................5
6.2 Événement de sécurité — acm_event_msg_t.........................................................................................5
6.3 Structure côté hôte..................................................................................................................................5
7. La couche de transport UART................................................................................................................ 8
8. Implémentations par plateforme........................................................................................................ 12
8.1 Côté STM32 (HAL)..................................................................................................................................12
8.2 Côté Raspberry Pi (termios)...................................................................................................................13
9. Exemples d’utilisation......................................................................................................................... 16
9.1 Côté STM32 — émission périodique......................................................................................................16
9.2 Côté Raspberry Pi — réception..............................................................................................................16
10. Test et validation............................................................................................................................... 16
11. Récapitulatif — étapes à suivre......................................................................................................... 16

Page 2
Communication UART STM32 ↔ Raspberry Pi

1. Vue d’ensemble
Objectif. Le STM32F4 acquiert et consolide les fonctions de carrosserie (BCM), de sûreté passive (ACM) et
de sièges (SEAT), puis transmet leur état à la Raspberry Pi 4 par une liaison série UART. La Raspberry Pi
affiche ces données sur le tableau de bord (IHM). Ce document décrit tout ce qu’il faut faire pour réaliser
cette communication : la conception du protocole, le format des trames, le câblage, la configuration, et le
code source complet des deux côtés.
La liaison est asymétrique mais bidirectionnelle : le STM32 émet principalement la télémétrie
(périodiquement) et les événements de sécurité (immédiatement) ; la Raspberry Pi reçoit, et peut
renvoyer des commandes (par exemple changer le profil de siège).

1.1 Les briques logicielles


Fichier Côté Rôle

Définit les charges utiles (BCM/ACM/SEAT) et les drapeaux


telemetry_protocol.h Partagé
de bits.

API publique (init/send/receive) + cœur portable (CRC,


protocol.h Partagé
automate).

CRC-16, construction de trame, automate de réception


protocol_core.c Partagé
octet par octet.

protocol_stm32.c STM32 Transport via HAL (USART2), émission et réception.

protocol_rpi.c Raspberry Pi Transport via termios (Linux), émission et réception.

2. Principes de conception (à respecter absolument)


2.1 Ne jamais mettre struct timespec dans la trame
C’est l’erreur la plus importante à éviter. Il ne faut pas placer un struct timespec last_update
dans la structure envoyée sur le fil, pour deux raisons :
• Sa taille varie selon la plateforme (8 octets en 32 bits, 16 octets en 64 bits sur la Raspberry Pi) : le
décodage côté RPi serait décalé.
• C’est un horodatage de réception qui appartient à la Raspberry Pi, pas une donnée que le STM32
produit.
La bonne pratique : le STM32 envoie son propre temps ( uint32_t t_stm_ms via HAL_GetTick()), et
la Raspberry Pi ajoute son struct timespec rx_time dans une structure côté hôte après décodage.

2.2 Les autres règles appliquées


• Séparer la trame de la charge utile. La structure de données = le payload. La couche
protocol_send(opcode, payload, len) ajoute le SOF, l’opcode, la longueur et le CRC-16.
Donc msg_id devient l’opcode de la trame — inutile de le répéter dans le payload.
• #pragma pack(1) + types à largeur fixe + _Static_assert pour garantir un layout identique
côté STM32 (Cortex-M4) et côté RPi (ARM Linux). Les deux sont petit-boutistes : aucune
conversion d’endianness n’est nécessaire.

Page 3
Communication UART STM32 ↔ Raspberry Pi

• Les états tout-ou-rien (feux, clignotants, occupation, ceintures, déploiement airbag…) vont dans
des champs de bits plutôt qu’un octet par état : c’est compact et extensible.
• Pas de float sur le fil : les grandeurs analogiques (accélération du MPU6050) sont transmises en
entier mis à l’échelle (milli-g).
• ACM = deux messages : un instantané périodique pour l’état, plus un message d’événement
asynchrone et prioritaire pour le crash (on ne veut pas attendre la trame périodique pour signaler
un impact).

3. Câblage matériel
Le STM32F4 et la Raspberry Pi fonctionnent tous deux en 3,3 V : on relie donc directement les broches
UART, sans adaptateur de niveau. On croise TX et RX, et on relie impérativement les masses.

STM32F4 Sens Raspberry Pi 4

PA2 — USART2_TX → GPIO15 / RXD (broche 10)

PA3 — USART2_RX ← GPIO14 / TXD (broche 8)

GND — GND (broche 6) — masse commune indispensable

Attention : ne jamais relier une broche 5 V de la Raspberry Pi à une entrée du STM32. Les signaux UART
restent en 3,3 V des deux côtés.

4. Configuration de l’UART
Réglage commun aux deux extrémités : 115200 bauds, 8 bits de données, pas de parité, 1 bit de stop
(8N1), sans contrôle de flux matériel.

4.1 Côté STM32 (STM32CubeMX)


• Activer USART2 en mode Asynchronous.
• Paramètres : Baud Rate = 115200, Word Length = 8 bits, Parity = None, Stop Bits = 1.
• Broches PA2 (TX) et PA3 (RX) en alternate function AF7.
• Pour la réception par interruption (recommandée), activer l’interruption USART2 global
interrupt dans l’onglet NVIC.

4.2 Côté Raspberry Pi


• Dans raspi-config → Interface Options → Serial Port : répondre Non à « login shell over serial »
et Oui à « serial port hardware enabled ».
• Vérifier enable_uart=1 dans /boot/[Link]. Le port est alors /dev/serial0.
• Un adaptateur USB–série (3,3 V) est une alternative simple : le port devient /dev/ttyUSB0.

5. Format de trame
Chaque message est encapsulé dans une trame à longueur connue, protégée par un CRC-16 :
+------+--------+-------+===================+--------+--------+
| SOF | opcode | len | payload | CRC_LO | CRC_HI |
| 0xAA | MSG_* | N | (N octets) | | |

Page 4
Communication UART STM32 ↔ Raspberry Pi

+------+--------+-------+===================+--------+--------+
Structure d’une trame sur le fil. Taille totale = N + 5 octets.

• Cadrage par longueur : le récepteur lit exactement len octets de payload. Pas de byte-stuffing
nécessaire — l’octet 0xAA peut donc apparaître dans les données.
• CRC-16/CCITT-FALSE (polynôme 0x1021, init 0xFFFF) calculé sur [opcode, len, payload],
transmis en petit-boutiste (LO puis HI).
• Re-synchronisation : en cas d’erreur CRC ou de longueur invalide, le récepteur repart à la
recherche du prochain SOF.
Tailles concrètes : la trame de télémétrie fait 31 octets (26 de payload + 5 d’encadrement) ; l’événement
ACM fait 17 octets (12 + 5).

6. La charge utile : structures de données


Le fichier telemetry_protocol.h est partagé tel quel entre le firmware STM32 et l’application RPi. Il
définit deux messages et leurs drapeaux de bits.

6.1 Instantané périodique — telemetry_msg_t (26 octets)


Envoyé régulièrement (par exemple 10 à 20 fois par seconde) avec l’opcode MSG_TELEMETRY. Il regroupe
l’état courant des trois services.

6.2 Événement de sécurité — acm_event_msg_t (12 octets)


Envoyé immédiatement quand un choc est détecté (opcode MSG_ACM_EVENT), sans attendre la trame
périodique : c’est le chemin critique de sûreté.

6.3 Structure côté hôte (Raspberry Pi uniquement)


C’est ici, et seulement ici, que vit l’horodatage de réception rx_time — rempli par la RPi après
décodage, jamais transmis sur le fil.

Fichier complet : telemetry_protocol.h


/* =====================================================================
* telemetry_protocol.h
* Protocole applicatif STM32F4 -> Raspberry Pi 4 (liaison UART)
*
* Transporte l'etat des services consolides BCM, ACM et SEAT vers le
* tableau de bord (IHM AromaUI). Ce fichier definit UNIQUEMENT les
* charges utiles (payloads). L'encapsulation de trame (SOF, opcode,
* longueur, CRC-16) est assuree par la couche Protocol :
*
* protocol_send(opcode, (const uint8_t *)&payload, sizeof(payload));
*
* Trame sur le fil (produite par protocol_send) :
*
* +------+--------+-------+===================+--------+--------+
* | SOF | opcode | len | payload | CRC_LO | CRC_HI |
* | 0xAA | MSG_* | N | (N octets) | | |
* +------+--------+-------+===================+--------+--------+
*
* CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF) calcule sur
* [opcode, len, payload]. STM32 et RPi sont petit-boutistes : aucune
* conversion d'octets n'est necessaire.
*

Page 5
Communication UART STM32 ↔ Raspberry Pi

* IMPORTANT : ce header est partage tel quel entre le firmware STM32 et


* l'application RPi (meme definition des deux cotes).
* ===================================================================== */
#ifndef TELEMETRY_PROTOCOL_H
#define TELEMETRY_PROTOCOL_H

#include <stdint.h>

#define PROTO_VERSION 1u /* version applicative du payload */

/* --------------------------------------------------------------------- */
/* Opcodes (= champ "opcode" de la trame, ex-"msg_id") */
/* --------------------------------------------------------------------- */
enum {
MSG_TELEMETRY = 0x10, /* instantane periodique BCM + ACM + SEAT */
MSG_ACM_EVENT = 0x11 /* evenement de securite (asynchrone) */
};

/* --------------------------------------------------------------------- */
/* BCM : carrosserie / confort (bits de bcm_flags) */
/* --------------------------------------------------------------------- */
#define BCM_LOWBEAM (1u << 0) /* feux de croisement */
#define BCM_HIGHBEAM (1u << 1) /* feux de route */
#define BCM_POSITION (1u << 2) /* feux de position */
#define BCM_FOG (1u << 3) /* antibrouillard */
#define BCM_INDIC_LEFT (1u << 4) /* clignotant gauche actif */
#define BCM_INDIC_RIGHT (1u << 5) /* clignotant droit actif */
#define BCM_HAZARD (1u << 6) /* feux de detresse (warning) */
#define BCM_HORN (1u << 7) /* klaxon actionne */
#define BCM_BRAKE_LIGHT (1u << 8) /* feu stop */
#define BCM_REVERSE_LIGHT (1u << 9) /* feu de recul */
#define BCM_DOORS_LOCKED (1u << 10) /* portes verrouillees */
#define BCM_DOOR_OPEN (1u << 11) /* au moins une porte ouverte */

/* Mode essuie-glaces (champ bcm_wiper_mode) */


enum {
WIPER_OFF = 0,
WIPER_INT = 1, /* intermittent */
WIPER_SLOW = 2,
WIPER_FAST = 3
};

/* --------------------------------------------------------------------- */
/* ACM : surete passive / airbag */
/* --------------------------------------------------------------------- */
/* Etat de la machine ACM (champ acm_state) */
enum {
ACM_STATE_INIT = 0, /* initialisation / auto-test */
ACM_STATE_ARMED = 1, /* surveillance active, aucun choc */
ACM_STATE_IMPACT = 2, /* choc detecte, deploiement en cours/effectue*/
ACM_STATE_FAULT = 3 /* defaut capteur / chaine de surete */
};

/* Bits de acm_flags */
#define ACM_OCC_DRIVER (1u << 0) /* place conducteur occupee */
#define ACM_OCC_PASSENGER (1u << 1) /* place passager occupee */
#define ACM_BELT_DRIVER (1u << 2) /* ceinture conducteur bouclee */
#define ACM_BELT_PASSENGER (1u << 3) /* ceinture passager bouclee */
#define ACM_AIRBAG_FIRED (1u << 4) /* airbag declenche */
#define ACM_PRETENS_FIRED (1u << 5) /* pretensionneur declenche */

Page 6
Communication UART STM32 ↔ Raspberry Pi

#define ACM_SENSOR_FAULT (1u << 6) /* defaut MPU6050 / capteur */

/* Codes d'evenement (champ event de acm_event_msg_t) */


enum {
ACM_EVT_IMPACT = 1,
ACM_EVT_AIRBAG_FIRED = 2,
ACM_EVT_PRETENS_FIRED= 3,
ACM_EVT_FAULT = 4
};

/* --------------------------------------------------------------------- */
/* SEAT : sieges electriques (bits de seat_flags) */
/* --------------------------------------------------------------------- */
#define SEAT_MOVING (1u << 0) /* moteur de siege en mouvement */
#define SEAT_OCCUPIED (1u << 1) /* siege occupe */
#define SEAT_MEM_BUSY (1u << 2) /* rappel/sauvegarde profil en cours*/
#define SEAT_PROFILE_SAVED (1u << 3) /* dernier "save" termine OK */
#define SEAT_DIR_FORWARD (1u << 4) /* sens du mouvement (sinon arriere)*/

/* ===================================================================== */
/* CHARGES UTILES (payloads) — packees, layout identique des deux cotes */
/* ===================================================================== */
#pragma pack(push, 1)

/* ---- MSG_TELEMETRY : instantane periodique (ex. 10 a 20 Hz) ------- */


typedef struct {
/* --- en-tete applicatif --- */
uint8_t version; /* PROTO_VERSION */
uint8_t reserved; /* reserve, mis a 0 (extension future) */
uint16_t seq; /* compteur incremental (perte de trame)*/
uint32_t t_stm_ms; /* horodatage STM32 : HAL_GetTick() (ms)*/

/* --- BCM (carrosserie / confort) --- */


uint16_t bcm_flags; /* combinaison de BCM_* */
uint8_t bcm_wiper_mode; /* WIPER_* */

/* --- ACM (surete passive) --- */


uint8_t acm_state; /* ACM_STATE_* */
uint8_t acm_flags; /* combinaison de ACM_* */
uint16_t acm_accel_mg; /* magnitude d'acceleration, milli-g */
int16_t acm_ax_mg; /* axe X, milli-g (affichage / debug) */
int16_t acm_ay_mg; /* axe Y, milli-g */
int16_t acm_az_mg; /* axe Z, milli-g */

/* --- SEAT (sieges electriques) --- */


uint8_t seat_profile; /* 0 = aucun, 1..3 */
uint8_t seat_flags; /* combinaison de SEAT_* */
uint8_t seat_pos_slide_pct; /* avance/recul 0..100 % */
uint8_t seat_pos_recline_pct;/* inclinaison dossier 0..100 % */
uint8_t seat_pos_height_pct; /* hauteur d'assise 0..100 % */
} telemetry_msg_t;

/* ---- MSG_ACM_EVENT : evenement de securite, envoye immediatement -- */


typedef struct {
uint8_t version; /* PROTO_VERSION */
uint8_t event; /* ACM_EVT_* */
uint16_t seq; /* compteur incremental */
uint32_t t_stm_ms; /* horodatage STM32 (ms) */
uint16_t peak_accel_mg; /* pic d'acceleration mesure, milli-g */
uint8_t severity; /* severite estimee 0..255 */

Page 7
Communication UART STM32 ↔ Raspberry Pi

uint8_t acm_flags; /* etat occupation/ceintures a l'impact */


} acm_event_msg_t;

#pragma pack(pop)

/* Garantit le meme layout cote STM32 et cote RPi (echoue a la compilation


si l'alignement differe ou si un champ a ete oublie). */
_Static_assert(sizeof(telemetry_msg_t) == 26, "telemetry_msg_t doit faire 26 octets");
_Static_assert(sizeof(acm_event_msg_t) == 12, "acm_event_msg_t doit faire 12 octets");

/* ===================================================================== */
/* STRUCTURE COTE HOTE (Raspberry Pi uniquement) — PAS sur le fil */
/* C'est ICI que vit l'horodatage de reception (et non dans le payload).*/
/* ===================================================================== */
#ifndef STM32 /* compile uniquement cote Linux/RPi */
#include <time.h>

typedef struct {
telemetry_msg_t data; /* derniere telemetrie decodee */
struct timespec rx_time; /* CLOCK_MONOTONIC, rempli par la RPi */
int valid; /* 1 si CRC OK et version compatible */
uint16_t last_seq; /* pour detecter les trames perdues */
} telemetry_state_t;
#endif

#endif /* TELEMETRY_PROTOCOL_H */

7. La couche de transport UART


L’API publique reprend exactement les signatures du rapport :
void protocol_init(void);
int protocol_send(uint8_t opcode, const uint8_t *payload, uint8_t len);
int protocol_receive(uint8_t *opcode, uint8_t *buf, uint8_t buflen,
uint32_t timeout_ms);
Trois fonctions identiques côté STM32 et côté RPi (implementations différentes).

• protocol_send construit la trame (proto_build_frame) puis l’émet sur l’UART. Renvoie


PROTO_OK ou PROTO_ERR.
• protocol_receive attend une trame valide (CRC vérifié) jusqu’au délai, copie le payload et
l’opcode, et renvoie le nombre d’octets reçus ou un code négatif.
• Le cœur portable (protocol_core.c) fournit le CRC, la construction de trame et l’automate de
réception octet par octet, réutilisé par les deux plateformes.

Fichier complet : protocol.h


/* =====================================================================
* protocol.h — Couche de transport UART STM32F4 <-> Raspberry Pi 4
*
* Cadrage des trames sur la liaison serie (UART, 115200 8N1 par defaut) :
*
* +------+--------+-------+===================+--------+--------+
* | SOF | opcode | len | payload | CRC_LO | CRC_HI |
* | 0xAA | MSG_* | N | (N octets) | | |
* +------+--------+-------+===================+--------+--------+
*
* - Cadrage par LONGUEUR (pas de byte-stuffing) : on lit exactement
* "len" octets de payload, donc 0xAA peut apparaitre dans les donnees.

Page 8
Communication UART STM32 ↔ Raspberry Pi

* - CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF) sur [opcode, len, payload],


* transmis en petit-boutiste (LO puis HI).
* - Re-synchronisation : en cas d'erreur CRC ou de longueur invalide, le
* recepteur repart a la recherche du prochain SOF.
*
* API identique cote STM32 et cote RPi (implementations differentes).
* ===================================================================== */
#ifndef PROTOCOL_H
#define PROTOCOL_H

#include <stdint.h>

#define PROTO_SOF 0xAAu


#define PROTO_BAUD 115200u
#define PROTO_MAX_PAYLOAD 64u
#define PROTO_OVERHEAD 5u /* SOF+op+len+CRC2 */
#define PROTO_MAX_FRAME (PROTO_MAX_PAYLOAD + PROTO_OVERHEAD)

/* ---- codes de retour ---- */


#define PROTO_OK 0
#define PROTO_ERR (-1) /* erreur generique (E/S, parametre) */
#define PROTO_TIMEOUT (-2) /* aucune trame valide avant le delai */
#define PROTO_ERR_CRC (-3) /* trame recue mais CRC invalide */
#define PROTO_ERR_OVERFLOW (-4) /* payload plus grand que le buffer fourni */

/* =====================================================================
* API de transport (signatures du rapport)
* ===================================================================== */
void protocol_init(void);

/* Construit la trame et l'emet sur l'UART. Renvoie PROTO_OK ou PROTO_ERR. */


int protocol_send(uint8_t opcode, const uint8_t *payload, uint8_t len);

/* Attend UNE trame valide (CRC verifie) jusqu'a timeout_ms.


* Copie la charge utile dans buf (max buflen) et l'opcode dans *opcode.
* Renvoie le nombre d'octets de payload (>= 0), ou un code PROTO_* < 0. */
int protocol_receive(uint8_t *opcode, uint8_t *buf, uint8_t buflen,
uint32_t timeout_ms);

/* =====================================================================
* Coeur portable (partage STM32 / RPi) — defini dans protocol_core.c
* ===================================================================== */
uint16_t crc16_ccitt(const uint8_t *data, uint32_t len);

/* Serialise une trame complete dans out[] ; renvoie sa taille ou PROTO_ERR. */


int proto_build_frame(uint8_t opcode, const uint8_t *payload, uint8_t len,
uint8_t *out, uint32_t outsz);

/* Callback appelee a chaque trame valide recue par l'automate. */


typedef void (*proto_frame_cb)(uint8_t opcode, const uint8_t *payload,
uint8_t len, void *user);

typedef enum { P_SOF = 0, P_OP, P_LEN, P_PAYLOAD, P_CRC_LO, P_CRC_HI } proto_state_t;

typedef struct {
proto_state_t state;
uint8_t opcode, len, idx;
uint8_t payload[PROTO_MAX_PAYLOAD];
uint16_t crc_calc; /* CRC courant sur opcode+len+payload */
uint8_t crc_lo;

Page 9
Communication UART STM32 ↔ Raspberry Pi

proto_frame_cb cb;
void *user;
uint32_t n_ok, n_crc_err, n_resync; /* statistiques de diagnostic */
} proto_parser_t;

void proto_parser_init(proto_parser_t *p, proto_frame_cb cb, void *user);


void proto_parser_byte(proto_parser_t *p, uint8_t b); /* 1 octet */
void proto_parser_feed(proto_parser_t *p, const uint8_t *buf, uint32_t n);

#endif /* PROTOCOL_H */

Fichier complet : protocol_core.c


/* =====================================================================
* protocol_core.c — Coeur portable du protocole (STM32 + RPi)
* CRC-16/CCITT, construction de trame, automate de reception.
* Aucune dependance materielle : compile tel quel des deux cotes.
* ===================================================================== */
#include "protocol.h"

/* ---- CRC-16/CCITT-FALSE : poly 0x1021, init 0xFFFF, sans reflexion ---- */


static uint16_t crc16_update(uint16_t crc, uint8_t b)
{
crc ^= (uint16_t)b << 8;
for (int i = 0; i < 8; i++)
crc = (crc & 0x8000u) ? (uint16_t)((crc << 1) ^ 0x1021u)
: (uint16_t)(crc << 1);
return crc;
}

uint16_t crc16_ccitt(const uint8_t *data, uint32_t len)


{
uint16_t crc = 0xFFFFu;
for (uint32_t i = 0; i < len; i++)
crc = crc16_update(crc, data[i]);
return crc;
}

/* ---- Construction d'une trame complete ---- */


int proto_build_frame(uint8_t opcode, const uint8_t *payload, uint8_t len,
uint8_t *out, uint32_t outsz)
{
if (len > PROTO_MAX_PAYLOAD) return PROTO_ERR;
uint32_t total = (uint32_t)len + PROTO_OVERHEAD; /* SOF+op+len+pl+CRC2 */
if (out == 0 || total > outsz) return PROTO_ERR;

out[0] = PROTO_SOF;
out[1] = opcode;
out[2] = len;
for (uint8_t i = 0; i < len; i++)
out[3 + i] = payload ? payload[i] : 0u;

uint16_t crc = crc16_ccitt(&out[1], (uint32_t)len + 2u); /* op+len+payload */


out[3 + len] = (uint8_t)(crc & 0xFFu); /* CRC_LO */
out[3 + len + 1] = (uint8_t)(crc >> 8); /* CRC_HI */
return (int)total;
}

/* ---- Automate de reception, octet par octet ---- */


void proto_parser_init(proto_parser_t *p, proto_frame_cb cb, void *user)
{

Page 10
Communication UART STM32 ↔ Raspberry Pi

p->state = P_SOF;
p->opcode = p->len = p->idx = 0;
p->crc_calc = 0xFFFFu;
p->crc_lo = 0;
p->cb = cb;
p->user = user;
p->n_ok = p->n_crc_err = p->n_resync = 0;
}

void proto_parser_byte(proto_parser_t *p, uint8_t b)


{
switch (p->state) {
case P_SOF:
if (b == PROTO_SOF) p->state = P_OP;
break;

case P_OP:
p->opcode = b;
p->crc_calc = crc16_update(0xFFFFu, b);
p->state = P_LEN;
break;

case P_LEN:
if (b > PROTO_MAX_PAYLOAD) { /* longueur impossible -> resync */
p->n_resync++;
p->state = P_SOF;
break;
}
p->len = b;
p->crc_calc = crc16_update(p->crc_calc, b);
p->idx = 0;
p->state = (b == 0) ? P_CRC_LO : P_PAYLOAD;
break;

case P_PAYLOAD:
p->payload[p->idx++] = b;
p->crc_calc = crc16_update(p->crc_calc, b);
if (p->idx >= p->len) p->state = P_CRC_LO;
break;

case P_CRC_LO:
p->crc_lo = b;
p->state = P_CRC_HI;
break;

case P_CRC_HI: {
uint16_t rx_crc = (uint16_t)((uint16_t)b << 8) | p->crc_lo;
if (rx_crc == p->crc_calc) {
p->n_ok++;
if (p->cb) p->cb(p->opcode, p->payload, p->len, p->user);
} else {
p->n_crc_err++;
}
p->state = P_SOF; /* repart sur le prochain SOF */
break;
}
default:
p->state = P_SOF;
break;
}

Page 11
Communication UART STM32 ↔ Raspberry Pi

void proto_parser_feed(proto_parser_t *p, const uint8_t *buf, uint32_t n)


{
for (uint32_t i = 0; i < n; i++)
proto_parser_byte(p, buf[i]);
}

8. Implémentations par plateforme


8.1 Côté STM32 (HAL)
Émission bloquante via HAL_UART_Transmit. Pour la réception, une version bloquante simple est
fournie, et une variante par interruption (recommandée en production, sans perte d’octet) est donnée
en commentaire à la fin du fichier.

Fichier complet : protocol_stm32.c


/* =====================================================================
* protocol_stm32.c — Transport UART cote STM32F4 (HAL)
*
* Pre-requis (genere par STM32CubeMX) :
* - USART2 : 115200 bauds, 8 bits, parite None, 1 stop (8N1)
* - Broches PA2 (TX) / PA3 (RX) en alternate function AF7
* - L'objet "huart2" est defini ailleurs (usart.c)
*
* Ce module fournit l'API du rapport : protocol_init / send / receive.
* Le STM32 EMET surtout la telemetrie (periodique) et RECOIT
* ponctuellement des commandes (ex. choix de profil de siege).
* ===================================================================== */
#include "protocol.h"
#include "main.h" /* fournit UART_HandleTypeDef, huartx, HAL_* */

extern UART_HandleTypeDef huart2;


#define PROTO_HUART huart2

void protocol_init(void)
{
/* L'UART est deja initialise par MX_USART2_UART_Init().
On vide un eventuel octet residuel. */
__HAL_UART_CLEAR_OREFLAG(&PROTO_HUART);
}

/* ---- emission bloquante d'une trame ---- */


int protocol_send(uint8_t opcode, const uint8_t *payload, uint8_t len)
{
uint8_t frame[PROTO_MAX_FRAME];
int n = proto_build_frame(opcode, payload, len, frame, sizeof(frame));
if (n < 0) return PROTO_ERR;

HAL_StatusTypeDef st = HAL_UART_Transmit(&PROTO_HUART, frame, (uint16_t)n, 50u);


return (st == HAL_OK) ? PROTO_OK : PROTO_ERR;
}

/* ---- contexte de capture pour protocol_receive ---- */


typedef struct {
uint8_t *buf; uint8_t buflen; uint8_t *opcode; int result; int got;
} rx_ctx_t;

Page 12
Communication UART STM32 ↔ Raspberry Pi

static void rx_cb(uint8_t opcode, const uint8_t *payload, uint8_t len, void *user)
{
rx_ctx_t *c = (rx_ctx_t *)user;
if (c->got) return;
if (len > c->buflen) { c->result = PROTO_ERR_OVERFLOW; c->got = 1; return; }
for (uint8_t i = 0; i < len; i++) c->buf[i] = payload[i];
if (c->opcode) *c->opcode = opcode;
c->result = (int)len;
c->got = 1;
}

/* ---- reception bloquante d'UNE trame valide (a appeler dans la boucle) --- */
int protocol_receive(uint8_t *opcode, uint8_t *buf, uint8_t buflen,
uint32_t timeout_ms)
{
rx_ctx_t ctx = { buf, buflen, opcode, PROTO_TIMEOUT, 0 };
proto_parser_t parser;
proto_parser_init(&parser, rx_cb, &ctx);

uint32_t deadline = HAL_GetTick() + timeout_ms;


while (![Link]) {
if ((int32_t)(HAL_GetTick() - deadline) >= 0) return PROTO_TIMEOUT;

uint8_t b;
HAL_StatusTypeDef st = HAL_UART_Receive(&PROTO_HUART, &b, 1u, 5u);
if (st == HAL_OK) proto_parser_byte(&parser, b);
else if (st == HAL_TIMEOUT) continue; /* pas d'octet, on re-essaie */
else { __HAL_UART_CLEAR_OREFLAG(&PROTO_HUART); } /* erreur -> on purge */
}
return [Link];
}

/* =====================================================================
* VARIANTE RECOMMANDEE EN PRODUCTION : reception par interruption.
* Ne perd aucun octet meme si la boucle principale est occupee.
*
* Dans protocol_init(), au lieu du flush, armer la reception IT :
* proto_parser_init(&g_parser, app_on_frame, NULL);
* HAL_UART_Receive_IT(&PROTO_HUART, &g_rx_byte, 1);
*
* Puis fournir le callback HAL (declenche a chaque octet recu) :
*
* static proto_parser_t g_parser;
* static uint8_t g_rx_byte;
*
* void HAL_UART_RxCpltCallback(UART_HandleTypeDef *h) {
* if (h->Instance == PROTO_HUART.Instance) {
* proto_parser_byte(&g_parser, g_rx_byte); // alimente l'automate
* HAL_UART_Receive_IT(&PROTO_HUART, &g_rx_byte, 1); // re-arme
* }
* }
*
* ou app_on_frame(opcode, payload, len, user) traite la commande recue
* (ex. MSG_SEAT_CMD -> ecrire le profil demande sur le DataBus interne).
* Pour de hauts debits, utiliser HAL_UART_Receive_DMA en mode circulaire.
* ===================================================================== */

Page 13
Communication UART STM32 ↔ Raspberry Pi

8.2 Côté Raspberry Pi (termios)


Ouverture et configuration du port série en mode brut (8N1, 115200), émission complète et réception
bloquante avec délai via poll().

Fichier complet : protocol_rpi.c


/* =====================================================================
* protocol_rpi.c — Transport UART cote Raspberry Pi 4 (Linux / termios)
*
* Compilation : gcc -std=c11 protocol_rpi.c protocol_core.c -o rx
* Port serie : /dev/serial0 (GPIO14/15) ou /dev/ttyUSB0 (adaptateur USB).
* Verifier : enable_uart=1 et console serie desactivee
* (raspi-config) pour liberer l'UART.
* ===================================================================== */
#define _DEFAULT_SOURCE /* expose cfmakeraw, CRTSCTS, clock_gettime */

#include "protocol.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <poll.h>
#include <time.h>

#ifndef PROTO_TTY
#define PROTO_TTY "/dev/serial0"
#endif

static int g_fd = -1;

/* ---- ouverture + configuration 8N1 a PROTO_BAUD, mode brut ---- */


void protocol_init(void)
{
g_fd = open(PROTO_TTY, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (g_fd < 0) { perror("open(" PROTO_TTY ")"); return; }

struct termios tio;


memset(&tio, 0, sizeof(tio));
if (tcgetattr(g_fd, &tio) != 0) { perror("tcgetattr"); return; }

cfmakeraw(&tio); /* 8N1, pas d'echo, pas de canon */


cfsetispeed(&tio, B115200);
cfsetospeed(&tio, B115200);
tio.c_cflag |= (CLOCAL | CREAD); /* ignore modem, active reception */
tio.c_cflag &= ~CRTSCTS; /* pas de controle de flux HW */
tio.c_cc[VMIN] = 0; /* lecture non bloquante (poll) */
tio.c_cc[VTIME] = 0;

tcflush(g_fd, TCIOFLUSH);
if (tcsetattr(g_fd, TCSANOW, &tio) != 0) { perror("tcsetattr"); }
}

/* ---- emission d'une trame ---- */


int protocol_send(uint8_t opcode, const uint8_t *payload, uint8_t len)
{

Page 14
Communication UART STM32 ↔ Raspberry Pi

if (g_fd < 0) return PROTO_ERR;


uint8_t frame[PROTO_MAX_FRAME];
int n = proto_build_frame(opcode, payload, len, frame, sizeof(frame));
if (n < 0) return PROTO_ERR;

ssize_t off = 0;
while (off < n) {
ssize_t w = write(g_fd, frame + off, (size_t)(n - off));
if (w < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
return PROTO_ERR;
}
off += w;
}
return PROTO_OK;
}

/* ---- contexte de capture pour protocol_receive ---- */


typedef struct {
uint8_t *buf; uint8_t buflen;
uint8_t *opcode; int result; int got;
} rx_ctx_t;

static void rx_cb(uint8_t opcode, const uint8_t *payload, uint8_t len, void *user)
{
rx_ctx_t *c = (rx_ctx_t *)user;
if (c->got) return; /* on ne garde que la 1ere trame */
if (len > c->buflen) { c->result = PROTO_ERR_OVERFLOW; c->got = 1; return; }
memcpy(c->buf, payload, len);
if (c->opcode) *c->opcode = opcode;
c->result = (int)len;
c->got = 1;
}

static long now_ms(void)


{
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
}

/* ---- reception bloquante d'UNE trame valide ---- */


int protocol_receive(uint8_t *opcode, uint8_t *buf, uint8_t buflen,
uint32_t timeout_ms)
{
if (g_fd < 0) return PROTO_ERR;

rx_ctx_t ctx = { buf, buflen, opcode, PROTO_TIMEOUT, 0 };


proto_parser_t parser;
proto_parser_init(&parser, rx_cb, &ctx);

long deadline = now_ms() + (long)timeout_ms;


uint8_t chunk[128];

while (![Link]) {
long remaining = deadline - now_ms();
if (remaining <= 0) return PROTO_TIMEOUT;

struct pollfd pfd = { g_fd, POLLIN, 0 };


int pr = poll(&pfd, 1, (int)remaining);
if (pr < 0) { if (errno == EINTR) continue; return PROTO_ERR; }

Page 15
Communication UART STM32 ↔ Raspberry Pi

if (pr == 0) return PROTO_TIMEOUT;

ssize_t r = read(g_fd, chunk, sizeof(chunk));


if (r < 0) { if (errno == EAGAIN || errno == EINTR) continue; return PROTO_ERR; }
if (r > 0) proto_parser_feed(&parser, chunk, (uint32_t)r);
}
return [Link];
}

9. Exemples d’utilisation
9.1 Côté STM32 — émission périodique
telemetry_msg_t t = {0};
[Link] = PROTO_VERSION;
[Link] = seq++;
t.t_stm_ms = HAL_GetTick();
/* lecture depuis le DataBus interne */
t.bcm_flags = BCM_LOWBEAM | BCM_INDIC_RIGHT | BCM_DOORS_LOCKED;
t.acm_state = ACM_STATE_ARMED;
t.acm_flags = ACM_OCC_DRIVER | ACM_BELT_DRIVER;
t.seat_profile = 2;
t.seat_pos_recline_pct = 45;
protocol_send(MSG_TELEMETRY, (const uint8_t *)&t, sizeof(t));
À appeler dans une tâche périodique (ex. toutes les 50–100 ms).

9.2 Côté Raspberry Pi — réception


telemetry_state_t st;
uint8_t op, buf[64];
int n = protocol_receive(&op, buf, sizeof(buf), 100 /*ms*/);
if (n == (int)sizeof(telemetry_msg_t) && op == MSG_TELEMETRY) {
memcpy(&[Link], buf, sizeof([Link]));
clock_gettime(CLOCK_MONOTONIC, &st.rx_time); /* horodatage ICI */
[Link] = ([Link] == PROTO_VERSION);
/* [Link] alimente directement les widgets AromaUI */
}
L’horodatage de réception est ajouté ici, côté hôte — jamais dans le payload.

10. Test et validation


Le cœur du protocole a été validé par un test en boucle (émission → réception simulée). Résultats
obtenus :
• Trame de télémétrie correctement construite : 31 octets (26 + 5).
• Octets de bruit insérés avant le SOF : ignorés ; la trame valide est ensuite décodée correctement
(seq, état ACM, position de siège).
• Trame volontairement corrompue : rejetée par le CRC (compteur d’erreurs incrémenté), sans livrer
de données erronées.
• Tailles vérifiées à la compilation par _Static_assert : telemetry_msg_t = 26,
acm_event_msg_t = 12.
Conseil de mise au point : commencer par un simple test « boucle locale » (relier TX et RX du STM32 sur
lui-même, ou émettre puis lire côté RPi avec cat /dev/serial0 | xxd) pour valider le câblage et la
vitesse avant d’intégrer le protocole.

Page 16
Communication UART STM32 ↔ Raspberry Pi

11. Récapitulatif — étapes à suivre


1. Câbler PA2→RXD, PA3←TXD et la masse commune (tout en 3,3 V).
2. Configurer USART2 à 115200 8N1 sous CubeMX ; activer l’IT USART2 si réception par interruption.
3. Activer l’UART matériel de la Raspberry Pi et libérer la console série (raspi-config).
4. Ajouter telemetry_protocol.h, protocol.h et protocol_core.c aux deux projets.
5. Compiler protocol_stm32.c côté firmware et protocol_rpi.c côté Linux.
6. Côté STM32 : remplir telemetry_msg_t depuis le DataBus et appeler protocol_send
périodiquement.
7. Côté RPi : appeler protocol_receive, vérifier l’opcode, puis alimenter l’IHM AromaUI.
8. Émettre l’événement ACM immédiatement à la détection d’un choc (chemin prioritaire).

Extension possible : un message MSG_VEHICLE (vitesse, régime, rapport, températures décodés du bus
CAN) peut être ajouté sur le même transport pour compléter les données affichées à partir de la
passerelle CAN.

Page 17

Vous aimerez peut-être aussi