Pour piloter un synthétiseur DDS AD9850 depuis une page web hébergée sur un Raspberry Pi 3 sous le dernier OS (Raspberry Pi OS Bookworm, qui utilise nativement la bibliothèque lgpio pour les GPIO), voici l’architecture complète, le schéma de flux, le câblage et le code source prêt à l’emploi.
1. Architecture et Flux de Données
Le système repose sur une architecture client-serveur légère où le Raspberry Pi héberge à la fois le serveur web et le contrôleur matériel.

2. Câblage Matériel (Raspberry Pi 3 vers AD9850)
Le module AD9850 est branché en mode série pour économiser les broches GPIO du Raspberry Pi.
| Broche AD9850 | Rôle | Broche Raspberry Pi 3 (BCM) | Numéro de Broche Physique (Header 40 pins) |
| VCC | Alimentation 3.3V / 5V | Pin 5V ou 3.3V | Pin 2 ou Pin 1 |
| GND | Masse | GND | Pin 6 |
| W_CLK | Word Load Clock | GPIO 11 | Pin 23 |
| FQ_UD | Frequency Update | GPIO 8 | Pin 24 |
| DATA | Données Série | GPIO 10 | Pin 19 |
| RESET | Réinitialisation | GPIO 25 | Pin 22 |
Note : La plupart des modules AD9850 du commerce possèdent un oscillateur à quartz de 125 MHz. C’est cette valeur qui est utilisée dans le calcul mathématique de la fréquence.
3. Prérequis et Installation sur le dernier OS (Bookworm)
Sur Raspberry Pi OS Bookworm, la gestion des GPIO a évolué vers lgpio et les environnements Python doivent être virtualisés.
Ouvrez un terminal sur votre Raspberry Pi et exécutez ces commandes :
Bash
# Mettre à jour le système
sudo apt update && sudo apt upgrade -y
# Installer Python 3, pip et l'outil de virtualisation
sudo apt install python3-pip python3-flask python3-lgpio -y
# Créer un dossier pour le projet
mkdir ad9850_web && cd ad9850_web
mkdir templates4. Code Source du Programme
A. Le Backend Python (app.py)
Créez le fichier principal du serveur web :
Python
from flask import Flask, render_template, request, jsonify
import time
import lgpio
app = Flask(__name__)
# Configuration des broches BCM pour Raspberry Pi 3
W_CLK = 11 # GPIO 11 (Pin 23)
FQ_UD = 8 # GPIO 8 (Pin 24)
DATA = 10 # GPIO 10 (Pin 19)
RESET = 25 # GPIO 25 (Pin 22)
# Fréquence de l'oscillateur de l'AD9850 (125 MHz par défaut)
F_CLK = 125000000.0
# Initialisation de la puce lgpio
h = lgpio.gpiochip_open(0)
lgpio.gpio_claim_output(h, W_CLK, 0)
lgpio.gpio_claim_output(h, FQ_UD, 0)
lgpio.gpio_claim_output(h, DATA, 0)
lgpio.gpio_claim_output(h, RESET, 0)
def pulse_pin(pin):
lgpio.gpio_write(h, pin, 0)
lgpio.gpio_write(h, pin, 1)
lgpio.gpio_write(h, pin, 0)
def reset_ad9850():
lgpio.gpio_write(h, RESET, 1)
time.sleep(0.001)
lgpio.gpio_write(h, RESET, 0)
time.sleep(0.001)
pulse_pin(W_CLK)
pulse_pin(FQ_UD)
def set_ad9850_frequency(frequency_hz):
# Calcul du Tuning Word : W = (F_out * 2^32) / F_clk
tuning_word = int((frequency_hz * (2**32)) / F_CLK)
# 40 bits : 32 bits de fréquence + 8 bits de contrôle (0x00)
data = tuning_word & 0xFFFFFFFF
control = 0x00
# Envoi des 32 bits (du LSB au MSB)
for i in range(32):
bit = (data >> i) & 0x01
lgpio.gpio_write(h, DATA, bit)
pulse_pin(W_CLK)
# Envoi des 8 bits de contrôle
for i in range(8):
bit = (control >> i) & 0x01
lgpio.gpio_write(h, DATA, bit)
pulse_pin(W_CLK)
# Validation par FQ_UD
pulse_pin(FQ_UD)
# Initialisation au démarrage du script
reset_ad9850()
@app.route('/')
def index():
return render_template('index.html')
@app.route('/set_freq', methods=['POST'])
def set_freq():
data = request.get_json()
try:
freq = float(data.get('frequency', 1000000)) # Défaut à 1 MHz
if 0 < freq <= 40000000: # Limite sécurisée de l'AD9850 (~40 MHz max conseillé)
set_ad9850_frequency(freq)
return jsonify({"status": "success", "frequency": freq})
else:
return jsonify({"status": "error", "message": "Fréquence hors limites (1 Hz - 40 MHz)"}), 400
except ValueError:
return jsonify({"status": "error", "message": "Valeur invalide"}), 400
if __name__ == '__main__':
# Lance le serveur accessible sur le réseau local du Raspberry Pi sur le port 5000
app.run(host='0.0.0.0', port=5000, debug=False)
B. L’Interface Web (templates/index.html)
Créez le fichier HTML de l’interface dans le dossier templates/ :
HTML
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contrôleur DDS AD9850</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f4f7f6; text-align: center; margin-top: 50px; }
.card { background: white; padding: 30px; border-radius: 10px; box-shadow: 0px 4px 10px rgba(0,0,0,0.1); display: inline-block; }
input { padding: 10px; font-size: 16px; width: 220px; text-align: center; }
button { padding: 10px 20px; font-size: 16px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; }
button:hover { background-color: #0056b3; }
#status { margin-top: 15px; font-weight: bold; }
</style>
</head>
<body>
<div class="card">
<h2>Synthétiseur DDS AD9850</h2>
<p>Pilotez la fréquence de sortie en temps réel</p>
<br>
<input type="number" id="freqInput" value="1000000" min="1" max="40000000" step="1">
<span>Hz</span>
<br><br>
<button onclick="updateFrequency()">Appliquer la Fréquence</button>
<div id="status"></div>
</div>
<script>
async function updateFrequency() {
const freq = document.getElementById('freqInput').value;
const statusDiv = document.getElementById('status');
statusDiv.style.color = "black";
statusDiv.innerText = "Mise à jour en cours...";
try {
const response = await fetch('/set_freq', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ frequency: parseFloat(freq) })
});
const result = await response.json();
if (response.ok) {
statusDiv.style.color = "green";
statusDiv.innerText = `Fréquence réglée à ${result.frequency} Hz avec succès !`;
} else {
statusDiv.style.color = "red";
statusDiv.innerText = `Erreur : ${result.message}`;
}
} catch (error) {
statusDiv.style.color = "red";
statusDiv.innerText = "Erreur de communication avec le Raspberry Pi.";
}
}
</script>
</body>
</html>
5. Lancement et Utilisation
- Démarrez l’application Flask depuis votre terminal :
Bashpython3 app.py - Ouvrez un navigateur web (depuis un ordinateur ou un smartphone connecté au même réseau Wi-Fi/Ethernet que le Raspberry Pi) et accédez à l’adresse IP de votre Pi sur le port 5000 :
Plaintexthttp://<IP_DE_VOTRE_RASPBERRY_PI>:5000 - Saisissez la fréquence désirée en Hertz (ex:
7100000pour 7.1 MHz) et cliquez sur Appliquer la Fréquence pour piloter instantanément votre module AD9850.
Annexe : Ajouter des mots de passe pour se connecter
Pour sécuriser l’accès à la page web et au pilotage du DDS AD9850 avec un système d’authentification multi-utilisateurs (plusieurs identifiants/mots de passe), nous allons utiliser le système de sessions Flask et de hachage sécurisé de mots de passe (werkzeug.security).
Voici les modifications à apporter au projet :
1. Mise à jour du code Backend (app.py)
Remplacez votre fichier app.py par la version suivante. Elle intègre un dictionnaire sécurisé d’utilisateurs, une page de connexion (/login), une déconnexion (/logout) et une protection par décorateur sur les routes sensibles.
Python
from flask import Flask, render_template, request, jsonify, session, redirect, url_for
from werkzeug.security import generate_password_hash, check_password_hash
import time
import lgpio
app = Flask(__name__)
# Clé secrète indispensable pour gérer les sessions de manière sécurisée
app.secret_key = 'votre_cle_secrete_tres_complexe_a_changer'
# --- GESTION DES UTILISATEURS (Multi-login) ---
# Les mots de passe sont hachés pour des raisons de sécurité.
# Exemples de comptes par défaut :
# - admin / MonMotDePasseAdmin123
# - operateur / OpeSecurise2026
USERS = {
"admin": generate_password_hash("MonMotDePasseAdmin123"),
"operateur": generate_password_hash("OpeSecurise2026")
}
# Configuration des broches BCM pour Raspberry Pi 3
W_CLK = 11 # GPIO 11 (Pin 23)
FQ_UD = 8 # GPIO 8 (Pin 24)
DATA = 10 # GPIO 10 (Pin 19)
RESET = 25 # GPIO 25 (Pin 22)
F_CLK = 125000000.0
# Initialisation de la puce lgpio
h = lgpio.gpiochip_open(0)
lgpio.gpio_claim_output(h, W_CLK, 0)
lgpio.gpio_claim_output(h, FQ_UD, 0)
lgpio.gpio_claim_output(h, DATA, 0)
lgpio.gpio_claim_output(h, RESET, 0)
def pulse_pin(pin):
lgpio.gpio_write(h, pin, 0)
lgpio.gpio_write(h, pin, 1)
lgpio.gpio_write(h, pin, 0)
def reset_ad9850():
lgpio.gpio_write(h, RESET, 1)
time.sleep(0.001)
lgpio.gpio_write(h, RESET, 0)
time.sleep(0.001)
pulse_pin(W_CLK)
pulse_pin(FQ_UD)
def set_ad9850_frequency(frequency_hz):
tuning_word = int((frequency_hz * (2**32)) / F_CLK)
data = tuning_word & 0xFFFFFFFF
control = 0x00
for i in range(32):
bit = (data >> i) & 0x01
lgpio.gpio_write(h, DATA, bit)
pulse_pin(W_CLK)
for i in range(8):
bit = (control >> i) & 0x01
lgpio.gpio_write(h, DATA, bit)
pulse_pin(W_CLK)
pulse_pin(FQ_UD)
reset_ad9850()
# --- ROUTES DE CONNEXION ---
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username in USERS and check_password_hash(USERS[username], password):
session['username'] = username
return redirect(url_for('index'))
else:
error = "Identifiant ou mot de passe incorrect."
return render_template('login.html', error=error)
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('login'))
# --- ROUTES PROTÉGÉES ---
@app.route('/')
def index():
if 'username' not in session:
return redirect(url_for('login'))
return render_template('index.html', username=session['username'])
@app.route('/set_freq', methods=['POST'])
def set_freq():
if 'username' not in session:
return jsonify({"status": "error", "message": "Non autorisé"}), 401
data = request.get_json()
try:
freq = float(data.get('frequency', 1000000))
if 0 < freq <= 40000000:
set_ad9850_frequency(freq)
return jsonify({"status": "success", "frequency": freq})
else:
return jsonify({"status": "error", "message": "Fréquence hors limites (1 Hz - 40 MHz)"}), 400
except ValueError:
return jsonify({"status": "error", "message": "Valeur invalide"}), 400
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
2. Création de la page de Connexion (templates/login.html)
Dans votre dossier templates/, créez un nouveau fichier nommé login.html :
HTML
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connexion - Contrôleur DDS AD9850</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f4f7f6; text-align: center; margin-top: 80px; }
.card { background: white; padding: 30px; border-radius: 10px; box-shadow: 0px 4px 10px rgba(0,0,0,0.1); display: inline-block; width: 300px; }
input { padding: 10px; font-size: 16px; width: 90%; margin-bottom: 15px; text-align: center; border: 1px solid #ccc; border-radius: 5px; }
button { padding: 10px 20px; font-size: 16px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; width: 100%; }
button:hover { background-color: #0056b3; }
.error { color: red; margin-bottom: 15px; font-size: 14px; }
</style>
</head>
<body>
<div class="card">
<h2>Connexion requise</h2>
<p>Pilote DDS AD9850</p>
<br>
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
<form method="POST">
<input type="text" name="username" placeholder="Identifiant" required autocomplete="username">
<input type="password" name="password" placeholder="Mot de passe" required autocomplete="current-password">
<button type="submit">Se connecter</button>
</form>
</div>
</body>
</html>
3. Ajout d’un bouton de déconnexion dans l’interface principale (templates/index.html)
Modifiez légèrement votre fichier templates/index.html pour y inclure un message de bienvenue et un bouton de déconnexion dans le coin supérieur (ou sous le titre) :
HTML
<!-- Ajoutez ce bloc en haut de votre carte dans index.html -->
<div style="text-align: right; margin-bottom: 10px;">
<span style="font-size: 14px; color: #555;">Connecté en tant que : <b>{{ username }}</b></span> |
<a href="/logout" style="color: #dc3545; text-decoration: none; font-size: 14px;">Déconnexion</a>
</div>
4. Comment ajouter de nouveaux utilisateurs ?
Pour ajouter d’autres comptes, vous pouvez générer le hachage sécurisé d’un mot de passe directement en Python dans un terminal sur votre Raspberry Pi :
Bash
python3 -c "from werkzeug.security import generate_password_hash; print(generate_password_hash('VotreNouveauMotDePasse'))"
Copiez la chaîne de caractères générée et ajoutez-la dans le dictionnaire USERS du fichier app.py :
Python
USERS = {
"admin": "...",
"operateur": "...",
"nouvel_utilisateur": "SCIENT_HASH_GÉNÉRÉ_ICI..."
}