# 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 dynamique** : Buffers statiques uniquement (ring buffer fixe)
- **Historique circulaire** : Jusqu'à 240 points (limite physique écran 240px)
- **Axes configurables** : Graduations statiques avec pas personnalisable
- **Grille configurable** : Grille horizontale/verticale avec labels personnalisés
- **Détection automatique du zéro** : La ligne zéro est surlignée en vert
- **Protection stricte des bordures** : Clamping des primitives à l'espace interne
- **Rendu ligne Bresenham** : Courbes lisses entre points de données
- **Rendu intelligent (Double Phase)** : Cadre, étiquettes et titres tracés une seule fois
- **Deux modes de rendu** :
- `LineChart` + `render()` : async, direct SPI
- `LineChartBuffered` + `render_buf()` + `flush()` : sync RAM, **zéro clignotement**
---
## Installation
```toml
[dependencies]
embassy-st7789v = "0.3"
embassy-st7789v-graphics = "0.2"
embassy-st7789v-plot = "0.2"
embedded-hal = "1.0"
embedded-hal-async = "1.0"
```
---
## Utilisation rapide
### Mode direct async (`LineChart`)
Simple mais peut produire un clignotement si le graphique est redessiné fréquemment.
```rust
use embassy_st7789v::{Color, St7789v, NoPin};
use embassy_st7789v_plot::{Graphics, AxisConfig, PlotConfig, LineChart};
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)");
let config = PlotConfig {
x: 10, y: 10, width: 220, height: 200,
margin_left: 40, margin_right: 10,
margin_top: 10, margin_bottom: 30,
x_axis, y_axis,
bg_color: Color::BLACK,
line_color: Color::GREEN,
axis_color: Color::WHITE,
grid_color: Color::GRAY,
text_color: Color::WHITE,
label_color: Color::CYAN,
};
let mut chart: LineChart<100> = LineChart::new(config);
chart.push(45.2);
chart.push(47.8);
let mut gfx = Graphics::new_no_rst(&mut display);
chart.render(&mut gfx).await;
```
### Mode framebuffer sync (`LineChartBuffered`) — zéro clignotement
Toutes les opérations de dessin écrivent en RAM. Un seul `flush()` envoie tout à l'écran.
Nécessite `St7789vBuffered` du crate `embassy-st7789v` (150 Ko RAM).
```rust
use embassy_st7789v::{Color, St7789v, St7789vBuffered};
use embassy_st7789v_plot::{AxisConfig, PlotConfig, LineChartBuffered};
let mut ecran = St7789vBuffered::new(St7789v::new(spi_dev, dc, rst));
ecran.driver().init().await.unwrap();
let config = PlotConfig { /* ... */ };
let mut chart: LineChartBuffered<100> = LineChartBuffered::new(config);
loop {
let value = sensor.read().await;
chart.push(value);
chart.render_buf(&mut ecran); // tout en RAM, synchrone
ecran.flush().await.unwrap(); // envoi unique → zéro clignotement
}
```
---
## Configuration des axes
```rust
// Temps 0–60 s, graduation tous les 10 s
let time_axis = AxisConfig::new(0.0, 60.0, 10.0, b"Time (s)");
// Température -10 à +50 °C, graduation tous les 10 °C
let temp_axis = AxisConfig::new(-10.0, 50.0, 10.0, b"Temp (C)");
// Tension 0–3.3 V, graduation tous les 0.5 V
let volt_axis = AxisConfig::new(0.0, 3.3, 0.5, b"U (V)");
// Nombre de graduations
assert_eq!(time_axis.tick_count(), 7); // 0, 10, 20, 30, 40, 50, 60
```
---
## Schéma de positionnement
(x, y) ┌──────────────────────────────────┐
│ margin_top │
│ ┌──────────────────────────┐ │
│ │ Label Y │ │
margin │ │ 50 ┼──────────• │ │ margin
left │ │ 25 │ • • │ │ right
│ │ 0 └────────────────── │ │
│ │ 0 2 4 6 8 10 │ │
│ │ Label X │ │
│ └──────────────────────────┘ │
│ margin_bottom │
└──────────────────────────────────┘
---
## Workflow de rendu (Double Phase)
**Phase d'initialisation (une seule fois) :**
1. Nettoyage global de l'espace d'affichage
2. Labels Y — valeurs des graduations (vert si zéro détecté)
3. Labels X — valeurs des graduations
4. Titres des axes
5. Bordures fixes (axis_color)
**Phase dynamique (à chaque rendu) :**
1. Nettoyage intérieur strict (sans toucher aux bordures)
2. Grille Y — lignes horizontales (vert pour le zéro)
3. Grille X — lignes verticales
4. Courbe — lignes Bresenham reliant les points
---
## Ring buffer (historique circulaire)
array[100], head=3, count=100 (plein)
data[0] = 97e valeur
data[1] = 98e valeur
data[2] = 99e valeur ← head (prochaine écriture)
data[3] = 1ère valeur ← oldest
...
---
## 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)"),
// ...
};
let mut osc: LineChartBuffered<240> = LineChartBuffered::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)"),
// ...
};
let mut temp: LineChartBuffered<120> = LineChartBuffered::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)"),
// ...
};
let mut pres: LineChartBuffered<100> = LineChartBuffered::new(config);
```
---
## API complète
### `LineChart<N>` et `LineChartBuffered<N>`
| `new(config)` | ✓ | ✓ |
| `push(value)` | ✓ | ✓ |
| `clear()` | ✓ | ✓ |
| `config()` | ✓ | — |
| `render(&mut gfx)` | ✓ async | — |
| `render_buf(&mut ecran)` | — | ✓ sync |
### `AxisConfig`
| `new(start, end, step, label)` | Crée une config d'axe |
| `is_valid()` | Vérifie cohérence |
| `tick_count()` | Nombre de graduations |
### `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) |
### Fonctions globales
| `line(gfx, x0, y0, x1, y1, color)` | async | Bresenham direct SPI |
---
## Limitations
- **Nombre de points** : Maximum 240 (largeur physique écran)
- **Précision** : Axes en virgule flottante, pixels en entier
- **Pas non-adaptatif** : Graduations statiques, pas de zoom automatique
- **RAM framebuffer** : `LineChartBuffered` nécessite 150 Ko (RP2350 ✓, RP2040 ✓, STM32F103 ✗)
---
## Licence
GPL-2.0-or-later — **Copyright (C) 2026 Jorge Andre Castro**
---
## Dépendances
- [`embassy-st7789v`](https://crates.io/crates/embassy-st7789v) — Driver ST7789V + framebuffer
- [`embassy-st7789v-graphics`](https://crates.io/crates/embassy-st7789v-graphics) — Primitives géométriques
- [`embedded-hal`](https://crates.io/crates/embedded-hal) — Traits HAL
- [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async) — Traits async HAL