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.
🎯 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
[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
use embassy_st7789v::{Color, St7789v, NoPin};
use embassy_st7789v_plot::{Graphics, AxisConfig, PlotConfig, LineChart};
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let mut display: St7789v<_, _, NoPin> = ;
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::from_rgb(64, 64, 64),
text_color: Color::WHITE,
label_color: Color::CYAN,
};
let mut chart: LineChart<100> = LineChart::new(config);
chart.push(45.2);
chart.push(47.8);
chart.push(52.1);
chart.push(48.9);
let mut gfx = Graphics::new_no_rst(&mut display);
chart.render(&mut gfx).await;
}
Configuration des axes
let time_axis = AxisConfig::new(0.0, 60.0, 10.0, b"Time (s)");
let temp_axis = AxisConfig::new(-10.0, 50.0, 10.0, b"Temp (C)");
let voltage_axis = AxisConfig::new(0.0, 3.3, 0.5, b"U (V)");
assert_eq!(time_axis.tick_count(), 7);
Validation de configuration
let axis = AxisConfig::new(0.0, 10.0, 0.5, b"X");
assert!(axis.is_valid());
let invalid = AxisConfig::new(10.0, 0.0, 1.0, b"X");
assert!(!invalid.is_valid());
🎨 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
- Fond → Rectangle de fond couleur (bg_color)
- Grille Y → Lignes horizontales + labels Y
- Grille X → Lignes verticales + labels X
- Labels des axes → Texte des titres
- Bordures → Cadre (axis_color)
- Courbe → Lignes Bresenham reliant les points
🔧 Cas d'usage
Oscilloscope 1 canal
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 oscilloscope: LineChart<240> = LineChart::new(config);
Moniteur de température
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_chart: LineChart<120> = LineChart::new(config);
Graphique de pression
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 pressure_chart: LineChart<100> = LineChart::new(config);
🛠️ API complète
LineChart<N>
| Méthode |
Description |
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
| Méthode |
Description |
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>
| Méthode |
Description |
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
| Fonction |
Description |
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
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let mut display = ;
let config = PlotConfig { };
let mut chart: LineChart<200> = LineChart::new(config);
let mut gfx = Graphics::new_no_rst(&mut display);
loop {
let value = sensor.read().await;
chart.push(value);
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