Initial commit — firmware WireClaw ESP32-S3 fonctionnel
- Heartbeat MAVLink v1 toutes les 1s (broadcast 192.168.4.255:14553) - Relais WiFi <-> UART (Jetson <-> Pi Zero) sur ports 14550-14553 - LED RGB WS2812 GPIO48 : rouge pulsé (attente), vert dim (idle), flash vert (heartbeat) - MODE_AP=true : hotspot WireClaw-CAM / false : connexion Jetson - Fix: retrait ARDUINO_USB_CDC_ON_BOOT (carte Freenove utilise CH343/UART0)
This commit is contained in:
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
|
||||
# Secrets
|
||||
include/credentials.h
|
||||
|
||||
# Fichiers temporaires
|
||||
src/main.cpp.old
|
||||
95
CLAUDE.md
Normal file
95
CLAUDE.md
Normal file
@ -0,0 +1,95 @@
|
||||
# WireClaw ESP32-S3 — Contexte projet
|
||||
|
||||
## Carte
|
||||
- **Freenove ESP32-S3-WROOM** (8MB Flash, PSRAM OPI)
|
||||
- LED RGB WS2812 sur **GPIO48** (adressable, lib FastLED)
|
||||
- UART2 : RX=GPIO16, TX=GPIO17 (vers Pi Zero, 57600 baud)
|
||||
- Flash mode : QIO 80MHz (`board_build.flash_mode = qio`)
|
||||
|
||||
## Rôle de ce firmware
|
||||
Nœud de communication drone WireClaw :
|
||||
- Envoie un **HEARTBEAT MAVLink v1** toutes les 1 seconde
|
||||
- Relaie les trames MAVLink **WiFi ↔ UART** (Jetson ↔ Pi Zero)
|
||||
- Indique l'état par la **LED RGB** (voir section LED)
|
||||
|
||||
## Basculer entre mode AP et mode Jetson
|
||||
En haut de `src/main.cpp`, une seule constante suffit :
|
||||
|
||||
```cpp
|
||||
#define MODE_AP true // true = ESP32 crée son propre hotspot (test sans Jetson)
|
||||
// false = ESP32 se connecte au hotspot WireClaw du Jetson
|
||||
```
|
||||
|
||||
- **`MODE_AP true`** : hotspot `WireClaw-CAM`, IP fixe `192.168.4.1`, heartbeat broadcast `192.168.4.255`
|
||||
- **`MODE_AP false`** : connexion à `WIFI_SSID`, heartbeat envoyé à `JETSON_IP:14553`
|
||||
|
||||
Les credentials sont dans `include/credentials.h` :
|
||||
```cpp
|
||||
// Mode AP
|
||||
#define AP_SSID "WireClaw-CAM"
|
||||
#define AP_PASSWORD ""
|
||||
|
||||
// Mode STA (Jetson)
|
||||
#define WIFI_SSID "WireClaw"
|
||||
#define WIFI_PASSWORD ""
|
||||
#define JETSON_IP "10.42.0.1"
|
||||
```
|
||||
|
||||
## LED RGB (GPIO48, WS2812, FastLED)
|
||||
| État | Couleur | Pattern |
|
||||
|---|---|---|
|
||||
| Attente WiFi / AP init | Rouge | Pulsé fade in/out |
|
||||
| Connecté, idle | Vert | Très dim fixe |
|
||||
| Heartbeat envoyé | Vert | Flash vif 80ms — 1 Hz |
|
||||
|
||||
## MAVLink Heartbeat
|
||||
- Implémenté sans lib externe dans `include/mavlink_heartbeat.h`
|
||||
- MAVLink v1, msg_id=0, SYS_ID=255 (GCS), CRC-16/MCRF4XX
|
||||
- Séquence incrémentale `mavSeq` (uint8_t, overflow auto)
|
||||
- Log hex dans le monitor série à chaque envoi
|
||||
|
||||
## Ports UDP
|
||||
| Constante | Port | Usage |
|
||||
|---|---|---|
|
||||
| `LOCAL_PORT` | 14550 | Écoute ESP32 (commandes depuis Jetson) |
|
||||
| `JETSON_PORT_MAVSDK` | 14551 | MAVLink vers Jetson (MAVLink SDK) |
|
||||
| `JETSON_PORT_PYMAV` | 14552 | MAVLink vers Jetson (pymavlink) |
|
||||
| `JETSON_PORT_HB` | 14553 | Heartbeat vers Jetson |
|
||||
|
||||
## Architecture réseau
|
||||
|
||||
### Mode AP (test)
|
||||
```
|
||||
PC/smartphone → WiFi "WireClaw-CAM" → ESP32-S3 (192.168.4.1)
|
||||
```
|
||||
|
||||
### Mode STA (production)
|
||||
```
|
||||
Jetson (10.42.0.1) ←→ WiFi "WireClaw" ←→ ESP32-S3 (10.42.0.77)
|
||||
↕ UART 57600
|
||||
Pi Zero (caméra CSI)
|
||||
```
|
||||
|
||||
## platformio.ini points importants
|
||||
- `board_build.flash_mode = qio` — obligatoire pour cette carte
|
||||
- `board_build.f_flash = 80000000L` — 80MHz
|
||||
- Ne PAS utiliser `-DARDUINO_USB_CDC_ON_BOOT=1` — la carte Freenove passe par un chip CH343 (UART0→COM11), pas par l'USB natif ESP32. Ce flag redirige Serial vers l'USB OTG qui n'est pas connecté → app muette.
|
||||
- Ne pas utiliser `board_build.arduino.memory_type = qio_opi` — cause boot loop
|
||||
|
||||
## Fichiers
|
||||
```
|
||||
wireclaw-esp32/
|
||||
├── CLAUDE.md ← ce fichier
|
||||
├── platformio.ini
|
||||
├── include/
|
||||
│ ├── credentials.h ← SSID, passwords, IP Jetson
|
||||
│ └── mavlink_heartbeat.h ← construction trame MAVLink v1
|
||||
└── src/
|
||||
└── main.cpp ← firmware principal
|
||||
```
|
||||
|
||||
## Travail futur
|
||||
- [ ] IP statique ESP32 via `WiFi.config()` en mode STA
|
||||
- [ ] Valider réception heartbeat côté Jetson (pymavlink)
|
||||
- [ ] Câblage UART ESP32 ↔ Pi Zero (GPIO16/17 ↔ GPIO14/15)
|
||||
- [ ] Remplacer broadcast par IP Jetson fixe une fois en production
|
||||
37
include/README
Normal file
37
include/README
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the convention is to give header files names that end with `.h'.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
73
include/mavlink_heartbeat.h
Normal file
73
include/mavlink_heartbeat.h
Normal file
@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// MAVLink v1 — HEARTBEAT (msg_id = 0)
|
||||
// Trame complète : 17 octets
|
||||
//
|
||||
// Byte Field
|
||||
// 0 STX 0xFE (MAVLink v1 magic)
|
||||
// 1 LEN 9 (payload = 9 octets)
|
||||
// 2 SEQ séquence incrémentale
|
||||
// 3 SYS_ID system id (on se déclare comme GCS = 255)
|
||||
// 4 COMP_ID component id (GCS = 0)
|
||||
// 5 MSG_ID 0 (HEARTBEAT)
|
||||
// 6-14 PAYLOAD 9 octets (voir ci-dessous)
|
||||
// 15-16 CRC CRC-16/MCRF4XX avec CRC_EXTRA = 50
|
||||
//
|
||||
// Payload HEARTBEAT (9 octets, little-endian) :
|
||||
// [0-3] custom_mode uint32 0x00000000
|
||||
// [4] type uint8 MAV_TYPE_GCS = 6
|
||||
// [5] autopilot uint8 MAV_AUTOPILOT_INVALID = 8
|
||||
// [6] base_mode uint8 0
|
||||
// [7] system_status uint8 MAV_STATE_ACTIVE = 4
|
||||
// [8] mavlink_version uint8 3
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
// CRC-16/MCRF4XX (algorithme MAVLink)
|
||||
static inline void mavCrcAccumulate(uint8_t data, uint16_t &crc) {
|
||||
uint8_t tmp = data ^ (uint8_t)(crc & 0xFF);
|
||||
tmp ^= (tmp << 4);
|
||||
crc = (crc >> 8) ^ ((uint16_t)tmp << 8) ^ ((uint16_t)tmp << 3) ^ ((uint16_t)tmp >> 4);
|
||||
}
|
||||
|
||||
// Construit un paquet HEARTBEAT MAVLink v1 dans buf (doit faire >= 17 octets)
|
||||
// Retourne la taille du paquet (toujours 17)
|
||||
static inline uint8_t mavlinkBuildHeartbeat(uint8_t *buf, uint8_t &seq) {
|
||||
const uint8_t PAYLOAD_LEN = 9;
|
||||
const uint8_t SYS_ID = 255; // GCS
|
||||
const uint8_t COMP_ID = 0; // GCS component
|
||||
const uint8_t MSG_ID = 0; // HEARTBEAT
|
||||
const uint8_t CRC_EXTRA = 50; // valeur fixe MAVLink pour msg #0
|
||||
|
||||
// Header
|
||||
buf[0] = 0xFE; // STX
|
||||
buf[1] = PAYLOAD_LEN;
|
||||
buf[2] = seq++; // numéro de séquence (overflow géré par uint8_t)
|
||||
buf[3] = SYS_ID;
|
||||
buf[4] = COMP_ID;
|
||||
buf[5] = MSG_ID;
|
||||
|
||||
// Payload (little-endian)
|
||||
buf[6] = 0x00; // custom_mode [0]
|
||||
buf[7] = 0x00; // custom_mode [1]
|
||||
buf[8] = 0x00; // custom_mode [2]
|
||||
buf[9] = 0x00; // custom_mode [3]
|
||||
buf[10] = 6; // type = MAV_TYPE_GCS
|
||||
buf[11] = 8; // autopilot = MAV_AUTOPILOT_INVALID
|
||||
buf[12] = 0; // base_mode
|
||||
buf[13] = 4; // system_status = MAV_STATE_ACTIVE
|
||||
buf[14] = 3; // mavlink_version
|
||||
|
||||
// CRC sur header (sans STX) + payload + CRC_EXTRA
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (uint8_t i = 1; i <= 5 + PAYLOAD_LEN; i++) {
|
||||
mavCrcAccumulate(buf[i], crc);
|
||||
}
|
||||
mavCrcAccumulate(CRC_EXTRA, crc);
|
||||
|
||||
buf[15] = crc & 0xFF; // CRC low byte
|
||||
buf[16] = (crc >> 8) & 0xFF; // CRC high byte
|
||||
|
||||
return 17;
|
||||
}
|
||||
17
platformio.ini
Normal file
17
platformio.ini
Normal file
@ -0,0 +1,17 @@
|
||||
[env:freenove_esp32s3]
|
||||
platform = espressif32
|
||||
board = esp32-s3-devkitc-1
|
||||
framework = arduino
|
||||
|
||||
board_build.flash_mode = qio
|
||||
board_build.f_flash = 80000000L
|
||||
board_build.partitions = default_8MB.csv
|
||||
board_upload.flash_size = 8MB
|
||||
|
||||
build_flags =
|
||||
|
||||
lib_deps =
|
||||
fastled/FastLED @ ^3.6.0
|
||||
|
||||
monitor_speed = 115200
|
||||
upload_speed = 460800
|
||||
192
src/main.cpp
Normal file
192
src/main.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiUDP.h>
|
||||
#include <FastLED.h>
|
||||
#include "credentials.h"
|
||||
#include "mavlink_heartbeat.h"
|
||||
|
||||
// ── Mode : true = ESP32 crée son propre hotspot false = connexion au Jetson ──
|
||||
#define MODE_AP true
|
||||
|
||||
// ── Ports ────────────────────────────────────────────────
|
||||
const uint16_t LOCAL_PORT = 14550;
|
||||
const uint16_t JETSON_PORT_MAVSDK = 14551;
|
||||
const uint16_t JETSON_PORT_PYMAV = 14552;
|
||||
const uint16_t JETSON_PORT_HB = 14553;
|
||||
|
||||
// ── UART vers Pi Zero ─────────────────────────────────────
|
||||
#define UART_BAUD 57600
|
||||
#define UART_RX_PIN 16
|
||||
#define UART_TX_PIN 17
|
||||
|
||||
// ── LED RGB WS2812 (GPIO48) ───────────────────────────────
|
||||
#define LED_PIN 48
|
||||
#define NUM_LEDS 1
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
typedef enum { LED_WAITING_WIFI, LED_HEARTBEAT, LED_IDLE } LedState;
|
||||
LedState currentLedState = LED_WAITING_WIFI;
|
||||
unsigned long ledStateStart = 0;
|
||||
|
||||
// ── Variables globales ────────────────────────────────────
|
||||
WiFiUDP udp;
|
||||
HardwareSerial MavSerial(2);
|
||||
bool wifiConnected = false;
|
||||
unsigned long lastHeartbeat = 0;
|
||||
uint8_t mavSeq = 0;
|
||||
|
||||
#if MODE_AP
|
||||
const char* BROADCAST_IP = "192.168.4.255";
|
||||
#else
|
||||
const char* BROADCAST_IP = JETSON_IP;
|
||||
#endif
|
||||
|
||||
// ── LED non bloquante ─────────────────────────────────────
|
||||
void updateLED() {
|
||||
unsigned long elapsed = millis() - ledStateStart;
|
||||
switch (currentLedState) {
|
||||
case LED_WAITING_WIFI:
|
||||
leds[0] = CRGB((uint8_t)(128 + 127 * sin(elapsed * 0.00628f)), 0, 0);
|
||||
break;
|
||||
case LED_HEARTBEAT:
|
||||
if (elapsed < 80) {
|
||||
leds[0] = CRGB(0, 255, 0);
|
||||
} else {
|
||||
currentLedState = LED_IDLE;
|
||||
ledStateStart = millis();
|
||||
}
|
||||
break;
|
||||
case LED_IDLE:
|
||||
leds[0] = CRGB(0, 8, 0);
|
||||
break;
|
||||
}
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
void setLedState(LedState s) {
|
||||
currentLedState = s;
|
||||
ledStateStart = millis();
|
||||
}
|
||||
|
||||
// ── Heartbeat MAVLink ─────────────────────────────────────
|
||||
void sendMavlinkHeartbeat() {
|
||||
uint8_t buf[17];
|
||||
uint8_t len = mavlinkBuildHeartbeat(buf, mavSeq);
|
||||
|
||||
udp.beginPacket(BROADCAST_IP, JETSON_PORT_HB);
|
||||
udp.write(buf, len);
|
||||
udp.endPacket();
|
||||
|
||||
setLedState(LED_HEARTBEAT);
|
||||
|
||||
Serial.printf("[HB] seq=%d [", mavSeq - 1);
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
Serial.printf("%02X", buf[i]);
|
||||
if (i < len - 1) Serial.print(" ");
|
||||
}
|
||||
Serial.println("]");
|
||||
}
|
||||
|
||||
// ── Reconnexion WiFi (mode STA uniquement) ────────────────
|
||||
void checkWiFi() {
|
||||
#if !MODE_AP
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
if (!wifiConnected) {
|
||||
wifiConnected = true;
|
||||
WiFi.setSleep(false);
|
||||
udp.begin(LOCAL_PORT);
|
||||
setLedState(LED_IDLE);
|
||||
Serial.print("WiFi connecte. IP: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (wifiConnected) {
|
||||
wifiConnected = false;
|
||||
setLedState(LED_WAITING_WIFI);
|
||||
Serial.println("WiFi perdu, reconnexion...");
|
||||
}
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ── Setup ─────────────────────────────────────────────────
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
|
||||
FastLED.setBrightness(80);
|
||||
setLedState(LED_WAITING_WIFI);
|
||||
|
||||
Serial.println("\n=== WireClaw ESP32-S3 ===");
|
||||
#if MODE_AP
|
||||
Serial.println("Mode: AP (hotspot autonome)");
|
||||
#else
|
||||
Serial.println("Mode: STA (connexion Jetson)");
|
||||
#endif
|
||||
|
||||
MavSerial.begin(UART_BAUD, SERIAL_8N1, UART_RX_PIN, UART_TX_PIN);
|
||||
Serial.printf("UART2: %d baud (RX=%d TX=%d)\n", UART_BAUD, UART_RX_PIN, UART_TX_PIN);
|
||||
|
||||
#if MODE_AP
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(AP_SSID, AP_PASSWORD);
|
||||
delay(500);
|
||||
wifiConnected = true;
|
||||
udp.begin(LOCAL_PORT);
|
||||
setLedState(LED_IDLE);
|
||||
Serial.printf("Hotspot: %s\n", AP_SSID);
|
||||
Serial.printf("IP : %s\n", WiFi.softAPIP().toString().c_str());
|
||||
#else
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.setSleep(false);
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||
Serial.printf("Connexion a %s...\n", WIFI_SSID);
|
||||
#endif
|
||||
|
||||
Serial.printf("UDP ecoute: port %d\n", LOCAL_PORT);
|
||||
delay(500);
|
||||
if (wifiConnected) sendMavlinkHeartbeat();
|
||||
}
|
||||
|
||||
// ── Loop ──────────────────────────────────────────────────
|
||||
void loop() {
|
||||
updateLED();
|
||||
checkWiFi();
|
||||
|
||||
if (wifiConnected && millis() - lastHeartbeat > 1000) {
|
||||
lastHeartbeat = millis();
|
||||
sendMavlinkHeartbeat();
|
||||
}
|
||||
|
||||
if (!wifiConnected) return;
|
||||
|
||||
// WiFi → UART (Jetson → Pi Zero)
|
||||
int packetSize = udp.parsePacket();
|
||||
if (packetSize > 0) {
|
||||
uint8_t buf[512];
|
||||
int len = udp.read(buf, sizeof(buf));
|
||||
if (len > 0) {
|
||||
MavSerial.write(buf, len);
|
||||
Serial.printf("[WiFi->UART] %d bytes\n", len);
|
||||
}
|
||||
}
|
||||
|
||||
// UART → WiFi (Pi Zero → Jetson)
|
||||
if (MavSerial.available()) {
|
||||
uint8_t buf[512];
|
||||
int len = 0;
|
||||
while (MavSerial.available() && len < (int)sizeof(buf)) {
|
||||
buf[len++] = MavSerial.read();
|
||||
}
|
||||
if (len > 0) {
|
||||
udp.beginPacket(BROADCAST_IP, JETSON_PORT_MAVSDK);
|
||||
udp.write(buf, len);
|
||||
udp.endPacket();
|
||||
udp.beginPacket(BROADCAST_IP, JETSON_PORT_PYMAV);
|
||||
udp.write(buf, len);
|
||||
udp.endPacket();
|
||||
Serial.printf("[UART->WiFi] %d bytes\n", len);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user