Communication STM32 RaspberryPi UART
Communication STM32 RaspberryPi UART
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).
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.
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.
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).
Page 5
Communication UART STM32 ↔ Raspberry Pi
#include <stdint.h>
/* --------------------------------------------------------------------- */
/* 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 */
/* --------------------------------------------------------------------- */
/* 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
/* --------------------------------------------------------------------- */
/* 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)
Page 7
Communication UART STM32 ↔ Raspberry Pi
#pragma pack(pop)
/* ===================================================================== */
/* 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 */
Page 8
Communication UART STM32 ↔ Raspberry Pi
#include <stdint.h>
/* =====================================================================
* API de transport (signatures du rapport)
* ===================================================================== */
void protocol_init(void);
/* =====================================================================
* Coeur portable (partage STM32 / RPi) — defini dans protocol_core.c
* ===================================================================== */
uint16_t crc16_ccitt(const uint8_t *data, uint32_t len);
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;
#endif /* PROTOCOL_H */
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;
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;
}
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 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);
}
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);
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
#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
tcflush(g_fd, TCIOFLUSH);
if (tcsetattr(g_fd, TCSANOW, &tio) != 0) { perror("tcsetattr"); }
}
Page 14
Communication UART STM32 ↔ Raspberry Pi
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;
}
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;
}
while (![Link]) {
long remaining = deadline - now_ms();
if (remaining <= 0) return PROTO_TIMEOUT;
Page 15
Communication UART STM32 ↔ Raspberry Pi
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).
Page 16
Communication UART STM32 ↔ Raspberry Pi
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