# embassy-st7789v-plot
Moteur de tracé de graphiques cartésiens (X, Y) adaptatifs et configurables pour écrans TFT LCD **ST7789V 240×320**, construit au-dessus de [`embassy-st7789v`](https://crates.io/crates/embassy-st7789v).
## 🎯 Caractéristiques
- ✅ **`#![no_std]` + `#![forbid(unsafe_code)]`** — Entièrement sûr et embarqué
- ✅ **Zéro allocation heap** — Buffers statiques uniquement (ring buffer fixe)
- ✅ **Ring buffer circulaire** — Jusqu'à 240 points (limite physique écran 240px)
- ✅ **Axes configurables** — Graduations statiques avec pas personnalisable
- ✅ **Grille adaptative** — Grille horizontale/verticale avec labels
- ✅ **API asynchrone** — Intégration complète Embassy pour non-bloquant
- ✅ **Protection des bordures** — Labels de graduations restent toujours visibles
- ✅ **Rendu ligne Bresenham** — Courbes lisses entre points de données
## 📦 Installation
```toml
[dependencies]
embassy-st7789v-plot = "0.1"
embassy-st7789v = "0.1"
embedded-hal = "1.0"
embedded-hal-async = "1.0"
```
## 🚀 Utilisation rapide
### Exemple basique — Single plot
```rust
use embassy_st7789v::{Color, St7789v, NoPin};
use embassy_st7789v_plot::{Graphics, AxisConfig, PlotConfig, LineChart};
#[embassy_executor::main]
async fn main(spawner: Spawner) {
// Initialiser le display
let mut display: St7789v<_, _, NoPin> = /* ... */;
// Créer les axes
let x_axis = AxisConfig::new(0.0, 10.0, 2.0, b"Time (s)");
let y_axis = AxisConfig::new(0.0, 100.0, 20.0, b"Temperature (C)");
// Configurer le graphique
let config = PlotConfig {
x: 10,
y: 10,
width: 220,
height: 200,
margin_left: 40, // Pour labels Y
margin_right: 10,
margin_top: 10,
margin_bottom: 30, // Pour labels X
x_axis,
y_axis,
bg_color: Color::BLACK,
line_color: Color::GREEN,
axis_color: Color::WHITE,
grid_color: Color::from_rgb(64, 64, 64),
text_color: Color::WHITE,
label_color: Color::CYAN,
};
// Créer le gestionnaire de graphique (100 points max)
let mut chart: LineChart<100> = LineChart::new(config);
// Ajouter des données
chart.push(45.2);
chart.push(47.8);
chart.push(52.1);
chart.push(48.9);
// Afficher
let mut gfx = Graphics::new_no_rst(&mut display);
chart.render(&mut gfx).await;
}
```
### Configuration des axes
```rust
// Axe X : temps de 0 à 60s, graduations tous les 10s
let time_axis = AxisConfig::new(0.0, 60.0, 10.0, b"Time (s)");
// Axe Y : température -10 à +50°C, graduations tous les 10°C
let temp_axis = AxisConfig::new(-10.0, 50.0, 10.0, b"Temp (C)");
// Axe Y : tensions 0 à 3.3V, graduations tous les 0.5V
let voltage_axis = AxisConfig::new(0.0, 3.3, 0.5, b"U (V)");
// Nombre de graduations affiché
assert_eq!(time_axis.tick_count(), 7); // 0, 10, 20, 30, 40, 50, 60
```
### Validation de configuration
```rust
let axis = AxisConfig::new(0.0, 10.0, 0.5, b"X");
assert!(axis.is_valid()); // step > 0 && end > start
let invalid = AxisConfig::new(10.0, 0.0, 1.0, b"X");
assert!(!invalid.is_valid()); // end <= start
```
## 🎨 Schéma de positionnement
```
┌─────────────────────────────────┐ y
│ (x, y) │
│ ┌──────────────────────────┐ │
│ │ Label Y (amplitude) │ │ margin_top
│ │ ┌────────────────────┐ │ │
│ │ │ 100 ┼─────────┐ │ │ │
│ │ │ 80 │ • │ │ │ │
│ │ │ 60 │ • │ │ │ │
│ │ │ 40 │ •–┤ │ │ │
│ │ │ 20 │ │ │ │ │
│ │ │ 0 └────────┘ │ │ │
│ │ │ 0 2 4 6 8 10 │ margin_bottom
│ │ │ Time (s) │
│ │ └──────────────────────── │
│ └──────────────────────────────┘
↑ ↑
margin_left margin_right
```
## 📊 Structure de données
### Ring buffer (historique circulaire)
```
Stockage interne : array[100]
head = 3, count = 100 (buffer plein)
data[0] = valeur 97e
data[1] = valeur 98e
data[2] = valeur 99e ← head (prochaine écriture)
data[3] = valeur 1ère ← oldest (plus ancienne)
data[4] = valeur 2e
...
```
### Workflow de rendu
1. **Fond** → Rectangle de fond couleur (bg_color)
2. **Grille Y** → Lignes horizontales + labels Y
3. **Grille X** → Lignes verticales + labels X
4. **Labels des axes** → Texte des titres
5. **Bordures** → Cadre (axis_color)
6. **Courbe** → Lignes Bresenham reliant les points
## 🔧 Cas d'usage
### Oscilloscope 1 canal
```rust
let config = PlotConfig {
x: 0, y: 0, width: 240, height: 320,
margin_left: 40, margin_right: 10,
margin_top: 10, margin_bottom: 30,
x_axis: AxisConfig::new(0.0, 240.0, 30.0, b"Samples"),
y_axis: AxisConfig::new(-5.0, 5.0, 1.0, b"Voltage (V)"),
// ... colors ...
};
let mut oscilloscope: LineChart<240> = LineChart::new(config);
```
### Moniteur de température
```rust
let config = PlotConfig {
x: 10, y: 10, width: 220, height: 150,
margin_left: 45, margin_right: 15,
margin_top: 15, margin_bottom: 30,
x_axis: AxisConfig::new(0.0, 120.0, 20.0, b"Time (min)"),
y_axis: AxisConfig::new(15.0, 35.0, 5.0, b"T (°C)"),
// ... colors ...
};
let mut temp_chart: LineChart<120> = LineChart::new(config);
```
### Graphique de pression
```rust
let config = PlotConfig {
x: 5, y: 5, width: 230, height: 310,
margin_left: 35, margin_right: 5,
margin_top: 5, margin_bottom: 30,
x_axis: AxisConfig::new(0.0, 1000.0, 100.0, b"Pa"),
y_axis: AxisConfig::new(900.0, 1050.0, 30.0, b"P (hPa)"),
// ... colors ...
};
let mut pressure_chart: LineChart<100> = LineChart::new(config);
```
## 🛠️ API complète
### `LineChart<N>`
| `new(config)` | Crée un nouveau graphique |
| `push(value)` | Ajoute une valeur à l'historique |
| `clear()` | Efface l'historique |
| `config()` | Retourne la configuration |
| `render(gfx)` | Affiche le graphique (async) |
### `AxisConfig`
| `new(start, end, step, label)` | Crée une config d'axe |
| `is_valid()` | Vérifie cohérence (step > 0 && end > start) |
| `tick_count()` | Nombre de graduations affiché |
### `Graphics<'a, SPI, DC, RST>`
| `new(display)` | Crée contexte avec RST |
| `new_no_rst(display)` | Crée contexte sans RST |
| `pixel(x, y, color)` | Trace pixel (async) |
### Fonction globale
| `line(gfx, x0, y0, x1, y1, color)` | Trace ligne Bresenham (async) |
## ⚠️ Limitations
- **Nombre de points** : Maximum 240 (largeur écran ST7789V)
- **Précision** : Axes en virgule flottante, pixel en entier
- **Pas adaptatif** : Les graduations sont statiques (pas de zoom automatique)
- **Labels** : Seulement texte ASCII sur Y-axis, valeurs flottantes sur graduations
## 📝 Exemple complet avec boucle acquisition
```rust
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let mut display = /* init ST7789V */;
let config = PlotConfig { /* ... */ };
let mut chart: LineChart<200> = LineChart::new(config);
let mut gfx = Graphics::new_no_rst(&mut display);
// Boucle d'acquisition
loop {
// Lire capteur
let value = sensor.read().await;
chart.push(value);
// Afficher tous les 100ms
Timer::after(Duration::from_millis(100)).await;
chart.render(&mut gfx).await;
}
}
```
## 📜 License
GPL-2.0-or-later
**Copyright (C) 2026 Jorge Andre Castro**
## 🔗 Dépendances
- [`embassy-st7789v`](https://crates.io/crates/embassy-st7789v) — Driver ST7789V
- [`embedded-hal`](https://crates.io/crates/embedded-hal) — Traits HAL
- [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async) — Traits async HAL