first commit

This commit is contained in:
Xavier 2022-08-24 08:34:25 +02:00
commit 6978f1cccc
56 changed files with 20147 additions and 0 deletions

BIN
Display.pdf Executable file

Binary file not shown.

275
README.md Executable file
View File

@ -0,0 +1,275 @@
RadioSonde Version 0.9.1
============================
<img src="http://xavier.debert.free.fr/RS/TTGO.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO2.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/TTGO3.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO4.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO5.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO6.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/TTGO7.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO8.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/Web4.png" width="20%"><img src="http://xavier.debert.free.fr/RS/Web.png" width="20%">
<img src="http://xavier.debert.free.fr/RS/Web3.png" width="20%"><img src="http://xavier.debert.free.fr/RS/Web2.png" width="20%">
<img src="http://xavier.debert.free.fr/RS/Web5.png" width="20%"><img src="http://xavier.debert.free.fr/RS/Web6.png" width="20%">
<img src="http://xavier.debert.free.fr/RS/Web7.png" width="20%">
Projet basé sur le travail de DL9RDZ
====================================
Pour TTGO LORA 32 esp32 pico D4 <br>
Décodage de RadioSonde RS41/92 and DFM06/09/17 et M10+/20
Attention à la version de votre TTGO! <br>
vous devez modifier dans config.txt, le port de l'écran OLED <br>
- TTGO v1: SDA=4 SCL=15, RST=16 <br>
- TTGO v2: SDA=21 SCL=22, RST=16
## Version en production 0.9.1 devel 0.9.2
## 0.9.1
Corection RS41 <br>
Correction DFM 06/09 <br>
Add DFM17 <br>
Correction for all trame recived for M10 and M20 1000ms to 1512ms,<br>
Correction formulaire QRG, and end RS no save
## 0.9.0
Add M20
## 0.8.8
Add M10+ <br>
Add Temps restant avant impacte au sol si 99: 0. 0 soit le balon de la sonde n a pas encore éclaté, <br>
ou les informations ne sont pas disponible actuellement <br>
Add Test Buzzer au démarrage "Arche Perdu" Lol pour des chasseurs de sonde!<br>
Compatible Lilygo esp32 GPS inboard pin 34 Rx, 12 Tx
## 0.8.7
correction bug Buzzer Off->On->Off <br>
Add GainLNA RX SX1278FSK on Web config paramètre <br>
Add update OTA Os + DataWeb <br>
correction bugs sondmap.html <br>
correction text upgrade Os et DataWeb <br>
correction texte boussole S et N <br>
correction bugs distance 4928Km si lat et lon =0 erroné <br>
correction bugs fonction Vbat <br>
Add Telemetry width export data.csv <br>
Suppression µSD incompatible avec pin SX1278FSK et SPI <br>
Add transfert Telemetry To µSD on put SD automatic
## 0.8.5
Evolution majeur du système <br>
affichage du pourcentage de la batterie en mode scanning <br>
création d'une fenetre Batterie, Boussole <br>
suppresion lib et code TFT <br>
création Azimute, elevation correction de Bugs majeur , mineur <br>
Ajout fonction Smetre, Buzzer, QTH, Gps on off ... <br>
mise à jour OTA <br>
trop de modification pour toutes les expliciter!
## 0.8.1
modification de la partie Web
## 0.8.0
travail de refonte et réécriture du code
-------------------------------------------------------------------------------
## Les Boutons optionnel à ajouter(souder)
sur les GPIO 1002 et 1004 <br>
attention:
+3.3V--[ SW ]---GPIO----[ R1 ]---/ R1=10 ou 12KOhms
- appuie court <1.5 seconds <br>
- appuie double court 0.5 seconds <br>
- appuie moyen 2-4 seconds <br>
- appuie long >5 seconds
## Buzzer optionnel à ajouter(souder)
sur les GPIO 25 ou 12 suivant le modèl
GPIO --[ BUZZER ]---/
## Wifi configuration
Au démarrage, si aucune connexion possible au wifi paramètré, il monte un Wifi AP<br>
le SSID et mot de passe par défaut est: <b>Radiosonde</b> <br>
en mode AP, il doit être en 192.168.4.1, <br>
mais vous avez aussi la possibilité de mettre http://radiosonde.local dans n'importe quel Wifi
connecté.
## Mode Scanne
Le fichier qrg.txt contient la liste par défaut des cannaux.
pour y configurer les noms, fréquences et mode [M=M10, 6=DFM06, 9=DFM09 et 4=RS41]
## Mode Réception
En réception, une seul fréquence est décodé, les infos de la sonde (ID, GPS, RSSI, ...)
seront affichées dans plusieur fenetre à choisir ( 0 à 6) à configurer dans la page Web
rubrique config.
In receiving mode, a single frequency will be decoded, and sonde info (ID, GPS
coordinates, RSSI) will be displayed. The bar above the IP address indicates,
for the last 18 frames, if reception was successfull (|) or failed (.)
A DOUBLE press will switch to scanning mode.
A SHORT press will switch to the next channel in channels.txt
## Mode Analyseur
Le mode analyseur de spectre (400..406 MHz) est affiché (chaque ligne == 50 kHz)
Pour les cartes TTGO sans bouton configurable, il y a un nouveau paramètre dans config.txt:
- spectrum=10 // 0=off / 1-99 nombre de seconds pour afficher l'analyseur
- timer=1 // 0=off / 1= afficher le compte à rebours du spectre dans l'affichage du spectre
- marker=1 // 0=off / 1= afficher la fréquence dans l'affichage du spectre
## Setup
voir Setup.md pour l'installation!
73
Xavier
RadioSonde Version 0.9.1
============================
<img src="http://xavier.debert.free.fr/RS/TTGO.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO2.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/TTGO3.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO4.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO5.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO6.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/TTGO7.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO8.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/Web4.png" width="20%"><img src="http://xavier.debert.free.fr/RS/Web.png" width="20%">
<img src="http://xavier.debert.free.fr/RS/Web3.png" width="20%"><img src="http://xavier.debert.free.fr/RS/Web2.png" width="20%">
<img src="http://xavier.debert.free.fr/RS/Web5.png" width="20%"><img src="http://xavier.debert.free.fr/RS/Web6.png" width="20%">
<img src="http://xavier.debert.free.fr/RS/Web7.png" width="20%">
Projet basé sur le travail de DL9RDZ
====================================
Pour TTGO LORA 32 esp32 pico D4 <br>
Décodage de RadioSonde RS41/92 and DFM06/09/17 et M10+/20
Attention à la version de votre TTGO! <br>
vous devez modifier dans config.txt, le port de l'écran OLED <br>
- TTGO v1: SDA=4 SCL=15, RST=16 <br>
- TTGO v2: SDA=21 SCL=22, RST=16
## Version en production 0.9.1 devel 0.9.2
## 0.9.1
Corection RS41 <br>
Correction DFM 06/09 <br>
Add DFM17 <br>
Correction for all trame recived for M10 and M20 1000ms to 1512ms,<br>
Correction formulaire QRG, and end RS no save
## 0.9.0
Add M20
## 0.8.8
Add M10+ <br>
Add Temps restant avant impacte au sol si 99: 0. 0 soit le balon de la sonde n a pas encore éclaté, <br>
ou les informations ne sont pas disponible actuellement <br>
Add Test Buzzer au démarrage "Arche Perdu" Lol pour des chasseurs de sonde!<br>
Compatible Lilygo esp32 GPS inboard pin 34 Rx, 12 Tx
## 0.8.7
correction bug Buzzer Off->On->Off <br>
Add GainLNA RX SX1278FSK on Web config paramètre <br>
Add update OTA Os + DataWeb <br>
correction bugs sondmap.html <br>
correction text upgrade Os et DataWeb <br>
correction texte boussole S et N <br>
correction bugs distance 4928Km si lat et lon =0 erroné <br>
correction bugs fonction Vbat <br>
Add Telemetry width export data.csv <br>
Suppression µSD incompatible avec pin SX1278FSK et SPI <br>
Add transfert Telemetry To µSD on put SD automatic
## 0.8.5
Evolution majeur du système <br>
affichage du pourcentage de la batterie en mode scanning <br>
création d'une fenetre Batterie, Boussole <br>
suppresion lib et code TFT <br>
création Azimute, elevation correction de Bugs majeur , mineur <br>
Ajout fonction Smetre, Buzzer, QTH, Gps on off ... <br>
mise à jour OTA <br>
trop de modification pour toutes les expliciter!
## 0.8.1
modification de la partie Web
## 0.8.0
travail de refonte et réécriture du code
-------------------------------------------------------------------------------
## Les Boutons optionnel à ajouter(souder)
sur les GPIO 1002 et 1004 <br>
attention:
+3.3V--[ SW ]---GPIO----[ R1 ]---/ R1=10 ou 12KOhms
- appuie court <1.5 seconds <br>
- appuie double court 0.5 seconds <br>
- appuie moyen 2-4 seconds <br>
- appuie long >5 seconds
## Buzzer optionnel à ajouter(souder)
sur les GPIO 25 ou 12 suivant le modèl
GPIO --[ BUZZER ]---/
## Wifi configuration
Au démarrage, si aucune connexion possible au wifi paramètré, il monte un Wifi AP<br>
le SSID et mot de passe par défaut est: <b>Radiosonde</b> <br>
en mode AP, il doit être en 192.168.4.1, <br>
mais vous avez aussi la possibilité de mettre http://radiosonde.local dans n'importe quel Wifi
connecté.
## Mode Scanne
Le fichier qrg.txt contient la liste par défaut des cannaux.
pour y configurer les noms, fréquences et mode [M=M10, 6=DFM06, 9=DFM09 et 4=RS41]
## Mode Réception
En réception, une seul fréquence est décodé, les infos de la sonde (ID, GPS, RSSI, ...)
seront affichées dans plusieur fenetre à choisir ( 0 à 6) à configurer dans la page Web
rubrique config.
In receiving mode, a single frequency will be decoded, and sonde info (ID, GPS
coordinates, RSSI) will be displayed. The bar above the IP address indicates,
for the last 18 frames, if reception was successfull (|) or failed (.)
A DOUBLE press will switch to scanning mode.
A SHORT press will switch to the next channel in channels.txt
## Mode Analyseur
Le mode analyseur de spectre (400..406 MHz) est affiché (chaque ligne == 50 kHz)
Pour les cartes TTGO sans bouton configurable, il y a un nouveau paramètre dans config.txt:
- spectrum=10 // 0=off / 1-99 nombre de seconds pour afficher l'analyseur
- timer=1 // 0=off / 1= afficher le compte à rebours du spectre dans l'affichage du spectre
- marker=1 // 0=off / 1= afficher la fréquence dans l'affichage du spectre
## Setup
voir Setup.md pour l'installation!
73
Xavier

2615
RadioSonde_FSK/RadioSonde_FSK.ino Executable file

File diff suppressed because it is too large Load Diff

110
RadioSonde_FSK/data/config.txt Executable file
View File

@ -0,0 +1,110 @@
#-------------------------------#
# Hardware depending settings
#-------------------------------#
# pin: 255=disabled; x=button x+128=touch button
#button_pin=2
#button2_pin=4
# No specification in config file: try autodetection (gpio4 pin level at startup)
#button_pin=0
#button2_pin=255
#button2_axp=0
# LED port
#led_pout=-1
# OLED Setup is depending on hardware of LoRa board
# TTGO v1: SDA=4 SCL=15, RST=16
# TTGO v2: SDA=21 SCL=22, RST=16
# T-BEAM, OLED: SDA=21, SCL=22, RST=16
# T-BEAM, ILI9225: SDA=4, CLK=21, RS=2, RST=22, CS=0
# No specification in config file: try autodetection (gpio4 pin level at startup)
#
# disptype: 0=OLED, 1=ILI9225
#disptype=0
#oled_sda=21
#oled_scl=22
#oled_rst=16
oled_orient=1
gpsOn=0
gps_rxd=-1
gps_txd=-1
gps_lat=43.59169
gps_lon=7.10071
gps_alt=123
# Show AFC value (for RS41 only, maybe also DFM, but useful for RS92 or M10)
# showafc=1
# Frequency correction, in Hz
# freqofs=0
#-------------------------------#
# General config settings
#-------------------------------#
maxsonde=20
debug=0
# wifi mode: 1=client in background; 2=AP in background; 3=client on startup, ap if failure
wifi=3
# TCP/IP KISS TNC in port 14590 for APRSdroid (0=disabled, 1=enabled)
kisstnc.active = 1
mdnsname=radiosonde
# display configuration. List of "displays"
# first entry: "Scanner" display
# second entry: default "Receiver" display
# additional entries: alternative receiver display, activated by button
display=0,1,2,3,4,5,6
# set to -1 to disable (used for "N" values in timers in screens.txt). Value in seconds
norx_timeout=20
vbatmax=1.86
vbatmin=1.64
telemetryOn=0
buzzerOn=0
buzzerPort=12
buzzerFreq=700
dbsmetre=1
gainLNA=0
#-------------------------------#
# Spectrum display settings
#-------------------------------#
startfreq=400
channelbw=10
spectrum=30 #10
noisefloor=-125
marker=1
#-------------------------------#
# APRS settings
#-------------------------------#
call=FR2013
passcode=14003
#-------------------------------#
# Sonde specific settings: bandwidth
# valid values: 3100, 3900, 5200, 6300, 7800, 10400, 12500,
# 15600, 20800, 25000, ...
# other values will be rounded up to the next valid value
# rs92.alt2d: default altitude used by RS92 decoder if only 3 sats available
#-------------------------------#
rs41.agcbw=12500
rs41.rxbw=6300
rs92.rxbw=12500
rs92.alt2d=480
dfm.agcbw=20800
dfm.rxbw=10400
#-------------------------------#
# axudp for sending to aprsmap
#-------------------------------#
# local use only, do not feed to public services
# data not sanitized / quality checked, outliers not filtered out
axudp.active=1
axudp.host=fra1od.fr.to
axudp.port=20350
axudp.symbol=/O
axudp.highrate=1
axudp.idformat=0
#-------------------------------#
# maybe some time in the future
#-------------------------------#
# currently simply not implemented, no need to put anything here anyway
tcp.active=1
tcp.host=fra1od.fr.to
tcp.port=14580
tcp.symbol=/O
tcp.highrate=20
tcp.idformat=0
#-------------------------------#
# EOF
#-------------------------------#

Binary file not shown.

Binary file not shown.

181
RadioSonde_FSK/data/index.html Executable file
View File

@ -0,0 +1,181 @@
<!DOCTYPE html>
<html>
<head>
<title>RadioSonde (version %VERSION_ID%)</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body style="background-color: #008CBA;">
<div style="background-color: #FFFFFF;">
<h2><i onclick="w3_open()" class="fa fa-bars"></i>&nbsp;&nbsp;RadioSonde (version %VERSION_ID%)</h2>
</div>
<!--
<p>GPIO state: <strong> %STATE%</strong></p>
<p><a href="/on"><button class="button">ON</button></a></p>
<p><a href="/off"><button class="button button2">OFF</button></a></p>
-->
<!-- Side Navigation -->
<nav style="text-align: left; background-color: #aaa; font-weight: bold; display:none" id="mySidebar">
<h3>MENU</h3>
<button class="tablinks fa fa-close" onclick="w3_close();"> Close</button><br>
<button class="tablinks fa fa-dashboard" onclick="selTab(event,'QRG'); w3_close()" id="defaultTab"> QRG</button><br>
<button class="tablinks fa fa-wifi" onclick="selTab(event,'WiFi'); w3_close()"> WiFi</button><br>
<button class="tablinks fa fa-database" onclick="selTab(event,'Data'); w3_close()"> Data</button><br>
<button class="tablinks fa fa-map-marker" onclick="selTab(event,'SondeMap'); w3_close()"> URL SondeMap</button><br>
<button class="tablinks fa fa-gears" onclick="selTab(event,'Config'); w3_close()"> Config</button><br>
<button class="tablinks fa fa-sliders" onclick="selTab(event,'Control'); w3_close()"> Control</button><br>
<button class="tablinks fa fa-download" onclick="selTab(event,'Update'); w3_close()"> Update</button><br>
<button class="tablinks fa fa-file" onclick="selTab(event,'Telemetry'); w3_close()"> Telemetry</button><br>
<button class="tablinks fa fa-support" onclick="selTab(event,'About'); w3_close()"> About</button><br><br><br><br>
</nav>
<div id="QRG" class="tabcontent">
<h3> QRG - Setup</h3>
<iframe src="qrg.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div>
<div id="WiFi" class="tabcontent">
<h3> WiFi - Settings</h3>
<iframe src="wifi.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div>
<div id="Data" class="tabcontent" data-src="status.html">
<h3>Data</h3>
<iframe src="status.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div>
<div id="SondeMap" class="tabcontent" data-src="sondemap.html">
<iframe src="sondemap.html" style="border:none;" width="98%%" height="98%%"></iframe>
</div>
<div id="Config" class="tabcontent">
<h3>Configuration</h3>
<iframe src="config.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div>
<div id="Control" class="tabcontent">
<h3>Control</h3>
<iframe src="control.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div>
<div id="Update" class="tabcontent">
<h3>Update</h3>
<iframe src="update.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div>
<div id="Telemetry" class="tabcontent">
<h3>Telemetry</h3>
<button onclick="javascript:window.open('/download','_self');">Telemetry</button>
</div>
<div id="About" class="tabcontent">
<h3>About</h3>
%VERSION_NAME%<br>
Copyright &copy; 2019 by Hansi Reiser, DL9RDZ<br>
(version %VERSION_ID%)<br>
with mods by <a href="https://www.dl2mf.de/" target="_blank">Meinhard Guenther, DL2MF</a><br>
<br>
Autodetect info: %AUTODETECT_INFO%<br>
<br>
CopyLeft 2020 Modifier par <a href="http://openpmr.cla.fr">FRS2013</a> & Vigor<br>
Licence GNU GPL <a href="https://www.gnu.org/licenses/gpl-2.0.txt">https://www.gnu.org/licenses/gpl-2.0.txt</a>
for details
</div>
<div style="background-color: #008CBA;"><br>
<h2>by FRS2013</h2>
</div>
<script>
function selTab(evt, id) {
var i, tabcontent, tablinks;
tabcontent=document.getElementsByClassName("tabcontent");
for(i=0; i<tabcontent.length; i++) {
tabcontent[i].style.display = "none";
var link = tabcontent[i].dataset.src;
if(link) {
var iframe = tabcontent[i].getElementsByTagName("iframe")[0];
iframe.setAttribute("src", "");
}
}
tablinks=document.getElementsByClassName("tablinks");
for(i=0; i<tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
var act = document.getElementById(id);
act.style.display = "block";
evt.currentTarget.className += " active";
var link = act.dataset.src;
if(link) {
var iframe = act.getElementsByTagName("iframe")[0];
iframe.setAttribute("src", link);
}
}
document.getElementById("defaultTab").click();
</script>
<!-- Script for Sidebar, Tabs, Accordions, Progress bars and slideshows -->
<script>
// Side navigation
function w3_open() {
var x = document.getElementById("mySidebar");
x.style.width = "100%";
x.style.height = "80%";
x.style.fontSize = "40px";
x.style.paddingTop = "0%";
x.style.display = "block";
}
function w3_close() {
document.getElementById("mySidebar").style.display = "none";
}
// Tabs
function openCity(evt, cityName) {
var i;
var x = document.getElementsByClassName("city");
for (i = 0; i < x.length; i++) {
x[i].style.display = "none";
}
var activebtn = document.getElementsByClassName("testbtn");
for (i = 0; i < x.length; i++) {
activebtn[i].className = activebtn[i].className.replace(" w3-dark-grey", "");
}
document.getElementById(cityName).style.display = "block";
evt.currentTarget.className += " w3-dark-grey";
}
var mybtn = document.getElementsByClassName("testbtn")[0];
mybtn.click();
// Accordions
function myAccFunc(id) {
var x = document.getElementById(id);
if (x.className.indexOf("w3-show") == -1) {
x.className += " w3-show";
} else {
x.className = x.className.replace(" w3-show", "");
}
}
// Slideshows
var slideIndex = 1;
function plusDivs(n) {
slideIndex = slideIndex + n;
showDivs(slideIndex);
}
function showDivs(n) {
var x = document.getElementsByClassName("mySlides");
if (n > x.length) {slideIndex = 1}
if (n < 1) {slideIndex = x.length} ;
for (i = 0; i < x.length; i++) {
x[i].style.display = "none";
}
x[slideIndex-1].style.display = "block";
}
showDivs(1);
</script>
</body>
</html>

View File

@ -0,0 +1,6 @@
Radiosonde
Radiosonde
Terreandro
terrexavier
TERREWIFI
terrexavier

24
RadioSonde_FSK/data/qrg.txt Executable file
View File

@ -0,0 +1,24 @@
# Frequency in Mhz (format nnn.nnn)
# Type (4=RS41, R=RS92, 6=DFM normal, DFM-06, 9=DFM inverted, DFM-09, D=DFM, M=M10, 2=M20)
#
400.000 9 + Test(FR)
401.500 4 - Santander(ES)
402.000 M + Nimes(FR)
402.800 4 + Cuneo(IT)
403.000 6 + Ajactio(FR)
403.010 6 + Canjuers(FR)
404.200 4 + Rome(IT)
404.500 4 - Payern(CH)
404.500 M - Bourges(FR)
404.789 9 + Frejus1(FR)
404.800 4 + Milan(IT)
405.000 R + Frejus2(FR)
401.199 M - Trappes(FR)
405.789 9 + Pegomas(FR)
405.100 4 - Lindenberg
405.700 4 - Bergen
405.900 4 - Bergen_2
405.100 4 - Meppen_2
405.300 4 - Essen
405.500 2 - Essen_2
# end

251
RadioSonde_FSK/data/screens.txt Executable file
View File

@ -0,0 +1,251 @@
# Definition of display content and action behaviour
#
# Timer: (view timer, rx timer, norx timer)
# - value -1: timer is disabled; value>=0: timer fires after (value) seconds
# - view timer: time since current view (display mode and sonde) was started
# - rx timer: time since when sonde data has been received continuously (trigger immediatly after RX)
# - norx timer: time since when no sonde data has been received continuously
# (rx and norx timer is started after tuning a new frequency and receiving a signal or not receiving
# anything for a 1s period)
#
# Actions:
# - W: activate WiFi scan
# - F: activate frequency spectrum display
# - 0: activate "Scan:" display (this is basically just display mode 0)
# - x: (1..N): activate display mode x [deprecated]
# - >: activate next display mode
# - D: activate default receiver display (display mode specified in config)
# - +: advance to next active sonde from QRG config
# - #: no action
#
# Display content (lower/upper case: small/large font)
# line,column=content
# for ILI9225 its also possible to indicate
# line,column,width=content for text within a box of width 'width'
# line,column,-width=content for right-justified text
#
# XText : Text
# F(suffix): frequency (with suffix, e.g., " MHz")
# L latitade
# O lOngitute
# A altitude
# Hm(suffix) hor. speed m/s (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Hk(suffix) hor. speed km/h (suffix: e.g. "km/h"; no suffix=>km/h as 16x8 bitmap for SSD1306 display only)
# V(suffix) vert. speef (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Ix sonde ID (dfm format by x: d=>dxlaprs, a=>autorx, s=>real serial number)
# Q signal quality statistics bar
# T type string (RS41/DFM9/DFM6/RS92)
# C afC value
# N ip address (only tiny font)
# S scan list entry info: l/empty: launch site name, #=entry nr, t=total entries, a=active entries, /: #/t
# K RS41 kill timer values: Kl launch timer, Kb burst timer, Kc kill countdown
# format: K_4: h:mm k_6: h:mm:ss k_s: sssss, nothing shown for other sonde
# Mx telemetry value x (t temp p preassure h hyg) [not yet implemented, maybe some day in future]
# Gx GPS-related data
# raw data from GPS: GA, GO, GH, GC: LAtitude, lOngitude, Altutide(Height), Course over ground
# relative to sonde: GD, GI, GB: Distance, dIrection (absolute), relative Bearing
# G0 GPS circle diagram e.g. 3,5=g0NCS,50,ff0000,000033,5,ffff00,4,ffffff
# "N" (what is on top: N=north C=course)
# "C" (where does the arrow point to: C=course, S=sonde)
# "S" (what is shown by the bullet: C=course, S=sonde)
# 50: circle radius, followed by fg and bg color
# 5: bullet radius, followed by fg color
# 4: arrow width, followed by fg color
# R RSSI
# B battery(T-Beam 1.0) S=status V=Batt.Volt C=charge current D=discharge current
# U=USB volt I=USB current T=IC temp
# AA Azimut degres
# Z duree vol H:m.s
# fonts=x,y can be used to select font (x=small, y=large) for all items below
# for SSD1306, x and y can be used to select one of those fonts:
# (y should be a 1x2 font (1,5,6,7), x a small font)
# u8x8_font_chroma48medium8_r, // 0 ** default small
# u8x8_font_7x14_1x2_f, // 1 ** default large
# u8x8_font_amstrad_cpc_extended_f, // 2
# u8x8_font_5x7_f, // 3
# u8x8_font_5x8_f, // 4
# u8x8_font_8x13_1x2_f, // 5
# u8x8_font_8x13B_1x2_f, // 6
# u8x8_font_7x14B_1x2_f, // 7
# u8x8_font_artossans8_r, // 8
# u8x8_font_artosserif8_r, // 9
# u8x8_font_torussansbold8_r, // 10
# u8x8_font_victoriabold8_r, // 11
# u8x8_font_victoriamedium8_r, // 12
# u8x8_font_pressstart2p_f, // 13
# u8x8_font_pcsenior_f, // 14
# u8x8_font_pxplusibmcgathin_f, // 15
# u8x8_font_pxplusibmcga_f, // 16
# u8x8_font_pxplustandynewtv_f, // 17
#
#
# color=rrggbb,rrggbb can be used to select color (foreground, background)
# see https://github.com/Nkawu/TFT_22_ILI9225/wiki#color-reference for example (use without "#"-sign)
#
# for TFT display, coordinates and width are multiplied by xscale,yscale and later used in pixels
# with scale=1,1 you can directly use pixel coordinates. (default: xscale=13,yscale=22 => 8 lines, 16 columns)
###########
#
# Default configuration for "Scanner" display:
# - view timer disabled; rx timer=0; norx timer = 0
# => after 1 second immediately an action is triggered
# (norx: go to next sonde; rx: go to default receiver display)
# - key1 actions: D,0,F,W
# => Button press activates default receiver view, double press does nothing
# Mid press activates Spectrum display, long press activates Wifi scan
# - key2 has no function
@Scanner
timer=-1,0,0
key1action=D,#,F,W
key2action=#,#,#,#
timeaction=#,D,+
0,0=XScan
0,5=S#:
0,9=T
3,0=F MHz
5,0=S
7,0=y%
7,5=n
############
# Default configuration for "Legacy" display:
# - view timer=-1, rx timer=-1 (disabled); norx timer=20 (or -1 for "old" behaviour)
# => norx timer fires after not receiving a singla for 20 seconds
# - key1 actions: +,0,F,W
# => Button1 press: next sonde; double press => @Scanner display
# => Mid press activates Spectrum display, long press activates Wifi scan
# - key2 actions: 2,#,#,#
# => BUtton2 activates display 2 (@Field)
# - timer actions: #,#,0
# (norx timer: if no signal for >20 seconds: go back to scanner mode)
#
@Legacy
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
0,0=t
0,5=f MHz
1,0=is
#1,8=c
1,8=z
2,0=L
2,10=a
3,10=h
4,0=O
4,9=v
5,9=gC
5,13=gB
6,0=R
6,7=Q
7,6=gD
7,12=gI°
############
# Configuratoon for "Field" display (display 2)
# similar to @Legacy, but no norx timer, and Key2 goes to display 4
@Field
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=Is
2,0=L
2,9=xEl:
2,12=gE
4,0=O
3,10=h
4,9=v
5,9=gC
5,13=gB
6,0=A
6,7=Q
7,6=gD
7,12=gI°
############
# Configuration for "Field2" display (display 3)
# similar to @Field
@Field2
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=Is
0,9=f
1,12=t
2,9=xEl:
2,12=gE
2,0=L
3,10=h
4,0=O
4,9=v
5,9=gC
5,13=gB
6,0=A
6,7=Q
7,6=gD
7,12=gI°
#############
# Configuration for "GPS" display
# not yet the final version, just for testing
@GPSDIST
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=Is
0,9=f
1,12=t
2,0=L
4,0=O
2,10=a
3,10=h
4,9=v
5,9=gC
5,13=gB
6,0=xEl:
6,2=gE
6,7=Q
7,0=gW
7,2=xd=
7,4=gD
7,11=gI°
############
# Boussole Oled
@BOUSSOLE
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=g0
0,10=a
1,10=h
2,9=v
3,10=gC
3,13=gB
4,9=gD
5,11=gI°
6,9=xEl:
6,11=gE
7,9=t
############
@BatteryOLED
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xBat.Status:
2,0=xCpu:
2,7=bVV
4,0=xVbus:
4,7=bCV
6,0=xPower:
6,7=bPmW

View File

@ -0,0 +1,10 @@
<html>
<head>
<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">
</head>
<body>
<button class="tablinks" onclick="javascript:window.open('http://wx.dl2mf.de/','_blank')"> Sonde Map</button><br><br>
<button class="tablinks" onclick="javascript:window.open('http://radiosondy.info/','_self')"> RadioSondy</button><br><br>
<button class="tablinks" onclick="javascript:window.open('http://tracker.sondehub.org/','_blank')"> SondeHub</button><br><br>
</body>
</html>

659
RadioSonde_FSK/data/style.css Executable file
View File

@ -0,0 +1,659 @@
body, html {
height: 100%;
margin: 0;
font-family: Arial;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
background-color: #ddd
}
td#caption {
text-align: center;
background-color: #aaa;
font-weight: bold;
}
td#sfreq {
background-color: #ccc;
}
.tab {
overflow: hidden;
border: 1px solid #ccc;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
.tab button:hover {
background-color: #ddd;
}
.tab button.active {
background-color: #ccc;
}
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
height: 100%;
background-color: #FFFFFF;
}
html {
font-family: Helvetica;
display: inline-block;
margin: 0px auto;
text-align: center;
}
h1{
color: #0F3376;
padding: 2vh;
}
p{
font-size: 1.5rem;
}
.button {
display: inline-block;
background-color: #008CBA;
border: none;
border-radius: 4px;
color: white;
padding: 16px 40px;
text-decoration: none;
font-size: 30px;
margin: 2px;
cursor: pointer;
}
.button2 {
background-color: #f44336;
}
/*!
* Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/@font-face{font-family:'FontAwesome';
src:url('fontawesome-webfont.eot?v=4.3.0');
src:url('fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),url('fontawesome-webfont.woff2?v=4.3.0') format('woff2'),url('fontawesome-webfont.woff?v=4.3.0') format('woff'),url('fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');
font-weight:normal;
font-style:normal}.fa{display:inline-block;
font:normal normal normal 14px/1 FontAwesome;
font-size:inherit;
text-rendering:auto;
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
transform:translate(0, 0)}.fa-lg{font-size:1.33333333em;
line-height:.75em;
vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;
text-align:center}.fa-ul{padding-left:0;
margin-left:2.14285714em;
list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;
left:-2.14285714em;
width:2.14285714em;
top:.14285714em;
text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;
border:solid .08em #eee;
border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;
animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);
animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);
transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);
transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);
transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);
transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
-webkit-transform:rotate(90deg);
-ms-transform:rotate(90deg);
transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
-webkit-transform:rotate(180deg);
-ms-transform:rotate(180deg);
transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
-webkit-transform:rotate(270deg);
-ms-transform:rotate(270deg);
transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
-webkit-transform:scale(-1, 1);
-ms-transform:scale(-1, 1);
transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
-webkit-transform:scale(1, -1);
-ms-transform:scale(1, -1);
transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;
display:inline-block;
width:2em;
height:2em;
line-height:2em;
vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;
left:0;
width:100%;
text-align:center}
.fa-stack-1x{line-height:inherit}
.fa-stack-2x{font-size:2em}
.fa-inverse{color:#fff}
.fa-glass:before{content:"\f000"}
.fa-music:before{content:"\f001"}
.fa-search:before{content:"\f002"}
.fa-envelope-o:before{content:"\f003"}
.fa-heart:before{content:"\f004"}
.fa-star:before{content:"\f005"}
.fa-star-o:before{content:"\f006"}
.fa-user:before{content:"\f007"}
.fa-film:before{content:"\f008"}
.fa-th-large:before{content:"\f009"}
.fa-th:before{content:"\f00a"}
.fa-th-list:before{content:"\f00b"}
.fa-check:before{content:"\f00c"}
.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}
.fa-search-plus:before{content:"\f00e"}
.fa-search-minus:before{content:"\f010"}
.fa-power-off:before{content:"\f011"}
.fa-signal:before{content:"\f012"}
.fa-gear:before,.fa-cog:before{content:"\f013"}
.fa-trash-o:before{content:"\f014"}
.fa-home:before{content:"\f015"}
.fa-file-o:before{content:"\f016"}
.fa-clock-o:before{content:"\f017"}
.fa-road:before{content:"\f018"}
.fa-download:before{content:"\f019"}
.fa-arrow-circle-o-down:before{content:"\f01a"}
.fa-arrow-circle-o-up:before{content:"\f01b"}
.fa-inbox:before{content:"\f01c"}
.fa-play-circle-o:before{content:"\f01d"}
.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}
.fa-refresh:before{content:"\f021"}
.fa-list-alt:before{content:"\f022"}
.fa-lock:before{content:"\f023"}
.fa-flag:before{content:"\f024"}
.fa-headphones:before{content:"\f025"}
.fa-volume-off:before{content:"\f026"}
.fa-volume-down:before{content:"\f027"}
.fa-volume-up:before{content:"\f028"}
.fa-qrcode:before{content:"\f029"}
.fa-barcode:before{content:"\f02a"}
.fa-tag:before{content:"\f02b"}
.fa-tags:before{content:"\f02c"}
.fa-book:before{content:"\f02d"}
.fa-bookmark:before{content:"\f02e"}
.fa-print:before{content:"\f02f"}
.fa-camera:before{content:"\f030"}
.fa-font:before{content:"\f031"}
.fa-bold:before{content:"\f032"}
.fa-italic:before{content:"\f033"}
.fa-text-height:before{content:"\f034"}
.fa-text-width:before{content:"\f035"}
.fa-align-left:before{content:"\f036"}
.fa-align-center:before{content:"\f037"}
.fa-align-right:before{content:"\f038"}
.fa-align-justify:before{content:"\f039"}
.fa-list:before{content:"\f03a"}
.fa-dedent:before,.fa-outdent:before{content:"\f03b"}
.fa-indent:before{content:"\f03c"}
.fa-video-camera:before{content:"\f03d"}
.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}
.fa-pencil:before{content:"\f040"}
.fa-map-marker:before{content:"\f041"}
.fa-adjust:before{content:"\f042"}
.fa-tint:before{content:"\f043"}
.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}
.fa-share-square-o:before{content:"\f045"}
.fa-check-square-o:before{content:"\f046"}
.fa-arrows:before{content:"\f047"}
.fa-step-backward:before{content:"\f048"}
.fa-fast-backward:before{content:"\f049"}
.fa-backward:before{content:"\f04a"}
.fa-play:before{content:"\f04b"}
.fa-pause:before{content:"\f04c"}
.fa-stop:before{content:"\f04d"}
.fa-forward:before{content:"\f04e"}
.fa-fast-forward:before{content:"\f050"}
.fa-step-forward:before{content:"\f051"}
.fa-eject:before{content:"\f052"}
.fa-chevron-left:before{content:"\f053"}
.fa-chevron-right:before{content:"\f054"}
.fa-plus-circle:before{content:"\f055"}
.fa-minus-circle:before{content:"\f056"}
.fa-times-circle:before{content:"\f057"}
.fa-check-circle:before{content:"\f058"}
.fa-question-circle:before{content:"\f059"}
.fa-info-circle:before{content:"\f05a"}
.fa-crosshairs:before{content:"\f05b"}
.fa-times-circle-o:before{content:"\f05c"}
.fa-check-circle-o:before{content:"\f05d"}
.fa-ban:before{content:"\f05e"}
.fa-arrow-left:before{content:"\f060"}
.fa-arrow-right:before{content:"\f061"}
.fa-arrow-up:before{content:"\f062"}
.fa-arrow-down:before{content:"\f063"}
.fa-mail-forward:before,.fa-share:before{content:"\f064"}
.fa-expand:before{content:"\f065"}
.fa-compress:before{content:"\f066"}
.fa-plus:before{content:"\f067"}
.fa-minus:before{content:"\f068"}
.fa-asterisk:before{content:"\f069"}
.fa-exclamation-circle:before{content:"\f06a"}
.fa-gift:before{content:"\f06b"}
.fa-leaf:before{content:"\f06c"}
.fa-fire:before{content:"\f06d"}
.fa-eye:before{content:"\f06e"}
.fa-eye-slash:before{content:"\f070"}
.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}
.fa-plane:before{content:"\f072"}
.fa-calendar:before{content:"\f073"}
.fa-random:before{content:"\f074"}
.fa-comment:before{content:"\f075"}
.fa-magnet:before{content:"\f076"}
.fa-chevron-up:before{content:"\f077"}
.fa-chevron-down:before{content:"\f078"}
.fa-retweet:before{content:"\f079"}
.fa-shopping-cart:before{content:"\f07a"}
.fa-folder:before{content:"\f07b"}
.fa-folder-open:before{content:"\f07c"}
.fa-arrows-v:before{content:"\f07d"}
.fa-arrows-h:before{content:"\f07e"}
.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}
.fa-twitter-square:before{content:"\f081"}
.fa-facebook-square:before{content:"\f082"}
.fa-camera-retro:before{content:"\f083"}
.fa-key:before{content:"\f084"}
.fa-gears:before,.fa-cogs:before{content:"\f085"}
.fa-comments:before{content:"\f086"}
.fa-thumbs-o-up:before{content:"\f087"}
.fa-thumbs-o-down:before{content:"\f088"}
.fa-star-half:before{content:"\f089"}
.fa-heart-o:before{content:"\f08a"}
.fa-sign-out:before{content:"\f08b"}
.fa-linkedin-square:before{content:"\f08c"}
.fa-thumb-tack:before{content:"\f08d"}
.fa-external-link:before{content:"\f08e"}
.fa-sign-in:before{content:"\f090"}
.fa-trophy:before{content:"\f091"}
.fa-github-square:before{content:"\f092"}
.fa-upload:before{content:"\f093"}
.fa-lemon-o:before{content:"\f094"}
.fa-phone:before{content:"\f095"}
.fa-square-o:before{content:"\f096"}
.fa-bookmark-o:before{content:"\f097"}
.fa-phone-square:before{content:"\f098"}
.fa-twitter:before{content:"\f099"}
.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}
.fa-github:before{content:"\f09b"}
.fa-unlock:before{content:"\f09c"}
.fa-credit-card:before{content:"\f09d"}
.fa-rss:before{content:"\f09e"}
.fa-hdd-o:before{content:"\f0a0"}
.fa-bullhorn:before{content:"\f0a1"}
.fa-bell:before{content:"\f0f3"}
.fa-certificate:before{content:"\f0a3"}
.fa-hand-o-right:before{content:"\f0a4"}
.fa-hand-o-left:before{content:"\f0a5"}
.fa-hand-o-up:before{content:"\f0a6"}
.fa-hand-o-down:before{content:"\f0a7"}
.fa-arrow-circle-left:before{content:"\f0a8"}
.fa-arrow-circle-right:before{content:"\f0a9"}
.fa-arrow-circle-up:before{content:"\f0aa"}
.fa-arrow-circle-down:before{content:"\f0ab"}
.fa-globe:before{content:"\f0ac"}
.fa-wrench:before{content:"\f0ad"}
.fa-tasks:before{content:"\f0ae"}
.fa-filter:before{content:"\f0b0"}
.fa-briefcase:before{content:"\f0b1"}
.fa-arrows-alt:before{content:"\f0b2"}
.fa-group:before,.fa-users:before{content:"\f0c0"}
.fa-chain:before,.fa-link:before{content:"\f0c1"}
.fa-cloud:before{content:"\f0c2"}
.fa-flask:before{content:"\f0c3"}
.fa-cut:before,.fa-scissors:before{content:"\f0c4"}
.fa-copy:before,.fa-files-o:before{content:"\f0c5"}
.fa-paperclip:before{content:"\f0c6"}
.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}
.fa-square:before{content:"\f0c8"}
.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}
.fa-list-ul:before{content:"\f0ca"}
.fa-list-ol:before{content:"\f0cb"}
.fa-strikethrough:before{content:"\f0cc"}
.fa-underline:before{content:"\f0cd"}
.fa-table:before{content:"\f0ce"}
.fa-magic:before{content:"\f0d0"}
.fa-truck:before{content:"\f0d1"}
.fa-pinterest:before{content:"\f0d2"}
.fa-pinterest-square:before{content:"\f0d3"}
.fa-google-plus-square:before{content:"\f0d4"}
.fa-google-plus:before{content:"\f0d5"}
.fa-money:before{content:"\f0d6"}
.fa-caret-down:before{content:"\f0d7"}
.fa-caret-up:before{content:"\f0d8"}
.fa-caret-left:before{content:"\f0d9"}
.fa-caret-right:before{content:"\f0da"}
.fa-columns:before{content:"\f0db"}
.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}
.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}
.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}
.fa-envelope:before{content:"\f0e0"}
.fa-linkedin:before{content:"\f0e1"}
.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}
.fa-legal:before,.fa-gavel:before{content:"\f0e3"}
.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}
.fa-comment-o:before{content:"\f0e5"}
.fa-comments-o:before{content:"\f0e6"}
.fa-flash:before,.fa-bolt:before{content:"\f0e7"}
.fa-sitemap:before{content:"\f0e8"}
.fa-umbrella:before{content:"\f0e9"}
.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}
.fa-lightbulb-o:before{content:"\f0eb"}
.fa-exchange:before{content:"\f0ec"}
.fa-cloud-download:before{content:"\f0ed"}
.fa-cloud-upload:before{content:"\f0ee"}
.fa-user-md:before{content:"\f0f0"}
.fa-stethoscope:before{content:"\f0f1"}
.fa-suitcase:before{content:"\f0f2"}
.fa-bell-o:before{content:"\f0a2"}
.fa-coffee:before{content:"\f0f4"}
.fa-cutlery:before{content:"\f0f5"}
.fa-file-text-o:before{content:"\f0f6"}
.fa-building-o:before{content:"\f0f7"}
.fa-hospital-o:before{content:"\f0f8"}
.fa-ambulance:before{content:"\f0f9"}
.fa-medkit:before{content:"\f0fa"}
.fa-fighter-jet:before{content:"\f0fb"}
.fa-beer:before{content:"\f0fc"}
.fa-h-square:before{content:"\f0fd"}
.fa-plus-square:before{content:"\f0fe"}
.fa-angle-double-left:before{content:"\f100"}
.fa-angle-double-right:before{content:"\f101"}
.fa-angle-double-up:before{content:"\f102"}
.fa-angle-double-down:before{content:"\f103"}
.fa-angle-left:before{content:"\f104"}
.fa-angle-right:before{content:"\f105"}
.fa-angle-up:before{content:"\f106"}
.fa-angle-down:before{content:"\f107"}
.fa-desktop:before{content:"\f108"}
.fa-laptop:before{content:"\f109"}
.fa-tablet:before{content:"\f10a"}
.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}
.fa-circle-o:before{content:"\f10c"}
.fa-quote-left:before{content:"\f10d"}
.fa-quote-right:before{content:"\f10e"}
.fa-spinner:before{content:"\f110"}
.fa-circle:before{content:"\f111"}
.fa-mail-reply:before,.fa-reply:before{content:"\f112"}
.fa-github-alt:before{content:"\f113"}
.fa-folder-o:before{content:"\f114"}
.fa-folder-open-o:before{content:"\f115"}
.fa-smile-o:before{content:"\f118"}
.fa-frown-o:before{content:"\f119"}
.fa-meh-o:before{content:"\f11a"}
.fa-gamepad:before{content:"\f11b"}
.fa-keyboard-o:before{content:"\f11c"}
.fa-flag-o:before{content:"\f11d"}
.fa-flag-checkered:before{content:"\f11e"}
.fa-terminal:before{content:"\f120"}
.fa-code:before{content:"\f121"}
.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}
.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}
.fa-location-arrow:before{content:"\f124"}
.fa-crop:before{content:"\f125"}
.fa-code-fork:before{content:"\f126"}
.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}
.fa-question:before{content:"\f128"}
.fa-info:before{content:"\f129"}
.fa-exclamation:before{content:"\f12a"}
.fa-superscript:before{content:"\f12b"}
.fa-subscript:before{content:"\f12c"}
.fa-eraser:before{content:"\f12d"}
.fa-puzzle-piece:before{content:"\f12e"}
.fa-microphone:before{content:"\f130"}
.fa-microphone-slash:before{content:"\f131"}
.fa-shield:before{content:"\f132"}
.fa-calendar-o:before{content:"\f133"}
.fa-fire-extinguisher:before{content:"\f134"}
.fa-rocket:before{content:"\f135"}
.fa-maxcdn:before{content:"\f136"}
.fa-chevron-circle-left:before{content:"\f137"}
.fa-chevron-circle-right:before{content:"\f138"}
.fa-chevron-circle-up:before{content:"\f139"}
.fa-chevron-circle-down:before{content:"\f13a"}
.fa-html5:before{content:"\f13b"}
.fa-css3:before{content:"\f13c"}
.fa-anchor:before{content:"\f13d"}
.fa-unlock-alt:before{content:"\f13e"}
.fa-bullseye:before{content:"\f140"}
.fa-ellipsis-h:before{content:"\f141"}
.fa-ellipsis-v:before{content:"\f142"}
.fa-rss-square:before{content:"\f143"}
.fa-play-circle:before{content:"\f144"}
.fa-ticket:before{content:"\f145"}
.fa-minus-square:before{content:"\f146"}
.fa-minus-square-o:before{content:"\f147"}
.fa-level-up:before{content:"\f148"}
.fa-level-down:before{content:"\f149"}
.fa-check-square:before{content:"\f14a"}
.fa-pencil-square:before{content:"\f14b"}
.fa-external-link-square:before{content:"\f14c"}
.fa-share-square:before{content:"\f14d"}
.fa-compass:before{content:"\f14e"}
.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}
.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}
.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}
.fa-euro:before,.fa-eur:before{content:"\f153"}
.fa-gbp:before{content:"\f154"}
.fa-dollar:before,.fa-usd:before{content:"\f155"}
.fa-rupee:before,.fa-inr:before{content:"\f156"}
.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}
.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}
.fa-won:before,.fa-krw:before{content:"\f159"}
.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}
.fa-file:before{content:"\f15b"}
.fa-file-text:before{content:"\f15c"}
.fa-sort-alpha-asc:before{content:"\f15d"}
.fa-sort-alpha-desc:before{content:"\f15e"}
.fa-sort-amount-asc:before{content:"\f160"}
.fa-sort-amount-desc:before{content:"\f161"}
.fa-sort-numeric-asc:before{content:"\f162"}
.fa-sort-numeric-desc:before{content:"\f163"}
.fa-thumbs-up:before{content:"\f164"}
.fa-thumbs-down:before{content:"\f165"}
.fa-youtube-square:before{content:"\f166"}
.fa-youtube:before{content:"\f167"}
.fa-xing:before{content:"\f168"}
.fa-xing-square:before{content:"\f169"}
.fa-youtube-play:before{content:"\f16a"}
.fa-dropbox:before{content:"\f16b"}
.fa-stack-overflow:before{content:"\f16c"}
.fa-instagram:before{content:"\f16d"}
.fa-flickr:before{content:"\f16e"}
.fa-adn:before{content:"\f170"}
.fa-bitbucket:before{content:"\f171"}
.fa-bitbucket-square:before{content:"\f172"}
.fa-tumblr:before{content:"\f173"}
.fa-tumblr-square:before{content:"\f174"}
.fa-long-arrow-down:before{content:"\f175"}
.fa-long-arrow-up:before{content:"\f176"}
.fa-long-arrow-left:before{content:"\f177"}
.fa-long-arrow-right:before{content:"\f178"}
.fa-apple:before{content:"\f179"}
.fa-windows:before{content:"\f17a"}
.fa-android:before{content:"\f17b"}
.fa-linux:before{content:"\f17c"}
.fa-dribbble:before{content:"\f17d"}
.fa-skype:before{content:"\f17e"}
.fa-foursquare:before{content:"\f180"}
.fa-trello:before{content:"\f181"}
.fa-female:before{content:"\f182"}
.fa-male:before{content:"\f183"}
.fa-gittip:before,.fa-gratipay:before{content:"\f184"}
.fa-sun-o:before{content:"\f185"}
.fa-moon-o:before{content:"\f186"}
.fa-archive:before{content:"\f187"}
.fa-bug:before{content:"\f188"}
.fa-vk:before{content:"\f189"}
.fa-weibo:before{content:"\f18a"}
.fa-renren:before{content:"\f18b"}
.fa-pagelines:before{content:"\f18c"}
.fa-stack-exchange:before{content:"\f18d"}
.fa-arrow-circle-o-right:before{content:"\f18e"}
.fa-arrow-circle-o-left:before{content:"\f190"}
.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}
.fa-dot-circle-o:before{content:"\f192"}
.fa-wheelchair:before{content:"\f193"}
.fa-vimeo-square:before{content:"\f194"}
.fa-turkish-lira:before,.fa-try:before{content:"\f195"}
.fa-plus-square-o:before{content:"\f196"}
.fa-space-shuttle:before{content:"\f197"}
.fa-slack:before{content:"\f198"}
.fa-envelope-square:before{content:"\f199"}
.fa-wordpress:before{content:"\f19a"}
.fa-openid:before{content:"\f19b"}
.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}
.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}
.fa-yahoo:before{content:"\f19e"}
.fa-google:before{content:"\f1a0"}
.fa-reddit:before{content:"\f1a1"}
.fa-reddit-square:before{content:"\f1a2"}
.fa-stumbleupon-circle:before{content:"\f1a3"}
.fa-stumbleupon:before{content:"\f1a4"}
.fa-delicious:before{content:"\f1a5"}
.fa-digg:before{content:"\f1a6"}
.fa-pied-piper:before{content:"\f1a7"}
.fa-pied-piper-alt:before{content:"\f1a8"}
.fa-drupal:before{content:"\f1a9"}
.fa-joomla:before{content:"\f1aa"}
.fa-language:before{content:"\f1ab"}
.fa-fax:before{content:"\f1ac"}
.fa-building:before{content:"\f1ad"}
.fa-child:before{content:"\f1ae"}
.fa-paw:before{content:"\f1b0"}
.fa-spoon:before{content:"\f1b1"}
.fa-cube:before{content:"\f1b2"}
.fa-cubes:before{content:"\f1b3"}
.fa-behance:before{content:"\f1b4"}
.fa-behance-square:before{content:"\f1b5"}
.fa-steam:before{content:"\f1b6"}
.fa-steam-square:before{content:"\f1b7"}
.fa-recycle:before{content:"\f1b8"}
.fa-automobile:before,.fa-car:before{content:"\f1b9"}
.fa-cab:before,.fa-taxi:before{content:"\f1ba"}
.fa-tree:before{content:"\f1bb"}
.fa-spotify:before{content:"\f1bc"}
.fa-deviantart:before{content:"\f1bd"}
.fa-soundcloud:before{content:"\f1be"}
.fa-database:before{content:"\f1c0"}
.fa-file-pdf-o:before{content:"\f1c1"}
.fa-file-word-o:before{content:"\f1c2"}
.fa-file-excel-o:before{content:"\f1c3"}
.fa-file-powerpoint-o:before{content:"\f1c4"}
.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}
.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}
.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}
.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}
.fa-file-code-o:before{content:"\f1c9"}
.fa-vine:before{content:"\f1ca"}
.fa-codepen:before{content:"\f1cb"}
.fa-jsfiddle:before{content:"\f1cc"}
.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}
.fa-circle-o-notch:before{content:"\f1ce"}
.fa-ra:before,.fa-rebel:before{content:"\f1d0"}
.fa-ge:before,.fa-empire:before{content:"\f1d1"}
.fa-git-square:before{content:"\f1d2"}
.fa-git:before{content:"\f1d3"}
.fa-hacker-news:before{content:"\f1d4"}
.fa-tencent-weibo:before{content:"\f1d5"}
.fa-qq:before{content:"\f1d6"}
.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}
.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}
.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}
.fa-history:before{content:"\f1da"}
.fa-genderless:before,.fa-circle-thin:before{content:"\f1db"}
.fa-header:before{content:"\f1dc"}
.fa-paragraph:before{content:"\f1dd"}
.fa-sliders:before{content:"\f1de"}
.fa-share-alt:before{content:"\f1e0"}
.fa-share-alt-square:before{content:"\f1e1"}
.fa-bomb:before{content:"\f1e2"}
.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}
.fa-tty:before{content:"\f1e4"}
.fa-binoculars:before{content:"\f1e5"}
.fa-plug:before{content:"\f1e6"}
.fa-slideshare:before{content:"\f1e7"}
.fa-twitch:before{content:"\f1e8"}
.fa-yelp:before{content:"\f1e9"}
.fa-newspaper-o:before{content:"\f1ea"}
.fa-wifi:before{content:"\f1eb"}
.fa-calculator:before{content:"\f1ec"}
.fa-paypal:before{content:"\f1ed"}
.fa-google-wallet:before{content:"\f1ee"}
.fa-cc-visa:before{content:"\f1f0"}
.fa-cc-mastercard:before{content:"\f1f1"}
.fa-cc-discover:before{content:"\f1f2"}
.fa-cc-amex:before{content:"\f1f3"}
.fa-cc-paypal:before{content:"\f1f4"}
.fa-cc-stripe:before{content:"\f1f5"}
.fa-bell-slash:before{content:"\f1f6"}
.fa-bell-slash-o:before{content:"\f1f7"}
.fa-trash:before{content:"\f1f8"}
.fa-copyright:before{content:"\f1f9"}
.fa-at:before{content:"\f1fa"}
.fa-eyedropper:before{content:"\f1fb"}
.fa-paint-brush:before{content:"\f1fc"}
.fa-birthday-cake:before{content:"\f1fd"}
.fa-area-chart:before{content:"\f1fe"}
.fa-pie-chart:before{content:"\f200"}
.fa-line-chart:before{content:"\f201"}
.fa-lastfm:before{content:"\f202"}
.fa-lastfm-square:before{content:"\f203"}
.fa-toggle-off:before{content:"\f204"}
.fa-toggle-on:before{content:"\f205"}
.fa-bicycle:before{content:"\f206"}
.fa-bus:before{content:"\f207"}
.fa-ioxhost:before{content:"\f208"}
.fa-angellist:before{content:"\f209"}
.fa-cc:before{content:"\f20a"}
.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}
.fa-meanpath:before{content:"\f20c"}
.fa-buysellads:before{content:"\f20d"}
.fa-connectdevelop:before{content:"\f20e"}
.fa-dashcube:before{content:"\f210"}
.fa-forumbee:before{content:"\f211"}
.fa-leanpub:before{content:"\f212"}
.fa-sellsy:before{content:"\f213"}
.fa-shirtsinbulk:before{content:"\f214"}
.fa-simplybuilt:before{content:"\f215"}
.fa-skyatlas:before{content:"\f216"}
.fa-cart-plus:before{content:"\f217"}
.fa-cart-arrow-down:before{content:"\f218"}
.fa-diamond:before{content:"\f219"}
.fa-ship:before{content:"\f21a"}
.fa-user-secret:before{content:"\f21b"}
.fa-motorcycle:before{content:"\f21c"}
.fa-street-view:before{content:"\f21d"}
.fa-heartbeat:before{content:"\f21e"}
.fa-venus:before{content:"\f221"}
.fa-mars:before{content:"\f222"}
.fa-mercury:before{content:"\f223"}
.fa-transgender:before{content:"\f224"}
.fa-transgender-alt:before{content:"\f225"}
.fa-venus-double:before{content:"\f226"}
.fa-mars-double:before{content:"\f227"}
.fa-venus-mars:before{content:"\f228"}
.fa-mars-stroke:before{content:"\f229"}
.fa-mars-stroke-v:before{content:"\f22a"}
.fa-mars-stroke-h:before{content:"\f22b"}
.fa-neuter:before{content:"\f22c"}
.fa-facebook-official:before{content:"\f230"}
.fa-pinterest-p:before{content:"\f231"}
.fa-whatsapp:before{content:"\f232"}
.fa-server:before{content:"\f233"}
.fa-user-plus:before{content:"\f234"}
.fa-user-times:before{content:"\f235"}
.fa-hotel:before,.fa-bed:before{content:"\f236"}
.fa-viacoin:before{content:"\f237"}
.fa-train:before{content:"\f238"}
.fa-subway:before{content:"\f239"}
.fa-medium:before{content:"\f23a"}

2
RadioSonde_FSK/version.h Executable file
View File

@ -0,0 +1,2 @@
const char *version_name = "RadioSonde";
const char *version_id = "0.9.1";

101
Setup.md Executable file
View File

@ -0,0 +1,101 @@
# Près-requis
<img src="http://xavier.debert.free.fr/RS/TTGO2.jpg" width="50%">
## Arduino IDE
Avoir une version à jour exemple 1.8.13(juillet2020)
## ESP32 support
Fichier -> Préférences
en bas dans la case "URL de gestionnaire de cartes supplémentaires"
Ajouter *https://dl.espressif.com/dl/package_esp32_index.json* et appuier sur oK
Puis dans Outils
Outils -> type de cartes -> Gestionnaire de cartes
dans la case de recherche taper "esp32"
Installer "esp32 by Espressif Systems"
Puis après
## ESP32 Flash Filesystem Upload support
Télécharger le fichier zip de la dernière version
https://github.com/me-no-dev/arduino-esp32fs-plugin/releases/
Décompresser l'archive dans le répértoire tools de votre Arduino IDE
## Ajouter les bibliothèques
Séléctionner Outils -> Gestionnaire de bibliothèques
Installer "U8g2"
Installer "MicroNMEA"
Installer "TFT_22_ILI9225" nécessaire pas pour l'écran car j'ai tout supprimé
mais pour les fonts, car le TTGO fonctionne avec OLED SSD1306 par défaut!
## Ajouter les bibliothèques, parties 2
Depuis https://github.com/me-no-dev/ESPAsyncWebServer télécharger le ZIP , l'extraire dans "libraries"
, renommer le répertoire en ESPAsyncWebServer (supprimer juste "-master")
Depuis https://github.com/me-no-dev/AsyncTCP télécharger le ZIP, l'extraire dans "libraries", et renommer le répertoire en AsyncTCP
de même pour https://github.com/lewisxhe/AXP202X_Library télécharger le ZIP, l'extraire dans "libraries", et renommer le répertoire en AXP202X_Library
## Ajouter les bibliothèques, parties 3
Copier libraries/SX1278FSK
et libraries/SondeLib
et libraries/fonts
fourni dans libraries
```
ou sous Linux un lien symbolique est aussi possible mais pas obligatoire!
cd ~/Arduino/libraries
ln -s <whereyouclonedthegit>/radiosonde/libraries/SondeLib/ .
ln -s <whereyouclonedthegit>/radiosonde/libraries/SX1278FSK/ .
```
Redémarrer Arduino IDE
## Ajout carte esp32
Allez dans Outils-> type de cartes -> gestionnaire de cartes
puis dans la case taper esp32 et Installer.
## Dernière parties
Dans Outils -> Esp32 arduino: ->
Séléctionner "TTGO LoRa32-OLED v1"
Puis il vous faut ouvrir le fichier
RadioSonde_FSK.ino
Compiler et Téléverser le dans votre TTGO
puis il faut téléverser maintenant les DATA!
Dans Outils
cliquer sur ESP32 Sketch Data Upload
voila le TTGO est prêt!
Pour les futur mise à jour,
j'ai prévue une mise à jour directe via OTA depuis le TTGO donc
s'il est connecté à internet depuis votre Smartphone ou votre Box.
Cela se fera depuis le menu de la page Web.
73
Xavier

861
libraries/SX1278FSK/SX1278FSK.cpp Executable file
View File

@ -0,0 +1,861 @@
/*
* Functions for using SX127x in FSK mode (mainly receive)
* Copyright (C) 2019 Hansi Reiser, dl9rdz
*
* Partially based on the SX1278 libraray for managing Semtech modules
* Copyright (C) 2015 Wireless Open Source
* http://wirelessopensource.com
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "SX1278FSK.h"
#include "SPI.h"
#include <Sonde.h>
#include <Display.h>
SX1278FSK::SX1278FSK()
{
// Initialize class variables
};
static SPISettings spiset = SPISettings(40000000L, MSBFIRST, SPI_MODE0);
/*
Function: Turns the module ON.
Returns: 0 on success, 1 otherwise
*/
uint8_t SX1278FSK::ON()
{
uint8_t state = 2;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'ON'"));
#endif
// Powering the module
pinMode(SX1278_SS, OUTPUT);
digitalWrite(SX1278_SS, HIGH);
//Configure the MISO, MOSI, CS, SPCR.
SPI.begin();
//Set most significant bit first
SPI.setBitOrder(MSBFIRST);
//Divide the clock frequency
SPI.setClockDivider(SPI_CLOCK_DIV2);
//Set data mode
SPI.setDataMode(SPI_MODE0);
// Set Maximum Over Current Protection
state = setMaxCurrent(0x1B);
if( state == 0 )
{
#if (SX1278FSK_debug_mode > 1)
Serial.println(F("## Setting ON with maximum current supply ##"));
Serial.println();
#endif
}
else
{
return 1;
}
// set FSK mode
state = setFSK();
return state;
}
/*
Function: Turn the module OFF.
Returns: Nothing
*/
void SX1278FSK::OFF()
{
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'OFF'"));
#endif
SPI.end();
// Powering the module
pinMode(SX1278_SS,OUTPUT);
digitalWrite(SX1278_SS,LOW);
#if (SX1278FSK_debug_mode > 1)
Serial.println(F("## Setting OFF ##"));
Serial.println();
#endif
}
/*
Function: Reads the indicated register.
Returns: The content of the register
Parameters:
address: address register to read from
*/
byte SX1278FSK::readRegister(byte address)
{
byte value = 0x00;
SPI.beginTransaction(spiset);
digitalWrite(SX1278_SS,LOW);
//delay(1);
bitClear(address, 7); // Bit 7 cleared to write in registers
SPI.transfer(address);
value = SPI.transfer(0x00);
digitalWrite(SX1278_SS,HIGH);
SPI.endTransaction();
#if (SX1278FSK_debug_mode > 1)
if(address!=0x3F) {
Serial.print(F("## Reading: ##\t"));
Serial.print(F("Register "));
Serial.print(address, HEX);
Serial.print(F(": "));
Serial.print(value, HEX);
Serial.println();
}
#endif
return value;
}
/*
Function: Writes on the indicated register.
Returns: Nothing
Parameters:
address: address register to write in
data: value to write in the register
*/
void SX1278FSK::writeRegister(byte address, byte data)
{
SPI.beginTransaction(spiset);
digitalWrite(SX1278_SS,LOW);
//delay(1);
bitSet(address, 7); // Bit 7 set to read from registers
SPI.transfer(address);
SPI.transfer(data);
digitalWrite(SX1278_SS,HIGH);
SPI.endTransaction();
#if (SX1278FSK_debug_mode > 1)
Serial.print(F("## Writing: ##\t"));
Serial.print(F("Register "));
bitClear(address, 7);
Serial.print(address, HEX);
Serial.print(F(": "));
Serial.print(data, HEX);
Serial.println();
#endif
}
/*
* Function: Clears the IRQ flags
*
* Configuration registers are accessed through the SPI interface.
* Registers are readable in all device mode including Sleep. However, they
* should be written only in Sleep and Stand-by modes.
*
* Returns: Nothing
*/
void SX1278FSK::clearIRQFlags()
{
byte st0;
// Save the previous status
st0 = readRegister(REG_OP_MODE);
// Stdby mode to write in registers
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
// FSK mode flags1 register
writeRegister(REG_IRQ_FLAGS1, 0xFF);
// FSK mode flags2 register
writeRegister(REG_IRQ_FLAGS2, 0xFF);
// Getting back to previous status
if(st0 != FSK_STANDBY_MODE) {
writeRegister(REG_OP_MODE, st0);
}
#if (SX1278FSK_debug_mode > 1)
Serial.println(F("## FSK flags cleared ##"));
#endif
}
/*
Function: Sets the module in FSK mode.
Returns: Integer that determines if there has been any error
state = 2 --> The command has not been executed
state = 1 --> There has been an error while executing the command
state = 0 --> The command has been executed with no errors
*/
uint8_t SX1278FSK::setFSK()
{
uint8_t state = 2;
byte st0;
//byte config1;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'setFSK'"));
#endif
writeRegister(REG_OP_MODE, FSK_SLEEP_MODE); // Sleep mode (mandatory to change mode)
// If we are in LORA mode, above line activate Sleep mode, but does not change mode to FSK
// as mode change is only allowed in sleep mode. Next line changes to FSK
writeRegister(REG_OP_MODE, FSK_SLEEP_MODE);
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE); // FSK standby mode
//writeRegister(REG_FIFO_THRESH, 0x80); // condition to start packet tx
//config1 = readRegister(REG_SYNC_CONFIG);
//config1 = config1 & B00111111;
//writeRegister(REG_SYNC_CONFIG,config1);
delay(100);
st0 = readRegister(REG_OP_MODE); // Reading config mode
if( st0 == FSK_STANDBY_MODE )
{ // FSK mode
state = 0;
#if (SX1278FSK_debug_mode > 1)
Serial.println(F("## FSK set with success ##"));
Serial.println();
#endif
} else { // LoRa mode
state = 1;
Serial.println( st0 );
#if (SX1278FSK_debug_mode > 1)
Serial.println(F("** There has been an error while setting FSK **"));
Serial.println();
#endif
}
return state;
}
/* Function: Sets FSK bitrate
* Returns: 0 for success, >0 in case of error
* Parameters: bps: requested bitrate
* (raw data rate, for Mancester encoding, the effective bitrate is bps/2)
*/
uint8_t SX1278FSK::setBitrate(float bps)
{
// TODO: Check if FSK mode is active
// check if bitrate is allowed allowed bitrate
if((bps < 1200) || (bps > 300000)) {
return 1;
}
// set mode to FSK STANDBY
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
// set bit rate
uint16_t bitRate = (SX127X_CRYSTAL_FREQ * 1.0) / bps;
writeRegister(REG_BITRATE_MSB, (bitRate & 0xFF00) >> 8);
writeRegister(REG_BITRATE_LSB, (bitRate & 0x00FF));
// also set fractional part
uint16_t fracRate = (SX127X_CRYSTAL_FREQ * 16.0) / bps - bitRate * 16 + 0.5;
writeRegister(REG_BIT_RATE_FRAC, fracRate&0x0F);
return 0;
}
/* Function: Gets configured bitrate
* Returns bitrate in bit/second
*/
float SX1278FSK::getBitrate()
{
uint8_t fmsb = readRegister(REG_BITRATE_MSB);
uint8_t flsb = readRegister(REG_BITRATE_LSB);
uint8_t ffrac = readRegister(REG_BIT_RATE_FRAC) & 0x0F;
return SX127X_CRYSTAL_FREQ / ( (fmsb<<8) + flsb + ffrac / 16.0 );
}
//typedef struct rxbwset { float bw; uint8_t mant; uint8_t rxp; } st_rxbwsettings;
uint8_t SX1278FSK::setRxBandwidth(float bw)
{
// TODO: Check if in FSK mode
//
if(bw<2600 || bw>250000) { return 1; /* invalid */ }
uint8_t rxbwexp = 1;
bw = SX127X_CRYSTAL_FREQ / bw / 8;
while(bw>31) { rxbwexp++; bw/=2.0; }
uint8_t rxbwmant = bw<17?0 : bw<21? 1:2;
// set mode to FSK STANDBY
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
writeRegister(REG_RX_BW, rxbwexp | (rxbwmant<<3));
return 0;
}
float SX1278FSK::getRxBandwidth()
{
uint8_t rxbw = readRegister(REG_RX_BW);
uint8_t rxbwexp = rxbw&0x07;
uint8_t rxbwmant = (rxbw>>3)&0x03;
rxbwmant = 16 + 4*rxbwmant;
return SX127X_CRYSTAL_FREQ / ( rxbwmant << (rxbwexp+2));
}
uint8_t SX1278FSK::setAFCBandwidth(float bw)
{
// TODO: Check if in FSK mode
//
if(bw<2600 || bw>250000) { return 1; /* invalid */ }
uint8_t rxbwexp = 1;
bw = SX127X_CRYSTAL_FREQ / bw / 8;
while(bw>31) { rxbwexp++; bw/=2.0; }
uint8_t rxbwmant = bw<17?0 : bw<21? 1:2;
// set mode to FSK STANDBY
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
writeRegister(REG_AFC_BW, rxbwexp | (rxbwmant<<3));
return 0;
}
float SX1278FSK::getAFCBandwidth()
{
uint8_t rxbw = readRegister(REG_AFC_BW);
uint8_t rxbwexp = rxbw&0x07;
uint8_t rxbwmant = (rxbw>>3)&0x03;
rxbwmant = 16 + 4*rxbwmant;
return SX127X_CRYSTAL_FREQ / ( rxbwmant << (rxbwexp+2));
}
uint8_t SX1278FSK::setFrequency(float freq) {
// set mode to FSK STANDBY
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
freq += sonde.config.freqofs; // manual frequency correction
uint32_t frf = freq * 1.0 * (1<<19) / SX127X_CRYSTAL_FREQ;
writeRegister(REG_FRF_MSB, (frf&0xff0000)>>16);
writeRegister(REG_FRF_MID, (frf&0x00ff00)>>8);
writeRegister(REG_FRF_LSB, (frf&0x0000ff));
return 0;
}
float SX1278FSK::getFrequency() {
uint8_t fmsb = readRegister(REG_FRF_MSB);
uint8_t fmid = readRegister(REG_FRF_MID);
uint8_t flsb = readRegister(REG_FRF_LSB);
return ((fmsb<<16)|(fmid<<8)|flsb) * 1.0 / (1<<19) * SX127X_CRYSTAL_FREQ;
}
static int gaintab[]={-999,0,-6,-12,-24,-36,-48,-999};
int SX1278FSK::getLNAGain() {
int gain = (readRegister(REG_LNA)>>5)&0x07;
return gaintab[gain];
}
uint8_t SX1278FSK::setLNAGain(int gain) {
uint8_t g=1;
while(gain<gaintab[g] && g<6) {g++; }
writeRegister(REG_LNA, g<<5);
return 0;
}
uint8_t SX1278FSK::getRxConf() {
return readRegister(REG_RX_CONFIG);
}
uint8_t SX1278FSK::setRxConf(uint8_t conf) {
writeRegister(REG_RX_CONFIG, conf);
return 0;
}
uint8_t SX1278FSK::setSyncConf(uint8_t conf, int len, const uint8_t *syncpattern) {
int res=0;
writeRegister(REG_SYNC_CONFIG, conf);
if(len>8) return 1;
for(int i=0; i<len; i++) {
writeRegister(REG_SYNC_VALUE1+i, syncpattern[i]);
}
return res;
}
uint8_t SX1278FSK::getSyncConf() {
return sx1278.readRegister(REG_SYNC_CONFIG);
}
uint8_t SX1278FSK::setPreambleDetect(uint8_t conf) {
sx1278.writeRegister(REG_PREAMBLE_DETECT, conf);
return 0;
}
uint8_t SX1278FSK::getPreambleDetect() {
return sx1278.readRegister(REG_PREAMBLE_DETECT);
}
uint8_t SX1278FSK::setPacketConfig(uint8_t conf1, uint8_t conf2)
{
uint8_t ret=0;
sx1278.writeRegister(REG_PACKET_CONFIG1, conf1);
sx1278.writeRegister(REG_PACKET_CONFIG2, conf2);
return ret;
};
uint16_t SX1278FSK::getPacketConfig() {
uint8_t c1 = sx1278.readRegister(REG_PACKET_CONFIG1);
uint8_t c2 = sx1278.readRegister(REG_PACKET_CONFIG2);
return (c2<<8)|c1;
}
/*
Function: Gets the preamble length from the module.
Returns: preamble length
*/
uint16_t SX1278FSK::getPreambleLength()
{
uint16_t p_length;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'getPreambleLength'"));
#endif
p_length = readRegister(REG_PREAMBLE_MSB_FSK);
p_length = (p_length<<8) | readRegister(REG_PREAMBLE_LSB_FSK);
#if (SX1278FSK_debug_mode > 1)
Serial.print(F("## Preamble length configured is "));
Serial.print(p_length, HEX);
Serial.print(F(" ##"));
Serial.println();
#endif
return p_length;
}
/*
Function: Sets the preamble length in the module
Returns: Integer that determines if there has been any error
state = 2 --> The command has not been executed
state = 1 --> There has been an error while executing the command
state = 0 --> The command has been executed with no errors
Parameters:
l: length value to set as preamble length.
*/
uint8_t SX1278FSK::setPreambleLength(uint16_t l)
{
byte st0;
int8_t state = 2;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'setPreambleLength'"));
#endif
st0 = readRegister(REG_OP_MODE); // Save the previous status
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE); // Set Standby mode to write in registers
// Storing MSB preamble length in FSK mode
writeRegister(REG_PREAMBLE_MSB_FSK, l>>8);
writeRegister(REG_PREAMBLE_LSB_FSK, l&0xFF);
state = 0;
#if (SX1278FSK_debug_mode > 1)
Serial.print(F("## Preamble length "));
Serial.print(l, HEX);
Serial.println(F(" has been successfully set ##"));
Serial.println();
#endif
if(st0 != FSK_STANDBY_MODE) {
writeRegister(REG_OP_MODE, st0); // Getting back to previous status
}
return state;
}
/*
Function: Gets the payload length from the module.
Returns: configured length; -1 on error
*/
int SX1278FSK::getPayloadLength()
{
int length;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'getPayloadLength'"));
#endif
length = readRegister(REG_PAYLOAD_LENGTH_FSK);
#if (SX1278FSK_debug_mode > 1)
Serial.print(F("## Payload length configured is "));
Serial.print(length);
Serial.println(F(" ##"));
#endif
return length;
}
/*
Function: Sets the payload length from the module.
Returns: 0 for ok, otherwise error
// TODO: Larger than 255 bytes?
*/
uint8_t SX1278FSK::setPayloadLength(int len)
{
#if (SX1278FSK_debug_mode > 1)
Serial.print(F("Starting 'setPayloadLength'"));
Serial.println(len);
#endif
uint8_t conf2 = readRegister(REG_PACKET_CONFIG2);
conf2 = (conf2 & 0xF8) | ( (len>>8)&0x7 );
writeRegister(REG_PACKET_CONFIG2, conf2);
writeRegister(REG_PAYLOAD_LENGTH_FSK, len&0xFF);
return 0;
}
/*
Function: Gets the current value of RSSI.
Returns: RSSI value
*/
int16_t SX1278FSK::getRSSI()
{
int16_t RSSI;
//int rssi_mean = 0;
int total = 1;
/// FSK mode
// get mean value of RSSI
for(int i = 0; i < total; i++)
{
RSSI = readRegister(REG_RSSI_VALUE_FSK);
//rssi_mean += _RSSI;
}
//rssi_mean = rssi_mean / total;
//RSSI = rssi_mean;
#if (SX1278FSK_debug_mode > 0)
Serial.print(F("## RSSI value is "));
Serial.print(RSSI);
Serial.println(F(" ##"));
#endif
return RSSI;
}
/*
Function: Gets the current value of FEI (frequency error indication)
Returns: FEI value in Hz
*/
int32_t SX1278FSK::getFEI()
{
int32_t FEI;
int16_t regval = (readRegister(REG_FEI_MSB)<<8) | readRegister(REG_FEI_LSB);
//Serial.printf("feireg: %04x\n", regval);
FEI = (int32_t)(regval * SX127X_FSTEP);
return FEI;
}
/*
Function: Gets the current value of AFC (automated frequency correction)
Returns: AFC value in Hz
*/
int32_t SX1278FSK::getAFC()
{
int32_t AFC;
int16_t regval = (readRegister(REG_AFC_MSB)<<8) | readRegister(REG_AFC_LSB);
//Serial.printf("afcreg: %04x\n", regval);
AFC = (int32_t)(regval * SX127X_FSTEP);
return AFC;
}
/*
Function: Gets the current supply limit of the power amplifier, protecting battery chemistries.
Returns: Integer that determines if there has been any error
state = 2 --> The command has not been executed
state = 1 --> There has been an error while executing the command
state = 0 --> The command has been executed with no errors
Parameters:
rate: value to compute the maximum current supply. Maximum current is 45+5*'rate' [mA]
*/
int SX1278FSK::getMaxCurrent()
{
int value;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'getMaxCurrent'"));
#endif
value = readRegister(REG_OCP);
// extract only the OcpTrim value from the OCP register
value &= B00011111;
if( value <= 15 ) {
value = (45 + (5 * value));
} else if( value <= 27 ) {
value = (-30 + (10 * value));
} else {
value = 240;
}
#if (SX1278FSK_debug_mode > 1)
Serial.print(F("## Maximum current supply configured is "));
Serial.print(value, DEC);
Serial.println(F(" mA ##"));
Serial.println();
#endif
return value;
}
/*
Function: Limits the current supply of the power amplifier, protecting battery chemistries.
Returns: Integer that determines if there has been any error
state = 2 --> The command has not been executed
state = 1 --> There has been an error while executing the command
state = 0 --> The command has been executed with no errors
state = -1 --> Forbidden parameter value for this function
Parameters:
rate: value to compute the maximum current supply. Range: 0x00 to 0x1B. The
Maximum current is:
Imax = 45+5*OcpTrim [mA] if OcpTrim <= 15 (120 mA) /
Imax = -30+10*OcpTrim [mA] if 15 < OcpTrim <= 27 (130 to 240 mA)
Imax = 240mA for higher settings
*/
int8_t SX1278FSK::setMaxCurrent(uint8_t rate)
{
int8_t state = 2;
byte st0;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'setMaxCurrent'"));
#endif
// Maximum rate value = 0x1B, because maximum current supply = 240 mA
if (rate > 0x1B)
{
state = -1;
#if (SX1278FSK_debug_mode > 1)
Serial.print(F("** Maximum current supply is 240 mA, "));
Serial.println(F("so maximum parameter value must be 27 (DEC) or 0x1B (HEX) **"));
Serial.println();
#endif
}
else
{
// Enable Over Current Protection
rate |= B00100000;
state = 1;
st0 = readRegister(REG_OP_MODE); // Save the previous status
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE); // Set FSK Standby mode to write in registers
writeRegister(REG_OCP, rate); // Modifying maximum current supply
if(st0 != FSK_STANDBY_MODE) {
writeRegister(REG_OP_MODE, st0); // Getting back to previous status
}
state = 0;
}
return state;
}
/*
Function: Configures the module to receive information.
Returns: Integer that determines if there has been any error
state = 2 --> The command has not been executed
state = 1 --> There has been an error while executing the command
state = 0 --> The command has been executed with no errors
*/
uint8_t SX1278FSK::receive()
{
uint8_t state = 1;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'receive'"));
#endif
// TODO: Is there anything else to be done?
//
writeRegister(REG_OP_MODE, FSK_RX_MODE);
state = 0;
#if (SX1278FSK_debug_mode > 1)
Serial.println(F("## Receiving FSK mode activated with success ##"));
#endif
return state;
}
/*
Function: Configures the module to receive a packet
Returns: Integer that determines if there has been any error
state = 2 --> The command has not been executed
state = 1 --> There has been an error while executing the command
state = 0 --> The command has been executed with no errors
Parameters:
wait: timeout in ms
data: memory where to place received data
*/
uint8_t SX1278FSK::receivePacketTimeout(uint32_t wait, byte *data)
{
int di=0;
uint8_t state = 2;
unsigned long previous;
byte value = 0x00;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'receivePacketTimeout'"));
#endif
// set RX mode
state = receive();
if(state != 0) { return state; }
#if (SX1278FSK_debug_mode > 0)
Serial.println(F("RX mode sucessfully activated"));
#endif
previous = millis();
/// FSK mode
value = readRegister(REG_IRQ_FLAGS2);
byte ready=0;
// while not yet done or FIFO not yet empty
while( (!ready || bitRead(value,6)==0) && (millis() - previous < wait) )
{
if( bitRead(value,2)==1 ) ready=1;
if( bitRead(value, 6) == 0 ) { // FIFO not empty
data[di++] = readRegister(REG_FIFO);
// It's a bit of a hack.... get RSSI and AFC (a) at beginning of packet and
// for RS41 after about 0.5 sec. It might be more logical to put this decoder-specific
// code into RS41.cpp instead of this file... (maybe TODO?)
if(di==1 || di==290 ) {
int rssi=getRSSI();
int afc=getAFC();
#if 0
Serial.printf("Test(%d): RSSI=%d", rxtask.currentSonde, rssi/2);
Serial.print("Test: AFC="); Serial.println(afc);
#endif
sonde.sondeList[rxtask.currentSonde].rssi = rssi;
sonde.sondeList[rxtask.currentSonde].afc = afc;
if(rxtask.receiveResult==0xFFFF)
rxtask.receiveResult = RX_UPDATERSSI;
//sonde.si()->rssi = rssi;
//sonde.si()->afc = afc;
}
if(di>520) {
// TODO
Serial.println("TOO MUCH DATA");
break;
}
previous = millis(); // reset timeout after receiving data
} else {
delay(10);
}
value = readRegister(REG_IRQ_FLAGS2);
}
if( !ready || bitRead(value, 6)==0) {
#if 1&&(SX1278FSK_debug_mode > 0)
Serial.println(F("** The timeout has expired **"));
Serial.println();
#endif
sonde.si()->rssi = getRSSI();
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE); // Setting standby FSK mode
return 1; // TIMEOUT
}
#if (SX1278FSK_debug_mode > 0)
Serial.println(F("## Packet received:"));
for(unsigned int i = 0; i < di; i++)
{
Serial.print(data[i], HEX); // Printing payload
Serial.print("|");
}
Serial.println(F(" ##"));
#endif
state = 0;
// Initializing flags
clearIRQFlags();
return state;
}
#if 0
/*
Function: It gets the temperature from the measurement block module.
Returns: Integer that determines if there has been any error
state = 2 --> The command has not been executed
state = 1 --> There has been an error while executing the command
state = 0 --> The command has been executed with no errors
*/
uint8_t SX1278FSK::getTemp()
{
byte st0;
uint8_t state = 2;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
Serial.println(F("Starting 'getTemp'"));
#endif
st0 = readRegister(REG_OP_MODE); // Save the previous status
if( _modem == LORA )
{ // Allowing access to FSK registers while in LoRa standby mode
writeRegister(REG_OP_MODE, LORA_STANDBY_FSK_REGS_MODE);
}
state = 1;
// Saving temperature value
_temp = readRegister(REG_TEMP);
if( _temp & 0x80 ) // The SNR sign bit is 1
{
// Invert and divide by 4
_temp = ( ( ~_temp + 1 ) & 0xFF );
}
else
{
// Divide by 4
_temp = ( _temp & 0xFF );
}
#if (SX1278FSK_debug_mode > 1)
Serial.print(F("## Temperature is: "));
Serial.print(_temp);
Serial.println(F(" ##"));
Serial.println();
#endif
if( _modem == LORA )
{
writeRegister(REG_OP_MODE, st0); // Getting back to previous status
}
state = 0;
return state;
}
/*
Function: It prints the registers related to RX
Returns: Integer that determines if there has been any error
state = 2 --> The command has not been executed
state = 1 --> There has been an error while executing the command
state = 0 --> The command has been executed with no errors
*/
void SX1278FSK::showRxRegisters()
{
Serial.println(F("\n--- Show RX register ---"));
// variable
byte reg;
for(int i = 0x00; i < 0x80; i++)
{
reg = readRegister(i);
Serial.print(F("Reg 0x"));
Serial.print(i, HEX);
Serial.print(F(":"));
Serial.print(reg, HEX);
Serial.println();
delay(100);
}
Serial.println(F("------------------------"));
}
#endif
SX1278FSK sx1278 = SX1278FSK();

278
libraries/SX1278FSK/SX1278FSK.h Executable file
View File

@ -0,0 +1,278 @@
/*
* Functions for using SX127x in FSK mode (mainly receive)
* Copyright (C) 2019 Hansi Reiser, dl9rdz
*
* Partially based on the SX1278 libraray for managing Semtech modules
* Copyright (C) 2015 Wireless Open Source
* http://wirelessopensource.com
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#ifndef SX1278FSK_h
#define SX1278FSK_h
/******************************************************************************
* Includes
******************************************************************************/
#include <stdlib.h>
#include <stdint.h>
#include <Arduino.h>
#include <SPI.h>
#ifndef inttypes_h
#include <inttypes.h>
#endif
/******************************************************************************
* Definitions & Declarations
*****************************************************************************/
#define SX127X_CRYSTAL_FREQ 32000000
#define SX127X_FSTEP (SX127X_CRYSTAL_FREQ*1.0/(1<<19))
#define SX1278FSK_debug_mode 0
#define SX1278_SS SS
//! MACROS //
#define bitRead(value, bit) (((value) >> (bit)) & 0x01) // read a bit
#define bitSet(value, bit) ((value) |= (1UL << (bit))) // set bit to '1'
#define bitClear(value, bit) ((value) &= ~(1UL << (bit))) // set bit to '0'
//! REGISTERS //
// FSK Commun LORA
#define REG_FIFO 0x00
#define REG_OP_MODE 0x01
#define REG_BITRATE_MSB 0x02
#define REG_BITRATE_LSB 0x03
#define REG_FDEV_MSB 0x04
#define REG_FDEV_LSB 0x05
#define REG_FRF_MSB 0x06
#define REG_FRF_MID 0x07
#define REG_FRF_LSB 0x08
#define REG_PA_CONFIG 0x09
#define REG_PA_RAMP 0x0A
#define REG_OCP 0x0B
#define REG_LNA 0x0C
#define REG_RX_CONFIG 0x0D
#define REG_FIFO_ADDR_PTR 0x0D
#define REG_RSSI_CONFIG 0x0E
#define REG_FIFO_TX_BASE_ADDR 0x0E
#define REG_RSSI_COLLISION 0x0F
#define REG_FIFO_RX_BASE_ADDR 0x0F
#define REG_RSSI_THRESH 0x10
#define REG_FIFO_RX_CURRENT_ADDR 0x10
#define REG_RSSI_VALUE_FSK 0x11
#define REG_IRQ_FLAGS_MASK 0x11
#define REG_RX_BW 0x12
#define REG_IRQ_FLAGS 0x12
#define REG_AFC_BW 0x13
#define REG_RX_NB_BYTES 0x13
#define REG_OOK_PEAK 0x14
#define REG_RX_HEADER_CNT_VALUE_MSB 0x14
#define REG_OOK_FIX 0x15
#define REG_RX_HEADER_CNT_VALUE_LSB 0x15
#define REG_OOK_AVG 0x16
#define REG_RX_PACKET_CNT_VALUE_MSB 0x16
#define REG_RX_PACKET_CNT_VALUE_LSB 0x17
#define REG_MODEM_STAT 0x18
#define REG_PKT_SNR_VALUE 0x19
#define REG_AFC_FEI 0x1A
#define REG_PKT_RSSI_VALUE 0x1A
#define REG_AFC_MSB 0x1B
#define REG_RSSI_VALUE_LORA 0x1B
#define REG_AFC_LSB 0x1C
#define REG_HOP_CHANNEL 0x1C
#define REG_FEI_MSB 0x1D
#define REG_MODEM_CONFIG1 0x1D
#define REG_FEI_LSB 0x1E
#define REG_MODEM_CONFIG2 0x1E
#define REG_PREAMBLE_DETECT 0x1F
#define REG_SYMB_TIMEOUT_LSB 0x1F
#define REG_RX_TIMEOUT1 0x20
#define REG_PREAMBLE_MSB_LORA 0x20
#define REG_RX_TIMEOUT2 0x21
#define REG_PREAMBLE_LSB_LORA 0x21
#define REG_RX_TIMEOUT3 0x22
#define REG_PAYLOAD_LENGTH_LORA 0x22
#define REG_RX_DELAY 0x23
#define REG_MAX_PAYLOAD_LENGTH 0x23
#define REG_OSC 0x24
#define REG_HOP_PERIOD 0x24
#define REG_PREAMBLE_MSB_FSK 0x25
#define REG_FIFO_RX_BYTE_ADDR 0x25
#define REG_PREAMBLE_LSB_FSK 0x26
#define REG_MODEM_CONFIG3 0x26
#define REG_SYNC_CONFIG 0x27
#define REG_SYNC_VALUE1 0x28
#define REG_LORA_FEI_MSB 0x28
#define REG_SYNC_VALUE2 0x29
#define REG_LORA_FEI_MID 0x29
#define REG_SYNC_VALUE3 0x2A
#define REG_LORA_FEI_LSB 0x2A
#define REG_SYNC_VALUE4 0x2B
#define REG_SYNC_VALUE5 0x2C
#define REG_RSSI_WIDEBAND 0x2C
#define REG_SYNC_VALUE6 0x2D
#define REG_SYNC_VALUE7 0x2E
#define REG_SYNC_VALUE8 0x2F
#define REG_PACKET_CONFIG1 0x30
#define REG_PACKET_CONFIG2 0x31
#define REG_DETECT_OPTIMIZE 0x31
#define REG_PAYLOAD_LENGTH_FSK 0x32
#define REG_NODE_ADRS 0x33
#define REG_INVERT_IQ 0x33
#define REG_BROADCAST_ADRS 0x34
#define REG_FIFO_THRESH 0x35
#define REG_SEQ_CONFIG1 0x36
#define REG_SEQ_CONFIG2 0x37
#define REG_DETECTION_THRESHOLD 0x37
#define REG_TIMER_RESOL 0x38
#define REG_TIMER1_COEF 0x39
#define REG_SYNC_WORD 0x39
#define REG_TIMER2_COEF 0x3A
#define REG_IMAGE_CAL 0x3B
#define REG_TEMP 0x3C
#define REG_LOW_BAT 0x3D
#define REG_IRQ_FLAGS1 0x3E
#define REG_IRQ_FLAGS2 0x3F
#define REG_DIO_MAPPING1 0x40
#define REG_DIO_MAPPING2 0x41
#define REG_VERSION 0x42
#define REG_PLL_HOP 0x44
#define REG_TCXO 0x4B
#define REG_PA_DAC 0x4D
#define REG_FORMER_TEMP 0x5B
#define REG_BIT_RATE_FRAC 0x5D
#define REG_AGC_REF 0x61
#define REG_AGC_THRESH1 0x62
#define REG_AGC_THRESH2 0x63
#define REG_AGC_THRESH3 0x64
#define REG_PLL 0x70
//FSK MODES:
const uint8_t FSK_SLEEP_MODE = 0x00;
const uint8_t FSK_STANDBY_MODE = 0x01;
const uint8_t FSK_TX_MODE = 0x03;
const uint8_t FSK_RX_MODE = 0x05;
/******************************************************************************
* SX1278FSK Class
* Functions and variables for managing SX127x transceiver chips in FSK mode,
* mainly for receiving radiosonde transmissions
******************************************************************************/
class SX1278FSK
{
public:
// class constructor
SX1278FSK();
// Turn on SX1278 module (return 0 on sucess, 1 otherwise)
uint8_t ON();
// Turn off SX1278 module
void OFF();
// Read internal register
byte readRegister(byte address);
// Write internal register
void writeRegister(byte address, byte data);
// Clear IRQ flags
void clearIRQFlags();
// Activate FSK mode (return 0 on success, 1 otherwise)
uint8_t setFSK();
// Configures bitrate register (closest approximation to requested bitrate)
uint8_t setBitrate(float bps);
float getBitrate();
// Configures RX bandwidth (next largest supported bandwith if exact value not possible)
uint8_t setRxBandwidth(float bps);
float getRxBandwidth();
// Configures AFC bandwidth (next largest supported bandwith if exact value not possible)
uint8_t setAFCBandwidth(float bps);
float getAFCBandwidth();
// Configures RX frequency (closest approximation to requested frequency)
uint8_t setFrequency(float freq);
float getFrequency();
int getLNAGain();
uint8_t setLNAGain(int gain);
uint8_t getRxConf();
uint8_t setRxConf(uint8_t conf);
uint8_t setSyncConf(uint8_t conf, int len, const uint8_t *syncpattern);
uint8_t getSyncConf();
uint8_t setPreambleDetect(uint8_t conf);
uint8_t getPreambleDetect();
uint8_t setPacketConfig(uint8_t conf1, uint8_t conf2);
uint16_t getPacketConfig();
// Get configured preamble length (used for TX only?)
uint16_t getPreambleLength();
// Sets the preamble length.
uint8_t setPreambleLength(uint16_t l);
// Gets the payload length (expected length for receive)
int getPayloadLength();
uint8_t setPayloadLength(int len);
// Get current RSSI value
int16_t getRSSI();
// Get current FEI (frequency error indication) value
int32_t getFEI();
// Get current AFC value
int32_t getAFC();
// Get the maximum current supply by the module.
int getMaxCurrent();
// Set the maximum current supply by the module.
int8_t setMaxCurrent(uint8_t rate);
// Put the module in reception mode.
//return '0' on success, '1' otherwise
uint8_t receive();
// Receive a packet
uint8_t receivePacketTimeout(uint32_t wait, byte *data);
#if 0
//! It gets the internal temperature of the module.
/*!
It stores in global '_temp' variable the module temperature.
\return '0' on success, '1' otherwise
*/
uint8_t getTemp();
//! It prints the registers related to RX via USB
/*!
* \return void
*/
void showRxRegisters();
#endif
};
extern SX1278FSK sx1278;
#endif

657
libraries/SondeLib/DFM.cpp Executable file
View File

@ -0,0 +1,657 @@
/* DFM decoder functions */
#include "DFM.h"
#include "SX1278FSK.h"
#include "Sonde.h"
#define DFM_DEBUG 1
#if DFM_DEBUG
#define DFM_DBG(x) x
#else
#define DFM_DBG(x)
#endif
#define DFM_FRAMELEN 33
// single data structure, search restarts after decoder change
static struct st_dfmstat {
int idcnt0;
int idcnt1;
int lastfrid;
int lastfrcnt;
uint8_t start[50];
uint16_t dat[50*2];
uint8_t cnt[50*2];
uint8_t nameregok;
uint8_t nameregtop;
} dfmstate;
int DFM::setup(float frequency, int type)
{
stype = type;
#if DFM_DEBUG
Serial.printf("Setup sx1278 for DFM sonde (type=%d)\n", stype);
#endif
if(sx1278.ON()!=0) {
DFM_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(sx1278.setFSK()!=0) {
DFM_DBG(Serial.println("Setting FSM mode FAILED"));
return 1;
}
if(sx1278.setBitrate(2500)!=0) {
DFM_DBG(Serial.println("Setting bitrate 2500bit/s FAILED"));
return 1;
}
#if DFM_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(sonde.config.dfm.agcbw)!=0) {
DFM_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.dfm.agcbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.dfm.rxbw)!=0) {
DFM_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.dfm.rxbw));
return 1;
}
if(type == STYPE_DFM09 || type == STYPE_DFM06) {
// packet mode, old version, misses some frames because chip enables rx too late after
// one frame was recevied. TODO: check if this can be fixed by changing parameters
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x1E)!=0) {
DFM_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// Set autostart_RX to 01, preamble 0, SYNC detect==on, syncsize=3 (==4 byte
//char header[] = "0110.0101 0110.0110 1010.0101 1010.1010";
const char *SYNC=(stype==STYPE_DFM09)?"\x9A\x99\x5A\x55":"\x65\x66\xA5\xAA";
if(sx1278.setSyncConf(0x53, 4, (const uint8_t *)SYNC)!=0) {
DFM_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
//if(sx1278.setPreambleDetect(0xA8)!=0) {
if(sx1278.setPreambleDetect(0xAA)!=0) {
DFM_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
// Packet config 1: fixed len, mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x28, 0x40)!=0) {
DFM_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
sx1278.setPayloadLength(33); // Expect 33 bytes (7+13+13 bytes)
} else {
// continuous mode
// Enable auto-AFC, auto-AGC, RX Trigger by preamble ????
if(sx1278.setRxConf(0x1E)!=0) {
DFM_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// working with continuous RX
const char *SYNC="\xAA\xAA";
if(sx1278.setSyncConf(0x70, 2, (const uint8_t *)SYNC)!=0) {
DFM_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
if(sx1278.setPreambleDetect(0xA8)!=0) {
//if(sx1278.setPreambleDetect(0x9F)!=0) {
DFM_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
DFM_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
sx1278.setPayloadLength(0); // infinite for now...
}
Serial.print("DFM: setting RX frequency to ");
Serial.println(frequency);
int retval = sx1278.setFrequency(frequency);
sx1278.clearIRQFlags();
// Do this only once in setup in continous mode
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
memset((void *)&dfmstate, 0, sizeof(dfmstate));
DFM_DBG(Serial.println("Setting SX1278 config for DFM finished\n"); Serial.println());
return retval;
}
#define bitpick(value,bitpos) (((value)>>(7-(bitpos)))&0x01)
// Input: str: packed data, MSB first
void DFM::deinterleave(uint8_t *str, int L, uint8_t *block) {
int i, j;
for (j = 0; j < B; j++) { // L = 7 (CFG), 13 (DAT1, DAT2)
for (i = 0; i < L; i++) {
block[B*i+j] = bitpick( str[(L*j+i)/8], (L*j+i)&7 )?0:1;
}
}
}
uint32_t DFM::bits2val(const uint8_t *bits, int len) {
uint32_t val = 0;
for (int j = 0; j < len; j++) {
val |= (bits[j] << (len-1-j));
}
return val;
}
// Error correction for hamming code
// returns 0: ok >0: 1 error was corrected -1: uncorrectable error
int DFM::check(uint8_t code[8]) {
int i, j;
uint32_t synval = 0;
uint8_t syndrom[4];
int ret=0;
for (i = 0; i < 4; i++) {
syndrom[i] = 0;
for (j = 0; j < 8; j++) {
syndrom[i] ^= H[i][j] & code[j];
}
}
synval = bits2val(syndrom, 4);
if (synval) {
ret = -1;
for (j = 0; j < 8; j++) { // 1-bit-error
if (synval == He[j]) { // reicht auf databits zu pruefen, d.h.
ret = j+1; // (systematischer Code) He[0..3]
break;
}
}
}
else ret = 0;
if (ret > 0) code[ret-1] ^= 0x1;
return ret;
}
// Extended (8,4) Hamming code
// Return number of corrected bits, -1 if uncorrectable error
int DFM::hamming(uint8_t *ham, int L, uint8_t *sym) {
int i, j;
int ret = 0; // DFM: length L = 7 or 13
for (i = 0; i < L; i++) { // L bytes (4bit data, 4bit parity)
if (use_ecc) {
int res = check(ham+8*i);
if(ret>=0 && res>=0) ret += res; else ret=-1;
}
// systematic Hamming code: copy bits 0..3
for (j = 0; j < 4; j++) {
sym[4*i+j] = ham[8*i+j];
}
}
return ret;
}
DFM::DFM() {
}
void DFM::printRaw(const char *label, int len, int ret, const uint8_t *data)
{
Serial.print(label); Serial.print("(");
Serial.print(ret);
Serial.print("):");
int i;
for(i=0; i<len/2; i++) {
char str[10];
snprintf(str, 10, "%02X", data[i]);
Serial.print(str);
}
Serial.print(data[i]&0x0F, HEX);
Serial.print(" ");
}
const char* typestr[16]={
"", "", "", "", "", "", // 00..05
"DFM6", // 06 => DFM6
"PS15", // 07 => PS15 (untested)
"", "",
"DFM9", // 0A => DFM9
"DF17", // 0B => DFM17?
"DF9P", // 0C => DFM9P or DFM17 test
"DF17", // 0D => DFM17
"", ""
};
#define DFMIDTHRESHOLD 2
/* inspired by oe5dxl's finddnmae in sondeudp.c of dxlaprs */
void DFM::finddfname(uint8_t *b)
{
uint8_t st;
uint32_t thres;
uint32_t i;
uint8_t ix;
uint16_t d;
st = b[0]; /* frame start byte */
ix = b[3]; /* hi/lo part of ser; (LSB due to our bitsToBytes...) */
d = (b[1]<<8) + b[2]; /* data byte */
/* find highest channel number single frame serial,
(2 frame serial will make a single serial too) */
if(dfmstate.idcnt0 < DFMIDTHRESHOLD && dfmstate.idcnt1 < DFMIDTHRESHOLD) {
uint32_t v = (st<<20) | (d<<4) | ix;
if ( st > (dfmstate.lastfrid>>20) ) {
dfmstate.lastfrid = v;
Serial.print(" MAXCH:"); Serial.print(st);
dfmstate.lastfrcnt = 0;
} else if ( st == (dfmstate.lastfrid>>20) ) {
/* same id found */
if (v == dfmstate.lastfrid) {
++dfmstate.lastfrcnt;
thres = DFMIDTHRESHOLD * 2;
/* may be a 2 frame serial so increase safety level */
if (ix <= 1) thres *= 2;
/* may be not a dfm6 so increase safety level */
if ( (st>>4) != 6) thres *= 2;
if (dfmstate.lastfrcnt >= thres) {
/* id found */
if (dfmstate.lastfrcnt == thres) {
uint32_t id = ((st&0x0F)<<20) | (d<<4) | ix;
uint32_t chkid = id;
int i;
/* check validity */
for(i=0; i<6; i++) {
if((chkid&0x0f)>9) { break; /* not ok */ }
chkid >>= 4;
}
if(i==6) {
snprintf(sonde.si()->id, 10, "D%x ", id);
sonde.si()->validID = true;
strncpy(sonde.si()->typestr, typestr[ (st>>4)&0x0F ], 5);
return;
}
dfmstate.lastfrcnt = 0;
Serial.print(" NOT NUMERIC SERIAL");
}
//anonym->idtime = osic_time();
} else {
Serial.print(" MAXCHCNT/SECLVL:");
Serial.print(dfmstate.lastfrcnt);
Serial.print("/");
Serial.print(thres);
}
} else {
dfmstate.lastfrid = v; /* not stable ser */
dfmstate.lastfrcnt = 0UL;
}
}
} /*find highest channel number single frame serial */
i = 0;
while (i<dfmstate.nameregtop && dfmstate.start[i]!=st) i++;
Serial.printf(" %02x:i=%d,top=%d", st, i, dfmstate.nameregtop);
if (i<dfmstate.nameregtop) {
if (ix<=1UL && (dfmstate.cnt[2*i+ix]==0 || dfmstate.dat[2*i+ix]==d)) {
dfmstate.dat[2*i+ix] = d;
if(dfmstate.cnt[2*i+ix] < 255) dfmstate.cnt[2*i+ix]++;
Serial.print(" ID:");
Serial.print(st, HEX);
Serial.print("[");
Serial.print(ix);
Serial.print("] CNT:");
Serial.print(dfmstate.cnt[2*i]);
Serial.print(",");
Serial.print(dfmstate.cnt[2*i+1]);
Serial.print(",st=");
Serial.print(st);
Serial.print(",lastfrid=");
Serial.print(dfmstate.lastfrid>>20);
if( (dfmstate.cnt[2*i]>DFMIDTHRESHOLD && dfmstate.cnt[2*i+1]>DFMIDTHRESHOLD) ||
(dfmstate.cnt[2*i]>0 && dfmstate.cnt[2*i+1]>0 && st == (dfmstate.lastfrid>>20) && (st>>4)>6) ) {
if(dfmstate.idcnt0 <= 1) {
dfmstate.idcnt0 = dfmstate.cnt[2*i];
dfmstate.idcnt1 = dfmstate.cnt[2*i+1];
dfmstate.nameregok = i;
// generate id.....
snprintf(sonde.si()->id, 10, "D%d", ((dfmstate.dat[2*i]<<16)|dfmstate.dat[2*i+1])%100000000);
Serial.print("\nNEW AUTOID:");
Serial.println(sonde.si()->id);
sonde.si()->validID = true;
strncpy(sonde.si()->typestr, typestr[ (st>>4)&0x0F ], 5);
}
if(dfmstate.nameregok==i) {
Serial.print(" ID OK");
// idtime = .... /* TODO */
}
}
} else {
/* data changed so not ser */
dfmstate.cnt[2*i] = 0;
dfmstate.cnt[2*i+1] = 0;
if(dfmstate.nameregok == i) { /* found id wrong */
dfmstate.idcnt0 = 0;
dfmstate.idcnt1 = 0;
}
}
} else if (ix<=1) { /* add new entry for possible ID */
dfmstate.start[dfmstate.nameregtop] = st;
dfmstate.cnt[2*dfmstate.nameregtop] = 0;
dfmstate.cnt[2*dfmstate.nameregtop+1] = 0;
dfmstate.cnt[2*dfmstate.nameregtop+ix] = 1;
dfmstate.dat[2*dfmstate.nameregtop+ix] = d;
if(dfmstate.nameregtop<49) dfmstate.nameregtop++;
}
}
void DFM::decodeCFG(uint8_t *cfg)
{
#if 1
// new ID
finddfname(cfg);
// new aprs ID (dxlaprs, autorx) is now "D" + serial (8 digits) by consensus
memcpy(sonde.si()->ser, sonde.si()->id+1, 9);
#else
// old ID
static int lowid, highid, idgood=0, type=0;
if((cfg[0]>>4)==0x06 && type==0) { // DFM-6 ID
lowid = ((cfg[0]&0x0F)<<20) | (cfg[1]<<12) | (cfg[2]<<4) | (cfg[3]&0x0f);
Serial.print("DFM-06 ID: "); Serial.print(lowid, HEX);
snprintf(sonde.si()->id, 10, "%x", lowid);
sonde.si()->validID = true;
}
if((cfg[0]>>4)==0x0A) { // DMF-9 ID
type=9;
if(cfg[3]==1) {
lowid = (cfg[1]<<8) | cfg[2];
idgood |= 1;
} else {
highid = (cfg[1]<<8) | cfg[2];
idgood |= 2;
}
if(idgood==3) {
uint32_t dfmid = (highid<<16) | lowid;
Serial.print("DFM-09 ID: "); Serial.print(dfmid);
snprintf(sonde.si()->ser, 10, "%d", dfmid);
// dxlAPRS sonde number (DF6 (why??) and 5 last digits of serial number as hex number
snprintf(sonde.si()->id, 9, "DF6%05X", dfmid&0xfffff);
sonde.si()->validID = true;
}
}
#endif
}
static int bitCount(int x) {
int m4 = 0x1 | (0x1<<8) | (0x1<<16) | (0x1<<24);
int m1 = 0xFF;
int s4 = (x&m4) + ((x>>1)&m4) + ((x>>2)&m4) + ((x>>3)&m4) + ((x>>4)&m4) + ((x>>5)&m4) + ((x>>6)&m4) + ((x>>7)&m4);
int s1 = (s4&m1) + ((s4>>8)&m1) + ((s4>>16)&m1) + ((s4>>24)&m1);
return s1;
}
static uint16_t MON[]={0,0,31,59,90,120,151,181,212,243,273,304,334};
void DFM::decodeDAT(uint8_t *dat)
{
Serial.print(" DAT["); Serial.print(dat[6]); Serial.print("]: ");
switch(dat[6]) {
case 0:
Serial.print("Packet counter: "); Serial.print(dat[3]);
sonde.si()->frame = dat[3];
break;
case 1:
{
int val = (((uint16_t)dat[4])<<8) + (uint16_t)dat[5];
Serial.print("UTC-msec: "); Serial.print(val);
sonde.si()->sec = val/1000;
uint32_t tmp = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + ((uint32_t)dat[3]);
sonde.si()->sats = bitCount(tmp); // maybe!?!?!?
}
break;
case 2:
{
float lat, vh;
lat = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + ((uint32_t)dat[3]);
vh = ((uint16_t)dat[4]<<8) + dat[5];
Serial.print("GPS-lat: "); Serial.print(lat*0.0000001);
Serial.print(", hor-V: "); Serial.print(vh*0.01);
sonde.si()->lat = lat*0.0000001;
sonde.si()->hs = vh*0.01;
sonde.si()->validPos |= 0x11;
}
break;
case 3:
{
float lon, dir;
lon = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + (uint32_t)dat[3];
dir = ((uint16_t)dat[4]<<8) + dat[5];
Serial.print("GPS-lon: "); Serial.print(lon*0.0000001);
Serial.print(", dir: "); Serial.print(dir*0.01);
sonde.si()->lon = lon*0.0000001;
sonde.si()->dir = dir*0.01;
sonde.si()->validPos |= 0x42;
}
break;
case 4:
{
float alt, vv;
alt = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + dat[3];
vv = (int16_t)( ((int16_t)dat[4]<<8) | dat[5] );
Serial.print("GPS-height: "); Serial.print(alt*0.01);
Serial.print(", vv: "); Serial.print(vv*0.01);
sonde.si()->alt = alt*0.01;
sonde.si()->vs = vv*0.01;
sonde.si()->validPos |= 0x0C;
}
break;
case 8:
{
int y = (dat[0]<<4) + (dat[1]>>4);
int m = dat[1]&0x0F;
int d = dat[2]>>3;
int h = ((dat[2]&0x07)<<2) + (dat[3]>>6);
int mi = (dat[3]&0x3F);
char buf[100];
snprintf(buf, 100, "%04d-%02d-%02d %02d:%02dz", y, m, d, h, mi);
Serial.print("Date: "); Serial.print(buf);
// convert to unix time
int tt = (y-1970)*365 + (y-1969)/4; // days since 1970
if(m<=12) { tt += MON[m]; if((y%4)==0 && m>2) tt++; }
tt = (tt+d-1)*(60*60*24) + h*3600 + mi*60;
sonde.si()->time = tt;
}
break;
default:
Serial.print("(?)");
break;
}
}
void DFM::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len)
{
int i;
for(i=0; i<len*4; i++) {
//Serial.print(bits[i]?"1":"0");
bytes[i/8] = (bytes[i/8]<<1) | (bits[i]?1:0);
}
bytes[(i-1)/8] &= 0x0F;
}
static int haveNewFrame = 0;
int DFM::processDFMdata(uint8_t dt) {
static uint8_t data[1024];
static uint32_t rxdata = 0;
static uint8_t rxbitc = 0;
static uint8_t rxbyte = 0;
static uint8_t rxsearching = 1;
static uint8_t rxp;
static int rssi=0, fei=0, afc=0;
static uint8_t invers = 0;
for(int i=0; i<8; i++) {
uint8_t d = (dt&0x80)?1:0;
dt <<= 1;
rxdata = (rxdata<<1) | d;
if( (rxbitc&1)==0 ) {
// "bit1"
rxbyte = (rxbyte<<1) | d;
} else {
// "bit2" ==> 01 or 10 => 1, otherweise => 0
// not here: (10=>1, 01=>0)!!! rxbyte = rxbyte ^ d;
}
//
if(rxsearching) {
if( rxdata == 0x6566A5AA || rxdata == 0x9A995A55 ) {
rxsearching = false;
rxbitc = 0;
rxp = 0;
rxbyte = 0;
rssi=sx1278.getRSSI();
fei=sx1278.getFEI();
afc=sx1278.getAFC();
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
invers = (rxdata == 0x6566A5AA)?1:0;
}
} else {
rxbitc = (rxbitc+1)%16; // 16;
if(rxbitc == 0) { // got 8 data bit
if(invers) rxbyte=~rxbyte;
data[rxp++] = rxbyte&0xff; // (rxbyte>>1)&0xff;
if(rxp>=DFM_FRAMELEN) {
rxsearching = true;
//Serial.println("Got a DFM frame!");
Serial.print("[RSSI="); Serial.print(rssi);
Serial.print(" FEI="); Serial.print(fei);
Serial.print(" AFC="); Serial.print(afc); Serial.print("] ");
decodeFrameDFM(data);
haveNewFrame = 1;
}
}
}
}
return 0;
}
int DFM::receive() {
if( stype == STYPE_DFM ) {
return receiveNew();
} else {
return receiveOld();
}
}
int DFM::receiveNew() {
int rxframes = 4;
// tentative continuous RX version...
unsigned long t0 = millis();
while( ( millis() - t0 ) < 1000 ) {
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
if ( bitRead(value, 7) ) {
Serial.println("FIFO full");
}
if ( bitRead(value, 4) ) {
Serial.println("FIFO overflow");
// new: (maybe clear only overflow??) TODO
sx1278.clearIRQFlags();
}
if ( bitRead(value, 2) == 1 ) {
Serial.println("FIFO: payload ready()");
// does not make much sence? (from m10): TODO
// ??????? sx1278.clearIRQFlags();
}
if(bitRead(value, 6) == 0) { // while FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
processDFMdata(data);
value = sx1278.readRegister(REG_IRQ_FLAGS2);
} else {
#if 0
if(headerDetected) {
t0 = millis(); // restart timer... don't time out if header detected...
headerDetected = 0;
}
#endif
if(haveNewFrame) {
//Serial.printf("DFM::receive(): new frame complete after %ldms\n", millis()-t0);
haveNewFrame = 0;
rxframes--;
if(rxframes==0) return RX_OK;
}
delay(2);
}
}
return RX_TIMEOUT;
}
int DFM::receiveOld() {
byte data[1000]; // pending data from previous mode may write more than 33 bytes. TODO.
for(int i=0; i<2; i++) {
sx1278.setPayloadLength(33); // Expect 33 bytes (7+13+13 bytes)
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
//int t = millis();
int e = sx1278.receivePacketTimeout(1000, data);
//Serial.printf("rxPTO done after %d ms", (int)(millis()-t));
if(e) { return RX_TIMEOUT; } //if timeout... return 1
if(!(stype==STYPE_DFM09)) { for(int i=0; i<33; i++) { data[i]^=0xFF; } }
decodeFrameDFM(data);
}
return RX_OK;
}
int DFM::decodeFrameDFM(uint8_t *data) {
deinterleave(data, 7, hamming_conf);
deinterleave(data+7, 13, hamming_dat1);
deinterleave(data+20, 13, hamming_dat2);
#if 0
Serial.print("RAWCFG:");
for(int i=0; i<7*8; i++) {
Serial.print(hamming_conf[i]?"1":"0");
}
#endif
int ret0 = hamming(hamming_conf, 7, block_conf);
int ret1 = hamming(hamming_dat1, 13, block_dat1);
int ret2 = hamming(hamming_dat2, 13, block_dat2);
byte byte_conf[4], byte_dat1[7], byte_dat2[7];
bitsToBytes(block_conf, byte_conf, 7);
bitsToBytes(block_dat1, byte_dat1, 13);
bitsToBytes(block_dat2, byte_dat2, 13);
printRaw("CFG", 7, ret0, byte_conf);
printRaw("DAT", 13, ret1, byte_dat1);
printRaw("DAT", 13, ret2, byte_dat2);
decodeCFG(byte_conf);
decodeDAT(byte_dat1);
decodeDAT(byte_dat2);
Serial.println("");
return RX_OK;
}
// moved to a single function in Sonde(). This function can be used for additional
// processing here, that takes too long for doing in the RX task loop
int DFM::waitRXcomplete() {
#if 0
int res=0;
uint32_t t0 = millis();
while( rxtask.receiveResult < 0 && millis()-t0 < 2000) { delay(50); }
if( rxtask.receiveResult<0 || rxtask.receiveResult==RX_TIMEOUT) {
res = RX_TIMEOUT;
} else if ( rxtask.receiveResult ==0) {
res = RX_OK;
} else {
res = RX_ERROR;
}
rxtask.receiveResult = -1;
Serial.printf("waitRXcomplete returning %d\n", res);
return res;
#endif
return 0;
}
DFM dfm = DFM();

74
libraries/SondeLib/DFM.h Executable file
View File

@ -0,0 +1,74 @@
/*
* DFM.h
* Functions for decoding DFM sondes with SX127x chips
* Copyright (C) 2019 Hansi Reiser, dl9rdz
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef DFM_h
#define DFM_h
#include <stdlib.h>
#include <stdint.h>
#include <Arduino.h>
#ifndef inttypes_h
#include <inttypes.h>
#endif
#define DFM_NORMAL 0
#define DFM_INVERSE 1
/* Main class */
class DFM
{
private:
int stype;
char *stypename=NULL;
void deinterleave(uint8_t *str, int L, uint8_t *block);
uint32_t bits2val(const uint8_t *bits, int len);
int check(uint8_t code[8]);
int hamming(uint8_t *ham, int L, uint8_t *sym);
void printRaw(const char *prefix, int len, int ret, const uint8_t* data);
void finddfname(uint8_t *cfg);
void decodeCFG(uint8_t *cfg);
void decodeDAT(uint8_t *dat);
void bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
int processDFMdata(uint8_t dt);
int decodeFrameDFM(uint8_t *data);
int receiveOld();
int receiveNew();
#define B 8
#define S 4
uint8_t hamming_conf[ 7*B]; // 7*8=56
uint8_t hamming_dat1[13*B]; // 13*8=104
uint8_t hamming_dat2[13*B];
uint8_t block_conf[ 7*S]; // 7*4=28
uint8_t block_dat1[13*S]; // 13*4=52
uint8_t block_dat2[13*S];
uint8_t H[4][8] = // extended Hamming(8,4) particy check matrix
{{ 0, 1, 1, 1, 1, 0, 0, 0},
{ 1, 0, 1, 1, 0, 1, 0, 0},
{ 1, 1, 0, 1, 0, 0, 1, 0},
{ 1, 1, 1, 0, 0, 0, 0, 1}};
uint8_t He[8] = { 0x7, 0xB, 0xD, 0xE, 0x8, 0x4, 0x2, 0x1}; // Spalten von H:
// 1-bit-error-Syndrome
public:
DFM();
// main decoder API
int setup(float frequency, int type);
int receive();
int waitRXcomplete();
int use_ecc = 1;
};
extern DFM dfm;
#endif

367
libraries/SondeLib/DefaultFonts.c Executable file
View File

@ -0,0 +1,367 @@
// SPDX-License-Identifier: GPL-3.0
// original source: https://github.com/Nkawu/TFT_22_ILI9225
#ifdef __AVR__
#include <avr/pgmspace.h>
#elif defined(ESP8266) || defined(ESP32)
#include <pgmspace.h>
#endif
#if defined(ARDUINO_ARCH_SAM) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32F1) || defined(STM32F1) || defined(ESP32)
#define PROGMEM
#define fontdatatype const char
#else
#define fontdatatype const uint8_t
#endif
//Font Generated by MikroElektronika GLCD Font Creator 1.2.0.0
//MikroElektronika 2011
//http://www.mikroe.com
//GLCD FontName : Terminal6x8
//GLCD FontSize : 6 x 8
fontdatatype Terminal6x8[] PROGMEM = {
0x06, 0x08, 0x20, 0x60,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char
0x05, 0x00, 0x00, 0x06, 0x5F, 0x06, 0x00, // Code for char !
0x06, 0x00, 0x07, 0x03, 0x00, 0x07, 0x03, // Code for char "
0x06, 0x00, 0x24, 0x7E, 0x24, 0x7E, 0x24, // Code for char #
0x05, 0x00, 0x24, 0x2B, 0x6A, 0x12, 0x00, // Code for char $
0x06, 0x00, 0x63, 0x13, 0x08, 0x64, 0x63, // Code for char %
0x06, 0x00, 0x36, 0x49, 0x56, 0x20, 0x50, // Code for char &
0x04, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, // Code for char '
0x04, 0x00, 0x00, 0x3E, 0x41, 0x00, 0x00, // Code for char (
0x04, 0x00, 0x00, 0x41, 0x3E, 0x00, 0x00, // Code for char )
0x06, 0x00, 0x08, 0x3E, 0x1C, 0x3E, 0x08, // Code for char *
0x06, 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, // Code for char +
0x04, 0x00, 0x00, 0xE0, 0x60, 0x00, 0x00, // Code for char ,
0x06, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, // Code for char -
0x04, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, // Code for char .
0x06, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, // Code for char /
0x06, 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E, // Code for char 0
0x05, 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00, // Code for char 1
0x06, 0x00, 0x62, 0x51, 0x49, 0x49, 0x46, // Code for char 2
0x06, 0x00, 0x22, 0x49, 0x49, 0x49, 0x36, // Code for char 3
0x06, 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10, // Code for char 4
0x06, 0x00, 0x2F, 0x49, 0x49, 0x49, 0x31, // Code for char 5
0x06, 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30, // Code for char 6
0x06, 0x00, 0x01, 0x71, 0x09, 0x05, 0x03, // Code for char 7
0x06, 0x00, 0x36, 0x49, 0x49, 0x49, 0x36, // Code for char 8
0x06, 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E, // Code for char 9
0x04, 0x00, 0x00, 0x6C, 0x6C, 0x00, 0x00, // Code for char :
0x04, 0x00, 0x00, 0xEC, 0x6C, 0x00, 0x00, // Code for char ;
0x05, 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, // Code for char <
0x06, 0x00, 0x24, 0x24, 0x24, 0x24, 0x24, // Code for char =
0x06, 0x00, 0x00, 0x41, 0x22, 0x14, 0x08, // Code for char >
0x06, 0x00, 0x02, 0x01, 0x59, 0x09, 0x06, // Code for char ?
0x06, 0x00, 0x3E, 0x41, 0x5D, 0x55, 0x1E, // Code for char @
0x06, 0x00, 0x7E, 0x11, 0x11, 0x11, 0x7E, // Code for char A
0x06, 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36, // Code for char B
0x06, 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22, // Code for char C
0x06, 0x00, 0x7F, 0x41, 0x41, 0x41, 0x3E, // Code for char D
0x06, 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41, // Code for char E
0x06, 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01, // Code for char F
0x06, 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A, // Code for char G
0x06, 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F, // Code for char H
0x05, 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00, // Code for char I
0x06, 0x00, 0x30, 0x40, 0x40, 0x40, 0x3F, // Code for char J
0x06, 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41, // Code for char K
0x06, 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40, // Code for char L
0x06, 0x00, 0x7F, 0x02, 0x04, 0x02, 0x7F, // Code for char M
0x06, 0x00, 0x7F, 0x02, 0x04, 0x08, 0x7F, // Code for char N
0x06, 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E, // Code for char O
0x06, 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06, // Code for char P
0x06, 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E, // Code for char Q
0x06, 0x00, 0x7F, 0x09, 0x09, 0x19, 0x66, // Code for char R
0x06, 0x00, 0x26, 0x49, 0x49, 0x49, 0x32, // Code for char S
0x06, 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01, // Code for char T
0x06, 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F, // Code for char U
0x06, 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F, // Code for char V
0x06, 0x00, 0x3F, 0x40, 0x3C, 0x40, 0x3F, // Code for char W
0x06, 0x00, 0x63, 0x14, 0x08, 0x14, 0x63, // Code for char X
0x06, 0x00, 0x07, 0x08, 0x70, 0x08, 0x07, // Code for char Y
0x05, 0x00, 0x71, 0x49, 0x45, 0x43, 0x00, // Code for char Z
0x05, 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, // Code for char [
0x06, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, // Code for char BackSlash
0x05, 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00, // Code for char ]
0x06, 0x00, 0x04, 0x02, 0x01, 0x02, 0x04, // Code for char ^
0x06, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // Code for char _
0x04, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, // Code for char `
0x06, 0x00, 0x20, 0x54, 0x54, 0x54, 0x78, // Code for char a
0x06, 0x00, 0x7F, 0x44, 0x44, 0x44, 0x38, // Code for char b
0x06, 0x00, 0x38, 0x44, 0x44, 0x44, 0x28, // Code for char c
0x06, 0x00, 0x38, 0x44, 0x44, 0x44, 0x7F, // Code for char d
0x06, 0x00, 0x38, 0x54, 0x54, 0x54, 0x08, // Code for char e
0x05, 0x00, 0x08, 0x7E, 0x09, 0x09, 0x00, // Code for char f
0x06, 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C, // Code for char g
0x05, 0x00, 0x7F, 0x04, 0x04, 0x78, 0x00, // Code for char h
0x05, 0x00, 0x00, 0x00, 0x7D, 0x40, 0x00, // Code for char i
0x05, 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00, // Code for char j
0x05, 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, // Code for char k
0x05, 0x00, 0x00, 0x00, 0x7F, 0x40, 0x00, // Code for char l
0x06, 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78, // Code for char m
0x05, 0x00, 0x7C, 0x04, 0x04, 0x78, 0x00, // Code for char n
0x06, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, // Code for char o
0x06, 0x00, 0xFC, 0x44, 0x44, 0x44, 0x38, // Code for char p
0x06, 0x00, 0x38, 0x44, 0x44, 0x44, 0xFC, // Code for char q
0x06, 0x00, 0x44, 0x78, 0x44, 0x04, 0x08, // Code for char r
0x06, 0x00, 0x08, 0x54, 0x54, 0x54, 0x20, // Code for char s
0x05, 0x00, 0x04, 0x3E, 0x44, 0x24, 0x00, // Code for char t
0x05, 0x00, 0x3C, 0x40, 0x20, 0x7C, 0x00, // Code for char u
0x06, 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C, // Code for char v
0x06, 0x00, 0x3C, 0x60, 0x30, 0x60, 0x3C, // Code for char w
0x05, 0x00, 0x6C, 0x10, 0x10, 0x6C, 0x00, // Code for char x
0x05, 0x00, 0x9C, 0xA0, 0x60, 0x3C, 0x00, // Code for char y
0x05, 0x00, 0x64, 0x54, 0x54, 0x4C, 0x00, // Code for char z
0x05, 0x00, 0x08, 0x3E, 0x41, 0x41, 0x00, // Code for char {
0x04, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, // Code for char |
0x06, 0x00, 0x00, 0x41, 0x41, 0x3E, 0x08, // Code for char }
0x05, 0x00, 0x02, 0x01, 0x02, 0x01, 0x00, // Code for char ~
0x06, 0x00, 0x3C, 0x26, 0x23, 0x26, 0x3C // Code for char 
};
//Font Generated by MikroElektronika GLCD Font Creator 1.2.0.0
//MikroElektronika 2011
//http://www.mikroe.com
//GLCD FontName : Terminal11x16
//GLCD FontSize : 11 x 16
fontdatatype Terminal11x16[] PROGMEM = {
0x0B, 0x10, 0x20, 0x60,
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFF, 0x33, 0xFF, 0x33, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char !
0x08, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char "
0x0B, 0x00, 0x02, 0x10, 0x1E, 0x90, 0x1F, 0xF0, 0x03, 0x7E, 0x02, 0x1E, 0x1E, 0x90, 0x1F, 0xF0, 0x03, 0x7E, 0x02, 0x1E, 0x00, 0x10, 0x00, // Code for char #
0x09, 0x00, 0x00, 0x78, 0x04, 0xFC, 0x0C, 0xCC, 0x0C, 0xFF, 0x3F, 0xFF, 0x3F, 0xCC, 0x0C, 0xCC, 0x0F, 0x88, 0x07, 0x00, 0x00, 0x00, 0x00, // Code for char $
0x0B, 0x00, 0x30, 0x38, 0x38, 0x38, 0x1C, 0x38, 0x0E, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x38, 0x70, 0x38, 0x38, 0x38, 0x1C, 0x00, // Code for char %
0x0A, 0x00, 0x00, 0x00, 0x1F, 0xB8, 0x3F, 0xFC, 0x31, 0xC6, 0x21, 0xE2, 0x37, 0x3E, 0x1E, 0x1C, 0x1C, 0x00, 0x36, 0x00, 0x22, 0x00, 0x00, // Code for char &
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x3F, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char '
0x08, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0xFC, 0x0F, 0xFE, 0x1F, 0x07, 0x38, 0x01, 0x20, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char (
0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x01, 0x20, 0x07, 0x38, 0xFE, 0x1F, 0xFC, 0x0F, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char )
0x09, 0x00, 0x00, 0x98, 0x0C, 0xB8, 0x0E, 0xE0, 0x03, 0xF8, 0x0F, 0xF8, 0x0F, 0xE0, 0x03, 0xB8, 0x0E, 0x98, 0x0C, 0x00, 0x00, 0x00, 0x00, // Code for char *
0x09, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xF0, 0x0F, 0xF0, 0x0F, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, // Code for char +
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0x00, 0xF8, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ,
0x09, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, // Code for char -
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char .
0x0B, 0x00, 0x18, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, // Code for char /
0x0B, 0xF8, 0x07, 0xFE, 0x1F, 0x06, 0x1E, 0x03, 0x33, 0x83, 0x31, 0xC3, 0x30, 0x63, 0x30, 0x33, 0x30, 0x1E, 0x18, 0xFE, 0x1F, 0xF8, 0x07, // Code for char 0
0x0A, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x30, 0x0C, 0x30, 0x0E, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, // Code for char 1
0x0B, 0x1C, 0x30, 0x1E, 0x38, 0x07, 0x3C, 0x03, 0x3E, 0x03, 0x37, 0x83, 0x33, 0xC3, 0x31, 0xE3, 0x30, 0x77, 0x30, 0x3E, 0x30, 0x1C, 0x30, // Code for char 2
0x0B, 0x0C, 0x0C, 0x0E, 0x1C, 0x07, 0x38, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xE7, 0x39, 0x7E, 0x1F, 0x3C, 0x0E, // Code for char 3
0x0B, 0xC0, 0x03, 0xE0, 0x03, 0x70, 0x03, 0x38, 0x03, 0x1C, 0x03, 0x0E, 0x03, 0x07, 0x03, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x03, 0x00, 0x03, // Code for char 4
0x0B, 0x3F, 0x0C, 0x7F, 0x1C, 0x63, 0x38, 0x63, 0x30, 0x63, 0x30, 0x63, 0x30, 0x63, 0x30, 0x63, 0x30, 0xE3, 0x38, 0xC3, 0x1F, 0x83, 0x0F, // Code for char 5
0x0B, 0xC0, 0x0F, 0xF0, 0x1F, 0xF8, 0x39, 0xDC, 0x30, 0xCE, 0x30, 0xC7, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x39, 0x80, 0x1F, 0x00, 0x0F, // Code for char 6
0x0B, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x30, 0x03, 0x3C, 0x03, 0x0F, 0xC3, 0x03, 0xF3, 0x00, 0x3F, 0x00, 0x0F, 0x00, 0x03, 0x00, // Code for char 7
0x0B, 0x00, 0x0F, 0xBC, 0x1F, 0xFE, 0x39, 0xE7, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xE7, 0x30, 0xFE, 0x39, 0xBC, 0x1F, 0x00, 0x0F, // Code for char 8
0x0B, 0x3C, 0x00, 0x7E, 0x00, 0xE7, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x38, 0xC3, 0x1C, 0xC3, 0x0E, 0xE7, 0x07, 0xFE, 0x03, 0xFC, 0x00, // Code for char 9
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x1C, 0x70, 0x1C, 0x70, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char :
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x9C, 0x70, 0xFC, 0x70, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ;
0x09, 0x00, 0x00, 0xC0, 0x00, 0xE0, 0x01, 0xF0, 0x03, 0x38, 0x07, 0x1C, 0x0E, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, // Code for char <
0x0A, 0x00, 0x00, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x00, 0x00, // Code for char =
0x09, 0x00, 0x00, 0x03, 0x30, 0x07, 0x38, 0x0E, 0x1C, 0x1C, 0x0E, 0x38, 0x07, 0xF0, 0x03, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char >
0x0A, 0x1C, 0x00, 0x1E, 0x00, 0x07, 0x00, 0x03, 0x00, 0x83, 0x37, 0xC3, 0x37, 0xE3, 0x00, 0x77, 0x00, 0x3E, 0x00, 0x1C, 0x00, 0x00, 0x00, // Code for char ?
0x0B, 0xF8, 0x0F, 0xFE, 0x1F, 0x07, 0x18, 0xF3, 0x33, 0xFB, 0x37, 0x1B, 0x36, 0xFB, 0x37, 0xFB, 0x37, 0x07, 0x36, 0xFE, 0x03, 0xF8, 0x01, // Code for char @
0x0A, 0x00, 0x38, 0x00, 0x3F, 0xE0, 0x07, 0xFC, 0x06, 0x1F, 0x06, 0x1F, 0x06, 0xFC, 0x06, 0xE0, 0x07, 0x00, 0x3F, 0x00, 0x38, 0x00, 0x00, // Code for char A
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xE7, 0x30, 0xFE, 0x39, 0xBC, 0x1F, 0x00, 0x0F, 0x00, 0x00, // Code for char B
0x0A, 0xF0, 0x03, 0xFC, 0x0F, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x07, 0x38, 0x0E, 0x1C, 0x0C, 0x0C, 0x00, 0x00, // Code for char C
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x07, 0x38, 0x0E, 0x1C, 0xFC, 0x0F, 0xF0, 0x03, 0x00, 0x00, // Code for char D
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0x03, 0x30, 0x03, 0x30, 0x00, 0x00, // Code for char E
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, // Code for char F
0x0A, 0xF0, 0x03, 0xFC, 0x0F, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC7, 0x3F, 0xC6, 0x3F, 0x00, 0x00, // Code for char G
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, // Code for char H
0x08, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x30, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char I
0x0A, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x38, 0xFF, 0x1F, 0xFF, 0x07, 0x00, 0x00, // Code for char J
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0xC0, 0x00, 0xE0, 0x01, 0xF0, 0x03, 0x38, 0x07, 0x1C, 0x0E, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0x00, 0x00, // Code for char K
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, // Code for char L
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0x1E, 0x00, 0x78, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0x78, 0x00, 0x1E, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, // Code for char M
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0x0E, 0x00, 0x38, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0x00, 0x07, 0x00, 0x1C, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, // Code for char N
0x0A, 0xF0, 0x03, 0xFC, 0x0F, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0x03, 0x30, 0x07, 0x38, 0x0E, 0x1C, 0xFC, 0x0F, 0xF0, 0x03, 0x00, 0x00, // Code for char O
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0x83, 0x01, 0x83, 0x01, 0x83, 0x01, 0x83, 0x01, 0x83, 0x01, 0xC7, 0x01, 0xFE, 0x00, 0x7C, 0x00, 0x00, 0x00, // Code for char P
0x0A, 0xF0, 0x03, 0xFC, 0x0F, 0x0E, 0x1C, 0x07, 0x38, 0x03, 0x30, 0x03, 0x36, 0x07, 0x3E, 0x0E, 0x1C, 0xFC, 0x3F, 0xF0, 0x33, 0x00, 0x00, // Code for char Q
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0x83, 0x01, 0x83, 0x01, 0x83, 0x03, 0x83, 0x07, 0x83, 0x0F, 0xC7, 0x1D, 0xFE, 0x38, 0x7C, 0x30, 0x00, 0x00, // Code for char R
0x0A, 0x3C, 0x0C, 0x7E, 0x1C, 0xE7, 0x38, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC7, 0x39, 0x8E, 0x1F, 0x0C, 0x0F, 0x00, 0x00, // Code for char S
0x09, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char T
0x0A, 0xFF, 0x07, 0xFF, 0x1F, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x38, 0xFF, 0x1F, 0xFF, 0x07, 0x00, 0x00, // Code for char U
0x0A, 0x07, 0x00, 0x3F, 0x00, 0xF8, 0x01, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0x3E, 0xC0, 0x0F, 0xF8, 0x01, 0x3F, 0x00, 0x07, 0x00, 0x00, 0x00, // Code for char V
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x1C, 0x00, 0x06, 0x80, 0x03, 0x80, 0x03, 0x00, 0x06, 0x00, 0x1C, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, // Code for char W
0x0A, 0x03, 0x30, 0x0F, 0x3C, 0x1C, 0x0E, 0x30, 0x03, 0xE0, 0x01, 0xE0, 0x01, 0x30, 0x03, 0x1C, 0x0E, 0x0F, 0x3C, 0x03, 0x30, 0x00, 0x00, // Code for char X
0x0A, 0x03, 0x00, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x00, 0xC0, 0x3F, 0xC0, 0x3F, 0xF0, 0x00, 0x3C, 0x00, 0x0F, 0x00, 0x03, 0x00, 0x00, 0x00, // Code for char Y
0x0A, 0x03, 0x30, 0x03, 0x3C, 0x03, 0x3E, 0x03, 0x33, 0xC3, 0x31, 0xE3, 0x30, 0x33, 0x30, 0x1F, 0x30, 0x0F, 0x30, 0x03, 0x30, 0x00, 0x00, // Code for char Z
0x08, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char [
0x0B, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x18, // Code for char BackSlash
0x08, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ]
0x0B, 0x60, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0x60, 0x00, // Code for char ^
0x0B, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, // Code for char _
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x7E, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char `
0x0A, 0x00, 0x1C, 0x40, 0x3E, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0xE0, 0x3F, 0xC0, 0x3F, 0x00, 0x00, // Code for char a
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0xC0, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xE0, 0x38, 0xC0, 0x1F, 0x80, 0x0F, 0x00, 0x00, // Code for char b
0x0A, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x38, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xC0, 0x18, 0x80, 0x08, 0x00, 0x00, // Code for char c
0x0A, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x38, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xE0, 0x30, 0xC0, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, // Code for char d
0x0A, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x3B, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0xC0, 0x13, 0x80, 0x01, 0x00, 0x00, // Code for char e
0x08, 0xC0, 0x00, 0xC0, 0x00, 0xFC, 0x3F, 0xFE, 0x3F, 0xC7, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char f
0x0A, 0x80, 0x03, 0xC0, 0xC7, 0xE0, 0xCE, 0x60, 0xCC, 0x60, 0xCC, 0x60, 0xCC, 0x60, 0xCC, 0x60, 0xE6, 0xE0, 0x7F, 0xE0, 0x3F, 0x00, 0x00, // Code for char g
0x09, 0xFF, 0x3F, 0xFF, 0x3F, 0xC0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xC0, 0x3F, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, // Code for char h
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0x30, 0xEC, 0x3F, 0xEC, 0x3F, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char i
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xC0, 0x60, 0xC0, 0xEC, 0xFF, 0xEC, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char j
0x09, 0x00, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x03, 0x80, 0x07, 0xC0, 0x0F, 0xE0, 0x1C, 0x60, 0x38, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, // Code for char k
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char l
0x0A, 0xE0, 0x3F, 0xC0, 0x3F, 0xE0, 0x00, 0xE0, 0x00, 0xC0, 0x3F, 0xC0, 0x3F, 0xE0, 0x00, 0xE0, 0x00, 0xC0, 0x3F, 0x80, 0x3F, 0x00, 0x00, // Code for char m
0x0A, 0x00, 0x00, 0xE0, 0x3F, 0xE0, 0x3F, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xC0, 0x3F, 0x80, 0x3F, 0x00, 0x00, // Code for char n
0x0A, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x38, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xE0, 0x38, 0xC0, 0x1F, 0x80, 0x0F, 0x00, 0x00, // Code for char o
0x0A, 0xE0, 0xFF, 0xE0, 0xFF, 0x60, 0x0C, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0xE0, 0x1C, 0xC0, 0x0F, 0x80, 0x07, 0x00, 0x00, // Code for char p
0x0A, 0x80, 0x07, 0xC0, 0x0F, 0xE0, 0x1C, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x0C, 0xE0, 0xFF, 0xE0, 0xFF, 0x00, 0x00, // Code for char q
0x0A, 0x00, 0x00, 0xE0, 0x3F, 0xE0, 0x3F, 0xC0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00, // Code for char r
0x08, 0xC0, 0x11, 0xE0, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x3F, 0x40, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char s
0x08, 0x60, 0x00, 0x60, 0x00, 0xFE, 0x1F, 0xFE, 0x3F, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char t
0x0A, 0xE0, 0x0F, 0xE0, 0x1F, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x18, 0xE0, 0x3F, 0xE0, 0x3F, 0x00, 0x00, // Code for char u
0x0A, 0x60, 0x00, 0xE0, 0x01, 0x80, 0x07, 0x00, 0x1E, 0x00, 0x38, 0x00, 0x38, 0x00, 0x1E, 0x80, 0x07, 0xE0, 0x01, 0x60, 0x00, 0x00, 0x00, // Code for char v
0x0A, 0xE0, 0x07, 0xE0, 0x1F, 0x00, 0x38, 0x00, 0x1C, 0xE0, 0x0F, 0xE0, 0x0F, 0x00, 0x1C, 0x00, 0x38, 0xE0, 0x1F, 0xE0, 0x07, 0x00, 0x00, // Code for char w
0x09, 0x60, 0x30, 0xE0, 0x38, 0xC0, 0x1D, 0x80, 0x0F, 0x00, 0x07, 0x80, 0x0F, 0xC0, 0x1D, 0xE0, 0x38, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, // Code for char x
0x09, 0x00, 0x00, 0x60, 0x00, 0xE0, 0x81, 0x80, 0xE7, 0x00, 0x7E, 0x00, 0x1E, 0x80, 0x07, 0xE0, 0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char y
0x09, 0x60, 0x30, 0x60, 0x38, 0x60, 0x3C, 0x60, 0x36, 0x60, 0x33, 0xE0, 0x31, 0xE0, 0x30, 0x60, 0x30, 0x20, 0x30, 0x00, 0x00, 0x00, 0x00, // Code for char z
0x09, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x01, 0xFC, 0x1F, 0x7E, 0x3F, 0x07, 0x70, 0x03, 0x60, 0x03, 0x60, 0x03, 0x60, 0x00, 0x00, 0x00, 0x00, // Code for char {
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char |
0x09, 0x00, 0x00, 0x03, 0x60, 0x03, 0x60, 0x03, 0x60, 0x07, 0x70, 0x7E, 0x3F, 0xFC, 0x1F, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char }
0x0A, 0x10, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x10, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x04, 0x00, 0x00, 0x00, // Code for char ~
0x0A, 0x00, 0x0F, 0x80, 0x0F, 0xC0, 0x0C, 0x60, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x60, 0x0C, 0xC0, 0x0C, 0x80, 0x0F, 0x00, 0x0F, 0x00, 0x00 // Code for char 
};
//Font Generated by MikroElektronika GLCD Font Creator 1.2.0.0
//MikroElektronika 2011
//http://www.mikroe.com
//GLCD FontName : Terminal12x16
//GLCD FontSize : 12 x 16
fontdatatype Terminal12x16[] PROGMEM = {
0x0C, 0x10, 0x20, 0x60,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFF, 0x33, 0xFF, 0x33, 0xFF, 0x33, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char !
0x09, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char "
0x0C, 0x00, 0x02, 0x10, 0x1E, 0x90, 0x1F, 0xF0, 0x1F, 0xFE, 0x03, 0x7E, 0x1E, 0x9E, 0x1F, 0xF0, 0x1F, 0xFE, 0x03, 0x7E, 0x02, 0x1E, 0x00, 0x10, 0x00, // Code for char #
0x0A, 0x00, 0x00, 0x78, 0x04, 0xFC, 0x0C, 0xFC, 0x0C, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0xCC, 0x0F, 0xCC, 0x0F, 0x88, 0x07, 0x00, 0x00, 0x00, 0x00, // Code for char $
0x0C, 0x00, 0x30, 0x38, 0x38, 0x38, 0x3C, 0x38, 0x1E, 0x38, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x39, 0xF0, 0x38, 0x78, 0x38, 0x3C, 0x38, 0x1C, 0x00, // Code for char %
0x0B, 0x00, 0x00, 0x00, 0x1F, 0xB8, 0x3F, 0xFC, 0x3F, 0xFE, 0x31, 0xE6, 0x37, 0xFE, 0x3F, 0x3E, 0x1E, 0x1C, 0x3E, 0x00, 0x36, 0x00, 0x22, 0x00, 0x00, // Code for char &
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char '
0x09, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0xFC, 0x0F, 0xFE, 0x1F, 0xFF, 0x3F, 0x07, 0x38, 0x01, 0x20, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char (
0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x01, 0x20, 0x07, 0x38, 0xFF, 0x3F, 0xFE, 0x1F, 0xFC, 0x0F, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char )
0x0A, 0x00, 0x00, 0x98, 0x0C, 0xB8, 0x0E, 0xF8, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xB8, 0x0E, 0x98, 0x0C, 0x00, 0x00, 0x00, 0x00, // Code for char *
0x0A, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, // Code for char +
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ,
0x0A, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, // Code for char -
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char .
0x0C, 0x00, 0x18, 0x00, 0x1C, 0x00, 0x1E, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0E, 0x00, // Code for char /
0x0C, 0xF8, 0x07, 0xFE, 0x1F, 0xFE, 0x1F, 0x07, 0x3F, 0x83, 0x33, 0xC3, 0x31, 0xE3, 0x30, 0x73, 0x30, 0x3F, 0x38, 0xFE, 0x1F, 0xFE, 0x1F, 0xF8, 0x07, // Code for char 0
0x0B, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x30, 0x0C, 0x30, 0x0E, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, // Code for char 1
0x0C, 0x1C, 0x30, 0x1E, 0x38, 0x1F, 0x3C, 0x07, 0x3E, 0x03, 0x3F, 0x83, 0x37, 0xC3, 0x33, 0xE3, 0x31, 0xF7, 0x30, 0x7F, 0x30, 0x3E, 0x30, 0x1C, 0x30, // Code for char 2
0x0C, 0x0C, 0x0C, 0x0E, 0x1C, 0x0F, 0x3C, 0xC7, 0x38, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xE7, 0x39, 0xFF, 0x3F, 0x7E, 0x1F, 0x3C, 0x0E, // Code for char 3
0x0C, 0xC0, 0x03, 0xE0, 0x03, 0xF0, 0x03, 0x78, 0x03, 0x3C, 0x03, 0x1E, 0x03, 0x0F, 0x03, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x03, 0x00, 0x03, // Code for char 4
0x0C, 0x3F, 0x0C, 0x7F, 0x1C, 0x7F, 0x3C, 0x63, 0x38, 0x63, 0x30, 0x63, 0x30, 0x63, 0x30, 0x63, 0x30, 0xE3, 0x38, 0xE3, 0x3F, 0xC3, 0x1F, 0x83, 0x0F, // Code for char 5
0x0C, 0xC0, 0x0F, 0xF0, 0x1F, 0xF8, 0x3F, 0xFC, 0x39, 0xDE, 0x30, 0xCF, 0x30, 0xC7, 0x30, 0xC3, 0x30, 0xC3, 0x39, 0xC3, 0x3F, 0x80, 0x1F, 0x00, 0x0F, // Code for char 6
0x0C, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x30, 0x03, 0x3C, 0x03, 0x3F, 0xC3, 0x0F, 0xF3, 0x03, 0xFF, 0x00, 0x3F, 0x00, 0x0F, 0x00, 0x03, 0x00, // Code for char 7
0x0C, 0x00, 0x0F, 0xBC, 0x1F, 0xFE, 0x3F, 0xFF, 0x39, 0xE7, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xE7, 0x30, 0xFF, 0x39, 0xFE, 0x3F, 0xBC, 0x1F, 0x00, 0x0F, // Code for char 8
0x0C, 0x3C, 0x00, 0x7E, 0x00, 0xFF, 0x30, 0xE7, 0x30, 0xC3, 0x30, 0xC3, 0x38, 0xC3, 0x3C, 0xC3, 0x1E, 0xE7, 0x0F, 0xFF, 0x07, 0xFE, 0x03, 0xFC, 0x00, // Code for char 9
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x1C, 0x70, 0x1C, 0x70, 0x1C, 0x70, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char :
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x9C, 0x70, 0xFC, 0x70, 0xFC, 0x70, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ;
0x0A, 0x00, 0x00, 0xC0, 0x00, 0xE0, 0x01, 0xF0, 0x03, 0xF8, 0x07, 0x3C, 0x0F, 0x1E, 0x1E, 0x0F, 0x3C, 0x07, 0x38, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, // Code for char <
0x0B, 0x00, 0x00, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x00, 0x00, // Code for char =
0x0A, 0x00, 0x00, 0x03, 0x30, 0x07, 0x38, 0x0F, 0x3C, 0x1E, 0x1E, 0x3C, 0x0F, 0xF8, 0x07, 0xF0, 0x03, 0xE0, 0x01, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char >
0x0B, 0x1C, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x07, 0x00, 0x83, 0x37, 0xC3, 0x37, 0xE3, 0x37, 0xF7, 0x00, 0x7F, 0x00, 0x3E, 0x00, 0x1C, 0x00, 0x00, 0x00, // Code for char ?
0x0C, 0xF8, 0x0F, 0xFE, 0x1F, 0xFF, 0x1F, 0xF7, 0x3B, 0xFB, 0x37, 0xFB, 0x37, 0xFB, 0x37, 0xFB, 0x37, 0xFF, 0x37, 0xFF, 0x37, 0xFE, 0x03, 0xF8, 0x01, // Code for char @
0x0B, 0x00, 0x38, 0x00, 0x3F, 0xE0, 0x3F, 0xFC, 0x07, 0xFF, 0x06, 0x1F, 0x06, 0xFF, 0x06, 0xFC, 0x07, 0xE0, 0x3F, 0x00, 0x3F, 0x00, 0x38, 0x00, 0x00, // Code for char A
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xE7, 0x30, 0xFF, 0x39, 0xFE, 0x3F, 0xBC, 0x1F, 0x00, 0x0F, 0x00, 0x00, // Code for char B
0x0B, 0xF0, 0x03, 0xFC, 0x0F, 0xFE, 0x1F, 0x0F, 0x3C, 0x07, 0x38, 0x03, 0x30, 0x03, 0x30, 0x07, 0x38, 0x0F, 0x3C, 0x0E, 0x1C, 0x0C, 0x0C, 0x00, 0x00, // Code for char C
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x07, 0x38, 0x0F, 0x3C, 0xFE, 0x1F, 0xFC, 0x0F, 0xF0, 0x03, 0x00, 0x00, // Code for char D
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0x03, 0x30, 0x03, 0x30, 0x00, 0x00, // Code for char E
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, // Code for char F
0x0B, 0xF0, 0x03, 0xFC, 0x0F, 0xFE, 0x1F, 0x0F, 0x3C, 0x07, 0x38, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC7, 0x3F, 0xC7, 0x3F, 0xC6, 0x3F, 0x00, 0x00, // Code for char G
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, // Code for char H
0x09, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x30, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char I
0x0B, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x3E, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x38, 0xFF, 0x3F, 0xFF, 0x1F, 0xFF, 0x07, 0x00, 0x00, // Code for char J
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0xE0, 0x01, 0xF0, 0x03, 0xF8, 0x07, 0x3C, 0x0F, 0x1E, 0x1E, 0x0F, 0x3C, 0x07, 0x38, 0x03, 0x30, 0x00, 0x00, // Code for char K
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, // Code for char L
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x7E, 0x00, 0xF8, 0x01, 0xE0, 0x01, 0xF8, 0x01, 0x7E, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, // Code for char M
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x3E, 0x00, 0xF8, 0x00, 0xF0, 0x03, 0xC0, 0x07, 0x00, 0x1F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, // Code for char N
0x0B, 0xF0, 0x03, 0xFC, 0x0F, 0xFE, 0x1F, 0x0F, 0x3C, 0x07, 0x38, 0x03, 0x30, 0x07, 0x38, 0x0F, 0x3C, 0xFE, 0x1F, 0xFC, 0x0F, 0xF0, 0x03, 0x00, 0x00, // Code for char O
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x83, 0x01, 0x83, 0x01, 0x83, 0x01, 0x83, 0x01, 0xC7, 0x01, 0xFF, 0x01, 0xFE, 0x00, 0x7C, 0x00, 0x00, 0x00, // Code for char P
0x0B, 0xF0, 0x03, 0xFC, 0x0F, 0xFE, 0x1F, 0x0F, 0x3C, 0x07, 0x38, 0x03, 0x36, 0x07, 0x3E, 0x0F, 0x3E, 0xFE, 0x3F, 0xFC, 0x3F, 0xF0, 0x33, 0x00, 0x00, // Code for char Q
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x83, 0x01, 0x83, 0x03, 0x83, 0x07, 0x83, 0x0F, 0xC7, 0x1F, 0xFF, 0x3D, 0xFE, 0x38, 0x7C, 0x30, 0x00, 0x00, // Code for char R
0x0B, 0x3C, 0x0C, 0x7E, 0x1C, 0xFF, 0x3C, 0xE7, 0x38, 0xC3, 0x30, 0xC3, 0x30, 0xC3, 0x30, 0xC7, 0x39, 0xCF, 0x3F, 0x8E, 0x1F, 0x0C, 0x0F, 0x00, 0x00, // Code for char S
0x0A, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char T
0x0B, 0xFF, 0x07, 0xFF, 0x1F, 0xFF, 0x3F, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x38, 0xFF, 0x3F, 0xFF, 0x1F, 0xFF, 0x07, 0x00, 0x00, // Code for char U
0x0B, 0x07, 0x00, 0x3F, 0x00, 0xFF, 0x01, 0xF8, 0x0F, 0xC0, 0x3F, 0x00, 0x3E, 0xC0, 0x3F, 0xF8, 0x0F, 0xFF, 0x01, 0x3F, 0x00, 0x07, 0x00, 0x00, 0x00, // Code for char V
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x1E, 0x80, 0x07, 0x80, 0x03, 0x80, 0x07, 0x00, 0x1E, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, // Code for char W
0x0B, 0x03, 0x30, 0x0F, 0x3C, 0x1F, 0x3E, 0x3C, 0x0F, 0xF0, 0x03, 0xE0, 0x01, 0xF0, 0x03, 0x3C, 0x0F, 0x1F, 0x3E, 0x0F, 0x3C, 0x03, 0x30, 0x00, 0x00, // Code for char X
0x0B, 0x03, 0x00, 0x0F, 0x00, 0x3F, 0x00, 0xFC, 0x00, 0xF0, 0x3F, 0xC0, 0x3F, 0xF0, 0x3F, 0xFC, 0x00, 0x3F, 0x00, 0x0F, 0x00, 0x03, 0x00, 0x00, 0x00, // Code for char Y
0x0B, 0x03, 0x30, 0x03, 0x3C, 0x03, 0x3E, 0x03, 0x3F, 0xC3, 0x33, 0xE3, 0x31, 0xF3, 0x30, 0x3F, 0x30, 0x1F, 0x30, 0x0F, 0x30, 0x03, 0x30, 0x00, 0x00, // Code for char Z
0x09, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char [
0x0C, 0x0E, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x1C, 0x00, 0x18, // Code for char BackSlash
0x09, 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ]
0x0C, 0x60, 0x00, 0x70, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0x70, 0x00, 0x60, 0x00, // Code for char ^
0x0C, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, // Code for char _
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char `
0x0B, 0x00, 0x1C, 0x40, 0x3E, 0x60, 0x3F, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0xE0, 0x3F, 0xE0, 0x3F, 0xC0, 0x3F, 0x00, 0x00, // Code for char a
0x0B, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0xE0, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xE0, 0x38, 0xE0, 0x3F, 0xC0, 0x1F, 0x80, 0x0F, 0x00, 0x00, // Code for char b
0x0B, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x3F, 0xE0, 0x38, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xE0, 0x38, 0xC0, 0x18, 0x80, 0x08, 0x00, 0x00, // Code for char c
0x0B, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x3F, 0xE0, 0x38, 0x60, 0x30, 0x60, 0x30, 0xE0, 0x30, 0xE0, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, // Code for char d
0x0B, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x3F, 0xE0, 0x3B, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0xE0, 0x33, 0xC0, 0x13, 0x80, 0x01, 0x00, 0x00, // Code for char e
0x09, 0xC0, 0x00, 0xC0, 0x00, 0xFC, 0x3F, 0xFE, 0x3F, 0xFF, 0x3F, 0xC7, 0x00, 0xC3, 0x00, 0xC3, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char f
0x0B, 0x80, 0x03, 0xC0, 0xC7, 0xE0, 0xCF, 0xE0, 0xCE, 0x60, 0xCC, 0x60, 0xCC, 0x60, 0xCC, 0x60, 0xEE, 0xE0, 0xFF, 0xE0, 0x7F, 0xE0, 0x3F, 0x00, 0x00, // Code for char g
0x0A, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0xE0, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xE0, 0x3F, 0xC0, 0x3F, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, // Code for char h
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0x30, 0xEC, 0x3F, 0xEC, 0x3F, 0xEC, 0x3F, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char i
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xE0, 0x60, 0xC0, 0xEC, 0xFF, 0xEC, 0xFF, 0xEC, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char j
0x0A, 0x00, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x80, 0x07, 0xC0, 0x0F, 0xE0, 0x1F, 0xE0, 0x3C, 0x60, 0x38, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, // Code for char k
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x30, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char l
0x0B, 0xE0, 0x3F, 0xE0, 0x3F, 0xE0, 0x3F, 0xE0, 0x00, 0xE0, 0x3F, 0xC0, 0x3F, 0xE0, 0x3F, 0xE0, 0x00, 0xE0, 0x3F, 0xC0, 0x3F, 0x80, 0x3F, 0x00, 0x00, // Code for char m
0x0B, 0x00, 0x00, 0xE0, 0x3F, 0xE0, 0x3F, 0xE0, 0x3F, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xE0, 0x3F, 0xC0, 0x3F, 0x80, 0x3F, 0x00, 0x00, // Code for char n
0x0B, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x3F, 0xE0, 0x38, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xE0, 0x38, 0xE0, 0x3F, 0xC0, 0x1F, 0x80, 0x0F, 0x00, 0x00, // Code for char o
0x0B, 0xE0, 0xFF, 0xE0, 0xFF, 0xE0, 0xFF, 0x60, 0x1C, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0xE0, 0x1C, 0xE0, 0x1F, 0xC0, 0x0F, 0x80, 0x07, 0x00, 0x00, // Code for char p
0x0B, 0x80, 0x07, 0xC0, 0x0F, 0xE0, 0x1F, 0xE0, 0x1C, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x1C, 0xE0, 0xFF, 0xE0, 0xFF, 0xE0, 0xFF, 0x00, 0x00, // Code for char q
0x0B, 0x00, 0x00, 0xE0, 0x3F, 0xE0, 0x3F, 0xE0, 0x3F, 0xE0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00, // Code for char r
0x09, 0xC0, 0x11, 0xE0, 0x33, 0xE0, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x33, 0x60, 0x3F, 0x60, 0x3F, 0x40, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char s
0x09, 0x60, 0x00, 0x60, 0x00, 0xFE, 0x1F, 0xFE, 0x3F, 0xFE, 0x3F, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char t
0x0B, 0xE0, 0x0F, 0xE0, 0x1F, 0xE0, 0x3F, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x38, 0xE0, 0x3F, 0xE0, 0x3F, 0xE0, 0x3F, 0x00, 0x00, // Code for char u
0x0B, 0x60, 0x00, 0xE0, 0x01, 0xE0, 0x07, 0x80, 0x1F, 0x00, 0x3E, 0x00, 0x38, 0x00, 0x3E, 0x80, 0x1F, 0xE0, 0x07, 0xE0, 0x01, 0x60, 0x00, 0x00, 0x00, // Code for char v
0x0B, 0xE0, 0x07, 0xE0, 0x1F, 0xE0, 0x3F, 0x00, 0x3C, 0xE0, 0x1F, 0xE0, 0x0F, 0xE0, 0x1F, 0x00, 0x3C, 0xE0, 0x3F, 0xE0, 0x1F, 0xE0, 0x07, 0x00, 0x00, // Code for char w
0x0A, 0x60, 0x30, 0xE0, 0x38, 0xE0, 0x3D, 0xC0, 0x1F, 0x80, 0x0F, 0x80, 0x0F, 0xC0, 0x1F, 0xE0, 0x3D, 0xE0, 0x38, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, // Code for char x
0x0A, 0x00, 0x00, 0x60, 0x00, 0xE0, 0x81, 0xE0, 0xE7, 0x80, 0xFF, 0x00, 0x7E, 0x80, 0x1F, 0xE0, 0x07, 0xE0, 0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char y
0x0A, 0x60, 0x30, 0x60, 0x38, 0x60, 0x3C, 0x60, 0x3E, 0x60, 0x37, 0xE0, 0x33, 0xE0, 0x31, 0xE0, 0x30, 0x60, 0x30, 0x20, 0x30, 0x00, 0x00, 0x00, 0x00, // Code for char z
0x0A, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x01, 0xFC, 0x1F, 0xFE, 0x3F, 0x7F, 0x7F, 0x07, 0x70, 0x03, 0x60, 0x03, 0x60, 0x03, 0x60, 0x00, 0x00, 0x00, 0x00, // Code for char {
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char |
0x0A, 0x00, 0x00, 0x03, 0x60, 0x03, 0x60, 0x03, 0x60, 0x07, 0x70, 0x7F, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char }
0x0B, 0x10, 0x00, 0x18, 0x00, 0x1C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x1C, 0x00, 0x18, 0x00, 0x18, 0x00, 0x1C, 0x00, 0x0C, 0x00, 0x04, 0x00, 0x00, 0x00, // Code for char ~
0x0B, 0x00, 0x0F, 0x80, 0x0F, 0xC0, 0x0F, 0xE0, 0x0C, 0x70, 0x0C, 0x30, 0x0C, 0x70, 0x0C, 0xE0, 0x0C, 0xC0, 0x0F, 0x80, 0x0F, 0x00, 0x0F, 0x00, 0x00 // Code for char 
};
//Font Generated by MikroElektronika GLCD Font Creator 1.2.0.0
//MikroElektronika 2011
//http://www.mikroe.com
//GLCD FontName : Trebuchet_MS16x21
//GLCD FontSize : 16 x 21
fontdatatype Trebuchet_MS16x21[] PROGMEM = {
0x10, 0x15, 0x2E, 0x0D,
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char .
0x0C, 0x00, 0x00, 0x10, 0x00, 0x00, 0x1E, 0x00, 0xC0, 0x1F, 0x00, 0xF0, 0x1F, 0x00, 0xFE, 0x0F, 0x80, 0xFF, 0x03, 0xF0, 0x7F, 0x00, 0xFE, 0x0F, 0x00, 0xFF, 0x03, 0x00, 0x7F, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char /
0x10, 0x00, 0x00, 0x00, 0xC0, 0x7F, 0x00, 0xF0, 0xFF, 0x03, 0xFC, 0xFF, 0x07, 0xFE, 0xFF, 0x0F, 0x3E, 0x80, 0x0F, 0x0F, 0x00, 0x1E, 0x07, 0x00, 0x1C, 0x07, 0x00, 0x1C, 0x07, 0x00, 0x1C, 0x0F, 0x00, 0x1E, 0x1F, 0x80, 0x0F, 0xFE, 0xFF, 0x0F, 0xFC, 0xFF, 0x07, 0xF8, 0xFF, 0x01, 0xC0, 0x7F, 0x00, // Code for char 0
0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x78, 0x00, 0x00, 0x38, 0x00, 0x00, 0x3C, 0x00, 0x00, 0xFE, 0xFF, 0x1F, 0xFE, 0xFF, 0x1F, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char 1
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x18, 0x1E, 0x00, 0x1C, 0x0E, 0x00, 0x1F, 0x0F, 0x80, 0x1F, 0x07, 0xC0, 0x1F, 0x07, 0xF0, 0x1F, 0x07, 0xF8, 0x1D, 0x07, 0xFE, 0x1C, 0x0F, 0x3F, 0x1C, 0xFE, 0x1F, 0x1C, 0xFE, 0x0F, 0x1C, 0xFC, 0x03, 0x1C, 0xF8, 0x00, 0x1C, 0x00, 0x00, 0x1C, // Code for char 2
0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x0E, 0x0E, 0x00, 0x0F, 0x0F, 0x00, 0x1E, 0x07, 0x00, 0x1C, 0x07, 0x07, 0x1C, 0x07, 0x07, 0x1C, 0x07, 0x07, 0x1C, 0x8F, 0x0F, 0x1E, 0xFF, 0x1F, 0x1E, 0xFE, 0xFD, 0x0F, 0xFC, 0xFD, 0x0F, 0x78, 0xF8, 0x07, 0x00, 0xF0, 0x01, 0x00, 0x00, 0x00, // Code for char 3
0x10, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xF0, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xEF, 0x00, 0x80, 0xE7, 0x00, 0xC0, 0xE3, 0x00, 0xF0, 0xE0, 0x00, 0x78, 0xE0, 0x00, 0xFC, 0xFF, 0x1F, 0xFE, 0xFF, 0x1F, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0x1F, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, // Code for char 4
0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xFF, 0x03, 0x0F, 0xFF, 0x07, 0x0E, 0xFF, 0x03, 0x1C, 0xFF, 0x01, 0x1C, 0xC7, 0x01, 0x1C, 0xC7, 0x01, 0x1C, 0xC7, 0x01, 0x1E, 0xC7, 0x03, 0x1F, 0xC7, 0xFF, 0x0F, 0x87, 0xFF, 0x0F, 0x07, 0xFF, 0x07, 0x00, 0xFC, 0x01, 0x00, 0x00, 0x00, // Code for char 5
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x80, 0xFF, 0x03, 0xE0, 0xFF, 0x07, 0xF0, 0xFF, 0x0F, 0xF8, 0x0F, 0x1E, 0x7C, 0x07, 0x1C, 0x3E, 0x07, 0x1C, 0x1E, 0x07, 0x1C, 0x0F, 0x07, 0x1C, 0x07, 0x0F, 0x1E, 0x02, 0xFE, 0x0F, 0x00, 0xFE, 0x0F, 0x00, 0xFC, 0x07, 0x00, 0xF0, 0x01, // Code for char 6
0x10, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x07, 0x00, 0x00, 0x07, 0x00, 0x18, 0x07, 0x00, 0x1E, 0x07, 0xC0, 0x1F, 0x07, 0xF0, 0x1F, 0x07, 0xFC, 0x0F, 0x07, 0xFF, 0x01, 0xC7, 0x7F, 0x00, 0xF7, 0x0F, 0x00, 0xFF, 0x03, 0x00, 0xFF, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x03, 0x00, 0x00, // Code for char 7
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x03, 0x78, 0xF8, 0x07, 0xFC, 0xFD, 0x0F, 0xFE, 0xFF, 0x0F, 0xFF, 0x1F, 0x1E, 0x8F, 0x0F, 0x1C, 0x07, 0x07, 0x1C, 0x07, 0x0F, 0x1C, 0x8F, 0x0F, 0x1C, 0xFF, 0x3F, 0x1E, 0xFE, 0xFD, 0x0F, 0xFE, 0xFD, 0x0F, 0x78, 0xF0, 0x07, 0x00, 0xE0, 0x03, // Code for char 8
0x0F, 0x00, 0x00, 0x00, 0xF0, 0x01, 0x00, 0xFC, 0x07, 0x00, 0xFE, 0x0F, 0x00, 0xFE, 0x0F, 0x08, 0x0F, 0x1E, 0x1C, 0x07, 0x1C, 0x1E, 0x07, 0x1C, 0x0F, 0x07, 0x9C, 0x0F, 0x07, 0xDC, 0x07, 0x0F, 0xFE, 0x03, 0xFE, 0xFF, 0x01, 0xFC, 0xFF, 0x00, 0xF8, 0x3F, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0x00, 0x00, // Code for char 9
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x0C, 0xC0, 0x03, 0x1E, 0xC0, 0x03, 0x1E, 0x80, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Code for char :
};

1712
libraries/SondeLib/Display.cpp Executable file

File diff suppressed because it is too large Load Diff

165
libraries/SondeLib/Display.h Executable file
View File

@ -0,0 +1,165 @@
#ifndef Display_h
#define Display_h
#define FONT_LARGE 1
#define FONT_SMALL 0
#include <SPI.h>
//#include <TFT22_ILI9225.h>
#include "gfxfont.h"
#include <U8x8lib.h>
#include <SPIFFS.h>
#define WIDTH_AUTO 9999
struct DispEntry {
int16_t y;
int16_t x;
int16_t fmt, width;
uint16_t fg,bg;
void (*func)(DispEntry *de);
const char *extra;
};
#define GPSUSE_BASE 1
#define GPSUSE_DIST 2
#define GPSUSE_BEARING 4
struct DispInfo {
DispEntry *de;
uint8_t *actions;
int16_t *timeouts;
const char *label;
uint8_t usegps;
};
struct StatInfo {
uint8_t len;
uint8_t size;
};
// Now starting towards supporting different Display types / libraries
class RawDisplay {
public:
virtual void begin() = 0;
virtual void clear() = 0;
virtual void setFont(uint8_t fontindex) = 0;
virtual void getDispSize(uint8_t *height, uint8_t *width, uint8_t *lineskip, uint8_t *colskip) = 0;
virtual void drawString(uint8_t x, uint8_t y, const char *s, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0 ) = 0;
virtual void drawTile(uint8_t x, uint8_t y, uint8_t cnt, uint8_t *tile_ptr) = 0;
virtual void drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color, bool fill) = 0;
virtual void drawBitmap(uint16_t x1, uint16_t y1, const uint16_t* bitmap, int16_t w, int16_t h) = 0;
virtual void welcome() = 0;
virtual void drawIP(uint8_t x, uint8_t y, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0 ) = 0;
virtual void drawQS(uint8_t x, uint8_t y, uint8_t len, uint8_t size, uint8_t *stat, uint16_t fg=0xffff, uint16_t bg=0) = 0;
};
class U8x8Display : public RawDisplay {
private:
U8X8 *u8x8 = NULL; // initialize later after reading config file
int _type;
const uint8_t **fontlist;
int nfonts;
public:
U8x8Display(int type = 0) { _type = type; }
void begin();
void clear();
void setFont(uint8_t fontindex);
void getDispSize(uint8_t *height, uint8_t *width, uint8_t *lineskip, uint8_t *colskip);
void drawString(uint8_t x, uint8_t y, const char *s, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0);
void drawTile(uint8_t x, uint8_t y, uint8_t cnt, uint8_t *tile_ptr);
void drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color, bool fill);
void drawBitmap(uint16_t x1, uint16_t y1, const uint16_t* bitmap, int16_t w, int16_t h);
void welcome();
void drawIP(uint8_t x, uint8_t y, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0);
void drawQS(uint8_t x, uint8_t y, uint8_t len, uint8_t size, uint8_t *stat, uint16_t fg=0xffff, uint16_t bg=0);
};
class Display {
private:
void replaceLayouts(DispInfo *newlayout, int nnew);
int allocDispInfo(int entries, DispInfo *d, char *label);
void parseDispElement(char *text, DispEntry *de);
int xscale=13, yscale=22;
int fontsma=0, fontlar=1;
uint16_t colfg, colbg;
static void circ(int x, int y);
static int countEntries(File f);
void calcGPS();
void calcVbat();
void calcDurVol();
boolean gpsValid;
float gpsLat, gpsLon;
int gpsAlt;
int gpsDist; // -1: invalid
int gpsCourse, gpsDir, gpsBear; // 0..360; -1: invalid
boolean gpsCourseOld;
static const int LINEBUFLEN{ 255 };
static char lineBuf[LINEBUFLEN];
static const char *trim(char *s) {
char *ret = s;
while(*ret && isspace(*ret)) { ret++; }
int lastidx;
while(1) {
lastidx = strlen(ret)-1;
if(lastidx>=0 && isspace(ret[lastidx]))
ret[lastidx] = 0;
else
break;
}
return ret;
}
public:
void initFromFile();
int layoutIdx;
DispInfo *layout;
DispInfo *layouts;
int nLayouts;
static RawDisplay *rdis;
Display();
void init();
static char buf[17];
static void drawLat(DispEntry *de);
static void drawLon(DispEntry *de);
static void drawAz(DispEntry *de);
static void drawVBat(DispEntry *de);
static void drawDurVol(DispEntry *de);
static void drawAlt(DispEntry *de);
static void drawHS(DispEntry *de);
static void drawVS(DispEntry *de);
static void drawID(DispEntry *de);
static void drawRSSI(DispEntry *de);
static void drawQS(DispEntry *de);
static void drawType(DispEntry *de);
static void drawFreq(DispEntry *de);
static void drawAFC(DispEntry *de);
static void drawIP(DispEntry *de);
static void drawSite(DispEntry *de);
static void drawTelemetry(DispEntry *de);
static void drawKilltimer(DispEntry *de);
static void drawGPS(DispEntry *de);
static void drawText(DispEntry *de);
static void drawBatt(DispEntry *de);
static void drawString(DispEntry *de, const char *str);
void clearIP();
void setIP(const char *ip, bool AP);
void updateDisplayPos();
void updateDisplayPos2();
void updateDisplayAz();
void updateDisplayVBat();
void updateDisplayID();
void updateDisplayRSSI();
void updateStat();
void updateDisplayRXConfig();
void updateDisplayIP();
void updateDisplay();
void updateDisplayVBatt();
void setLayout(int layout);
};
extern Display disp;
#endif

530
libraries/SondeLib/M10.cpp Executable file
View File

@ -0,0 +1,530 @@
/* M10 decoder functions */
#include "M10.h"
#include "SX1278FSK.h"
#include "rsc.h"
#include "Sonde.h"
#include <SPIFFS.h>
// well...
//#include "rs92gps.h"
#define M10_DEBUG 1
#if M10_DEBUG
#define M10_DBG(x) x
#else
#define M10_DBG(x)
#endif
static byte data1[512];
static byte *dataptr=data1;
static uint8_t rxbitc;
static uint16_t rxbyte;
static int rxp=0;
static int haveNewFrame = 0;
static int lastFrame = 0;
static int headerDetected = 0;
int M10::setup(float frequency)
{
#if M10_DEBUG
Serial.println("Setup sx1278 for M10 sonde");
#endif
//if(!initialized) {
//Gencrctab();
//initrsc();
// not here for now.... get_eph("/brdc.19n");
// initialized = true;
//}
if(sx1278.ON()!=0) {
M10_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(sx1278.setFSK()!=0) {
M10_DBG(Serial.println("Setting FSJ mode FAILED"));
return 1;
}
if(sx1278.setBitrate(9616)!=0) {
M10_DBG(Serial.println("Setting bitrate 9600bit/s FAILED"));
return 1;
}
#if M10_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(sonde.config.rs92.rxbw)!=0) {
M10_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.rs92.rxbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.rs92.rxbw)!=0) {
M10_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.rs92.rxbw));
return 1;
}
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x1E)!=0) {
M10_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// Set autostart_RX to 01, preamble 0, SYNC detect==on, syncsize=3 (==4 byte
//char header[] = "0110.0101 0110.0110 1010.0101 1010.1010";
//const char *SYNC="\x10\xB6\xCA\x11\x22\x96\x12\xF8";
//const char *SYNC="\x08\x6D\x53\x88\x44\x69\x48\x1F";
// was 0x57
//const char *SYNC="\x99\x9A";
#if 1
// version 1, working with continuous RX
//const char *SYNC="\x66\x65";
const char *SYNC="\x99\x99\x4C\x99";
if(sx1278.setSyncConf(0x70, 4, (const uint8_t *)SYNC)!=0) {
M10_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
if((sx1278.setPreambleDetect(0x9F)!=0)&&((sx1278.setPreambleDetect(0xAF)!=0))) {
M10_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
#endif
#if 0
// version 2, with per-packet rx start, untested
// header is 2a 10 65, i.e. with lsb first
// 0 0101 0100 1 0 0000 1000 1 0 1010 0110 1
// 10 10011001 10011010 01 10 10101010 01101010 01 10 01100110 10010110 01
// preamble 0x6A 0x66 0x6A
// i.e. preamble detector on (0x80), preamble detector size 1 (0x00), preample chip errors??? (0x0A)
// after 2a2a2a2a2a1065
if(sx1278.setPreambleDetect(0xA8)!=0) {
M10_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
// sync config: ato restart (01), preamble polarity AA (0), sync on (1), resevered (0), syncsize 2+1 (010) => 0x52
const char *SYNC="\x6A\x66\x69";
if(sx1278.setSyncConf(0x52, 3, (const uint8_t *)SYNC)!=0) {
M10_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
// payload length is ((240 - 7)*10 +6)/8 = 292
#endif
// Packet config 1: fixed len, no mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
M10_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
Serial.print("M10: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
//sx1278.setPayloadLength(292);
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
#if M10_DEBUG
M10_DBG(Serial.println("Setting SX1278 config for M10 finished\n"); Serial.println());
#endif
return res;
}
#if 0
int M10::setFrequency(float frequency) {
Serial.print("M10: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
return res;
}
#endif
#if 0
uint32_t M10::bits2val(const uint8_t *bits, int len) {
uint32_t val = 0;
for (int j = 0; j < len; j++) {
val |= (bits[j] << (len-1-j));
}
return val;
}
#endif
M10::M10() {
}
#define M10_FRAMELEN 101
#define M10_CRCPOS 99
void M10::printRaw(uint8_t *data, int len)
{
char buf[3];
int i;
for(i=0; i<len; i++) {
snprintf(buf, 3, "%02X ", data[i]);
Serial.print(buf);
}
Serial.println();
}
static int update_checkM10(int c, uint8_t b) {
int c0, c1, t, t6, t7, s;
c1 = c & 0xFF;
// B
b = (b >> 1) | ((b & 1) << 7);
b ^= (b >> 2) & 0xFF;
// A1
t6 = ( c & 1) ^ ((c >> 2) & 1) ^ ((c >> 4) & 1);
t7 = ((c >> 1) & 1) ^ ((c >> 3) & 1) ^ ((c >> 5) & 1);
t = (c & 0x3F) | (t6 << 6) | (t7 << 7);
// A2
s = (c >> 7) & 0xFF;
s ^= (s >> 2) & 0xFF;
c0 = b ^ t ^ s;
return ((c1 << 8) | c0) & 0xFFFF;
}
static bool checkM10crc(uint8_t *msg) {
int i, cs, cs1;
cs = 0;
for (i = 0; i < M10_CRCPOS; i++) {
cs = update_checkM10(cs, msg[i]);
}
cs = cs & 0xFFFF;
cs1 = (msg[M10_CRCPOS] << 8) | msg[M10_CRCPOS+1];
return (cs1 == cs);
}
typedef uint32_t SET256[8];
static SET256 sondeudp_VARSET = {0x03BBBBF0UL,0x80600000UL,0x06A001A0UL,
0x0000001CUL,0x00000000UL,0x00000000UL,0x00000000UL,
0x00000000UL};
// VARSET=SET256{4..9,11..13,15..17,19..21,23..25,53..54,63,69,71,72,85,87,89,90,98..100};
static uint8_t fixcnt[M10_FRAMELEN];
static uint8_t fixbytes[M10_FRAMELEN];
static int32_t getint32(uint8_t *data) {
return (int32_t)( data[3]|(data[2]<<8)|(data[1]<<16)|(data[0]<<24) );
}
static int16_t getint16(uint8_t *data) {
return (int16_t)(data[1]|((uint16_t)data[0]<<8));
}
static char dez(uint8_t nr) {
nr = nr%10;
return '0'+nr;
}
static char hex(uint8_t nr) {
nr = nr&0x0f;
if(nr<10) return '0'+nr;
else return 'A'+nr-10;
}
const static float DEGMUL = 1.0/0xB60B60;
#define VMUL 0.005
#ifndef PI
#define PI (3.1415926535897932384626433832795)
#endif
#define RAD (PI/180)
// ret: 1=frame ok; 2=frame with errors; 0=ignored frame (m10dop-alternativ)
int M10::decodeframeM10(uint8_t *data) {
int repairstep = 16;
int repl = 0;
bool crcok;
// error correction, inspired by oe5dxl's sondeudp
do {
crcok = checkM10crc(data);
if(crcok) break;
repl = 0;
for(int i=0; i<M10_CRCPOS; i++) {
if( ((sondeudp_VARSET[i/32]&(1<<(i%32))) != 1) && (fixcnt[i]>=repairstep) ) {
repl++;
data[i] = fixbytes[i];
}
}
repairstep >>= 1;
} while(repairstep>0);
if(crcok) {
for(int i=0; i<M10_CRCPOS; i++) {
if(fixbytes[i]==data[i] &&fixcnt[i]<255) fixcnt[i]++;
else { fixcnt[i]=0; fixbytes[i]=data[i]; }
}
}
Serial.println(crcok?"CRC OK":"CRC NOT OK");
//M10 0x9F M10Plus 0xAF
if((data[1]==0x9F && data[2]==0x20) || (data[1]==0xAF && data[2]==0x20)) {
Serial.println("Decoding...");
// Its a M10
// getid...
char ids[11];
ids[0] = 'M';
ids[1] = 'E';
ids[2] = hex(data[95]/16);
ids[3] = hex(data[95]);
ids[4] = hex(data[93]);
uint32_t id = data[96] + data[97]*256;
ids[5] = hex(id/4096);
ids[6] = hex(id/256);
ids[7] = hex(id/16);
ids[8] = hex(id);
ids[9] = 0;
strncpy(sonde.si()->id, ids, 10);
ids[0] = hex(data[95]/16);
ids[1] = dez((data[95]&0x0f)/10);
ids[2] = dez((data[95]&0x0f));
ids[3] = dez(data[93]);
ids[4] = dez(id>>13);
id &= 0x1fff;
ids[5] = dez(id/1000);
ids[6] = dez((id/100)%10);
ids[7] = dez((id/10)%10);
ids[8] = dez(id%10);
strncpy(sonde.si()->ser, ids, 10);
sonde.si()->validID = true;
Serial.printf("ID is %s [%02x %02x %d]\n", ids, data[95], data[93], id);
// ID printed on sonde is ...-.-abbbb, with a=id>>13, bbbb=id&0x1fff in decimal
// position data
sonde.si()->lat = getint32(data+14) * DEGMUL;
sonde.si()->lon = getint32(data+18) * DEGMUL;
sonde.si()->alt = getint32(data+22) * 0.001;
float ve = getint16(data+4)*VMUL;
float vn = getint16(data+6)*VMUL;
sonde.si()->vs = getint16(data+8) * VMUL;
sonde.si()->hs = sqrt(ve*ve+vn*vn);
float dir = atan2(vn, ve)*(1.0/RAD);
if(dir<0) dir+=360;
sonde.si()->dir = dir;
sonde.si()->validPos = 0x3f;
uint32_t gpstime = getint32(data+10);
uint16_t gpsweek = getint16(data+32);
// UTC is GPSTIME - 18s (24*60*60-18 = 86382)
// one week = 7*24*60*60 = 604800 seconds
// unix epoch starts jan 1st 1970 0:00
// gps time starts jan 6, 1980 0:00. thats 315964800 epoch seconds.
// subtracting 86400 yields 315878400UL
sonde.si()->time = (gpstime/1000) + 86382 + gpsweek*604800 + 315878400UL;
sonde.si()->validTime = true;
} else {
Serial.printf("data is %02x %02x %02x\n", data[0], data[1], data[2]);
return 0;
}
return crcok?1:2;
}
static uint32_t rxdata;
static bool rxsearching=true;
// search for
// //101001100110011010011010011001100110100110101010100110101001
// //1010011001100110100110100110 0110.0110 1001.1010 1010.1001 1010.1001 => 0x669AA9A9
void M10::processM10data(uint8_t dt)
{
for(int i=0; i<8; i++) {
uint8_t d = (dt&0x80)?1:0;
dt <<= 1;
rxdata = (rxdata<<1) | d;
//uint8_t value = ((rxdata>>1)^rxdata)&0x01;
//if((rxbitc&1)==1) { rxbyte = (rxbyte>>1) + ((value)<<8); } // mancester decoded data
//rxbyte = (rxbyte>>1) | (d<<8);
if( (rxbitc&1)==0 ) {
// "bit1"
rxbyte = (rxbyte<<1) | d;
} else {
// "bit2" ==> 01 or 10 => 1, otherweise => 0
rxbyte = rxbyte ^ d;
}
//
if(rxsearching) {
if( rxdata == 0xcccca64c || rxdata == 0x333359b3 ) {
rxsearching = false;
rxbitc = 0;
rxp = 0;
#if 1
int rssi=sx1278.getRSSI();
int fei=sx1278.getFEI();
int afc=sx1278.getAFC();
Serial.print("Test: RSSI="); Serial.print(rssi);
Serial.print(" FEI="); Serial.print(fei);
Serial.print(" AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
#endif
}
} else {
rxbitc = (rxbitc+1)%16; // 16;
if(rxbitc == 0) { // got 8 data bit
//Serial.printf("%03x ",rxbyte);
dataptr[rxp++] = rxbyte&0xff; // (rxbyte>>1)&0xff;
#if 0
if(rxp==7 && dataptr[6] != 0x65) {
Serial.printf("wrong start: %02x\n",dataptr[6]);
rxsearching = true;
}
#endif
if(rxp>=M10_FRAMELEN) {
rxsearching = true;
haveNewFrame = decodeframeM10(dataptr);
}
}
}
}
}
int M10::receive() {
unsigned long t0 = millis();
Serial.printf("M10::receive() start at %ld\n",t0);
while( millis() - t0 < 1512 ) {
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
if ( bitRead(value, 7) ) {
Serial.println("FIFO full");
}
if ( bitRead(value, 4) ) {
Serial.println("FIFO overflow");
}
if ( bitRead(value, 2) == 1 ) {
Serial.println("FIFO: ready()");
sx1278.clearIRQFlags();
}
if(bitRead(value, 6) == 0) { // while FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
//Serial.printf("%02x",data);
processM10data(data);
value = sx1278.readRegister(REG_IRQ_FLAGS2);
} else {
if(headerDetected) {
t0 = millis(); // restart timer... don't time out if header detected...
headerDetected = 0;
}
if(haveNewFrame) {
Serial.printf("M10::receive(): new frame complete after %ldms\n", millis()-t0);
printRaw(dataptr, M10_FRAMELEN);
int retval = haveNewFrame==1 ? RX_OK: RX_ERROR;
haveNewFrame = 0;
return retval;
}
delay(2);
}
}
Serial.printf("M10::receive() timed out\n");
return RX_TIMEOUT; // TODO RX_OK;
}
#define M10MAXLEN (240)
int M10::waitRXcomplete() {
// called after complete...
#if 0
Serial.printf("decoding frame %d\n", lastFrame);
print_frame(lastFrame==1?data1:data2, 240);
SondeInfo *si = sonde.sondeList+rxtask.receiveSonde;
si->lat = gpx.lat;
si->lon = gpx.lon;
si->alt = gpx.alt;
si->vs = gpx.vU;
si->hs = gpx.vH;
si->dir = gpx.vD;
si->validPos = 0x3f;
memcpy(si->id, gpx.id, 9);
si->validID = true;
int res=0;
uint32_t t0 = millis();
while( rxtask.receiveResult == 0xFFFF && millis()-t0 < 2000) { delay(20); }
if( rxtask.receiveResult<0 || rxtask.receiveResult==RX_TIMEOUT) {
res = RX_TIMEOUT;
} else if ( rxtask.receiveResult==0) {
res = RX_OK;
} else {
res = RX_ERROR;
}
rxtask.receiveResult = 0xFFFF;
Serial.printf("M10::waitRXcomplete returning %d (%s)\n", res, RXstr[res]);
return res;
#endif
return 0;
}
#if 0
int oldwaitRXcomplete() {
Serial.println("M10: receive frame...\n");
sx1278receiveData = true;
delay(6000); // done in other task....
//sx1278receiveData = false;
#if 0
//sx1278.setPayloadLength(518-8); // Expect 320-8 bytes or 518-8 bytes (8 byte header)
//sx1278.setPayloadLength(0); // infinite for now...
////// test code for continuous reception
// sx1278.receive(); /// active FSK RX mode -- already done above...
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
unsigned long previous = millis();
byte ready=0;
uint32_t wait = 8000;
// while not yet done or FIFO not yet empty
// bit 6: FIFO Empty
// bit 2 payload ready
int by=0;
while( (!ready || bitRead(value,6)==0) && (millis() - previous < wait) )
{
if( bitRead(value, 7) ) { Serial.println("FIFO full"); }
if( bitRead(value, 4) ) { Serial.println("FIFO overflow"); }
if( bitRead(value,2)==1 ) ready=1;
if( bitRead(value, 6) == 0 ) { // FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
process8N1data(data);
by++;
#if 0
if(di==1) {
int rssi=getRSSI();
int fei=getFEI();
int afc=getAFC();
Serial.print("Test: RSSI="); Serial.println(rssi);
Serial.print("Test: FEI="); Serial.println(fei);
Serial.print("Test: AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
}
if(di>520) {
// TODO
Serial.println("TOO MUCH DATA");
break;
}
previous = millis(); // reset timeout after receiving data
#endif
}
value = sx1278.readRegister(REG_IRQ_FLAGS2);
}
Serial.printf("processed %d bytes before end/timeout\n", by);
#endif
/////
#if 0
int e = sx1278.receivePacketTimeout(1000, data+8);
if(e) { Serial.println("TIMEOUT"); return RX_TIMEOUT; } //if timeout... return 1
printRaw(data, M10MAXLEN);
//for(int i=0; i<M10MAXLEN; i++) { data[i] = reverse(data[i]); }
//printRaw(data, MAXLEN);
//for(int i=0; i<M10MAXLEN; i++) { data[i] = data[i] ^ scramble[i&0x3F]; }
//printRaw(data, MAXLEN);
//int res = decode41(data, M10MAXLEN);
#endif
int res=0;
return res==0 ? RX_OK : RX_ERROR;
}
#endif
M10 m10 = M10();

96
libraries/SondeLib/M10.h Executable file
View File

@ -0,0 +1,96 @@
/*
* M10.h
* Functions for decoding Meteomodem M10 sondes with SX127x chips
* Copyright (C) 2019 Hansi Reiser, dl9rdz
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef M10_h
#define M10_h
#include <stdlib.h>
#include <stdint.h>
#include <Arduino.h>
#ifndef inttypes_h
#include <inttypes.h>
#endif
#if 0
struct CONTEXTR9 {
char calibdata[512];
uint32_t calibok;
char mesok;
char posok;
char framesent;
double lat;
double lon;
double heig;
double speed;
double dir;
double climb;
double lastlat;
double laslong;
double lastalt;
double lastspeed;
double lastdir;
double lastclb;
float hrmsc;
float vrmsc;
double hp;
double hyg;
double temp;
double ozontemp;
double ozon;
uint32_t goodsats;
uint32_t timems;
uint32_t framenum;
};
#endif
/* Main class */
class M10
{
private:
void printRaw(uint8_t *data, int len);
void processM10data(uint8_t data);
int decodeframeM10(uint8_t *data);
#if 0
void stobyte92(uint8_t byte);
void dogps(const uint8_t *sf, int sf_len,
struct CONTEXTR9 * cont, uint32_t * timems,
uint32_t * gpstime);
uint32_t bits2val(const uint8_t *bits, int len);
int bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
int decode92(byte *data, int maxlen);
uint8_t hamming_conf[ 7*8]; // 7*8=56
uint8_t hamming_dat1[13*8]; // 13*8=104
uint8_t hamming_dat2[13*8];
uint8_t block_conf[ 7*4]; // 7*4=28
uint8_t block_dat1[13*4]; // 13*4=52
uint8_t block_dat2[13*4];
uint8_t H[4][8] = // extended Hamming(8,4) particy check matrix
{{ 0, 1, 1, 1, 1, 0, 0, 0},
{ 1, 0, 1, 1, 0, 1, 0, 0},
{ 1, 1, 0, 1, 0, 0, 1, 0},
{ 1, 1, 1, 0, 0, 0, 0, 1}};
uint8_t He[8] = { 0x7, 0xB, 0xD, 0xE, 0x8, 0x4, 0x2, 0x1}; // Spalten von H:
// 1-bit-error-Syndrome
boolean initialized = false;
#endif
public:
M10();
int setup(float frequency);
int receive();
int waitRXcomplete();
//int use_ecc = 1;
};
extern M10 m10;
#endif

535
libraries/SondeLib/M20.cpp Executable file
View File

@ -0,0 +1,535 @@
/* M20 decoder functions */
#include "M20.h"
#include "SX1278FSK.h"
#include "rsc.h"
#include "Sonde.h"
#include <SPIFFS.h>
// well...
//#include "rs92gps.h"
#define M20_DEBUG 1
#if M20_DEBUG
#define M20_DBG(x) x
#else
#define M20_DBG(x)
#endif
static byte data1[512];
static byte *dataptr=data1;
static uint8_t rxbitc;
static uint16_t rxbyte;
static int rxp=0;
static int haveNewFrame = 0;
static int lastFrame = 0;
static int headerDetected = 0;
int M20::setup(float frequency)
{
#if M20_DEBUG
Serial.println("Setup sx1278 for M20 sonde");
#endif
//if(!initialized) {
//Gencrctab();
//initrsc();
// not here for now.... get_eph("/brdc.19n");
// initialized = true;
//}
if(sx1278.ON()!=0) {
M20_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(sx1278.setFSK()!=0) {
M20_DBG(Serial.println("Setting FSJ mode FAILED"));
return 1;
}
if(sx1278.setBitrate(9600)!=0) {
M20_DBG(Serial.println("Setting bitrate 9600bit/s FAILED"));
return 1;
}
#if M20_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(sonde.config.rs92.rxbw)!=0) {
M20_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.rs92.rxbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.rs92.rxbw)!=0) {
M20_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.rs92.rxbw));
return 1;
}
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x1E)!=0) {
M20_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// Set autostart_RX to 01, preamble 0, SYNC detect==on, syncsize=3 (==4 byte
//char header[] = "0110.0101 0110.0110 1010.0101 1010.1010";
//const char *SYNC="\x10\xB6\xCA\x11\x22\x96\x12\xF8";
//const char *SYNC="\x08\x6D\x53\x88\x44\x69\x48\x1F";
// was 0x57
//const char *SYNC="\x99\x9A";
#if 1
// version 1, working with continuous RX
//const char *SYNC="\x66\x65";
const char *SYNC="\x99\x99\x4C\x99";
if(sx1278.setSyncConf(0x70, 4, (const uint8_t *)SYNC)!=0) {
M20_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
//if(sx1278.setPreambleDetect(0xA8)!=0) {
if(sx1278.setPreambleDetect(0x20)!=0) {
M20_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
#endif
#if 0
// version 2, with per-packet rx start, untested
// header is 2a 10 65, i.e. with lsb first
// 0 0101 0100 1 0 0000 1000 1 0 1010 0110 1
// 10 10011001 10011010 01 10 10101010 01101010 01 10 01100110 10010110 01
// preamble 0x6A 0x66 0x6A
// i.e. preamble detector on (0x80), preamble detector size 1 (0x00), preample chip errors??? (0x0A)
// after 2a2a2a2a2a1065
if(sx1278.setPreambleDetect(0xA8)!=0) {
M20_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
// sync config: ato restart (01), preamble polarity AA (0), sync on (1), resevered (0), syncsize 2+1 (010) => 0x52
const char *SYNC="\x6A\x66\x69";
if(sx1278.setSyncConf(0x52, 3, (const uint8_t *)SYNC)!=0) {
M20_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
// payload length is ((240 - 7)*10 +6)/8 = 292
#endif
// Packet config 1: fixed len, no mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
M20_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
Serial.print("M20: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
//sx1278.setPayloadLength(292);
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
#if M20_DEBUG
M20_DBG(Serial.println("Setting SX1278 config for M20 finished\n"); Serial.println());
#endif
return res;
}
#if 0
int M20::setFrequency(float frequency) {
Serial.print("M20: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
return res;
}
#endif
#if 0
uint32_t M20::bits2val(const uint8_t *bits, int len) {
uint32_t val = 0;
for (int j = 0; j < len; j++) {
val |= (bits[j] << (len-1-j));
}
return val;
}
#endif
M20::M20() {
}
#define M20_FRAMELEN 70
#define M20_CRCPOS 68
void M20::printRaw(uint8_t *data, int len)
{
char buf[3];
int i;
for(i=0; i<len; i++) {
snprintf(buf, 3, "%02X ", data[i]);
Serial.print(buf);
}
Serial.println();
}
static int update_checkM20(int c, uint8_t b) {
int c0, c1, t, t6, t7, s;
c1 = c & 0xFF;
// B
b = (b >> 1) | ((b & 1) << 7);
b ^= (b >> 2) & 0xFF;
// A1
t6 = ( c & 1) ^ ((c >> 2) & 1) ^ ((c >> 4) & 1);
t7 = ((c >> 1) & 1) ^ ((c >> 3) & 1) ^ ((c >> 5) & 1);
t = (c & 0x3F) | (t6 << 6) | (t7 << 7);
// A2
s = (c >> 7) & 0xFF;
s ^= (s >> 2) & 0xFF;
c0 = b ^ t ^ s;
return ((c1 << 8) | c0) & 0xFFFF;
}
static bool checkM20crc(uint8_t *msg) {
int i, cs, cs1;
cs = 0;
for (i = 0; i < M20_CRCPOS; i++) {
cs = update_checkM20(cs, msg[i]);
}
cs = cs & 0xFFFF;
cs1 = (msg[M20_CRCPOS] << 8) | msg[M20_CRCPOS+1];
return (cs1 == cs);
}
typedef uint32_t SET256[8];
static SET256 sondeudp_VARSET = {0x03BBBBF0UL,0x80600000UL,0x06A001A0UL,
0x0000001CUL,0x00000000UL,0x00000000UL,0x00000000UL,
0x00000000UL};
// VARSET=SET256{4..9,11..13,15..17,19..21,23..25,53..54,63,69,71,72,85,87,89,90,98..100};
static uint8_t fixcnt[M20_FRAMELEN];
static uint8_t fixbytes[M20_FRAMELEN];
static int32_t getint32(uint8_t *data) {
return (int32_t)( data[3]|(data[2]<<8)|(data[1]<<16)|(data[0]<<24) );
}
static int32_t getint24(uint8_t *data) {
return (int32_t)(data[2]|(data[1]<<8)|(data[0]<<16) );
}
static int16_t getint16(uint8_t *data) {
return (int16_t)(data[1]|((uint16_t)data[0]<<8));
}
static int16_t getint16_r(uint8_t *data) {
return (int16_t)(((uint16_t)data[1]<<8) |data[0]);
}
static char dez(uint8_t nr) {
nr = nr%10;
return '0'+nr;
}
static char hex(uint8_t nr) {
nr = nr&0x0f;
if(nr<10) return '0'+nr;
else return 'A'+nr-10;
}
const static float DEGMUL = 1.0/0xB60B60;
#define VMUL 0.005
#define VMUL_M20 0.01
#ifndef PI
#define PI (3.1415926535897932384626433832795)
#endif
#define RAD (PI/180)
// ret: 1=frame ok; 2=frame with errors; 0=ignored frame (m20dop-alternativ)
int M20::decodeframeM20(uint8_t *data) {
int repairstep = 16;
int repl = 0;
bool crcok;
// error correction, inspired by oe5dxl's sondeudp
do {
crcok = checkM20crc(data);
if(crcok) break;
repl = 0;
for(int i=0; i<M20_CRCPOS; i++) {
if( ((sondeudp_VARSET[i/32]&(1<<(i%32))) != 1) && (fixcnt[i]>=repairstep) ) {
repl++;
data[i] = fixbytes[i];
}
}
repairstep >>= 1;
} while(repairstep>0);
if(crcok) {
for(int i=0; i<M20_CRCPOS; i++) {
if(fixbytes[i]==data[i] &&fixcnt[i]<255) fixcnt[i]++;
else { fixcnt[i]=0; fixbytes[i]=data[i]; }
}
}
Serial.println(crcok?"CRC OK":"CRC NOT OK");
//M20 0x20
if(data[1]==0x20) {
Serial.println("Decoding...");
// Its a M20
// getid...
char ids[11]={'M','E','0','0','0','0','0','0','0','0','0'};
ids[0] = 'M';
ids[1] = 'E';
uint32_t id = getint16(data+18);
ids[2] = hex(id/16);
ids[3] = hex(id);
//
id = getint16_r(data+19)/4;
ids[4] = (char)((id/10000)%10+48);
ids[5] = (char)((id/1000)%10+48);
ids[6] = (char)((id/100)%10+48);
ids[7] = (char)((id/10)%10+48);
ids[8] = (char)(id%10+48);
strncpy(sonde.si()->ser, ids, 10);
sonde.si()->validID = true;
//Serial.printf("ID is %s [%02x %02x %d]\n", ids, data[95], data[93], id);
// ID printed on sonde is ...-.-abbbb, with a=id>>13, bbbb=id&0x1fff in decimal
// position data
// 0x1C 4 byte
sonde.si()->lat = getint32(data+28) * 1e-6;
//0x20 4 byte
sonde.si()->lon = getint32(data+32) * 1e-6;
//0x08 3 byte
sonde.si()->alt = getint24(data+8) * VMUL_M20;
//0x0B 2 byte
//VMUL_M20 specific
float ve = getint16(data+11)*VMUL_M20;
//0x0D 2 byte
float vn = getint16(data+13)*VMUL_M20;
//0x18 2 byte
sonde.si()->vs = getint16(data+24) * VMUL_M20;
sonde.si()->hs = sqrt(ve*ve+vn*vn);
float dir = atan2(vn, ve)*(1.0/RAD);
if(dir<0) dir+=360;
sonde.si()->dir = dir;
sonde.si()->validPos = 0x3f;
//0x0F 3 byte
uint32_t tow = getint24(data+15);
uint16_t week = getint16(data+26);
sonde.si()->time = (tow+week*604800+315964800)-18;
sonde.si()->validTime = true;
} else {
Serial.printf("data is %02x %02x %02x\n", data[0], data[1], data[2]);
return 0;
}
return crcok?1:2;
}
static uint32_t rxdata;
static bool rxsearching=true;
// search for
// //101001100110011010011010011001100110100110101010100110101001
// //1010011001100110100110100110 0110.0110 1001.1010 1010.1001 1010.1001 => 0x669AA9A9
void M20::processM20data(uint8_t dt)
{
for(int i=0; i<8; i++) {
uint8_t d = (dt&0x80)?1:0;
dt <<= 1;
rxdata = (rxdata<<1) | d;
//uint8_t value = ((rxdata>>1)^rxdata)&0x01;
//if((rxbitc&1)==1) { rxbyte = (rxbyte>>1) + ((value)<<8); } // mancester decoded data
//rxbyte = (rxbyte>>1) | (d<<8);
if( (rxbitc&1)==0 ) {
// "bit1"
rxbyte = (rxbyte<<1) | d;
} else {
// "bit2" ==> 01 or 10 => 1, otherweise => 0
rxbyte = rxbyte ^ d;
}
//
if(rxsearching) {
if( rxdata == 0xcccca64c || rxdata == 0x333359b3 ) {
rxsearching = false;
rxbitc = 0;
rxp = 0;
#if 1
int rssi=sx1278.getRSSI();
int fei=sx1278.getFEI();
int afc=sx1278.getAFC();
Serial.print("Test: RSSI="); Serial.print(rssi);
Serial.print(" FEI="); Serial.print(fei);
Serial.print(" AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
#endif
}
} else {
rxbitc = (rxbitc+1)%16; // 16;
if(rxbitc == 0) { // got 8 data bit
//Serial.printf("%03x ",rxbyte);
dataptr[rxp++] = rxbyte&0xff; // (rxbyte>>1)&0xff;
#if 0
if(rxp==7 && dataptr[6] != 0x65) {
Serial.printf("wrong start: %02x\n",dataptr[6]);
rxsearching = true;
}
#endif
if(rxp>=M20_FRAMELEN) {
rxsearching = true;
haveNewFrame = decodeframeM20(dataptr);
}
}
}
}
}
int M20::receive() {
unsigned long t0 = millis();
Serial.printf("M20::receive() start at %ld\n",t0);
while( millis() - t0 < 1512 ) {
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
if ( bitRead(value, 7) ) {
Serial.println("FIFO full");
}
if ( bitRead(value, 4) ) {
Serial.println("FIFO overflow");
}
if ( bitRead(value, 2) == 1 ) {
Serial.println("FIFO: ready()");
sx1278.clearIRQFlags();
}
if(bitRead(value, 6) == 0) { // while FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
//Serial.printf("%02x",data);
processM20data(data);
value = sx1278.readRegister(REG_IRQ_FLAGS2);
} else {
if(headerDetected) {
t0 = millis(); // restart timer... don't time out if header detected...
headerDetected = 0;
}
if(haveNewFrame) {
Serial.printf("M20::receive(): new frame complete after %ldms\n", millis()-t0);
printRaw(dataptr, M20_FRAMELEN);
int retval = haveNewFrame==1 ? RX_OK: RX_ERROR;
haveNewFrame = 0;
return retval;
}
delay(2);
}
}
Serial.printf("M20::receive() timed out\n");
return RX_TIMEOUT; // TODO RX_OK;
}
#define M20MAXLEN (240)
int M20::waitRXcomplete() {
// called after complete...
#if 0
Serial.printf("decoding frame %d\n", lastFrame);
print_frame(lastFrame==1?data1:data2, 240);
SondeInfo *si = sonde.sondeList+rxtask.receiveSonde;
si->lat = gpx.lat;
si->lon = gpx.lon;
si->alt = gpx.alt;
si->vs = gpx.vU;
si->hs = gpx.vH;
si->dir = gpx.vD;
si->validPos = 0x3f;
memcpy(si->id, gpx.id, 9);
si->validID = true;
int res=0;
uint32_t t0 = millis();
while( rxtask.receiveResult == 0xFFFF && millis()-t0 < 2000) { delay(20); }
if( rxtask.receiveResult<0 || rxtask.receiveResult==RX_TIMEOUT) {
res = RX_TIMEOUT;
} else if ( rxtask.receiveResult==0) {
res = RX_OK;
} else {
res = RX_ERROR;
}
rxtask.receiveResult = 0xFFFF;
Serial.printf("M20::waitRXcomplete returning %d (%s)\n", res, RXstr[res]);
return res;
#endif
return 0;
}
#if 0
int oldwaitRXcomplete() {
Serial.println("M20: receive frame...\n");
sx1278receiveData = true;
delay(6000); // done in other task....
//sx1278receiveData = false;
#if 0
//sx1278.setPayloadLength(518-8); // Expect 320-8 bytes or 518-8 bytes (8 byte header)
//sx1278.setPayloadLength(0); // infinite for now...
////// test code for continuous reception
// sx1278.receive(); /// active FSK RX mode -- already done above...
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
unsigned long previous = millis();
byte ready=0;
uint32_t wait = 8000;
// while not yet done or FIFO not yet empty
// bit 6: FIFO Empty
// bit 2 payload ready
int by=0;
while( (!ready || bitRead(value,6)==0) && (millis() - previous < wait) )
{
if( bitRead(value, 7) ) { Serial.println("FIFO full"); }
if( bitRead(value, 4) ) { Serial.println("FIFO overflow"); }
if( bitRead(value,2)==1 ) ready=1;
if( bitRead(value, 6) == 0 ) { // FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
process8N1data(data);
by++;
#if 0
if(di==1) {
int rssi=getRSSI();
int fei=getFEI();
int afc=getAFC();
Serial.print("Test: RSSI="); Serial.println(rssi);
Serial.print("Test: FEI="); Serial.println(fei);
Serial.print("Test: AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
}
if(di>520) {
// TODO
Serial.println("TOO MUCH DATA");
break;
}
previous = millis(); // reset timeout after receiving data
#endif
}
value = sx1278.readRegister(REG_IRQ_FLAGS2);
}
Serial.printf("processed %d bytes before end/timeout\n", by);
#endif
/////
#if 0
int e = sx1278.receivePacketTimeout(1000, data+8);
if(e) { Serial.println("TIMEOUT"); return RX_TIMEOUT; } //if timeout... return 1
printRaw(data, M20MAXLEN);
//for(int i=0; i<M20MAXLEN; i++) { data[i] = reverse(data[i]); }
//printRaw(data, MAXLEN);
//for(int i=0; i<M20MAXLEN; i++) { data[i] = data[i] ^ scramble[i&0x3F]; }
//printRaw(data, MAXLEN);
//int res = decode41(data, M20MAXLEN);
#endif
int res=0;
return res==0 ? RX_OK : RX_ERROR;
}
#endif
M20 m20 = M20();

96
libraries/SondeLib/M20.h Executable file
View File

@ -0,0 +1,96 @@
/*
* M20.h
* Functions for decoding Meteomodem M20 sondes with SX127x chips
* Copyright (C) 2019 Hansi Reiser, dl9rdz
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef M20_h
#define M20_h
#include <stdlib.h>
#include <stdint.h>
#include <Arduino.h>
#ifndef inttypes_h
#include <inttypes.h>
#endif
#if 0
struct CONTEXTR9 {
char calibdata[512];
uint32_t calibok;
char mesok;
char posok;
char framesent;
double lat;
double lon;
double heig;
double speed;
double dir;
double climb;
double lastlat;
double laslong;
double lastalt;
double lastspeed;
double lastdir;
double lastclb;
float hrmsc;
float vrmsc;
double hp;
double hyg;
double temp;
double ozontemp;
double ozon;
uint32_t goodsats;
uint32_t timems;
uint32_t framenum;
};
#endif
/* Main class */
class M20
{
private:
void printRaw(uint8_t *data, int len);
void processM20data(uint8_t data);
int decodeframeM20(uint8_t *data);
#if 0
void stobyte92(uint8_t byte);
void dogps(const uint8_t *sf, int sf_len,
struct CONTEXTR9 * cont, uint32_t * timems,
uint32_t * gpstime);
uint32_t bits2val(const uint8_t *bits, int len);
int bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
int decode92(byte *data, int maxlen);
uint8_t hamming_conf[ 7*8]; // 7*8=56
uint8_t hamming_dat1[13*8]; // 13*8=104
uint8_t hamming_dat2[13*8];
uint8_t block_conf[ 7*4]; // 7*4=28
uint8_t block_dat1[13*4]; // 13*4=52
uint8_t block_dat2[13*4];
uint8_t H[4][8] = // extended Hamming(8,4) particy check matrix
{{ 0, 1, 1, 1, 1, 0, 0, 0},
{ 1, 0, 1, 1, 0, 1, 0, 0},
{ 1, 1, 0, 1, 0, 0, 1, 0},
{ 1, 1, 1, 0, 0, 0, 0, 1}};
uint8_t He[8] = { 0x7, 0xB, 0xD, 0xE, 0x8, 0x4, 0x2, 0x1}; // Spalten von H:
// 1-bit-error-Syndrome
boolean initialized = false;
#endif
public:
M20();
int setup(float frequency);
int receive();
int waitRXcomplete();
//int use_ecc = 1;
};
extern M20 m20;
#endif

557
libraries/SondeLib/RS41.cpp Executable file
View File

@ -0,0 +1,557 @@
/* RS41 decoder functions */
#include "RS41.h"
#include "SX1278FSK.h"
#include "rsc.h"
#include "Sonde.h"
#define RS41_DEBUG 0
#if RS41_DEBUG
#define RS41_DBG(x) x
#else
#define RS41_DBG(x)
#endif
#define RS41MAXLEN (320)
static byte data[800];
static int dpos = 0;
static uint16_t CRCTAB[256];
#define X2C_DIVR(a, b) ((b) != 0.0f ? (a)/(b) : (a))
#define X2C_DIVL(a, b) ((a)/(b))
static uint32_t X2C_LSH(uint32_t a, int32_t length, int32_t n)
{
uint32_t m;
m = 0;
m = (length == 32) ? 0xFFFFFFFFl : (1 << length) - 1;
if (n > 0) {
if (n >= (int32_t)length)
return 0;
return (a << n) & m;
}
if (n <= (int32_t)-length)
return 0;
return (a >> -n) & m;
}
static double atang2(double x, double y)
{
double w;
if (fabs(x)>fabs(y)) {
w = (double)atan((float)(X2C_DIVL(y,x)));
if (x<0.0) {
if (y>0.0) w = 3.1415926535898+w;
else w = w-3.1415926535898;
}
}
else if (y!=0.0) {
w = (double)(1.5707963267949f-atan((float)(X2C_DIVL(x,
y))));
if (y<0.0) w = w-3.1415926535898;
}
else w = 0.0;
return w;
} /* end atang2() */
static void Gencrctab(void)
{
uint16_t j;
uint16_t i;
uint16_t crc;
for (i = 0U; i<=255U; i++) {
crc = (uint16_t)(i*256U);
for (j = 0U; j<=7U; j++) {
if ((0x8000U & crc)) crc = X2C_LSH(crc,16,1)^0x1021U;
else crc = X2C_LSH(crc,16,1);
} /* end for */
CRCTAB[i] = X2C_LSH(crc,16,-8)|X2C_LSH(crc,16,8);
} /* end for */
} /* end Gencrctab() */
int RS41::setup(float frequency)
{
#if RS41_DEBUG
Serial.println("Setup sx1278 for RS41 sonde");
#endif
if(!initialized) {
Gencrctab();
initrsc();
initialized = true;
}
if(sx1278.ON()!=0) {
RS41_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(sx1278.setFSK()!=0) {
RS41_DBG(Serial.println("Setting FSK mode FAILED"));
return 1;
}
if(sx1278.setBitrate(4800)!=0) {
RS41_DBG(Serial.println("Setting bitrate 4800bit/s FAILED"));
return 1;
}
#if RS41_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(sonde.config.rs41.agcbw)!=0) {
RS41_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.rs41.agcbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.rs41.rxbw)!=0) {
RS41_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.rs41.rxbw));
return 1;
}
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x1E)!=0) {
RS41_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// Set autostart_RX to 01, preamble 0, SYNC detect==on, syncsize=3 (==4 byte
//char header[] = "0110.0101 0110.0110 1010.0101 1010.1010";
//const char *SYNC="\x10\xB6\xCA\x11\x22\x96\x12\xF8";
const char *SYNC="\x08\x6D\x53\x88\x44\x69\x48\x1F";
if(sx1278.setSyncConf(0x57, 8, (const uint8_t *)SYNC)!=0) {
RS41_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
if(sx1278.setPreambleDetect(0xA8)!=0) {
RS41_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
// Packet config 1: fixed len, no mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
RS41_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
Serial.print("RS41: setting RX frequency to ");
Serial.println(frequency);
int retval = sx1278.setFrequency(frequency);
dpos = 0;
#if RS41_DEBUG
RS41_DBG(Serial.println("Setting SX1278 config for RS41 finished\n"); Serial.println());
#endif
sx1278.clearIRQFlags();
// the following is already done in receivePacketTimeout()
// sx1278.setPayloadLength(RS41MAXLEN-8); // Expect 320-8 bytes or 518-8 bytes (8 byte header)
// sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
return retval;
}
uint32_t RS41::bits2val(const uint8_t *bits, int len) {
uint32_t val = 0;
for (int j = 0; j < len; j++) {
val |= (bits[j] << (len-1-j));
}
return val;
}
RS41::RS41() {
}
/* RS41 reed solomon decoder, from dxlAPRS
*/
static int32_t reedsolomon41(byte buf[], uint32_t buf_len, uint32_t len2)
{
uint32_t i;
int32_t res1;
/*tb1, */
int32_t res;
char b1[256];
char b[256];
uint32_t eraspos[24];
uint32_t tmp;
for (i = 0UL; i<=255UL; i++) {
b[i] = 0;
b1[i] = 0;
} /* end for */
tmp = len2;
i = 0UL;
if (i<=tmp) for (;; i++) {
b[230UL-i] = buf[i*2UL+56UL];
b1[230UL-i] = buf[i*2UL+57UL];
if (i==tmp) break;
} /* end for */
for (i = 0UL; i<=23UL; i++) {
b[254UL-i] = buf[i+8UL];
b1[254UL-i] = buf[i+32UL];
} /* end for */
res = decodersc(b, eraspos, 0);
res1 = decodersc(b1, eraspos, 0);
if (res>0L && res<=12L) {
tmp = len2;
i = 0UL;
if (i<=tmp) for (;; i++) {
buf[i*2UL+56UL] = b[230UL-i];
if (i==tmp) break;
} /* end for */
for (i = 0UL; i<=23UL; i++) {
buf[i+8UL] = b[254UL-i];
} /* end for */
}
if (res1>0L && res1<=12L) {
tmp = len2;
i = 0UL;
if (i<=tmp) for (;; i++) {
buf[i*2UL+57UL] = b1[230UL-i];
if (i==tmp) break;
} /* end for */
for (i = 0UL; i<=23UL; i++) {
buf[i+32UL] = b1[254UL-i];
} /* end for */
}
if (res<0L || res1<0L) return -1L;
else return res+res1;
return 0;
} /* end reedsolomon41() */
static char crcrs(const byte frame[], uint32_t frame_len,
int32_t from, int32_t to)
{
uint16_t crc;
int32_t i;
int32_t tmp;
crc = 0xFFFFU;
tmp = to-3L;
i = from;
if (i<=tmp) for (;; i++) {
crc = X2C_LSH(crc,16,-8)^CRCTAB[(uint32_t)((crc^(uint16_t)(uint8_t)frame[i])&0xFFU)];
if (i==tmp) break;
} /* end for */
return frame[to-1L]==(char)crc && frame[to-2L]==(char)X2C_LSH(crc,
16,-8);
} /* end crcrs() */
static int32_t getint32(const byte frame[], uint32_t frame_len,
uint32_t p)
{
uint32_t n;
uint32_t i;
n = 0UL;
for (i = 3UL;; i--) {
n = n*256UL+(uint32_t)(uint8_t)frame[p+i];
if (i==0UL) break;
} /* end for */
return (int32_t)n;
} /* end getint32() */
static uint32_t getcard16(const byte frame[], uint32_t frame_len,
uint32_t p)
{
return (uint32_t)(uint8_t)frame[p]+256UL*(uint32_t)(uint8_t)
frame[p+1UL];
} /* end getcard16() */
static int32_t getint16(const byte frame[], uint32_t frame_len,
uint32_t p)
{
uint32_t n;
n = (uint32_t)(uint8_t)frame[p]+256UL*(uint32_t)(uint8_t)
frame[p+1UL];
if (n>=32768UL) return (int32_t)(n-65536UL);
return (int32_t)n;
} /* end getint16() */
static void wgs84r(double x, double y, double z,
double * lat, double * long0,
double * heig)
{
double sl;
double ct;
double st;
double t;
double rh;
double xh;
double h;
h = x*x+y*y;
if (h>0.0) {
rh = (double)sqrt((float)h);
xh = x+rh;
*long0 = atang2(xh, y)*2.0;
if (*long0>3.1415926535898) *long0 = *long0-6.2831853071796;
t = (double)atan((float)(X2C_DIVL(z*1.003364089821,
rh)));
st = (double)sin((float)t);
ct = (double)cos((float)t);
*lat = (double)atan((float)
(X2C_DIVL(z+4.2841311513312E+4*st*st*st,
rh-4.269767270718E+4*ct*ct*ct)));
sl = (double)sin((float)*lat);
*heig = X2C_DIVL(rh,(double)cos((float)*lat))-(double)(X2C_DIVR(6.378137E+6f,
sqrt((float)(1.0-6.6943799901413E-3*sl*sl))));
}
else {
*lat = 0.0;
*long0 = 0.0;
*heig = 0.0;
}
/* lat:=atan(z/(rh*(1.0 - E2))); */
/* heig:=sqrt(h + z*z) - EARTHA; */
} /* end wgs84r() */
// returns: 0=ok, -1=error
static void posrs41(const byte b[], uint32_t b_len, uint32_t p)
{
double dir;
double vu;
double ve;
double vn;
double vz;
double vy;
double vx;
double heig;
double long0;
double lat;
double z;
double y;
double x;
x = (double)getint32(b, b_len, p)*0.01;
y = (double)getint32(b, b_len, p+4UL)*0.01;
z = (double)getint32(b, b_len, p+8UL)*0.01;
wgs84r(x, y, z, &lat, &long0, &heig);
Serial.print(" ");
sonde.si()->lat = (float)(X2C_DIVL(lat,1.7453292519943E-2));
Serial.print(sonde.si()->lat);
Serial.print(" ");
sonde.si()->lon = (float)(X2C_DIVL(long0,1.7453292519943E-2));
Serial.print(sonde.si()->lon);
if (heig<1.E+5 && heig>(-1.E+5)) {
Serial.print(" ");
Serial.print((uint32_t)heig);
Serial.print("m");
}
/*speed */
vx = (double)getint16(b, b_len, p+12UL)*0.01;
vy = (double)getint16(b, b_len, p+14UL)*0.01;
vz = (double)getint16(b, b_len, p+16UL)*0.01;
vn = (-(vx*(double)sin((float)lat)*(double)
cos((float)long0))-vy*(double)
sin((float)lat)*(double)sin((float)
long0))+vz*(double)cos((float)lat);
ve = -(vx*(double)sin((float)long0))+vy*(double)
cos((float)long0);
vu = vx*(double)cos((float)lat)*(double)
cos((float)long0)+vy*(double)
cos((float)lat)*(double)sin((float)
long0)+vz*(double)sin((float)lat);
dir = X2C_DIVL(atang2(vn, ve),1.7453292519943E-2);
if (dir<0.0) dir = 360.0+dir;
sonde.si()->dir = dir;
Serial.print(" ");
//sonde.si()->hs = sqrt((float)(vn*vn+ve*ve))*3.6f;
sonde.si()->hs = sqrt((float)(vn*vn+ve*ve));
Serial.print(sonde.si()->hs);
Serial.print("km/h ");
Serial.print(dir);
Serial.print("deg ");
Serial.print((float)vu);
sonde.si()->vs = vu;
Serial.print("m/s ");
uint8_t sats = getcard16(b, b_len, p+18UL)&255UL;
Serial.print(sats);
Serial.print("Sats");
sonde.si()->sats = sats;
sonde.si()->alt = heig;
if( 0==(int)(lat*10000) && 0==(int)(long0*10000) ) {
if(sonde.si()->validPos) {
// we have an old position, so keep previous position and mark it as old
sonde.si()->validPos |= 0x80;
}
}
else
sonde.si()->validPos = 0x7f;
} /* end posrs41() */
// returns: 0: ok, -1: rs or crc error
int RS41::decode41(byte *data, int maxlen)
{
char buf[128];
int crcok = 0;
int32_t corr = reedsolomon41(data, 560, 131); // try short frame first
if(corr<0) {
corr = reedsolomon41(data, 560, 230); // try long frame
}
#if 0
Serial.print("RS result:");
Serial.print(corr);
Serial.println();
#endif
int p = 57; // 8 byte header, 48 byte RS
while(p<maxlen) { /* why 555? */
uint8_t typ = data[p++];
uint32_t len = data[p++]+2UL;
if(p+len>maxlen) break;
#if 0
// DEBUG OUTPUT
Serial.print("@");
Serial.print(p-2);
Serial.print(": ID:");
Serial.print(typ, HEX);
Serial.print(", len=");
Serial.print(len);
Serial.print(": ");
for(int i=0; i<len-1; i++) {
char buf[3];
snprintf(buf, 4, "%02X|", data[p+i]);
Serial.print(buf);
}
#endif
// check CRC
if(!crcrs(data, 560, p, p+len)) {
Serial.println("###CRC ERROR###");
} else {
crcok = 1;
switch(typ) {
case 'y': // name
{
Serial.print("#");
uint16_t fnr = data[p]+(data[p+1]<<8);
Serial.print(fnr);
sonde.si()->frame = fnr;
Serial.print("; RS41 ID ");
snprintf(buf, 10, "%.8s ", data+p+2);
Serial.print(buf);
sonde.si()->type=STYPE_RS41;
strncpy(sonde.si()->id, (const char *)(data+p+2), 8);
sonde.si()->id[8]=0;
strncpy(sonde.si()->ser, (const char *)(data+p+2), 8);
sonde.si()->ser[8]=0;
sonde.si()->validID=true;
int calnr = data[p+23];
// not sure about this
if(calnr==0x31) {
uint16_t bt = data[p+30] + 256*data[p+31];
sonde.si()->burstKT = bt;
}
// this should be right...
if(calnr==0x02) {
uint16_t kt = data[p+31] + 256*data[p+32];
sonde.si()->launchKT = kt;
}
// and this seems fine as well...
if(calnr==0x32) {
uint16_t cntdown = data[p+24] + (data[p+25]<<8);
uint16_t min = cntdown - (cntdown/3600)*3600;
Serial.printf("Countdown value: %d\n [%2d:%02d:%02d]", cntdown, cntdown/3600, min/60, min-(min/60)*60);
sonde.si()->countKT = cntdown;
sonde.si()->crefKT = fnr;
}
}
// TODO: some more data
break;
case '|': // date
{
uint32_t gpstime = getint32(data, 560, p+2);
uint16_t gpsweek = getint16(data, 560, p);
// UTC is GPSTIME - 18s (24*60*60-18 = 86382)
// one week = 7*24*60*60 = 604800 seconds
// unix epoch starts jan 1st 1970 0:00
// gps time starts jan 6, 1980 0:00. thats 315964800 epoch seconds.
// subtracting 86400 yields 315878400UL
sonde.si()->time = (gpstime/1000) + 86382 + gpsweek*604800 + 315878400UL;
sonde.si()->validTime = true;
}
break;
case '{': // pos
posrs41(data+p, len, 0);
break;
default:
break;
}}
p += len;
Serial.println();
}
return crcok ? 0 : -1;
}
void RS41::printRaw(uint8_t *data, int len)
{
char buf[3];
int i;
for(i=0; i<len; i++) {
snprintf(buf, 3, "%02X", data[i]);
Serial.print(buf);
}
Serial.println();
}
void RS41::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len)
{
int i;
for(i=0; i<len*4; i++) {
bytes[i/8] = (bytes[i/8]<<1) | (bits[i]?1:0);
}
bytes[(i-1)/8] &= 0x0F;
}
static unsigned char lookup[16] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };
static uint8_t reverse(uint8_t n) {
return (lookup[n&0x0f] << 4) | lookup[n>>4];
}
static uint8_t scramble[64] = {150U,131U,62U,81U,177U,73U,8U,152U,50U,5U,89U,
14U,249U,68U,198U,38U,33U,96U,194U,234U,121U,93U,109U,161U,
84U,105U,71U,12U,220U,232U,92U,241U,247U,118U,130U,127U,7U,
153U,162U,44U,147U,124U,48U,99U,245U,16U,46U,97U,208U,188U,
180U,182U,6U,170U,244U,35U,120U,110U,59U,174U,191U,123U,76U,
193U};
int RS41::receive() {
sx1278.setPayloadLength(RS41MAXLEN-8);
int e = sx1278.receivePacketTimeout(1000, data+8);
if(e) { Serial.println("TIMEOUT"); return RX_TIMEOUT; }
for(int i=0; i<RS41MAXLEN; i++) { data[i] = reverse(data[i]); }
for(int i=0; i<RS41MAXLEN; i++) { data[i] = data[i] ^ scramble[i&0x3F]; }
return decode41(data, RS41MAXLEN);
}
int RS41::waitRXcomplete() {
// Currently not used. can be used for additinoal post-processing
// (required for RS92 to avoid FIFO overrun in rx task)
#if 0
int res;
uint32_t t0 = millis();
while(rxtask.receiveResult<0 && millis()-t0 < 3000) { delay(50); }
if(rxtask.receiveResult<0 || rxtask.receiveResult==RX_TIMEOUT) {
res = RX_TIMEOUT;
} else if (rxtask.receiveResult==0) {
res = RX_OK;
} else {
res = RX_ERROR;
}
rxtask.receiveResult = -1;
Serial.printf("waitRXcomplete returning %d\n", res);
return res;
#endif
return 0;
}
RS41 rs41 = RS41();

67
libraries/SondeLib/RS41.h Executable file
View File

@ -0,0 +1,67 @@
/*
* RS41.h
* Functions for decoding RS41 sondes with SX127x chips
* Copyright (C) 2019 Hansi Reiser, dl9rdz
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef RS41_h
#define RS41_h
#include <stdlib.h>
#include <stdint.h>
#include <Arduino.h>
#ifndef inttypes_h
#include <inttypes.h>
#endif
/* Main class */
class RS41
{
private:
uint32_t bits2val(const uint8_t *bits, int len);
void printRaw(uint8_t *data, int len);
void bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
int decode41(byte *data, int maxlen);
#define B 8
#define S 4
uint8_t hamming_conf[ 7*B]; // 7*8=56
uint8_t hamming_dat1[13*B]; // 13*8=104
uint8_t hamming_dat2[13*B];
uint8_t block_conf[ 7*S]; // 7*4=28
uint8_t block_dat1[13*S]; // 13*4=52
uint8_t block_dat2[13*S];
uint8_t H[4][8] = // extended Hamming(8,4) particy check matrix
{{ 0, 1, 1, 1, 1, 0, 0, 0},
{ 1, 0, 1, 1, 0, 1, 0, 0},
{ 1, 1, 0, 1, 0, 0, 1, 0},
{ 1, 1, 1, 0, 0, 0, 0, 1}};
uint8_t He[8] = { 0x7, 0xB, 0xD, 0xE, 0x8, 0x4, 0x2, 0x1}; // Spalten von H:
// 1-bit-error-Syndrome
boolean initialized = false;
public:
RS41();
// New interface:
// setup() is called when channel is activated (sets mode and frequency and activates receiver)
int setup(float frequency);
// processRXbyte is called by background task for each received byte
// should be fast enough to not cause sx127x fifo buffer overflow
// void processRXbyte(uint8_t data);
// is called approx. 1x per second, may do some post-processing of received data
// and update information in sonde data structure
// returns infomration about sucess/error (for timers and for quality bar in display)
int receive();
int waitRXcomplete();
//int receiveFrame();
int use_ecc = 1;
};
extern RS41 rs41;
#endif

727
libraries/SondeLib/RS92.cpp Executable file
View File

@ -0,0 +1,727 @@
/* RS92 decoder functions */
#include "RS92.h"
#include "SX1278FSK.h"
#include "rsc.h"
#include "Sonde.h"
#include <SPIFFS.h>
// well...
#include "rs92gps.h"
#define RS92_DEBUG 1
#if RS92_DEBUG
#define RS92_DBG(x) x
#else
#define RS92_DBG(x)
#endif
//static uint16_t CRCTAB[256];
uint16_t *CRCTAB = NULL;
#define X2C_DIVR(a, b) ((b) != 0.0f ? (a)/(b) : (a))
#define X2C_DIVL(a, b) ((a)/(b))
static uint32_t X2C_LSH(uint32_t a, int32_t length, int32_t n)
{
uint32_t m;
m = 0;
m = (length == 32) ? 0xFFFFFFFFl : (1 << length) - 1;
if (n > 0) {
if (n >= (int32_t)length)
return 0;
return (a << n) & m;
}
if (n <= (int32_t)-length)
return 0;
return (a >> -n) & m;
}
#if 0
static double atang2(double x, double y)
{
double w;
if (fabs(x)>fabs(y)) {
w = (double)atan((float)(X2C_DIVL(y,x)));
if (x<0.0) {
if (y>0.0) w = 3.1415926535898+w;
else w = w-3.1415926535898;
}
}
else if (y!=0.0) {
w = (double)(1.5707963267949f-atan((float)(X2C_DIVL(x,
y))));
if (y<0.0) w = w-3.1415926535898;
}
else w = 0.0;
return w;
} /* end atang2() */
#endif
static void Gencrctab(void)
{
uint16_t j;
uint16_t i;
uint16_t crc;
if(!CRCTAB) { CRCTAB=(uint16_t *)malloc(256*sizeof(uint16_t)); }
for (i = 0U; i<=255U; i++) {
crc = (uint16_t)(i*256U);
for (j = 0U; j<=7U; j++) {
if ((0x8000U & crc)) crc = X2C_LSH(crc,16,1)^0x1021U;
else crc = X2C_LSH(crc,16,1);
} /* end for */
CRCTAB[i] = X2C_LSH(crc,16,-8)|X2C_LSH(crc,16,8);
} /* end for */
} /* end Gencrctab() */
static byte data1[512]={0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x10};
static byte data2[512]={0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x10};
static byte *dataptr=data1;
static uint8_t rxbitc;
static int32_t asynst[10]={0};
static uint16_t rxbyte;
int rxp=0;
static int haveNewFrame = 0;
static int lastFrame = 0;
static int headerDetected = 0;
int RS92::setup(float frequency)
{
#if RS92_DEBUG
Serial.println("Setup sx1278 for RS92 sonde");
#endif
if(!initialized) {
Gencrctab();
initrsc();
// not here for now.... get_eph("/brdc.19n");
initialized = true;
}
if(sx1278.ON()!=0) {
RS92_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(sx1278.setFSK()!=0) {
RS92_DBG(Serial.println("Setting FSJ mode FAILED"));
return 1;
}
if(sx1278.setBitrate(4800)!=0) {
RS92_DBG(Serial.println("Setting bitrate 4800bit/s FAILED"));
return 1;
}
#if RS92_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(sonde.config.rs92.rxbw)!=0) {
RS92_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.rs92.rxbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.rs92.rxbw)!=0) {
RS92_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.rs92.rxbw));
return 1;
}
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x1E)!=0) {
RS92_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// Set autostart_RX to 01, preamble 0, SYNC detect==on, syncsize=3 (==4 byte
//char header[] = "0110.0101 0110.0110 1010.0101 1010.1010";
//const char *SYNC="\x10\xB6\xCA\x11\x22\x96\x12\xF8";
//const char *SYNC="\x08\x6D\x53\x88\x44\x69\x48\x1F";
// was 0x57
//const char *SYNC="\x99\x9A";
#if 1
// version 1, working with continuous RX
const char *SYNC="\x66\x65";
if(sx1278.setSyncConf(0x70, 2, (const uint8_t *)SYNC)!=0) {
RS92_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
if(sx1278.setPreambleDetect(0xA8)!=0) {
RS92_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
#endif
#if 0
// version 2, with per-packet rx start, untested
// header is 2a 10 65, i.e. with lsb first
// 0 0101 0100 1 0 0000 1000 1 0 1010 0110 1
// 10 10011001 10011010 01 10 10101010 01101010 01 10 01100110 10010110 01
// preamble 0x6A 0x66 0x6A
// i.e. preamble detector on (0x80), preamble detector size 1 (0x00), preample chip errors??? (0x0A)
// after 2a2a2a2a2a1065
if(sx1278.setPreambleDetect(0xA8)!=0) {
RS92_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
// sync config: ato restart (01), preamble polarity AA (0), sync on (1), resevered (0), syncsize 2+1 (010) => 0x52
const char *SYNC="\x6A\x66\x69";
if(sx1278.setSyncConf(0x52, 3, (const uint8_t *)SYNC)!=0) {
RS92_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
// payload length is ((240 - 7)*10 +6)/8 = 292
#endif
// Packet config 1: fixed len, no mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
RS92_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
Serial.print("RS92: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
sx1278.clearIRQFlags();
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
//sx1278.setPayloadLength(292);
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
#if RS92_DEBUG
RS92_DBG(Serial.println("Setting SX1278 config for RS92 finished\n"); Serial.println());
#endif
return res;
}
#if 0
int RS92::setFrequency(float frequency) {
Serial.print("RS92: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
return res;
}
#endif
uint32_t RS92::bits2val(const uint8_t *bits, int len) {
uint32_t val = 0;
for (int j = 0; j < len; j++) {
val |= (bits[j] << (len-1-j));
}
return val;
}
RS92::RS92() {
}
/* RS92 reed solomon decoder, from dxlAPRS
*/
#if 0
static char crcrs(const byte frame[], uint32_t frame_len,
int32_t from, int32_t to)
{
uint16_t crc;
int32_t i;
int32_t tmp;
crc = 0xFFFFU;
tmp = to-3L;
i = from;
if (i<=tmp) for (;; i++) {
crc = X2C_LSH(crc,16,-8)^CRCTAB[(uint32_t)((crc^(uint16_t)(uint8_t)frame[i])&0xFFU)];
if (i==tmp) break;
} /* end for */
return frame[to-1L]==(char)crc && frame[to-2L]==(char)X2C_LSH(crc,
16,-8);
} /* end crcrs() */
static int32_t getint32(const byte frame[], uint32_t frame_len,
uint32_t p)
{
uint32_t n;
uint32_t i;
n = 0UL;
for (i = 3UL;; i--) {
n = n*256UL+(uint32_t)(uint8_t)frame[p+i];
if (i==0UL) break;
} /* end for */
return (int32_t)n;
} /* end getint32() */
static uint32_t getcard16(const byte frame[], uint32_t frame_len,
uint32_t p)
{
return (uint32_t)(uint8_t)frame[p]+256UL*(uint32_t)(uint8_t)
frame[p+1UL];
} /* end getcard16() */
static int32_t getint16(const byte frame[], uint32_t frame_len,
uint32_t p)
{
uint32_t n;
n = (uint32_t)(uint8_t)frame[p]+256UL*(uint32_t)(uint8_t)
frame[p+1UL];
if (n>=32768UL) return (int32_t)(n-65536UL);
return (int32_t)n;
} /* end getint16() */
static void wgs84r(double x, double y, double z,
double * lat, double * long0,
double * heig)
{
double sl;
double ct;
double st;
double t;
double rh;
double xh;
double h;
h = x*x+y*y;
if (h>0.0) {
rh = (double)sqrt((float)h);
xh = x+rh;
*long0 = atang2(xh, y)*2.0;
if (*long0>3.1415926535898) *long0 = *long0-6.2831853071796;
t = (double)atan((float)(X2C_DIVL(z*1.003364089821,
rh)));
st = (double)sin((float)t);
ct = (double)cos((float)t);
*lat = (double)atan((float)
(X2C_DIVL(z+4.2841311513312E+4*st*st*st,
rh-4.269767270718E+4*ct*ct*ct)));
sl = (double)sin((float)*lat);
*heig = X2C_DIVL(rh,(double)cos((float)*lat))-(double)(X2C_DIVR(6.378137E+6f,
sqrt((float)(1.0-6.6943799901413E-3*sl*sl))));
}
else {
*lat = 0.0;
*long0 = 0.0;
*heig = 0.0;
}
/* lat:=atan(z/(rh*(1.0 - E2))); */
/* heig:=sqrt(h + z*z) - EARTHA; */
} /* end wgs84r() */
#endif
static int32_t reedsolomon92(uint8_t *buf, uint32_t buf_len)
{
uint32_t i;
int32_t res;
uint8_t b[256];
uint32_t eraspos[24];
for (i = 0UL; i<=255UL; i++) {
b[i] = 0;
} /* end for */
for (i = 0UL; i<=209UL; i++) {
b[230UL-i] = buf[i+6UL];
} /* end for */
for (i = 0UL; i<=23UL; i++) {
b[254UL-i] = buf[i+216UL];
} /* end for */
res = decodersc((char *)b, eraspos, 0L);
if (res>0L && res<=12L) {
for (i = 0UL; i<=209UL; i++) {
buf[i+6UL] = b[230UL-i];
} /* end for */
for (i = 0UL; i<=23UL; i++) {
buf[i+216UL] = b[254UL-i];
} /* end for */
}
return res;
} /* end reedsolomon92() */
void printRaw(uint8_t *data, int len)
{
char buf[3];
int i;
for(i=0; i<len; i++) {
snprintf(buf, 3, "%02X", data[i]);
Serial.print(buf);
}
Serial.println();
}
void RS92::decodeframe92(uint8_t *data)
{
//uint32_t gpstime;
//uint32_t flen;
//uint32_t j;
int32_t corr;
corr = reedsolomon92(data, 301ul);
//int calok;
//int mesok;
//uint32_t calibok;
lastFrame = (dataptr==data1)?1:2;
Serial.printf("rs corr is %d --- data:%p data1:%p data2:%p lastframe=%d\n", corr, data, data1, data2, lastFrame);
dataptr = (dataptr==data1)?data2:data1;
//print_frame(data, 240);
#if 0
/* from sondemod*/
int p=6;
while(1) {
uint8_t typ = data[p];
if(typ==0xff) break;
++p;
int len = ((uint32_t)data[p])*2 + 2;
Serial.printf("type %c: len=%d\n", typ, len);
//printRaw(data+p, len+2);
if(len>240) {
Serial.print("RS92 frame too long: ");
Serial.println(len);
break;
}
++p;
j=0;
uint16_t crc = 0xFFFF;
while(j<len) {
if(j < len-2) {
for(int ic = 0; ic<=7; ic++) {
if (((0x8000&crc)!=0) != ( ((1<<(7-ic))&data[p])!=0 )) {
crc <<= 1;
crc ^= 0x1021;
} else {
crc <<= 1;
}
}
}
++p;
++j;
if(p>240) {
Serial.println("eof");
return;
}
}
if ( (((uint8_t)(crc&0xff)) != data[p-2]) || (((uint8_t)(crc>>8)) != data[p-1])) {
Serial.printf("************ crc error: expected %04x\n",crc);
continue;
}
switch(typ) {
case 'e':
Serial.println("cal ");
//docalib(sf, 256, objname, 9, &contextr9, &mhz, &frameno);
// ...
break;
case 'i':
if(calok && calibok==0xffffffff) {
//domes(sf, 256, &hp, &hyg, &temp)
mesok = 1;
}
break;
case 'g':
Serial.println("gps ");
if(1||calok) {
//dogps(data+p-len, 256, &contextr9, &contextr9.timems, &gpstime);
}
break;
case 'h':
Serial.println("data "); break;
if(data[p+2]!=3) Serial.println("aux ");
// ..
break;
}
}
#endif
} /* end decodeframe92() */
void RS92::printRaw(uint8_t *data, int len)
{
char buf[3];
int i;
for(i=0; i<len; i++) {
snprintf(buf, 3, "%02X", data[i]);
Serial.print(buf);
}
Serial.println();
}
#if 0
// I guess this is old copy&paste stuff from RS41??
int RS92::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len)
{
int i;
for(i=0; i<len*4; i++) {
bytes[i/8] = (bytes[i/8]<<1) | (bits[i]?1:0);
}
bytes[(i-1)/8] &= 0x0F;
}
static unsigned char lookup[16] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };
static uint8_t reverse(uint8_t n) {
return (lookup[n&0x0f] << 4) | lookup[n>>4];
}
static uint8_t scramble[64] = {150U,131U,62U,81U,177U,73U,8U,152U,50U,5U,89U,
14U,249U,68U,198U,38U,33U,96U,194U,234U,121U,93U,109U,161U,
84U,105U,71U,12U,220U,232U,92U,241U,247U,118U,130U,127U,7U,
153U,162U,44U,147U,124U,48U,99U,245U,16U,46U,97U,208U,188U,
180U,182U,6U,170U,244U,35U,120U,110U,59U,174U,191U,123U,76U,
193U};
#endif
void RS92::stobyte92(uint8_t b)
{
dataptr[rxp] = b;
if(rxp>=5 || b=='*') rxp++; else rxp=0;
if(rxp==6) { // header detected
headerDetected = 1;
}
if(rxp>=240) { // frame complete... (240 byte)
rxp=0;
//printRaw(data, 240);
decodeframe92(dataptr);
haveNewFrame = 1;
}
} /* end stobyte92() */
uint32_t rxdata;
bool rxsearching=true;
// search for
// 101001100110011010011010011001100110100110101010100110101001
// 1010011001100110100110100110 0110.0110 1001.1010 1010.1001 1010.1001 => 0x669AA9A9
void RS92::process8N1data(uint8_t dt)
{
for(int i=0; i<8; i++) {
uint8_t d = (dt&0x80)?1:0;
rxdata = (rxdata<<1) | d;
if((rxbitc&1)==1) { rxbyte = (rxbyte>>1) + (d<<9); } // mancester decoded data
dt <<= 1;
//
if(rxsearching) {
if(rxdata == 0x669AA9A9) {
rxsearching = false;
rxbitc = 0;
rxp = 6;
int rssi=sx1278.getRSSI();
int fei=sx1278.getFEI();
int afc=sx1278.getAFC();
Serial.print("Test: RSSI="); Serial.print(rssi);
Serial.print(" FEI="); Serial.print(fei);
Serial.print(" AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
}
} else {
rxbitc = (rxbitc+1)%20;
if(rxbitc == 0) { // got startbit, 8 data bit, stop bit
//Serial.printf("%03x ",rxbyte);
dataptr[rxp++] = (rxbyte>>1)&0xff;
if(rxp==7 && dataptr[6] != 0x65) {
Serial.printf("wrong start: %02x\n",dataptr[6]);
rxsearching = true;
}
if(rxp>=240) {
rxsearching = true;
decodeframe92(dataptr);
haveNewFrame = 1;
}
}
}
}
}
void process8N1dataOrig(uint8_t data)
{
// data contains 8 bits (after mancester encoding; 4 real bit), big endian
for(int i=0; i<4; i++) {
uint8_t d = (data&0x80)?1:0;
data = data << 2;
rxbyte = (rxbyte>>1) + (d<<8);
int maxk = 0;
int max0 = 0;
for(int k = 0; k< 10; k++) {
int n = asynst[k] - asynst[(k+1)%10];
if(abs(n)>abs(max0)) {
max0 = n;
maxk = k;
}
}
//Serial.printf("<%d,%d,%d>",max0,maxk,rxbitc);
if(rxbitc == maxk) {
if(max0<0) { rxbyte = rxbyte ^ 0xFF; }
/////TODO stobyte92( rxbyte&0xff );
}
//Serial.printf("%d:",asynst[rxbitc]);
if(d) {
asynst[rxbitc] += (32767-asynst[rxbitc])/16;
} else {
asynst[rxbitc] -= (32767+asynst[rxbitc])/16;
}
//Serial.printf("%d ",asynst[rxbitc]);
rxbitc = (rxbitc+1) % 10;
}
}
int RS92::receive() {
unsigned long t0 = millis();
Serial.printf("RS92::receive() start at %ld\n",t0);
while( millis() - t0 < 1000 ) {
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
if ( bitRead(value, 7) ) {
Serial.println("FIFO full");
}
if ( bitRead(value, 4) ) {
Serial.println("FIFO overflow");
}
if ( bitRead(value, 2) == 1 ) {
Serial.println("FIFO: ready()");
sx1278.clearIRQFlags();
}
if(bitRead(value, 6) == 0) { // while FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
//Serial.printf("%02x",data);
process8N1data(data);
value = sx1278.readRegister(REG_IRQ_FLAGS2);
} else {
if(headerDetected) {
t0 = millis(); // restart timer... don't time out if header detected...
headerDetected = 0;
}
if(haveNewFrame) {
Serial.printf("RS92::receive(): new frame complete after %ldms\n", millis()-t0);
haveNewFrame = 0;
return RX_OK;
}
delay(2);
}
}
Serial.printf("RS92::receive() timed out\n");
return RX_TIMEOUT; // TODO RX_OK;
}
#define RS92MAXLEN (240)
int RS92::waitRXcomplete() {
// called after complete...
Serial.printf("decoding frame %d\n", lastFrame);
print_frame(lastFrame==1?data1:data2, 240);
SondeInfo *si = sonde.sondeList+rxtask.receiveSonde;
si->lat = gpx.lat;
si->lon = gpx.lon;
si->alt = gpx.alt;
si->vs = gpx.vU;
si->hs = gpx.vH;
si->dir = gpx.vD;
si->validPos = 0x3f;
memcpy(si->id, gpx.id, 9);
memcpy(si->ser, gpx.id, 9);
si->validID = true;
si->frame = gpx.frnr;
si->sats = gpx.k;
si->time = (gpx.gpssec/1000) + 86382 + gpx.week*604800 + 315878400UL;
si->validTime = true;
#if 0
int res=0;
uint32_t t0 = millis();
while( rxtask.receiveResult == 0xFFFF && millis()-t0 < 2000) { delay(20); }
if( rxtask.receiveResult<0 || rxtask.receiveResult==RX_TIMEOUT) {
res = RX_TIMEOUT;
} else if ( rxtask.receiveResult==0) {
res = RX_OK;
} else {
res = RX_ERROR;
}
rxtask.receiveResult = 0xFFFF;
Serial.printf("RS92::waitRXcomplete returning %d (%s)\n", res, RXstr[res]);
return res;
#endif
return 0;
}
#if 0
int oldwaitRXcomplete() {
Serial.println("RS92: receive frame...\n");
sx1278receiveData = true;
delay(6000); // done in other task....
//sx1278receiveData = false;
#if 0
//sx1278.setPayloadLength(518-8); // Expect 320-8 bytes or 518-8 bytes (8 byte header)
//sx1278.setPayloadLength(0); // infinite for now...
////// test code for continuous reception
// sx1278.receive(); /// active FSK RX mode -- already done above...
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
unsigned long previous = millis();
byte ready=0;
uint32_t wait = 8000;
// while not yet done or FIFO not yet empty
// bit 6: FIFO Empty
// bit 2 payload ready
int by=0;
while( (!ready || bitRead(value,6)==0) && (millis() - previous < wait) )
{
if( bitRead(value, 7) ) { Serial.println("FIFO full"); }
if( bitRead(value, 4) ) { Serial.println("FIFO overflow"); }
if( bitRead(value,2)==1 ) ready=1;
if( bitRead(value, 6) == 0 ) { // FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
process8N1data(data);
by++;
#if 0
if(di==1) {
int rssi=getRSSI();
int fei=getFEI();
int afc=getAFC();
Serial.print("Test: RSSI="); Serial.println(rssi);
Serial.print("Test: FEI="); Serial.println(fei);
Serial.print("Test: AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
}
if(di>520) {
// TODO
Serial.println("TOO MUCH DATA");
break;
}
previous = millis(); // reset timeout after receiving data
#endif
}
value = sx1278.readRegister(REG_IRQ_FLAGS2);
}
Serial.printf("processed %d bytes before end/timeout\n", by);
#endif
/////
#if 0
int e = sx1278.receivePacketTimeout(1000, data+8);
if(e) { Serial.println("TIMEOUT"); return RX_TIMEOUT; } //if timeout... return 1
printRaw(data, RS92MAXLEN);
//for(int i=0; i<RS92MAXLEN; i++) { data[i] = reverse(data[i]); }
//printRaw(data, MAXLEN);
//for(int i=0; i<RS92MAXLEN; i++) { data[i] = data[i] ^ scramble[i&0x3F]; }
//printRaw(data, MAXLEN);
//int res = decode41(data, RS92MAXLEN);
#endif
int res=0;
return res==0 ? RX_OK : RX_ERROR;
}
#endif
RS92 rs92 = RS92();

96
libraries/SondeLib/RS92.h Executable file
View File

@ -0,0 +1,96 @@
/*
* RS92.h
* Functions for decoding RS92 sondes with SX127x chips
* Copyright (C) 2019 Hansi Reiser, dl9rdz
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef RS92_h
#define RS92_h
#include <stdlib.h>
#include <stdint.h>
#include <Arduino.h>
#ifndef inttypes_h
#include <inttypes.h>
#endif
struct CONTEXTR9 {
char calibdata[512];
uint32_t calibok;
char mesok;
char posok;
char framesent;
double lat;
double lon;
double heig;
double speed;
double dir;
double climb;
double lastlat;
double laslong;
double lastalt;
double lastspeed;
double lastdir;
double lastclb;
float hrmsc;
float vrmsc;
double hp;
double hyg;
double temp;
double ozontemp;
double ozon;
uint32_t goodsats;
uint32_t timems;
uint32_t framenum;
};
/* Main class */
class RS92
{
private:
void process8N1data(uint8_t data);
void stobyte92(uint8_t byte);
void decodeframe92(uint8_t *data);
#if 0
void dogps(const uint8_t *sf, int sf_len,
struct CONTEXTR9 * cont, uint32_t * timems,
uint32_t * gpstime);
#endif
uint32_t bits2val(const uint8_t *bits, int len);
void printRaw(uint8_t *data, int len);
int bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
int decode92(byte *data, int maxlen);
uint8_t hamming_conf[ 7*8]; // 7*8=56
uint8_t hamming_dat1[13*8]; // 13*8=104
uint8_t hamming_dat2[13*8];
uint8_t block_conf[ 7*4]; // 7*4=28
uint8_t block_dat1[13*4]; // 13*4=52
uint8_t block_dat2[13*4];
uint8_t H[4][8] = // extended Hamming(8,4) particy check matrix
{{ 0, 1, 1, 1, 1, 0, 0, 0},
{ 1, 0, 1, 1, 0, 1, 0, 0},
{ 1, 1, 0, 1, 0, 0, 1, 0},
{ 1, 1, 1, 0, 0, 0, 0, 1}};
uint8_t He[8] = { 0x7, 0xB, 0xD, 0xE, 0x8, 0x4, 0x2, 0x1}; // Spalten von H:
// 1-bit-error-Syndrome
boolean initialized = false;
public:
RS92();
int setup(float frequency);
int receive();
int waitRXcomplete();
int use_ecc = 1;
};
extern RS92 rs92;
#endif

113
libraries/SondeLib/Scanner.cpp Executable file
View File

@ -0,0 +1,113 @@
#include "Scanner.h"
#include <SX1278FSK.h>
#include <U8x8lib.h>
#include "Sonde.h"
#include "Display.h"
#define CHANBW 10
#define PIXSAMPL (50/CHANBW)
#define SMOOTH 3
//#define STARTF 401000000
#define NCHAN ((int)(6000/CHANBW))
double STARTF = (sonde.config.startfreq * 1000000);
//int CHANBW = (sonde.config.channelbw);
//int NCHAN = ((int)(6000/CHANBW));
//int PIXSAMPL = (50/CHANBW);
int scanresult[NCHAN];
int scandisp[NCHAN/PIXSAMPL];
#define PLOT_N 128
#define TICK1 (128/6)
#define TICK2 (TICK1/4)
//#define PLOT_MIN -250
#define PLOT_MIN (sonde.config.noisefloor*2)
#define PLOT_SCALE(x) (x<PLOT_MIN?0:(x-PLOT_MIN)/2)
const byte tilepatterns[9]={0,0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0xFE,0xFF};
void Scanner::fillTiles(uint8_t *row, int value) {
for(int y=0; y<8; y++) {
int nbits = value - 8*(7-y);
if(nbits<0) { row[8*y]=0; continue; }
if(nbits>=8) { row[8*y]=255; continue; }
row[8*y] = tilepatterns[nbits];
}
}
/*
* There are 16*8 columns to plot, NPLOT must be lower than that
* currently, we use 128 * 50kHz channels
* There are 8*8 values to plot; MIN is bottom end,
*/
uint8_t tiles[16] = { 0x0f,0x0f,0x0f,0x0f,0xf0,0xf0,0xf0,0xf0, 1, 3, 7, 15, 31, 63, 127, 255};
void Scanner::plotResult()
{
uint8_t row[8*8];
for(int i=0; i<PLOT_N; i+=8) {
for(int j=0; j<8; j++) {
fillTiles(row+j, PLOT_SCALE(scandisp[i+j]));
if( ((i+j)%TICK1)==0) { row[j] |= 0x07; }
if( ((i+j)%TICK2)==0) { row[j] |= 0x01; }
}
for(int y=0; y<8; y++) {
if(sonde.config.marker && y==1) {
// don't overwrite MHz marker text
if(i<3*8 || (i>=7*8&&i<10*8) || i>=13*8) continue;
}
disp.rdis->drawTile(i/8, y, 1, row+8*y);
}
}
}
void Scanner::scan()
{
// Configure
sx1278.writeRegister(REG_PLL_HOP, 0x80); // FastHopOn
sx1278.setRxBandwidth(CHANBW*1000);
sx1278.writeRegister(REG_RSSI_CONFIG, SMOOTH&0x07);
sx1278.setFrequency(STARTF);
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
delay(20);
unsigned long start = millis();
uint32_t lastfrf=-1;
for(int iter=0; iter<2; iter++) { // two interations, to catch all RS41 transmissions
for(int i=0; i<NCHAN; i++) {
float freq = STARTF + 1000.0*i*CHANBW;
uint32_t frf = freq * 1.0 * (1<<19) / SX127X_CRYSTAL_FREQ;
if( (lastfrf>>16)!=(frf>>16) ) {
sx1278.writeRegister(REG_FRF_MSB, (frf&0xff0000)>>16);
}
if( ((lastfrf&0x00ff00)>>8) != ((frf&0x00ff00)>>8) ) {
sx1278.writeRegister(REG_FRF_MID, (frf&0x00ff00)>>8);
}
sx1278.writeRegister(REG_FRF_LSB, (frf&0x0000ff));
lastfrf = frf;
// Wait TS_HOP (20us) + TS_RSSI ( 2^(SMOOTH+1) / 4 / CHANBW us)
int wait = 20 + 1000*(1<<(SMOOTH+1))/4/CHANBW;
delayMicroseconds(wait+5);
int rssi = -(int)sx1278.readRegister(REG_RSSI_VALUE_FSK);
if(iter==0) { scanresult[i] = rssi; } else {
if(rssi>scanresult[i]) scanresult[i]=rssi;
}
}
}
unsigned long duration = millis()-start;
Serial.print("Scan time: ");
Serial.println(duration);
for(int i=0; i<NCHAN; i+=PIXSAMPL) {
scandisp[i/PIXSAMPL]=scanresult[i];
for(int j=1; j<PIXSAMPL; j++) { scandisp[i/PIXSAMPL]+=scanresult[i+j]; }
//for(int j=1; j<PIXSAMPL; j++) { if(scanresult[i+j]>scandisp[i/PIXSAMPL]) scandisp[i/PIXSAMPL] = scanresult[i+j]; }
Serial.print(scanresult[i]); Serial.print(", ");
}
Serial.println("\n");
for(int i=0; i<NCHAN/PIXSAMPL; i++) {
scandisp[i]/=PIXSAMPL;
Serial.print(scandisp[i]); Serial.print(", ");
}
Serial.println("\n");
}
Scanner scanner = Scanner();

24
libraries/SondeLib/Scanner.h Executable file
View File

@ -0,0 +1,24 @@
#ifndef _SCANNER_H
#define _SCANNER_H
#include <stdlib.h>
#include <stdint.h>
#include <Arduino.h>
#ifndef inttypes_h
#include <inttypes.h>
#endif
class Scanner
{
private:
void fillTiles(uint8_t *row, int value);
public:
void plotResult();
void scan(void);
};
extern Scanner scanner;
#endif

701
libraries/SondeLib/Sonde.cpp Executable file
View File

@ -0,0 +1,701 @@
#include <U8x8lib.h>
#include <U8g2lib.h>
#include "Sonde.h"
#include "RS41.h"
#include "RS92.h"
#include "DFM.h"
#include "M10.h"
#include "M20.h"
#include "SX1278FSK.h"
#include "Display.h"
#include <Wire.h>
extern SX1278FSK sx1278;
RXTask rxtask = { -1, -1, -1, 0xFFFF, 0 };
const char *evstring[]={"NONE", "KEY1S", "KEY1D", "KEY1M", "KEY1L", "KEY2S", "KEY2D", "KEY2M", "KEY2L",
"VIEWTO", "RXTO", "NORXTO", "(max)"};
const char *RXstr[]={"RX_OK", "RX_TIMEOUT", "RX_ERROR", "RX_UNKNOWN"};
int fingerprintValue[]={ 17, 31, 64, 4, 55, 48, 23, 128+23, -1 };
const char *fingerprintText[]={
"TTGO T-Beam (new version 1.0), I2C not working after powerup, assuming 0.9\" OLED@21,22",
"TTGO LORA32 v2.1_1.6 (0.9\" OLED@21,22)",
"TTGO LORA v1.0 (0.9\" OLED@4,15)",
"Heltec v1/v2 (0.9\"OLED@4,15)",
"TTGO T-Beam (old version), 0.9\" OLED@21,22",
"TTGO T-Beam (old version), SPI TFT@4,21,22",
"TTGO T-Beam (new version 1.0), 0.9\" OLED@21,22",
"TTGO T-Beam (new version 1.0), SPI TFT@4,13,14",
};
int getKeyPressEvent(); /* in RX_FSK.ino */
/* Task model:
* There is a background task for all SX1278 interaction.
* - On startup and on each mode/frequency change (requested by setting requestNextSonde
* to an sonde index >=0) it calls Sonde::setup(), which will call the new decoder's
* setup function. Setup will update the value currentSonde.
* - Periodically it calls Sonde::receive(), which calls the current decoder's receive()
* function. It should return control to the SX1278 main loop at least once per second.
* It will also set the internal variable receiveResult. The decoder's receive function
* must make sure that there are no FIFI overflows in the SX1278.
* - the Arduino main loop will call the waitRXcomplete function, which should return as
* soon as there is some new data to display, or no later than after 1s, returning the
* value of receiveResult (or timeout, if receiveResult was not set within 1s). It
* should also return immediately if there is some keyboard input.
*/
int initlevels[40];
Sonde::Sonde() {
for (int i = 0; i < 39; i++) {
initlevels[i] = gpio_get_level((gpio_num_t)i);
}
}
void Sonde::defaultConfig() {
fingerprint = initlevels[4];
fingerprint = (fingerprint<<1) | initlevels[12];
fingerprint = (fingerprint<<1) | initlevels[16];
fingerprint = (fingerprint<<1) | initlevels[17];
fingerprint = (fingerprint<<1) | initlevels[21];
fingerprint = (fingerprint<<1) | initlevels[22];
fingerprint = (fingerprint<<1) | initlevels[23];
Serial.printf("Board fingerprint is %d\n", fingerprint);
sondeList = (SondeInfo *)malloc((MAXSONDE+1)*sizeof(SondeInfo));
memset(sondeList, 0, (MAXSONDE+1)*sizeof(SondeInfo));
config.touch_thresh = 70;
config.led_pout = -1;
config.power_pout = -1;
config.spectrum=10;
// Try autodetecting board type
// Seems like on startup, GPIO4 is 1 on v1 boards, 0 on v2.1 boards?
config.gpsOn=0;
config.gps_rxd = -1;
config.gps_txd = -1;
strcpy(config.gps_lat,"43.591");
strcpy(config.gps_lon,"7.100");
config.gps_alt=123;
config.gps_Lat=0;
config.gps_Lon=0;
config.gps_Alt=0;
config.oled_rst = 16;
config.disptype = 0;
config.oled_orient = 1;
config.button2_axp = 0;
config.norx_timeout = 20;
if(initlevels[16]==0) {
config.oled_sda = 4;
config.oled_scl = 15;
config.button_pin = 0;
config.button2_pin = T4 + 128; // T4 == GPIO13
config.power_pout = 21; // for Heltec v2
config.led_pout = 2;
Serial.println("Autoconfig: looks like TTGO v1 / Heltec v1/V2 board");
} else {
config.oled_sda = 21;
config.oled_scl = 22;
if(initlevels[17]==0) { // T-Beam
if(initlevels[12]==0) { // T-Beam v1.0
Serial.println("Autoconfig: looks like T-Beam 1.0 board");
config.button_pin = 38;
config.button2_pin = 15 + 128; //T4 + 128; // T4 = GPIO13
// Maybe in future use as default only PWR as button2?
//config.button2_pin = 255;
config.button2_axp = 1;
config.gps_rxd = 34;
// Check for I2C-Display@21,22
#define SSD1306_ADDRESS 0x3c
Wire.begin(21, 22);
Wire.beginTransmission(SSD1306_ADDRESS);
byte err = Wire.endTransmission();
delay(100); // otherwise its too fast?!
Wire.beginTransmission(SSD1306_ADDRESS);
err = Wire.endTransmission();
if(err!=0 && fingerprint!=17) { // hmm. 17 after powerup with oled commected and no i2c answer!?!?
fingerprint |= 128;
Serial.println("no I2C display found, assuming large TFT display\n");
// CS=0, RST=14, RS=2, SDA=4, CLK=13
Serial.println("... with large TFT display\n");
config.disptype = 1;
config.oled_sda = 4;
config.oled_scl = 13;
config.oled_rst = 14;
config.spectrum = -1; // no spectrum for now on large display
} else {
// OLED display, pins 21,22 ok...
config.disptype = 0;
Serial.println("... with small OLED display\n");
}
} else {
Serial.println("Autoconfig: looks like T-Beam v0.7 board");
config.button_pin = 39;
config.button2_pin = T4 + 128; // T4 == GPIO13
config.gps_rxd = 12;
// Check if we possibly have a large display
if(initlevels[21]==0) {
Serial.println("Autoconfig: looks like T-Beam v0.7 board with large TFT display");
config.disptype = 1;
config.oled_sda = 4;
config.oled_scl = 21;
config.oled_rst = 22;
config.spectrum = -1; // no spectrum for now on large display
}
}
} else {
config.button_pin = 2 + 128; // GPIO2 / T2
config.button2_pin = 14 + 128; // GPIO14 / T6
config.led_pout = 25;
}
}
//
config.noisefloor = -125;
config.gainLNA=0;
strcpy(config.call,"NOCALL");
strcpy(config.passcode, "---");
strcpy(config.mdnsname, "radiosonde");
strcpy(config.vbatmax,"1.84");
strcpy(config.vbatmin,"1.64");
config.telemetryOn=0;
config.buzzerOn=0;
config.buzzerFreq=700;
config.buzzerPort=12;
config.dbsmetre=0;
config.degdec=0;
config.maxsonde=15;
config.debug=0;
config.wifi=1;
config.wifiap=1;
config.display[0]=0;
config.display[1]=1;
config.display[2]=-1;
config.startfreq=400;
config.channelbw=10;
config.marker=0;
config.showafc=0;
config.freqofs=0;
config.rs41.agcbw=12500;
config.rs41.rxbw=6300;
config.rs92.rxbw=12500;
config.rs92.alt2d=480;
config.dfm.agcbw=20800;
config.dfm.rxbw=10400;
config.udpfeed.active = 1;
config.udpfeed.type = 0;
strcpy(config.udpfeed.host, "fra1od.fr.to");
strcpy(config.udpfeed.symbol, "/O");
config.udpfeed.port = 14580;
config.udpfeed.highrate = 1;
config.udpfeed.idformat = ID_DFMGRAW;
config.tcpfeed.active = 0;
config.tcpfeed.type = 1;
strcpy(config.tcpfeed.host, "radiosondy.info");
strcpy(config.tcpfeed.symbol, "/O");
config.tcpfeed.port = 14580;
config.tcpfeed.highrate = 10;
config.tcpfeed.idformat = ID_DFMDXL;
config.kisstnc.active = 0;
}
void Sonde::setConfig(const char *cfg) {
while(*cfg==' '||*cfg=='\t') cfg++;
if(*cfg=='#') return;
char *s = strchr(cfg,'=');
if(!s) return;
char *val = s+1;
*s=0; s--;
while(s>cfg && (*s==' '||*s=='\t')) { *s=0; s--; }
Serial.printf("configuration option '%s'=%s \n", cfg, val);
if(strcmp(cfg,"noisefloor")==0) {
config.noisefloor = atoi(val);
if(config.noisefloor==0) config.noisefloor=-130;
} else if(strcmp(cfg,"gainLNA")==0) {
config.gainLNA = atoi(val);
} else if(strcmp(cfg,"call")==0) {
strncpy(config.call, val, 9);
config.call[9]=0;
} else if(strcmp(cfg,"passcode")==0) {
strncpy(config.passcode, val, 9);
} else if(strcmp(cfg,"button_pin")==0) {
config.button_pin = atoi(val);
} else if(strcmp(cfg,"button2_pin")==0) {
config.button2_pin = atoi(val);
} else if(strcmp(cfg,"button2_axp")==0) {
config.button2_axp = atoi(val);
} else if(strcmp(cfg,"touch_thresh")==0) {
config.touch_thresh = atoi(val);
} else if(strcmp(cfg,"led_pout")==0) {
config.led_pout = atoi(val);
} else if(strcmp(cfg,"power_pout")==0) {
config.power_pout = atoi(val);
} else if(strcmp(cfg,"disptype")==0) {
config.disptype = atoi(val);
} else if(strcmp(cfg,"oled_sda")==0) {
config.oled_sda = atoi(val);
} else if(strcmp(cfg,"oled_scl")==0) {
config.oled_scl = atoi(val);
} else if(strcmp(cfg,"oled_rst")==0) {
config.oled_rst = atoi(val);
} else if(strcmp(cfg,"oled_orient")==0) {
config.oled_orient = atoi(val);
} else if(strcmp(cfg,"gpsOn")==0) {
config.gpsOn = atoi(val);
} else if(strcmp(cfg,"gps_rxd")==0) {
config.gps_rxd = atoi(val);
} else if(strcmp(cfg,"gps_txd")==0) {
config.gps_txd = atoi(val);
} else if(strcmp(cfg,"gps_lat")==0) {
strncpy(config.gps_lat, val, 8);
} else if(strcmp(cfg,"gps_lon")==0) {
strncpy(config.gps_lon, val, 8);
} else if(strcmp(cfg,"gps_alt")==0) {
config.gps_alt = atoi(val);
} else if(strcmp(cfg,"maxsonde")==0) {
config.maxsonde = atoi(val);
if(config.maxsonde>MAXSONDE) config.maxsonde=MAXSONDE;
} else if(strcmp(cfg,"debug")==0) {
config.debug = atoi(val);
} else if(strcmp(cfg,"wifi")==0) {
config.wifi = atoi(val);
} else if(strcmp(cfg,"wifiap")==0) {
config.wifiap = atoi(val);
} else if(strcmp(cfg,"mdnsname")==0) {
strncpy(config.mdnsname, val, 14);
} else if(strcmp(cfg,"vbatmax")==0) {
strncpy(config.vbatmax,val,5);
} else if(strcmp(cfg,"vbatmin")==0) {
strncpy(config.vbatmin,val,5);
} else if(strcmp(cfg,"telemetryOn")==0) {
config.telemetryOn=atoi(val);
} else if(strcmp(cfg,"buzzerOn")==0) {
config.buzzerOn=atoi(val);
} else if(strcmp(cfg,"buzzerPort")==0) {
config.buzzerPort=atoi(val);
} else if(strcmp(cfg,"buzzerFreq")==0) {
config.buzzerFreq=atoi(val);
} else if(strcmp(cfg,"dbsmetre")==0) {
config.dbsmetre=atoi(val);
} else if(strcmp(cfg,"degdec")==0) {
config.degdec=atoi(val);
} else if(strcmp(cfg,"display")==0) {
int i = 0;
char *ptr;
while(val) {
ptr = strchr(val,',');
if(ptr) *ptr = 0;
config.display[i++] = atoi(val);
val = ptr?ptr+1:NULL;
Serial.printf("appending value %d next is %s\n", config.display[i-1], val?val:"");
}
config.display[i] = -1;
} else if (strcmp(cfg, "norx_timeout")==0) {
config.norx_timeout = atoi(val);
} else if(strcmp(cfg,"startfreq")==0) {
config.startfreq = atoi(val);
} else if(strcmp(cfg,"channelbw")==0) {
config.channelbw = atoi(val);
} else if(strcmp(cfg,"spectrum")==0) {
config.spectrum = atoi(val);
} else if(strcmp(cfg,"marker")==0) {
config.marker = atoi(val);
} else if(strcmp(cfg,"showafc")==0) {
config.showafc = atoi(val);
} else if(strcmp(cfg,"freqofs")==0) {
config.freqofs = atoi(val);
} else if(strcmp(cfg,"rs41.agcbw")==0) {
config.rs41.agcbw = atoi(val);
} else if(strcmp(cfg,"rs41.rxbw")==0) {
config.rs41.rxbw = atoi(val);
} else if(strcmp(cfg,"dfm.agcbw")==0) {
config.dfm.agcbw = atoi(val);
} else if(strcmp(cfg,"dfm.rxbw")==0) {
config.dfm.rxbw = atoi(val);
} else if(strcmp(cfg,"rs92.alt2d")==0) {
config.rs92.alt2d= atoi(val);
} else if(strcmp(cfg,"kisstnc.active")==0) {
config.kisstnc.active = atoi(val);
} else if(strcmp(cfg,"kisstnc.idformat")==0) {
config.kisstnc.idformat = atoi(val);
} else if(strcmp(cfg,"rs92.rxbw")==0) {
config.rs92.rxbw = atoi(val);
} else if(strcmp(cfg,"axudp.active")==0) {
config.udpfeed.active = atoi(val)>0;
} else if(strcmp(cfg,"axudp.host")==0) {
strncpy(config.udpfeed.host, val, 63);
} else if(strcmp(cfg,"axudp.port")==0) {
config.udpfeed.port = atoi(val);
} else if(strcmp(cfg,"axudp.symbol")==0) {
strncpy(config.udpfeed.symbol, val, 3);
} else if(strcmp(cfg,"axudp.highrate")==0) {
config.udpfeed.highrate = atoi(val);
} else if(strcmp(cfg,"axudp.idformat")==0) {
config.udpfeed.idformat = atoi(val);
} else if(strcmp(cfg,"tcp.active")==0) {
config.tcpfeed.active = atoi(val)>0;
} else if(strcmp(cfg,"tcp.host")==0) {
strncpy(config.tcpfeed.host, val, 63);
} else if(strcmp(cfg,"tcp.port")==0) {
config.tcpfeed.port = atoi(val);
} else if(strcmp(cfg,"tcp.symbol")==0) {
strncpy(config.tcpfeed.symbol, val, 3);
} else if(strcmp(cfg,"tcp.highrate")==0) {
config.tcpfeed.highrate = atoi(val);
} else if(strcmp(cfg,"tcp.idformat")==0) {
config.tcpfeed.idformat = atoi(val);
} else {
Serial.printf("Invalid config option '%s'=%s \n", cfg, val);
}
}
void Sonde::setIP(String ip, bool AP) {
ipaddr = ip;
isAP = AP;
}
void Sonde::clearSonde() {
nSonde = 0;
}
void Sonde::addSonde(float frequency, SondeType type, int active, char *launchsite) {
if(nSonde>=config.maxsonde) {
Serial.println("Cannot add another sonde, MAXSONDE reached");
return;
}
Serial.printf("Adding %f - %d - %d - %s\n", frequency, type, active, launchsite);
sondeList[nSonde].type = type;
sondeList[nSonde].freq = frequency;
sondeList[nSonde].active = active;
strncpy(sondeList[nSonde].launchsite, launchsite, 17);
memcpy(sondeList[nSonde].rxStat, "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", 18); // unknown/undefined
nSonde++;
}
// called by updateState (only)
void Sonde::nextConfig() {
currentSonde++;
if(currentSonde>=nSonde) {
currentSonde=0;
}
// Skip non-active entries (but don't loop forever if there are no active ones)
for(int i=0; i<config.maxsonde; i++) {
if(!sondeList[currentSonde].active) {
currentSonde++;
if(currentSonde>=nSonde) currentSonde=0;
}
}
}
void Sonde::nextRxSonde() {
rxtask.currentSonde++;
if(rxtask.currentSonde>=nSonde) {
rxtask.currentSonde=0;
}
for(int i=0; i<config.maxsonde; i++) {
if(!sondeList[rxtask.currentSonde].active) {
rxtask.currentSonde++;
if(rxtask.currentSonde>=nSonde) rxtask.currentSonde=0;
}
}
Serial.printf("nextRxSonde: %d\n", rxtask.currentSonde);
}
void Sonde::nextRxFreq(int addkhz) {
// last entry is for the variable frequency
rxtask.currentSonde = nSonde - 1;
sondeList[rxtask.currentSonde].active = 1;
sondeList[rxtask.currentSonde].freq += addkhz*0.001;
if(sondeList[rxtask.currentSonde].freq>406)
sondeList[rxtask.currentSonde].freq = 400;
Serial.printf("nextRxFreq: %d\n", rxtask.currentSonde);
}
SondeInfo *Sonde::si() {
return &sondeList[currentSonde];
}
void Sonde::setup() {
if(rxtask.currentSonde<0 || rxtask.currentSonde>=config.maxsonde) {
Serial.print("Invalid rxtask.currentSonde: ");
Serial.println(rxtask.currentSonde);
rxtask.currentSonde = 0;
}
// update receiver config
Serial.print("\nSonde::setup() on sonde index ");
Serial.println(rxtask.currentSonde);
switch(sondeList[rxtask.currentSonde].type) {
case STYPE_RS41:
rs41.setup(sondeList[rxtask.currentSonde].freq * 1000000);
break;
case STYPE_DFM06:
case STYPE_DFM09:
case STYPE_DFM:
dfm.setup( sondeList[rxtask.currentSonde].freq * 1000000, sondeList[rxtask.currentSonde].type==STYPE_DFM06?0:1 );
break;
case STYPE_RS92:
rs92.setup( sondeList[rxtask.currentSonde].freq * 1000000);
break;
case STYPE_M10:
m10.setup( sondeList[rxtask.currentSonde].freq * 1000000);
break;
case STYPE_M20:
m20.setup( sondeList[rxtask.currentSonde].freq * 1000000);
break;
}
// debug
float afcbw = sx1278.getAFCBandwidth();
float rxbw = sx1278.getRxBandwidth();
Serial.printf("AFC BW: %f RX BW: %f\n", afcbw, rxbw);
}
extern void flashLed(int ms);
extern void buzzerLed(int temps);
void Sonde::receive() {
uint16_t res = 0;
SondeInfo *si = &sondeList[rxtask.currentSonde];
switch(si->type) {
case STYPE_RS41:
res = rs41.receive();
break;
case STYPE_RS92:
res = rs92.receive();
break;
case STYPE_M10:
res = m10.receive();
break;
case STYPE_M20:
res = m20.receive();
break;
case STYPE_DFM:
case STYPE_DFM06:
case STYPE_DFM09:
res = dfm.receive();
break;
}
// state information for RX_TIMER / NORX_TIMER events
if(res==0) { // RX OK
flashLed(700);
if(sonde.config.buzzerOn==1) {
buzzerLed(500);
}
if(si->lastState != 1) {
si->rxStart = millis();
si->lastState = 1;
}
} else { // RX not ok
if(res==RX_ERROR) {
flashLed(100);
}
Serial.printf("RX result %d, laststate was %d\n", res, si->lastState);
if(si->lastState != 0) {
si->norxStart = millis();
si->lastState = 0;
}
}
// Serial.printf("debug: res was %d, now lastState is %d\n", res, si->lastState);
// we should handle timer events here, because after returning from receive,
// we'll directly enter setup
rxtask.receiveSonde = rxtask.currentSonde; // pass info about decoded sonde to main loop
int event = getKeyPressEvent();
if (!event) event = timeoutEvent(si);
int action = (event==EVT_NONE) ? ACT_NONE : disp.layout->actions[event];
Serial.printf("event %x: action is %x\n", event, action);
// If action is to move to a different sonde index, we do update things here, set activate
// to force the sx1278 task to call sonde.setup(), and pass information about sonde to
// main loop (display update...)
if(action == ACT_NEXTSONDE || action==ACT_PREVSONDE || (action>64&&action<128) ) {
// handled here...
if(action==ACT_NEXTSONDE||action==ACT_PREVSONDE)
nextRxSonde();
else
nextRxFreq( action-64 );
action = ACT_SONDE(rxtask.currentSonde);
if(rxtask.activate==-1) {
// race condition here. maybe better use mutex. TODO
rxtask.activate = action;
}
}
res = (action<<8) | (res&0xff);
Serial.printf("receive Result is %04x\n", res);
// let waitRXcomplete resume...
rxtask.receiveResult = res;
}
// return (action<<8) | (rxresult)
uint16_t Sonde::waitRXcomplete() {
uint16_t res=0;
uint32_t t0 = millis();
rxloop:
while( rxtask.receiveResult==0xFFFF && millis()-t0 < 3000) { delay(50); }
if( rxtask.receiveResult == RX_UPDATERSSI ) {
rxtask.receiveResult = 0xFFFF;
Serial.print("RSSI update: ");
disp.updateDisplayRSSI();
goto rxloop;
}
if( rxtask.receiveResult==0xFFFF) {
Serial.println("TIMEOUT in waitRXcomplete. Should never happen!\n");
res = RX_TIMEOUT;
} else {
res = rxtask.receiveResult;
}
rxtask.receiveResult = 0xFFFF;
/// TODO: THis has caused an exception when swithcing back to spectrumm...
Serial.printf("waitRXcomplete returning %04x (%s)\n", res, (res&0xff)<4?RXstr[res&0xff]:"");
// currently used only by RS92
switch(sondeList[rxtask.receiveSonde].type) {
case STYPE_RS41:
rs41.waitRXcomplete();
break;
case STYPE_RS92:
rs92.waitRXcomplete();
break;
case STYPE_M10:
m10.waitRXcomplete();
break;
case STYPE_M20:
m20.waitRXcomplete();
break;
case STYPE_DFM:
case STYPE_DFM06:
case STYPE_DFM09:
dfm.waitRXcomplete();
break;
}
memmove(sonde.si()->rxStat+1, sonde.si()->rxStat, 17);
sonde.si()->rxStat[0] = res;
return res;
}
uint8_t Sonde::timeoutEvent(SondeInfo *si) {
uint32_t now = millis();
#if 1
Serial.printf("Timeout check: %d - %d vs %d; %d - %d vs %d; %d - %d vs %d\n",
now, si->viewStart, disp.layout->timeouts[0],
now, si->rxStart, disp.layout->timeouts[1],
now, si->norxStart, disp.layout->timeouts[2]);
#endif
Serial.printf("lastState is %d\n", si->lastState);
if(disp.layout->timeouts[0]>=0 && now - si->viewStart >= disp.layout->timeouts[0]) {
Serial.println("View timer expired");
return EVT_VIEWTO;
}
if(si->lastState==1 && disp.layout->timeouts[1]>=0 && now - si->rxStart >= disp.layout->timeouts[1]) {
Serial.println("RX timer expired");
return EVT_RXTO;
}
if(si->lastState==0 && disp.layout->timeouts[2]>=0 && now - si->norxStart >= disp.layout->timeouts[2]) {
Serial.println("NORX timer expired");
return EVT_NORXTO;
}
return 0;
}
uint8_t Sonde::updateState(uint8_t event) {
Serial.printf("Sonde::updateState for event %d\n", event);
// No change
if(event==ACT_NONE) return 0xFF;
// In all cases (new display mode, new sonde) we reset the mode change timers
sonde.sondeList[sonde.currentSonde].viewStart = millis();
sonde.sondeList[sonde.currentSonde].lastState = -1;
// Moving to a different display mode
if (event==ACT_DISPLAY_SPECTRUM || event==ACT_DISPLAY_WIFI) {
// main loop will call setMode() and disable sx1278 background task
return event;
}
int n = event;
if(event==ACT_DISPLAY_DEFAULT) {
n = config.display[1];
} else if(event==ACT_DISPLAY_SCANNER) {
n= config.display[0];
} else if(event==ACT_DISPLAY_NEXT) {
int i;
for(i=0; config.display[i]!=-1; i++) {
if(config.display[i] == disp.layoutIdx) break;
}
if(config.display[i]==-1 || config.display[i+1]==-1) {
//unknown index, or end of list => loop to start
n = config.display[1];
} else {
n = config.display[i+1];
}
}
if(n>=0 && n<ACT_MAXDISPLAY) {
if(n>=disp.nLayouts) {
Serial.println("WARNNG: next layout out of range");
n = config.display[1];
}
Serial.printf("Setting display mode %d\n", n);
disp.setLayout(n);
sonde.clearDisplay();
return 0xFF;
}
// Moving to a different value for currentSonde
// TODO: THis should be done in sx1278 task, not in main loop!!!!!
if(event==ACT_NEXTSONDE) {
sonde.nextConfig();
Serial.printf("advancing to next sonde %d\n", sonde.currentSonde);
return event;
}
if (event==ACT_PREVSONDE) {
// TODO
Serial.printf("previous not supported, advancing to next sonde\n");
sonde.nextConfig();
return ACT_NEXTSONDE;
}
if(event&0x80) {
sonde.currentSonde = (event&0x7F);
return ACT_NEXTSONDE;
}
return 0xFF;
}
void Sonde::updateDisplayPos() {
disp.updateDisplayPos();
}
void Sonde::updateDisplayPos2() {
disp.updateDisplayPos2();
}
void Sonde::updateDisplayID() {
disp.updateDisplayID();
}
void Sonde::updateDisplayRSSI() {
disp.updateDisplayRSSI();
}
void Sonde::updateStat() {
disp.updateStat();
}
void Sonde::updateDisplayRXConfig() {
disp.updateDisplayRXConfig();
}
void Sonde::updateDisplayIP() {
disp.updateDisplayIP();
}
void Sonde::updateDisplay()
{
int t = millis();
disp.updateDisplay();
Serial.printf("updateDisplay took %d ms\n", (int)(millis()-t));
}
void Sonde::clearDisplay() {
disp.rdis->clear();
}
Sonde sonde = Sonde();

273
libraries/SondeLib/Sonde.h Executable file
View File

@ -0,0 +1,273 @@
#ifndef Sonde_h
#define Sonde_h
// RX_TIMEOUT: no header detected
// RX_ERROR: header detected, but data not decoded (crc error, etc.)
// RX_OK: header and data ok
enum RxResult { RX_OK, RX_TIMEOUT, RX_ERROR, RX_UNKNOWN, RX_NOPOS };
#define RX_UPDATERSSI 0xFFFE
// Events that change what is displayed (mode, sondenr)
// Keys:
// 1 Button (short) or Touch (short)
// 2 Button (double) or Touch (double)
// 3 Button (mid) or Touch (mid)
// 4 Button (long) or Touch (long)
// 5 Touch1/2 (short)
// 6 Touch1/2 (double)
// 7 Touch1/2 (mid)
// 8 Touch1/2 (long)
/* Keypress => Sonde++ / Sonde-- / Display:=N*/
enum Events { EVT_NONE, EVT_KEY1SHORT, EVT_KEY1DOUBLE, EVT_KEY1MID, EVT_KEY1LONG,
EVT_KEY2SHORT, EVT_KEY2DOUBLE, EVT_KEY2MID, EVT_KEY2LONG,
EVT_VIEWTO, EVT_RXTO, EVT_NORXTO,
EVT_MAX };
extern const char *evstring[];
extern const char *RXstr[];
#define EVENTNAME(s) evstring[s]
//int8_t actions[EVT_MAX];
#define ACT_NONE 255
#define ACT_DISPLAY(n) (n)
#define ACT_MAXDISPLAY 50
#define ACT_DISPLAY_SCANNER 0
#define ACT_DISPLAY_NEXT 64
#define ACT_DISPLAY_DEFAULT 63
#define ACT_DISPLAY_SPECTRUM 62
#define ACT_DISPLAY_WIFI 61
#define ACT_NEXTSONDE 65
#define ACT_PREVSONDE 66
#define ACT_ADDFREQ(n) ((n)+64)
#define ACT_SONDE(n) ((n)+128)
// 0000nnnn => goto display nnnn
// 01000000 => goto sonde -1
// 01000001 => goto sonde +1
#define NSondeTypes 7
enum SondeType { STYPE_DFM06, STYPE_DFM09, STYPE_RS41, STYPE_RS92, STYPE_M10, STYPE_M20, STYPE_DFM };
extern const char *sondeTypeStr[NSondeTypes];
extern const char sondeTypeChar[NSondeTypes];
typedef struct st_sondeinfo {
// receiver configuration
bool active;
SondeType type;
float freq;
// decoded ID
char typestr[5]; // decoded type (use type if *typestr==0)
char id[10];
char ser[12];
bool validID;
char launchsite[18];
// decoded position
float lat; // latitude
float lon; // longitude
float az; // azimut
float vbat; // vbat %
int durvolheure; // duree vol heure
int durvolminute; // duree vol minute
int durvolseconde; // duree vol seconde
float alt; // altitude
float vs; // vertical speed in m/s
float hs; // horizontal speed in m/s
float dir; // 0..360
uint8_t sats; // number of sats
uint8_t validPos; // bit pattern for validity of above 7 fields; 0x80: position is old
// decoded GPS time
uint32_t time;
uint16_t sec;
uint32_t frame;
bool validTime;
// RSSI from receiver
int rssi; // signal strength
int32_t afc; // afc correction value
// statistics
uint8_t rxStat[20];
uint32_t rxStart; // millis() timestamp of continuous rx start
uint32_t norxStart; // millis() timestamp of continuous no rx start
uint32_t viewStart; // millis() timestamp of viewinf this sonde with current display
int8_t lastState; // -1: disabled; 0: norx; 1: rx
// shut down timers, currently only for RS41; -1=disabled
int16_t launchKT, burstKT, countKT;
uint16_t crefKT; // frame number in which countKT was last sent
} SondeInfo;
// rxStat: 3=undef[empty] 1=timeout[.] 2=errro[E] 0=ok[|] 4=no valid position[°]
// Used for interacting with the RX background task
typedef struct st_RXTask {
// Variables set by Arduino main loop to value >=0 for requesting
// mode change to sonde reception for sonde <value) in RXTask.
// Will be reset to -1 by RXTask
int activate;
// Variables set by RXTask, corresponding to mode ST_DECODER (if active) or something else,
// and currently received sonde
int mainState;
int currentSonde;
// Variable set by RXTask to communicate status to Arduino task
// via waitRXcomplete function
uint16_t receiveResult;
uint16_t receiveSonde; // sonde inde corresponding to receiveResult
// status variabe set by decoder to indicate something is broken
// int fifoOverflow;
} RXTask;
extern RXTask rxtask;
struct st_rs41config {
int agcbw;
int rxbw;
};
struct st_rs92config {
int rxbw;
int alt2d;
};
struct st_dfmconfig {
int agcbw;
int rxbw;
};
enum IDTYPE { ID_DFMDXL, ID_DFMGRAW, ID_DFMAUTO };
struct st_feedinfo {
bool active;
int type; // 0:UDP(axudp), 1:TCP(aprs.fi)
char host[64];
int port;
char symbol[3];
int lowrate;
int highrate;
int lowlimit;
int idformat; // 0: dxl 1: real 2: auto
};
// maybe extend for external Bluetooth interface?
// internal bluetooth consumes too much memory
struct st_kisstnc {
bool active;
int idformat;
};
typedef struct st_rdzconfig {
// hardware configuration
int button_pin; // PIN port number menu button (+128 for touch mode)
int button2_pin; // PIN port number menu button (+128 for touch mode)
int button2_axp; // Use AXP192 power button as button2
int touch_thresh; // Threshold value (0..100) for touch input button
int led_pout; // POUT port number of LED (used as serial monitor)
int power_pout; // Power control pin (for Heltec v2)
int disptype; // 0=OLED; 1=ILI9225
int oled_sda; // OLED data pin
int oled_scl; // OLED clock pin
int oled_rst; // OLED reset pin
int oled_orient; // OLED orientation (default: 1)
int gpsOn; // GPS Active On=1/Off=0
int gps_rxd; // GPS module RXD pin. We expect 9600 baud NMEA data.
int gps_txd; // GPS module TXD pin
char gps_lat[20]; // QTH no gps latitude
char gps_lon[20]; // QTH no gps longitude
int gps_alt; // QTH no gps altitude
float gps_Lat,gps_Lon, gps_Alt; // QTH gps OM lat, lon, alt
// software configuration
int debug; // show port and config options after reboot
int wifi; // connect to known WLAN 0=skip
int wifiap; // enable/disable WiFi AccessPoint mode 0=disable
int8_t display[30]; // list of display mode (0:scanner, 1:default, 2,... additional modes)
int startfreq; // spectrum display start freq (400, 401, ...)
int channelbw; // spectrum channel bandwidth (valid: 5, 10, 20, 25, 50, 100 kHz)
int spectrum; // show freq spectrum for n seconds -1=disable; 0=forever
int marker; // show freq marker in spectrum 0=disable
int maxsonde; // number of max sonde in scan (range=1-99)
int norx_timeout; // Time after which rx mode switches to scan mode (without rx signal)
int noisefloor; // for spectrum display
int gainLNA;
char mdnsname[15]; // mDNS-Name, defaults to radiosonde
char vbatmax[5]; // Vbat maxi when bat charged
char vbatmin[5]; // Vbat minimum discharged
int telemetryOn; // Active Save information telemetry
int buzzerPort; // Buzzer port
int buzzerFreq; // Buzzer Frequency
int buzzerOn; // Buzzer On
int dbsmetre; // Db or Smetre display
int degdec; // Degres or Decimal 0=decimal 1=degres
// receiver configuration
int showafc; // show afc value in rx screen
int freqofs; // frequency offset (tuner config = rx frequency + freqofs) in Hz
struct st_rs41config rs41; // configuration options specific for RS41 receiver
struct st_rs92config rs92;
struct st_dfmconfig dfm;
// data feed configuration
// for now, one feed for each type is enough, but might get extended to more?
char call[10]; // APRS callsign
char passcode[9]; // APRS passcode
struct st_feedinfo udpfeed; // target for AXUDP messages
struct st_feedinfo tcpfeed; // target for APRS-IS TCP connections
struct st_kisstnc kisstnc; // target for KISS TNC (via TCP, mainly for APRSdroid)
} RDZConfig;
#define MAXSONDE 99
extern int fingerprintValue[];
extern const char *fingerprintText[];
class Sonde
{
private:
public:
RDZConfig config;
int fingerprint = 0;
int currentSonde = 0;
int nSonde;
String ipaddr;
bool isAP;
// moved to heap, saving space in .bss
//SondeInfo sondeList[MAXSONDE+1];
SondeInfo *sondeList;
Sonde();
void defaultConfig();
void setConfig(const char *str);
void clearSonde();
void addSonde(float frequency, SondeType type, int active, char *launchsite);
void nextConfig();
void nextRxSonde();
void nextRxFreq(int addkhz);
/* new interface */
void setup();
void receive();
uint16_t waitRXcomplete();
/* old and temp interface */
#if 0
void processRXbyte(uint8_t data);
int receiveFrame();
#endif
SondeInfo *si();
uint8_t timeoutEvent(SondeInfo *si);
uint8_t updateState(uint8_t event);
void updateDisplayPos();
void updateDisplayPos2();
void updateDisplayID();
void updateDisplayRSSI();
void updateDisplayRXConfig();
void updateStat();
void updateDisplayIP();
void updateDisplay();
void clearDisplay();
void setIP(String ip, bool isAP);
};
extern Sonde sonde;
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,468 @@
// SPDX-License-Identifier: GPL-3.0
// original source: https://github.com/Nkawu/TFT22_ILI9225
#ifndef TFT22_ILI9225_h
#define TFT22_ILI9225_h
#ifdef __STM32F1__
#define ARDUINO_STM32_FEATHER
#define PROGMEM
// if 'SPI_CHANNEL' is not defined, 'SPI' is used, only valid for STM32F1
//#define SPI_CHANNEL SPI_2
#endif
#define USE_STRING_CLASS
#ifdef USE_STRING_CLASS
#define STRING String
#else
#define STRING const char *
#endif
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <SPI.h>
#include "gfxfont.h"
#if defined(ARDUINO_STM32_FEATHER) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32F1) || defined(STM32F1)
typedef volatile uint32 RwReg;
#endif
#if defined(ARDUINO_FEATHER52)
typedef volatile uint32_t RwReg;
#endif
/* ILI9225 screen size */
#define ILI9225_LCD_WIDTH 176
#define ILI9225_LCD_HEIGHT 220
/* ILI9225 LCD Registers */
#define ILI9225_DRIVER_OUTPUT_CTRL (0x01u) // Driver Output Control
#define ILI9225_LCD_AC_DRIVING_CTRL (0x02u) // LCD AC Driving Control
#define ILI9225_ENTRY_MODE (0x03u) // Entry Mode
#define ILI9225_DISP_CTRL1 (0x07u) // Display Control 1
#define ILI9225_BLANK_PERIOD_CTRL1 (0x08u) // Blank Period Control
#define ILI9225_FRAME_CYCLE_CTRL (0x0Bu) // Frame Cycle Control
#define ILI9225_INTERFACE_CTRL (0x0Cu) // Interface Control
#define ILI9225_OSC_CTRL (0x0Fu) // Osc Control
#define ILI9225_POWER_CTRL1 (0x10u) // Power Control 1
#define ILI9225_POWER_CTRL2 (0x11u) // Power Control 2
#define ILI9225_POWER_CTRL3 (0x12u) // Power Control 3
#define ILI9225_POWER_CTRL4 (0x13u) // Power Control 4
#define ILI9225_POWER_CTRL5 (0x14u) // Power Control 5
#define ILI9225_VCI_RECYCLING (0x15u) // VCI Recycling
#define ILI9225_RAM_ADDR_SET1 (0x20u) // Horizontal GRAM Address Set
#define ILI9225_RAM_ADDR_SET2 (0x21u) // Vertical GRAM Address Set
#define ILI9225_GRAM_DATA_REG (0x22u) // GRAM Data Register
#define ILI9225_GATE_SCAN_CTRL (0x30u) // Gate Scan Control Register
#define ILI9225_VERTICAL_SCROLL_CTRL1 (0x31u) // Vertical Scroll Control 1 Register
#define ILI9225_VERTICAL_SCROLL_CTRL2 (0x32u) // Vertical Scroll Control 2 Register
#define ILI9225_VERTICAL_SCROLL_CTRL3 (0x33u) // Vertical Scroll Control 3 Register
#define ILI9225_PARTIAL_DRIVING_POS1 (0x34u) // Partial Driving Position 1 Register
#define ILI9225_PARTIAL_DRIVING_POS2 (0x35u) // Partial Driving Position 2 Register
#define ILI9225_HORIZONTAL_WINDOW_ADDR1 (0x36u) // Horizontal Address Start Position
#define ILI9225_HORIZONTAL_WINDOW_ADDR2 (0x37u) // Horizontal Address End Position
#define ILI9225_VERTICAL_WINDOW_ADDR1 (0x38u) // Vertical Address Start Position
#define ILI9225_VERTICAL_WINDOW_ADDR2 (0x39u) // Vertical Address End Position
#define ILI9225_GAMMA_CTRL1 (0x50u) // Gamma Control 1
#define ILI9225_GAMMA_CTRL2 (0x51u) // Gamma Control 2
#define ILI9225_GAMMA_CTRL3 (0x52u) // Gamma Control 3
#define ILI9225_GAMMA_CTRL4 (0x53u) // Gamma Control 4
#define ILI9225_GAMMA_CTRL5 (0x54u) // Gamma Control 5
#define ILI9225_GAMMA_CTRL6 (0x55u) // Gamma Control 6
#define ILI9225_GAMMA_CTRL7 (0x56u) // Gamma Control 7
#define ILI9225_GAMMA_CTRL8 (0x57u) // Gamma Control 8
#define ILI9225_GAMMA_CTRL9 (0x58u) // Gamma Control 9
#define ILI9225_GAMMA_CTRL10 (0x59u) // Gamma Control 10
#define ILI9225C_INVOFF 0x20
#define ILI9225C_INVON 0x21
// autoincrement modes (register ILI9225_ENTRY_MODE, bit 5..3 )
enum autoIncMode_t { R2L_BottomUp, BottomUp_R2L, L2R_BottomUp, BottomUp_L2R, R2L_TopDown, TopDown_R2L, L2R_TopDown, TopDown_L2R };
/* RGB 16-bit color table definition (RG565) */
#define COLOR_BLACK 0x0000 /* 0, 0, 0 */
#define COLOR_WHITE 0xFFFF /* 255, 255, 255 */
#define COLOR_BLUE 0x001F /* 0, 0, 255 */
#define COLOR_GREEN 0x07E0 /* 0, 255, 0 */
#define COLOR_RED 0xF800 /* 255, 0, 0 */
#define COLOR_NAVY 0x000F /* 0, 0, 128 */
#define COLOR_DARKBLUE 0x0011 /* 0, 0, 139 */
#define COLOR_DARKGREEN 0x03E0 /* 0, 128, 0 */
#define COLOR_DARKCYAN 0x03EF /* 0, 128, 128 */
#define COLOR_CYAN 0x07FF /* 0, 255, 255 */
#define COLOR_TURQUOISE 0x471A /* 64, 224, 208 */
#define COLOR_INDIGO 0x4810 /* 75, 0, 130 */
#define COLOR_DARKRED 0x8000 /* 128, 0, 0 */
#define COLOR_OLIVE 0x7BE0 /* 128, 128, 0 */
#define COLOR_GRAY 0x8410 /* 128, 128, 128 */
#define COLOR_GREY 0x8410 /* 128, 128, 128 */
#define COLOR_SKYBLUE 0x867D /* 135, 206, 235 */
#define COLOR_BLUEVIOLET 0x895C /* 138, 43, 226 */
#define COLOR_LIGHTGREEN 0x9772 /* 144, 238, 144 */
#define COLOR_DARKVIOLET 0x901A /* 148, 0, 211 */
#define COLOR_YELLOWGREEN 0x9E66 /* 154, 205, 50 */
#define COLOR_BROWN 0xA145 /* 165, 42, 42 */
#define COLOR_DARKGRAY 0x7BEF /* 128, 128, 128 */
#define COLOR_DARKGREY 0x7BEF /* 128, 128, 128 */
#define COLOR_SIENNA 0xA285 /* 160, 82, 45 */
#define COLOR_LIGHTBLUE 0xAEDC /* 172, 216, 230 */
#define COLOR_GREENYELLOW 0xAFE5 /* 173, 255, 47 */
#define COLOR_SILVER 0xC618 /* 192, 192, 192 */
#define COLOR_LIGHTGRAY 0xC618 /* 192, 192, 192 */
#define COLOR_LIGHTGREY 0xC618 /* 192, 192, 192 */
#define COLOR_LIGHTCYAN 0xE7FF /* 224, 255, 255 */
#define COLOR_VIOLET 0xEC1D /* 238, 130, 238 */
#define COLOR_AZUR 0xF7FF /* 240, 255, 255 */
#define COLOR_BEIGE 0xF7BB /* 245, 245, 220 */
#define COLOR_MAGENTA 0xF81F /* 255, 0, 255 */
#define COLOR_TOMATO 0xFB08 /* 255, 99, 71 */
#define COLOR_GOLD 0xFEA0 /* 255, 215, 0 */
#define COLOR_ORANGE 0xFD20 /* 255, 165, 0 */
#define COLOR_SNOW 0xFFDF /* 255, 250, 250 */
#define COLOR_YELLOW 0xFFE0 /* 255, 255, 0 */
/* Font defines */
#define FONT_HEADER_SIZE 4 // 1: pixel width of 1 font character, 2: pixel height,
#define readFontByte(x) pgm_read_byte(&cfont.font[x])
extern uint8_t Terminal6x8[];
extern uint8_t Terminal11x16[];
extern uint8_t Terminal12x16[];
extern uint8_t Trebuchet_MS16x21[];
struct _currentFont
{
uint8_t* font;
uint8_t width;
uint8_t height;
uint8_t offset;
uint8_t numchars;
uint8_t nbrows;
bool monoSp;
};
#define MONOSPACE 1
#if defined (ARDUINO_STM32_FEATHER)
#undef USE_FAST_PINIO
#elif defined (__AVR__) || defined(TEENSYDUINO) || defined(ESP8266) || defined(__arm__)
#define USE_FAST_PINIO
#endif
/// Main and core class
class TFT22_ILI9225 {
public:
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t SDI, int8_t CLK, int8_t LED);
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t LED);
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t SDI, int8_t CLK, int8_t LED, uint8_t brightness);
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t LED, uint8_t brightness);
/// Initialization
#ifndef ESP32
void begin(void);
#else
void begin(SPIClass &spi=SPI);
#endif
/// Clear the screen
void clear(void);
/// Invert screen
/// @param flag true to invert, false for normal screen
void invert(boolean flag);
/// Switch backlight on or off
/// @param flag true=on, false=off
void setBacklight(boolean flag);
/// Set backlight brightness
/// @param brightness sets backlight brightness 0-255
void setBacklightBrightness(uint8_t brightness);
/// Switch display on or off
/// @param flag true=on, false=off
void setDisplay(boolean flag);
/// Set orientation
/// @param orientation orientation, 0=portrait, 1=right rotated landscape, 2=reverse portrait, 3=left rotated landscape
void setOrientation(uint8_t orientation);
/// Get orientation
/// @return orientation orientation, 0=portrait, 1=right rotated landscape, 2=reverse portrait, 3=left rotated landscape
uint8_t getOrientation(void);
/// Font size, x-axis
/// @return horizontal size of current font, in pixels
// uint8_t fontX(void);
/// Font size, y-axis
/// @return vertical size of current font, in pixels
// uint8_t fontY(void);
/// Screen size, x-axis
/// @return horizontal size of the screen, in pixels
/// @note 240 means 240 pixels and thus 0..239 coordinates (decimal)
uint16_t maxX(void);
/// Screen size, y-axis
/// @return vertical size of the screen, in pixels
/// @note 220 means 220 pixels and thus 0..219 coordinates (decimal)
uint16_t maxY(void);
/// Draw circle
/// @param x0 center, point coordinate, x-axis
/// @param y0 center, point coordinate, y-axis
/// @param radius radius
/// @param color 16-bit color
void drawCircle(uint16_t x0, uint16_t y0, uint16_t radius, uint16_t color);
/// Draw solid circle
/// @param x0 center, point coordinate, x-axis
/// @param y0 center, point coordinate, y-axis
/// @param radius radius
/// @param color 16-bit color
void fillCircle(uint8_t x0, uint8_t y0, uint8_t radius, uint16_t color);
/// Set background color
/// @param color background color, default=black
void setBackgroundColor(uint16_t color = COLOR_BLACK);
/// Draw line, rectangle coordinates
/// @param x1 start point coordinate, x-axis
/// @param y1 start point coordinate, y-axis
/// @param x2 end point coordinate, x-axis
/// @param y2 end point coordinate, y-axis
/// @param color 16-bit color
void drawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
/// Draw rectangle, rectangle coordinates
/// @param x1 top left coordinate, x-axis
/// @param y1 top left coordinate, y-axis
/// @param x2 bottom right coordinate, x-axis
/// @param y2 bottom right coordinate, y-axis
/// @param color 16-bit color
void drawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
/// Draw solid rectangle, rectangle coordinates
/// @param x1 top left coordinate, x-axis
/// @param y1 top left coordinate, y-axis
/// @param x2 bottom right coordinate, x-axis
/// @param y2 bottom right coordinate, y-axis
/// @param color 16-bit color
void fillRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
/// Draw pixel
/// @param x1 point coordinate, x-axis
/// @param y1 point coordinate, y-axis
/// @param color 16-bit color
void drawPixel(uint16_t x1, uint16_t y1, uint16_t color);
/// Draw ASCII Text (pixel coordinates)
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param s text string
/// @param color 16-bit color, default=white
/// @return x-position behind text
uint16_t drawText(uint16_t x, uint16_t y, STRING s, uint16_t color = COLOR_WHITE);
/// width of an ASCII Text (pixel )
/// @param s text string
uint16_t getTextWidth( STRING s ) ;
/// Calculate 16-bit color from 8-bit Red-Green-Blue components
/// @param red red component, 0x00..0xff
/// @param green green component, 0x00..0xff
/// @param blue blue component, 0x00..0xff
/// @return 16-bit color
uint16_t setColor(uint8_t red, uint8_t green, uint8_t blue);
/// Calculate 8-bit Red-Green-Blue components from 16-bit color
/// @param rgb 16-bit color
/// @param red red component, 0x00..0xff
/// @param green green component, 0x00..0xff
/// @param blue blue component, 0x00..0xff
void splitColor(uint16_t rgb, uint8_t &red, uint8_t &green, uint8_t &blue);
/// Draw triangle, triangle coordinates
/// @param x1 corner 1 coordinate, x-axis
/// @param y1 corner 1 coordinate, y-axis
/// @param x2 corner 2 coordinate, x-axis
/// @param y2 corner 2 coordinate, y-axis
/// @param x3 corner 3 coordinate, x-axis
/// @param y3 corner 3 coordinate, y-axis
/// @param color 16-bit color
void drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color);
/// Draw solid triangle, triangle coordinates
/// @param x1 corner 1 coordinate, x-axis
/// @param y1 corner 1 coordinate, y-axis
/// @param x2 corner 2 coordinate, x-axis
/// @param y2 corner 2 coordinate, y-axis
/// @param x3 corner 3 coordinate, x-axis
/// @param y3 corner 3 coordinate, y-axis
/// @param color 16-bit color
void fillTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color);
/// Set current font
/// @param font Font name
void setFont(uint8_t* font, bool monoSp=false ); // default = proportional
/// Get current font
_currentFont getFont();
/// Draw single character (pixel coordinates)
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param ch ASCII character
/// @param color 16-bit color, default=white
/// @return width of character in display pixels
uint16_t drawChar(uint16_t x, uint16_t y, uint16_t ch, uint16_t color = COLOR_WHITE);
/// width of an ASCII character (pixel )
/// @param ch ASCII character
uint16_t getCharWidth( uint16_t ch ) ;
/// Draw bitmap
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param bitmap
/// @param w width
/// @param h height
/// @param color 16-bit color, default=white
/// @param bg 16-bit color, background
void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);
void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg);
void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);
void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg);
void drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);
void drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg);
/// Draw bitmap
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param bitmap, 2D 16bit color bitmap
/// @param w width
/// @param h height
void drawBitmap(uint16_t x, uint16_t y, const uint16_t** bitmap, int16_t w, int16_t h);
void drawBitmap(uint16_t x, uint16_t y, uint16_t** bitmap, int16_t w, int16_t h);
/// Draw bitmap
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param bitmap, 1D 16bit color bitmap
/// @param w width
/// @param h height
void drawBitmap(uint16_t x, uint16_t y, const uint16_t* bitmap, int16_t w, int16_t h);
void drawBitmap(uint16_t x, uint16_t y, uint16_t* bitmap, int16_t w, int16_t h);
/// Set current GFX font
/// @param f GFX font name defined in include file
void setGFXFont(const GFXfont *f = NULL);
/// Draw a string with the current GFX font
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param s string to print
/// @param color 16-bit color
void drawGFXText(int16_t x, int16_t y, STRING s, uint16_t color);
/// Get the width & height of a text string with the current GFX font
/// @param str string to analyze
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param w width in pixels of string
/// @param h height in pixels of string
void getGFXTextExtent(STRING str, int16_t x, int16_t y, int16_t *w, int16_t *h);
/// Draw a single character with the current GFX font
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param c character to draw
/// @param color 16-bit color
/// @return width of character in display pixels
uint16_t drawGFXChar(int16_t x, int16_t y, unsigned char c, uint16_t color);
uint16_t drawGFXcharBM(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t *bm, int bmwd);
void getGFXCharExtent(uint8_t c, int16_t *gw, int16_t *gh, int16_t *xa);
private:
void _spiWrite(uint8_t v);
void _spiWrite16(uint16_t v);
void _spiWriteCommand(uint8_t c);
void _spiWriteData(uint8_t d);
void _swap(uint16_t &a, uint16_t &b);
void _setWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void _setWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, autoIncMode_t mode);
void _resetWindow();
void _drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h,
uint16_t color, uint16_t bg, bool transparent, bool progmem, bool Xbit );
void _orientCoordinates(uint16_t &x1, uint16_t &y1);
void _writeRegister(uint16_t reg, uint16_t data);
void _writeData(uint8_t HI, uint8_t LO);
void _writeData16(uint16_t HILO);
void _writeCommand(uint8_t HI, uint8_t LO);
void _writeCommand16(uint16_t HILO);
uint16_t _maxX, _maxY, _bgColor;
#if defined (__AVR__) || defined(TEENSYDUINO)
int8_t _rst, _rs, _cs, _sdi, _clk, _led;
#ifdef USE_FAST_PINIO
volatile uint8_t *mosiport, *clkport, *dcport, *rsport, *csport;
uint8_t mosipinmask, clkpinmask, cspinmask, dcpinmask;
#endif
#elif defined (__arm__)
int32_t _rst, _rs, _cs, _sdi, _clk, _led;
#ifdef USE_FAST_PINIO
volatile RwReg *mosiport, *clkport, *dcport, *rsport, *csport;
uint32_t mosipinmask, clkpinmask, cspinmask, dcpinmask;
#endif
#elif defined (ESP8266) || defined (ESP32)
int8_t _rst, _rs, _cs, _sdi, _clk, _led;
#ifdef USE_FAST_PINIO
volatile uint32_t *mosiport, *clkport, *dcport, *rsport, *csport;
uint32_t mosipinmask, clkpinmask, cspinmask, dcpinmask;
#endif
#else
int8_t _rst, _rs, _cs, _sdi, _clk, _led;
#endif
uint8_t _orientation, _brightness;
// correspondig modes if orientation changed:
const autoIncMode_t modeTab [3][8] = {
// { R2L_BottomUp, BottomUp_R2L, L2R_BottomUp, BottomUp_L2R, R2L_TopDown, TopDown_R2L, L2R_TopDown, TopDown_L2R }//
/* 90° */ { BottomUp_L2R, L2R_BottomUp, TopDown_L2R, L2R_TopDown, BottomUp_R2L, R2L_BottomUp, TopDown_R2L, R2L_TopDown },
/*180° */ { L2R_TopDown , TopDown_L2R, R2L_TopDown, TopDown_R2L, L2R_BottomUp, BottomUp_L2R, R2L_BottomUp, BottomUp_R2L},
/*270° */ { TopDown_R2L , R2L_TopDown, BottomUp_R2L, R2L_BottomUp, TopDown_L2R, L2R_TopDown, BottomUp_L2R, L2R_BottomUp}
};
bool hwSPI, blState;
_currentFont cfont;
#ifdef ESP32
SPIClass _spi;
#endif
protected:
uint32_t writeFunctionLevel;
void startWrite(void);
void endWrite(void);
GFXfont *gfxFont;
};
#endif

350
libraries/SondeLib/aprs.cpp Executable file
View File

@ -0,0 +1,350 @@
/* Copyright (C) Hansi Reiser, dl9rdz
*
* partially based on dxlAPRS toolchain
*
* Copyright (C) Christian Rabler <oe5dxl@oevsv.at>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <stdio.h>
#include <WString.h>
#include <stdlib.h>
//#include <arpa/inet.h>
//#include <sys/socket.h>
#include <math.h>
#include <unistd.h>
#include <inttypes.h>
#include "aprs.h"
#if 0
int openudp(const char *ip, int port, struct sockaddr_in *si) {
int fd;
if((fd=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) return -1;
memset((char *)&si, 0, sizeof(si));
si->sin_family = AF_INET;
si->sin_port = htons(port);
if(inet_aton(ip, &(si->sin_addr))==0) {
return -1;
}
return fd;
}
int sendudp(int fd, struct sockaddr_in *si, char *frame, int framelen)
{
if(sendto(fd, frame, framelen, 0, (struct sockaddr *)si, sizeof(struct sockaddr_in))==-1) {
return -1;
}
return 0;
}
#endif
void aprsstr_append(char *b, const char *data)
{
int blen=strlen(b);
int len=strlen(data);
if(blen+len>APRS_MAXLEN) len=APRS_MAXLEN-blen;
strncat(b, data, len);
}
uint32_t realcard(float x) {
if(x<0) return 0;
else return (uint32_t)x;
}
/* CRC for AXUDP frames */
#define APRSCRC_POLY 0x8408
static uint8_t CRCL[256];
static uint8_t CRCH[256];
void aprs_gencrctab(void)
{
uint32_t c;
uint32_t crc;
uint32_t i;
for (c = 0UL; c<=255UL; c++) {
crc = 255UL-c;
for (i = 0UL; i<=7UL; i++) {
if ((crc&1)) crc = (uint32_t)((uint32_t)(crc>>1)^APRSCRC_POLY);
else crc = crc>>1;
} /* end for */
CRCL[c] = (uint8_t)crc;
CRCH[c] = (uint8_t)(255UL-(crc>>8));
} /* end for */
} /* end Gencrctab() */
static void aprsstr_appcrc(char frame[], uint32_t frame_len, int32_t size)
{
uint8_t h;
uint8_t l;
uint8_t b;
int32_t i;
int32_t tmp;
l = 0U;
h = 0U;
tmp = size-1L;
i = 0L;
if (i<=tmp) for (;; i++) {
b = (uint8_t)((uint8_t)(uint8_t)frame[i]^l);
l = CRCL[b]^h;
h = CRCH[b];
if (i==tmp) break;
} /* end for */
frame[size] = (char)l;
frame[size+1L] = (char)h;
} /* end aprsstr_appcrc() */
static int mkaprscall(int32_t * p, char raw[],
uint32_t * i, const char mon[],
char sep1, char sep2, char sep3,
uint32_t sbase)
{
uint32_t s;
uint32_t l;
l = 0UL;
while ((((mon[*i] && mon[*i]!=sep1) && mon[*i]!=sep2) && mon[*i]!=sep3)
&& mon[*i]!='-') {
s = (uint32_t)(uint8_t)mon[*i]*2UL&255UL;
if (s<=64UL) return 0;
raw[*p] = (char)s;
++*p;
++*i;
++l;
if (l>=7UL) return 0;
}
while (l<6UL) {
raw[*p] = '@';
++*p;
++l;
}
s = 0UL;
if (mon[*i]=='-') {
++*i;
while ((uint8_t)mon[*i]>='0' && (uint8_t)mon[*i]<='9') {
s = (s*10UL+(uint32_t)(uint8_t)mon[*i])-48UL;
++*i;
}
if (s>15UL) return 0;
}
raw[*p] = (char)((s+sbase)*2UL);
++*p;
return 1;
} /* end call() */
// returns raw len, 0 in case of error
extern int aprsstr_mon2raw(const char *mon, char raw[], int raw_len)
{
uint32_t r;
uint32_t n;
uint32_t i;
uint32_t tmp;
int p = 7L;
i = 0UL;
fprintf(stderr,"mon2raw for %s\n", mon);
if (!mkaprscall(&p, raw, &i, mon, '>', 0, 0, 48UL)) {
return 0;
}
p = 0L;
if (mon[i]!='>') return 0;
/* ">" */
++i;
if (!mkaprscall(&p, raw, &i, mon, ':', ',', 0, 112UL)) {
return 0;
}
p = 14L;
n = 0UL;
while (mon[i]==',') {
++i;
if (!mkaprscall(&p, raw, &i, mon, ':', ',', '*', 48UL)) {
return 0;
}
++n;
if (n>8UL) {
return 0;
}
if (mon[i]=='*') {
/* "*" has repeatet sign */
++i;
r = (uint32_t)p;
if (r>=21UL) for (tmp = (uint32_t)(r-21UL)/7UL;;) {
raw[r-1UL] = (char)((uint32_t)(uint8_t)raw[r-1UL]+128UL);
/* set "has repeated" flags */
if (!tmp) break;
--tmp;
r -= 7UL;
} /* end for */
}
}
if (p==0L || mon[i]!=':') {
return 0;
}
raw[p-1L] = (char)((uint32_t)(uint8_t)raw[p-1L]+1UL);
/* end address field mark */
raw[p] = '\003';
++p;
raw[p] = '\360';
++p;
++i;
n = 256UL;
while (mon[i]) {
/* copy info part */
if (p>=(int32_t)(raw_len-1)-2L || n==0UL) {
return 0;
}
raw[p] = mon[i];
++p;
++i;
--n;
}
aprsstr_appcrc(raw, raw_len, p);
fprintf(stderr,"results in %s\n",raw);
return p+2;
} /* end mon2raw() */
extern int aprsstr_mon2kiss(const char *mon, char raw[], int raw_len)
{
char tmp[201];
int len = aprsstr_mon2raw(mon, tmp, 201);
if(len==0) return 0;
int idx=0;
raw[idx++] = '\xC0';
for(int i=0; i<len-2; i++) { // -2: discard CRC, not used in KISS
if(tmp[i]=='\xC0') {
raw[idx++] = '\xDB';
raw[idx++] = '\xDC';
} else if (tmp[i]=='\xDB') {
raw[idx++] = '\xDB';
raw[idx++] = '\xDD';
} else {
raw[idx++] = tmp[i];
}
if(idx>=raw_len)
return 0;
}
return idx;
}
#define FEET (1.0/0.3048)
#define KNOTS (1.851984)
#define X2C_max_longcard 0xFFFFFFFFUL
static uint32_t X2C_TRUNCC(double x, uint32_t min0, uint32_t max0)
{
uint32_t i;
if (x < (double)min0)
i = (uint32_t)min0;
if (x > (double)max0)
i = (uint32_t)max0;
i = (uint32_t)x;
if ((double)i > x)
--i;
return i;
}
static uint32_t truncc(double r)
{
if (r<=0.0) return 0UL;
else if (r>=2.E+9) return 2000000000UL;
else return (uint32_t)X2C_TRUNCC(r,0UL,X2C_max_longcard);
return 0;
} /* end truncc() */
static uint32_t dao91(double x)
/* radix91(xx/1.1) of dddmm.mmxx */
{
double a;
a = fabs(x);
return ((truncc((a-(double)(float)truncc(a))*6.E+5)%100UL)
*20UL+11UL)/22UL;
} /* end dao91() */
char b[201];
char raw[201];
char *aprs_senddata(SondeInfo *s, const char *usercall, const char *sym) {
// float lat, float lon, float alt, float speed, float dir, float climb, const char *type, const char *objname, const char *usercall, const char *sym, const char *comm)
*b=0;
aprsstr_append(b, usercall);
aprsstr_append(b, ">");
const char *destcall="APZRDZ";
aprsstr_append(b, destcall);
// uncompressed
aprsstr_append(b, ":;");
char tmp[10];
snprintf(tmp,10,"%s ",s->id);
aprsstr_append(b, tmp);
aprsstr_append(b, "*");
// time
int i = strlen(b);
int sec = s->time % 86400;
snprintf(b+i, APRS_MAXLEN-1, "%02d%02d%02dz", sec/(60*60), (sec%(60*60))/60, sec%60);
i = strlen(b);
//aprsstr_append_data(time, ds);
int lati = abs((int)s->lat);
int latm = (fabs(s->lat)-lati)*6000;
snprintf(b+i, APRS_MAXLEN-i, "%02d%02d.%02d%c%c", lati, latm/100, latm%100, s->lat<0?'S':'N', sym[0]);
i = strlen(b);
int loni = abs((int)s->lon);
int lonm = (fabs(s->lon)-loni)*6000;
snprintf(b+i, APRS_MAXLEN-i, "%03d%02d.%02d%c%c", loni, lonm/100, lonm%100, s->lon<0?'W':'E', sym[1]);
if(s->hs>0.5) {
i=strlen(b);
snprintf(b+i, APRS_MAXLEN-i, "%03d/%03d", realcard(s->dir+1.5), realcard(s->hs*1.0/KNOTS+0.5));
}
if(s->alt>0.5) {
i=strlen(b);
snprintf(b+i, APRS_MAXLEN-i, "/A=%06d", realcard(s->alt*FEET+0.5));
}
int dao=1;
if(dao) {
i=strlen(b);
snprintf(b+i, APRS_MAXLEN-i, "!w%c%c!", 33+dao91(s->lat), 33+dao91(s->lon));
}
strcat(b, "&");
char comm[100];
snprintf(comm, 100, "Clb=%.1fm/s %.3fMHz Type=%s", s->vs, s->freq, sondeTypeStr[s->type]);
strcat(b, comm);
if(s->type==STYPE_M20||s->type==STYPE_M10||s->type==STYPE_DFM||s->type==STYPE_DFM06||s->type==STYPE_DFM09) {
snprintf(comm, 100, " ser=%s", s->ser);
strcat(b, comm);
}
return b;
}
#if 0
int main(int argc, char *argv[])
{
Gencrctab();
struct sockaddr_in si;
int fd = openudp("127.0.0.1",9002,&si);
if(fd<0) { fprintf(stderr,"open failed\n"); return 1; }
float lat=48, lon=10;
while(1) {
const char *str = aprs_senddata(lat, lon, 543, 5, 180, 1.5, "RS41", "TE0ST", "TE1ST", "EO");
int rawlen = aprsstr_mon2raw(str, raw, APRS_MAXLEN);
sendudp(fd, raw, rawlen);
str = "OE3XKC>APMI06,qAR,OE3XLR:;ER-341109*111111z4803.61NE01532.39E0145.650MHz R15k OE3XPA";
rawlen = aprsstr_mon2raw(str, raw, APRS_MAXLEN);
sendudp(fd, &si, raw, rawlen);
lat += 0.002; lon += 0.01;
sleep(5);
}
}
#endif

14
libraries/SondeLib/aprs.h Executable file
View File

@ -0,0 +1,14 @@
#ifndef _aprs_h
#define _aprs_h
#include "Sonde.h"
#define APRS_MAXLEN 201
void aprs_gencrctab(void);
int aprsstr_mon2raw(const char *mon, char raw[], int raw_len);
int aprsstr_mon2kiss(const char *mon, char raw[], int raw_len);
char *aprs_senddata(SondeInfo *s, const char *usercall, const char *sym);
#endif

View File

@ -0,0 +1,68 @@
Heltec board v1 => fingerprint 0000100 => 4
(sda,scl: 4,15) (same as LORA v1.0)
0:1 1:0 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:0 16:0 17:0 18:0 19:0 20:0 21:1 22:0 23:0 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
Heltec board v2
(sda,scl: 4,15) (similar to v1.0, but GPIO21 switches 3V3) => fingerprint 4
0:1 1:0 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:0 16:0 17:0 18:0 19:0 20:0 21:1 22:0 23:0 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:1 1:1 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:0 16:0 17:0 18:0 19:0 20:0 21:1 22:0 23:0 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup
TTGO LORA32 v2.1_1.6 (button1: touch gpio 2 => 130; button2: touch gpio14 => 142) fingerprint 0011111 => 31
(sda,scl: 21,22)
0:1 1:0 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:1 16:1 17:1 18:0 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:4 1:4 2:0 3:4 4:0 5:4 6:0 7:4 8:0 9:4 10:4 11:4 12:0 13:0 14:4 15:4 16:4 17:4 18:0 19:0 20:0 21:4 22:4 23:4 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (in Sonde())
TTGO LORA v1.0 => fingerprint 1000000 => 64
(sda,scl: 4,15) (button1: 0) (button2: touch gpio13 = 141)
0:1 1:0 2:0 3:1 4:1 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:1 16:0 17:0 18:0 19:0 20:0 21:0 22:0 23:0 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:4 1:4 2:0 3:4 4:4 5:4 6:0 7:4 8:0 9:4 10:4 11:4 12:0 13:0 14:4 15:4 16:0 17:0 18:0 19:0 20:0 21:0 22:0 23:0 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (in Sonde())
TTGO T-Beam => fingerprint 0110111 => 55
(sda,scl: 21,22) (button1: 39) (button2: touch gpio13 = 141) (gps rx: 12)
0:1 1:0 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:1 13:0 14:1 15:1 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:4 1:4 2:0 3:4 4:0 5:4 6:0 7:4 8:0 9:4 10:4 11:4 12:4 13:0 14:4 15:4 16:4 17:0 18:0 19:0 20:0 21:4 22:4 23:4 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup)
TTGO T-Beam with extern 2" ILI9225 Display => fingerprint 0110000 => 48
0:1 1:0 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:1 13:0 14:1 15:1 16:1 17:0 18:0 19:0 20:0 21:0 22:0 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:4 1:4 2:0 3:4 4:0 5:4 6:0 7:4 8:0 9:4 10:4 11:4 12:4 13:0 14:4 15:4 16:4 17:0 18:0 19:0 20:0 21:0 22:0 23:4 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup)
TTGO T-Beam 1.0 with OLED display => fingerprint 0010111 => 23
0:1 1:0 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:1 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:4 1:4 2:0 3:4 4:0 5:4 6:0 7:4 8:0 9:4 10:4 11:4 12:0 13:0 14:4 15:4 16:4 17:0 18:0 19:0 20:0 21:4 22:4 23:4 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup)
1
Fingerprint GPIOs: 4, 12, 16, 17, 21, 22, 23,
Current autodetect strategy:
RST always set to 16
GPIO16=0 (GPio22,23 would also work):
==Heltec or TTGO LORA v1.0==
SDA,SCL set to (4,15)
Button 1 set to GPIO 0
Button 2 set to Touch in GPIO 13 (141)
otherwise
==LORA32 v2.1 or T-Beam==
GPIO17=0:
== T-BEAM =
GPIO12==0: v1 (or check PMU via I2C?)
GPS RX set to 34
Button 1 set to GPIO28
BUtton 2 set to Touch GPIO13
else:
GPS RX set to 12
Button 1 set to GPIO39
Button 2 set to Touch GPIO13 (141)
GPIO21=0:
large display connected (use ILI9225 contig: SDA4 CLK21 RS2 RST22 CS0)
else:
small display connected, set SDA,SCL to (21,22)
otherweise:
SDA,SCL set to (21,22)
GPS disabled
Button 1 set to Touch GPIO2 (130)
Button 2 set to Touch GPIO14 (142)

124
libraries/SondeLib/dataweb.h Executable file
View File

@ -0,0 +1,124 @@
#include "FS.h"
#include <SPIFFS.h>
#include <WiFi.h>
//Write data web
//Fonction
void writedataweb(){
String updateHost = "xavier.debert.free.fr";
String updateDataWeb = "/RS/dataweb/index.html.txt";
String *updateData = &updateDataWeb;
String dispHost = updateHost.substring(0, 14);
//disp.rdis->drawString(2, 0, dispHost.c_str());
Serial.println("Connecting to: " + updateHost);
// Connect to Update host
if (client.connect(updateHost.c_str(), updatePort)) {
// Connection succeeded, fecthing the bin
Serial.println("Fetching index.html: " + String(*updateData));
// Get the contents of the bin file
client.print(String("GET ") + *updateData + " HTTP/1.1\r\n" +
"Host: " + updateHost + "\r\n" +
"Cache-Control: no-cache\r\n" +
"Connection: close\r\n\r\n");
// Check what is being sent
// Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
// "Host: " + host + "\r\n" +
// "Cache-Control: no-cache\r\n" +
// "Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
Serial.println("Client Timeout !");
client.stop();
return;
}
}
// Once the response is available,
// check stuff
/*
Response Structure
HTTP/1.1 200 OK
x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0=
x-amz-request-id: 2D56B47560B764EC
Date: Wed, 14 Jun 2017 03:33:59 GMT
Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT
ETag: "d2afebbaaebc38cd669ce36727152af9"
Accept-Ranges: bytes
Content-Type: application/octet-stream
Content-Length: 357280
Server: AmazonS3
{{BIN FILE CONTENTS}}
*/
while (client.available()) {
// read line till /n
String line = client.readStringUntil('\n');
// remove space, to check if the line is end of headers
line.trim();
// if the the line is empty,
// this is end of headers
// break the while and feed the
// remaining `client` to the
// Update.writeStream();
if (!line.length()) {
//headers ended
break; // and get the OTA started
}
// Check if the HTTP Response is 200
// else break and Exit Update
if (line.startsWith("HTTP/1.1")) {
if (line.indexOf("200") < 0) {
Serial.println("Got a non 200 status code from server. error.");
break;
}
}
// extract headers here
// Start with content length
if (line.startsWith("Content-Length: ")) {
contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
// Next, the content type
if (line.startsWith("Content-Type: ")) {
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/text") {
isValidContentType = true;
}
}
}
} else {
// Connect to updateHost failed
// May be try?
// Probably a choppy network?
Serial.println("Connection to " + String(updateHost) + " failed. Please check your setup");
// retry??
// execOTA();
}
/*
Serial.printf("\nDataWeb On\n");
//open file for appending new blank line to EOF.
File f = SPIFFS.open("/data.html", "w");
f.println("<html><body>TEST de DATAWeb1</body></html>");
f.close();
*/
}

View File

@ -0,0 +1,227 @@
const uint8_t FreeMono12pt7bBitmaps[] PROGMEM = {
0x49, 0x24, 0x92, 0x48, 0x01, 0xF8, 0xE7, 0xE7, 0x67, 0x42, 0x42, 0x42,
0x42, 0x09, 0x02, 0x41, 0x10, 0x44, 0x11, 0x1F, 0xF1, 0x10, 0x4C, 0x12,
0x3F, 0xE1, 0x20, 0x48, 0x12, 0x04, 0x81, 0x20, 0x48, 0x04, 0x07, 0xA2,
0x19, 0x02, 0x40, 0x10, 0x03, 0x00, 0x3C, 0x00, 0x80, 0x10, 0x06, 0x01,
0xE0, 0xA7, 0xC0, 0x40, 0x10, 0x04, 0x00, 0x3C, 0x19, 0x84, 0x21, 0x08,
0x66, 0x0F, 0x00, 0x0C, 0x1C, 0x78, 0x01, 0xE0, 0xCC, 0x21, 0x08, 0x43,
0x30, 0x78, 0x3E, 0x30, 0x10, 0x08, 0x02, 0x03, 0x03, 0x47, 0x14, 0x8A,
0x43, 0x11, 0x8F, 0x60, 0xFD, 0xA4, 0x90, 0x05, 0x25, 0x24, 0x92, 0x48,
0x92, 0x24, 0x11, 0x24, 0x89, 0x24, 0x92, 0x92, 0x90, 0x00, 0x04, 0x02,
0x11, 0x07, 0xF0, 0xC0, 0x50, 0x48, 0x42, 0x00, 0x08, 0x04, 0x02, 0x01,
0x00, 0x87, 0xFC, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x3B, 0x9C, 0xCE,
0x62, 0x00, 0xFF, 0xE0, 0xFF, 0x80, 0x00, 0x80, 0xC0, 0x40, 0x20, 0x20,
0x10, 0x10, 0x08, 0x08, 0x04, 0x04, 0x02, 0x02, 0x01, 0x01, 0x00, 0x80,
0x80, 0x40, 0x00, 0x1C, 0x31, 0x90, 0x58, 0x38, 0x0C, 0x06, 0x03, 0x01,
0x80, 0xC0, 0x60, 0x30, 0x34, 0x13, 0x18, 0x70, 0x30, 0xE1, 0x44, 0x81,
0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x81, 0x1F, 0xC0, 0x1E, 0x10, 0x90,
0x68, 0x10, 0x08, 0x0C, 0x04, 0x04, 0x04, 0x06, 0x06, 0x06, 0x06, 0x0E,
0x07, 0xFE, 0x3E, 0x10, 0x40, 0x08, 0x02, 0x00, 0x80, 0x40, 0xE0, 0x04,
0x00, 0x80, 0x10, 0x04, 0x01, 0x00, 0xD8, 0x63, 0xE0, 0x06, 0x0A, 0x0A,
0x12, 0x22, 0x22, 0x42, 0x42, 0x82, 0x82, 0xFF, 0x02, 0x02, 0x02, 0x0F,
0x7F, 0x20, 0x10, 0x08, 0x04, 0x02, 0xF1, 0x8C, 0x03, 0x00, 0x80, 0x40,
0x20, 0x18, 0x16, 0x18, 0xF0, 0x0F, 0x8C, 0x08, 0x08, 0x04, 0x04, 0x02,
0x79, 0x46, 0xC1, 0xE0, 0x60, 0x28, 0x14, 0x19, 0x08, 0x78, 0xFF, 0x81,
0x81, 0x02, 0x02, 0x02, 0x02, 0x04, 0x04, 0x04, 0x04, 0x08, 0x08, 0x08,
0x08, 0x3E, 0x31, 0xB0, 0x70, 0x18, 0x0C, 0x05, 0x8C, 0x38, 0x63, 0x40,
0x60, 0x30, 0x18, 0x1B, 0x18, 0xF8, 0x3C, 0x31, 0x30, 0x50, 0x28, 0x0C,
0x0F, 0x06, 0x85, 0x3C, 0x80, 0x40, 0x40, 0x20, 0x20, 0x63, 0xE0, 0xFF,
0x80, 0x07, 0xFC, 0x39, 0xCE, 0x00, 0x00, 0x06, 0x33, 0x98, 0xC4, 0x00,
0x00, 0xC0, 0x60, 0x18, 0x0C, 0x06, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03,
0x00, 0x30, 0x01, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x06,
0x00, 0x30, 0x01, 0x80, 0x18, 0x01, 0x80, 0xC0, 0x30, 0x18, 0x0C, 0x02,
0x00, 0x00, 0x3E, 0x60, 0xA0, 0x20, 0x10, 0x08, 0x08, 0x18, 0x10, 0x08,
0x00, 0x00, 0x00, 0x01, 0xC0, 0xE0, 0x1C, 0x31, 0x10, 0x50, 0x28, 0x14,
0x3A, 0x25, 0x22, 0x91, 0x4C, 0xA3, 0xF0, 0x08, 0x02, 0x01, 0x80, 0x7C,
0x3F, 0x00, 0x0C, 0x00, 0x48, 0x01, 0x20, 0x04, 0x40, 0x21, 0x00, 0x84,
0x04, 0x08, 0x1F, 0xE0, 0x40, 0x82, 0x01, 0x08, 0x04, 0x20, 0x13, 0xE1,
0xF0, 0xFF, 0x08, 0x11, 0x01, 0x20, 0x24, 0x04, 0x81, 0x1F, 0xC2, 0x06,
0x40, 0x68, 0x05, 0x00, 0xA0, 0x14, 0x05, 0xFF, 0x00, 0x1E, 0x48, 0x74,
0x05, 0x01, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x20, 0x04, 0x01, 0x01,
0x30, 0x87, 0xC0, 0xFE, 0x10, 0x44, 0x09, 0x02, 0x40, 0x50, 0x14, 0x05,
0x01, 0x40, 0x50, 0x14, 0x0D, 0x02, 0x41, 0x3F, 0x80, 0xFF, 0xC8, 0x09,
0x01, 0x20, 0x04, 0x00, 0x88, 0x1F, 0x02, 0x20, 0x40, 0x08, 0x01, 0x00,
0xA0, 0x14, 0x03, 0xFF, 0xC0, 0xFF, 0xE8, 0x05, 0x00, 0xA0, 0x04, 0x00,
0x88, 0x1F, 0x02, 0x20, 0x40, 0x08, 0x01, 0x00, 0x20, 0x04, 0x01, 0xF0,
0x00, 0x1F, 0x46, 0x19, 0x01, 0x60, 0x28, 0x01, 0x00, 0x20, 0x04, 0x00,
0x83, 0xF0, 0x0B, 0x01, 0x20, 0x23, 0x0C, 0x3E, 0x00, 0xE1, 0xD0, 0x24,
0x09, 0x02, 0x40, 0x90, 0x27, 0xF9, 0x02, 0x40, 0x90, 0x24, 0x09, 0x02,
0x40, 0xB8, 0x70, 0xFE, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10, 0x20,
0x40, 0x81, 0x1F, 0xC0, 0x0F, 0xE0, 0x10, 0x02, 0x00, 0x40, 0x08, 0x01,
0x00, 0x20, 0x04, 0x80, 0x90, 0x12, 0x02, 0x40, 0xC6, 0x30, 0x7C, 0x00,
0xF1, 0xE4, 0x0C, 0x41, 0x04, 0x20, 0x44, 0x04, 0x80, 0x5C, 0x06, 0x60,
0x43, 0x04, 0x10, 0x40, 0x84, 0x08, 0x40, 0xCF, 0x07, 0xF8, 0x04, 0x00,
0x80, 0x10, 0x02, 0x00, 0x40, 0x08, 0x01, 0x00, 0x20, 0x04, 0x04, 0x80,
0x90, 0x12, 0x03, 0xFF, 0xC0, 0xE0, 0x3B, 0x01, 0x94, 0x14, 0xA0, 0xA4,
0x89, 0x24, 0x49, 0x14, 0x48, 0xA2, 0x45, 0x12, 0x10, 0x90, 0x04, 0x80,
0x24, 0x01, 0x78, 0x3C, 0xE0, 0xF6, 0x02, 0x50, 0x25, 0x02, 0x48, 0x24,
0xC2, 0x44, 0x24, 0x22, 0x43, 0x24, 0x12, 0x40, 0xA4, 0x0A, 0x40, 0x6F,
0x06, 0x0F, 0x03, 0x0C, 0x60, 0x64, 0x02, 0x80, 0x18, 0x01, 0x80, 0x18,
0x01, 0x80, 0x18, 0x01, 0x40, 0x26, 0x06, 0x30, 0xC0, 0xF0, 0xFF, 0x10,
0x64, 0x05, 0x01, 0x40, 0x50, 0x34, 0x19, 0xFC, 0x40, 0x10, 0x04, 0x01,
0x00, 0x40, 0x3E, 0x00, 0x0F, 0x03, 0x0C, 0x60, 0x64, 0x02, 0x80, 0x18,
0x01, 0x80, 0x18, 0x01, 0x80, 0x18, 0x01, 0x40, 0x26, 0x06, 0x30, 0xC1,
0xF0, 0x0C, 0x01, 0xF1, 0x30, 0xE0, 0xFF, 0x04, 0x18, 0x40, 0xC4, 0x04,
0x40, 0x44, 0x0C, 0x41, 0x87, 0xE0, 0x43, 0x04, 0x10, 0x40, 0x84, 0x04,
0x40, 0x4F, 0x03, 0x1F, 0x48, 0x34, 0x05, 0x01, 0x40, 0x08, 0x01, 0xC0,
0x0E, 0x00, 0x40, 0x18, 0x06, 0x01, 0xE1, 0xA7, 0xC0, 0xFF, 0xF0, 0x86,
0x10, 0x82, 0x00, 0x40, 0x08, 0x01, 0x00, 0x20, 0x04, 0x00, 0x80, 0x10,
0x02, 0x00, 0x40, 0x7F, 0x00, 0xF0, 0xF4, 0x02, 0x40, 0x24, 0x02, 0x40,
0x24, 0x02, 0x40, 0x24, 0x02, 0x40, 0x24, 0x02, 0x40, 0x22, 0x04, 0x30,
0xC0, 0xF0, 0xF8, 0x7C, 0x80, 0x22, 0x01, 0x04, 0x04, 0x10, 0x20, 0x40,
0x80, 0x82, 0x02, 0x10, 0x08, 0x40, 0x11, 0x00, 0x48, 0x01, 0xA0, 0x03,
0x00, 0x0C, 0x00, 0xF8, 0x7C, 0x80, 0x22, 0x00, 0x88, 0xC2, 0x23, 0x10,
0x8E, 0x42, 0x29, 0x09, 0x24, 0x24, 0x90, 0x91, 0x41, 0x85, 0x06, 0x14,
0x18, 0x70, 0x60, 0x80, 0xF0, 0xF2, 0x06, 0x30, 0x41, 0x08, 0x09, 0x80,
0x50, 0x06, 0x00, 0x60, 0x0D, 0x00, 0x88, 0x10, 0xC2, 0x04, 0x60, 0x2F,
0x0F, 0xF0, 0xF2, 0x02, 0x10, 0x41, 0x04, 0x08, 0x80, 0x50, 0x05, 0x00,
0x20, 0x02, 0x00, 0x20, 0x02, 0x00, 0x20, 0x02, 0x01, 0xFC, 0xFF, 0x40,
0xA0, 0x90, 0x40, 0x40, 0x40, 0x20, 0x20, 0x20, 0x10, 0x50, 0x30, 0x18,
0x0F, 0xFC, 0xF2, 0x49, 0x24, 0x92, 0x49, 0x24, 0x9C, 0x80, 0x60, 0x10,
0x08, 0x02, 0x01, 0x00, 0x40, 0x20, 0x08, 0x04, 0x01, 0x00, 0x80, 0x20,
0x10, 0x04, 0x02, 0x00, 0x80, 0x40, 0xE4, 0x92, 0x49, 0x24, 0x92, 0x49,
0x3C, 0x08, 0x0C, 0x09, 0x0C, 0x4C, 0x14, 0x04, 0xFF, 0xFC, 0x84, 0x21,
0x3E, 0x00, 0x60, 0x08, 0x02, 0x3F, 0x98, 0x28, 0x0A, 0x02, 0xC3, 0x9F,
0x30, 0xE0, 0x01, 0x00, 0x08, 0x00, 0x40, 0x02, 0x00, 0x13, 0xE0, 0xA0,
0x86, 0x02, 0x20, 0x09, 0x00, 0x48, 0x02, 0x40, 0x13, 0x01, 0x14, 0x1B,
0x9F, 0x00, 0x1F, 0x4C, 0x19, 0x01, 0x40, 0x28, 0x01, 0x00, 0x20, 0x02,
0x00, 0x60, 0x43, 0xF0, 0x00, 0xC0, 0x08, 0x01, 0x00, 0x20, 0x04, 0x3C,
0x98, 0x52, 0x06, 0x80, 0x50, 0x0A, 0x01, 0x40, 0x24, 0x0C, 0xC2, 0x87,
0x98, 0x3F, 0x18, 0x68, 0x06, 0x01, 0xFF, 0xE0, 0x08, 0x03, 0x00, 0x60,
0xC7, 0xC0, 0x0F, 0x98, 0x08, 0x04, 0x02, 0x07, 0xF8, 0x80, 0x40, 0x20,
0x10, 0x08, 0x04, 0x02, 0x01, 0x03, 0xF8, 0x1E, 0x6C, 0x39, 0x03, 0x40,
0x28, 0x05, 0x00, 0xA0, 0x12, 0x06, 0x61, 0x43, 0xC8, 0x01, 0x00, 0x20,
0x08, 0x3E, 0x00, 0xC0, 0x10, 0x04, 0x01, 0x00, 0x40, 0x13, 0x87, 0x11,
0x82, 0x40, 0x90, 0x24, 0x09, 0x02, 0x40, 0x90, 0x2E, 0x1C, 0x08, 0x04,
0x02, 0x00, 0x00, 0x03, 0xC0, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00,
0x80, 0x43, 0xFE, 0x04, 0x08, 0x10, 0x00, 0x1F, 0xC0, 0x81, 0x02, 0x04,
0x08, 0x10, 0x20, 0x40, 0x81, 0x02, 0x0B, 0xE0, 0xE0, 0x02, 0x00, 0x20,
0x02, 0x00, 0x20, 0x02, 0x3C, 0x21, 0x02, 0x60, 0x2C, 0x03, 0x80, 0x24,
0x02, 0x20, 0x21, 0x02, 0x08, 0xE1, 0xF0, 0x78, 0x04, 0x02, 0x01, 0x00,
0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x43, 0xFE,
0xDC, 0xE3, 0x19, 0x90, 0x84, 0x84, 0x24, 0x21, 0x21, 0x09, 0x08, 0x48,
0x42, 0x42, 0x17, 0x18, 0xC0, 0x67, 0x83, 0x84, 0x20, 0x22, 0x02, 0x20,
0x22, 0x02, 0x20, 0x22, 0x02, 0x20, 0x2F, 0x07, 0x1F, 0x04, 0x11, 0x01,
0x40, 0x18, 0x03, 0x00, 0x60, 0x0A, 0x02, 0x20, 0x83, 0xE0, 0xCF, 0x85,
0x06, 0x60, 0x24, 0x01, 0x40, 0x14, 0x01, 0x40, 0x16, 0x02, 0x50, 0x44,
0xF8, 0x40, 0x04, 0x00, 0x40, 0x0F, 0x00, 0x1E, 0x6C, 0x3B, 0x03, 0x40,
0x28, 0x05, 0x00, 0xA0, 0x12, 0x06, 0x61, 0x43, 0xC8, 0x01, 0x00, 0x20,
0x04, 0x03, 0xC0, 0xE3, 0x8B, 0x13, 0x80, 0x80, 0x20, 0x08, 0x02, 0x00,
0x80, 0x20, 0x3F, 0x80, 0x1F, 0x58, 0x34, 0x05, 0x80, 0x1E, 0x00, 0x60,
0x06, 0x01, 0xC0, 0xAF, 0xC0, 0x20, 0x04, 0x00, 0x80, 0x10, 0x0F, 0xF0,
0x40, 0x08, 0x01, 0x00, 0x20, 0x04, 0x00, 0x80, 0x10, 0x03, 0x04, 0x3F,
0x00, 0xC1, 0xC8, 0x09, 0x01, 0x20, 0x24, 0x04, 0x80, 0x90, 0x12, 0x02,
0x61, 0xC7, 0xCC, 0xF8, 0xF9, 0x01, 0x08, 0x10, 0x60, 0x81, 0x08, 0x08,
0x40, 0x22, 0x01, 0x20, 0x05, 0x00, 0x30, 0x00, 0xF0, 0x7A, 0x01, 0x10,
0x08, 0x8C, 0x42, 0x62, 0x12, 0x90, 0xA5, 0x05, 0x18, 0x28, 0xC0, 0x86,
0x00, 0x78, 0xF3, 0x04, 0x18, 0x80, 0xD0, 0x06, 0x00, 0x70, 0x09, 0x81,
0x0C, 0x20, 0x6F, 0x8F, 0xF0, 0xF2, 0x02, 0x20, 0x41, 0x04, 0x10, 0x80,
0x88, 0x09, 0x00, 0x50, 0x06, 0x00, 0x20, 0x04, 0x00, 0x40, 0x08, 0x0F,
0xE0, 0xFF, 0x41, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x40, 0xBF,
0xC0, 0x19, 0x08, 0x42, 0x10, 0x84, 0x64, 0x18, 0x42, 0x10, 0x84, 0x20,
0xC0, 0xFF, 0xFF, 0xC0, 0xC1, 0x08, 0x42, 0x10, 0x84, 0x10, 0x4C, 0x42,
0x10, 0x84, 0x26, 0x00, 0x38, 0x13, 0x38, 0x38 };
const GFXglyph FreeMono12pt7bGlyphs[] PROGMEM = {
{ 0, 0, 0, 14, 0, 1 }, // 0x20 ' '
{ 0, 3, 15, 14, 6, -14 }, // 0x21 '!'
{ 6, 8, 7, 14, 3, -14 }, // 0x22 '"'
{ 13, 10, 16, 14, 2, -14 }, // 0x23 '#'
{ 33, 10, 17, 14, 2, -14 }, // 0x24 '$'
{ 55, 10, 15, 14, 2, -14 }, // 0x25 '%'
{ 74, 9, 12, 14, 3, -11 }, // 0x26 '&'
{ 88, 3, 7, 14, 5, -14 }, // 0x27 '''
{ 91, 3, 18, 14, 7, -14 }, // 0x28 '('
{ 98, 3, 18, 14, 4, -14 }, // 0x29 ')'
{ 105, 9, 9, 14, 3, -14 }, // 0x2A '*'
{ 116, 9, 11, 14, 3, -11 }, // 0x2B '+'
{ 129, 5, 7, 14, 3, -3 }, // 0x2C ','
{ 134, 11, 1, 14, 2, -6 }, // 0x2D '-'
{ 136, 3, 3, 14, 5, -2 }, // 0x2E '.'
{ 138, 9, 18, 14, 3, -15 }, // 0x2F '/'
{ 159, 9, 15, 14, 3, -14 }, // 0x30 '0'
{ 176, 7, 14, 14, 4, -13 }, // 0x31 '1'
{ 189, 9, 15, 14, 2, -14 }, // 0x32 '2'
{ 206, 10, 15, 14, 2, -14 }, // 0x33 '3'
{ 225, 8, 15, 14, 3, -14 }, // 0x34 '4'
{ 240, 9, 15, 14, 3, -14 }, // 0x35 '5'
{ 257, 9, 15, 14, 3, -14 }, // 0x36 '6'
{ 274, 8, 15, 14, 3, -14 }, // 0x37 '7'
{ 289, 9, 15, 14, 3, -14 }, // 0x38 '8'
{ 306, 9, 15, 14, 3, -14 }, // 0x39 '9'
{ 323, 3, 10, 14, 5, -9 }, // 0x3A ':'
{ 327, 5, 13, 14, 3, -9 }, // 0x3B ';'
{ 336, 11, 11, 14, 2, -11 }, // 0x3C '<'
{ 352, 12, 4, 14, 1, -8 }, // 0x3D '='
{ 358, 11, 11, 14, 2, -11 }, // 0x3E '>'
{ 374, 9, 14, 14, 3, -13 }, // 0x3F '?'
{ 390, 9, 16, 14, 3, -14 }, // 0x40 '@'
{ 408, 14, 14, 14, 0, -13 }, // 0x41 'A'
{ 433, 11, 14, 14, 2, -13 }, // 0x42 'B'
{ 453, 10, 14, 14, 2, -13 }, // 0x43 'C'
{ 471, 10, 14, 14, 2, -13 }, // 0x44 'D'
{ 489, 11, 14, 14, 2, -13 }, // 0x45 'E'
{ 509, 11, 14, 14, 2, -13 }, // 0x46 'F'
{ 529, 11, 14, 14, 2, -13 }, // 0x47 'G'
{ 549, 10, 14, 14, 2, -13 }, // 0x48 'H'
{ 567, 7, 14, 14, 4, -13 }, // 0x49 'I'
{ 580, 11, 14, 14, 2, -13 }, // 0x4A 'J'
{ 600, 12, 14, 14, 2, -13 }, // 0x4B 'K'
{ 621, 11, 14, 14, 2, -13 }, // 0x4C 'L'
{ 641, 13, 14, 14, 1, -13 }, // 0x4D 'M'
{ 664, 12, 14, 14, 1, -13 }, // 0x4E 'N'
{ 685, 12, 14, 14, 1, -13 }, // 0x4F 'O'
{ 706, 10, 14, 14, 2, -13 }, // 0x50 'P'
{ 724, 12, 17, 14, 1, -13 }, // 0x51 'Q'
{ 750, 12, 14, 14, 2, -13 }, // 0x52 'R'
{ 771, 10, 14, 14, 2, -13 }, // 0x53 'S'
{ 789, 11, 14, 14, 2, -13 }, // 0x54 'T'
{ 809, 12, 14, 14, 1, -13 }, // 0x55 'U'
{ 830, 14, 14, 14, 0, -13 }, // 0x56 'V'
{ 855, 14, 14, 14, 0, -13 }, // 0x57 'W'
{ 880, 12, 14, 14, 1, -13 }, // 0x58 'X'
{ 901, 12, 14, 14, 1, -13 }, // 0x59 'Y'
{ 922, 9, 14, 14, 3, -13 }, // 0x5A 'Z'
{ 938, 3, 18, 14, 7, -14 }, // 0x5B '['
{ 945, 9, 18, 14, 3, -15 }, // 0x5C '\'
{ 966, 3, 18, 14, 5, -14 }, // 0x5D ']'
{ 973, 9, 6, 14, 3, -14 }, // 0x5E '^'
{ 980, 14, 1, 14, 0, 3 }, // 0x5F '_'
{ 982, 4, 4, 14, 4, -15 }, // 0x60 '`'
{ 984, 10, 10, 14, 2, -9 }, // 0x61 'a'
{ 997, 13, 15, 14, 0, -14 }, // 0x62 'b'
{ 1022, 11, 10, 14, 2, -9 }, // 0x63 'c'
{ 1036, 11, 15, 14, 2, -14 }, // 0x64 'd'
{ 1057, 10, 10, 14, 2, -9 }, // 0x65 'e'
{ 1070, 9, 15, 14, 4, -14 }, // 0x66 'f'
{ 1087, 11, 14, 14, 2, -9 }, // 0x67 'g'
{ 1107, 10, 15, 14, 2, -14 }, // 0x68 'h'
{ 1126, 9, 15, 14, 3, -14 }, // 0x69 'i'
{ 1143, 7, 19, 14, 3, -14 }, // 0x6A 'j'
{ 1160, 12, 15, 14, 1, -14 }, // 0x6B 'k'
{ 1183, 9, 15, 14, 3, -14 }, // 0x6C 'l'
{ 1200, 13, 10, 14, 1, -9 }, // 0x6D 'm'
{ 1217, 12, 10, 14, 1, -9 }, // 0x6E 'n'
{ 1232, 11, 10, 14, 2, -9 }, // 0x6F 'o'
{ 1246, 12, 14, 14, 1, -9 }, // 0x70 'p'
{ 1267, 11, 14, 14, 2, -9 }, // 0x71 'q'
{ 1287, 10, 10, 14, 3, -9 }, // 0x72 'r'
{ 1300, 10, 10, 14, 2, -9 }, // 0x73 's'
{ 1313, 11, 14, 14, 1, -13 }, // 0x74 't'
{ 1333, 11, 10, 14, 2, -9 }, // 0x75 'u'
{ 1347, 13, 10, 14, 1, -9 }, // 0x76 'v'
{ 1364, 13, 10, 14, 1, -9 }, // 0x77 'w'
{ 1381, 12, 10, 14, 1, -9 }, // 0x78 'x'
{ 1396, 12, 14, 14, 1, -9 }, // 0x79 'y'
{ 1417, 9, 10, 14, 3, -9 }, // 0x7A 'z'
{ 1429, 5, 18, 14, 5, -14 }, // 0x7B '{'
{ 1441, 1, 18, 14, 7, -14 }, // 0x7C '|'
{ 1444, 5, 18, 14, 5, -14 }, // 0x7D '}'
{ 1456, 10, 3, 14, 2, -7 } }; // 0x7E '~'
const GFXfont FreeMono12pt7b PROGMEM = {
(uint8_t *)FreeMono12pt7bBitmaps,
(GFXglyph *)FreeMono12pt7bGlyphs,
0x20, 0x7E, 24 };
// Approx. 2132 bytes

View File

@ -0,0 +1,176 @@
const uint8_t FreeMono9pt7bBitmaps[] PROGMEM = {
0xAA, 0xA8, 0x0C, 0xED, 0x24, 0x92, 0x48, 0x24, 0x48, 0x91, 0x2F, 0xE4,
0x89, 0x7F, 0x28, 0x51, 0x22, 0x40, 0x08, 0x3E, 0x62, 0x40, 0x30, 0x0E,
0x01, 0x81, 0xC3, 0xBE, 0x08, 0x08, 0x71, 0x12, 0x23, 0x80, 0x23, 0xB8,
0x0E, 0x22, 0x44, 0x70, 0x38, 0x81, 0x02, 0x06, 0x1A, 0x65, 0x46, 0xC8,
0xEC, 0xE9, 0x24, 0x5A, 0xAA, 0xA9, 0x40, 0xA9, 0x55, 0x5A, 0x80, 0x10,
0x22, 0x4B, 0xE3, 0x05, 0x11, 0x00, 0x10, 0x20, 0x47, 0xF1, 0x02, 0x04,
0x00, 0x6B, 0x48, 0xFF, 0x00, 0xF0, 0x02, 0x08, 0x10, 0x60, 0x81, 0x04,
0x08, 0x20, 0x41, 0x02, 0x08, 0x00, 0x38, 0x8A, 0x0C, 0x18, 0x30, 0x60,
0xC1, 0x82, 0x88, 0xE0, 0x27, 0x28, 0x42, 0x10, 0x84, 0x21, 0x3E, 0x38,
0x8A, 0x08, 0x10, 0x20, 0x82, 0x08, 0x61, 0x03, 0xF8, 0x7C, 0x06, 0x02,
0x02, 0x1C, 0x06, 0x01, 0x01, 0x01, 0x42, 0x3C, 0x18, 0xA2, 0x92, 0x8A,
0x28, 0xBF, 0x08, 0x21, 0xC0, 0x7C, 0x81, 0x03, 0xE4, 0x40, 0x40, 0x81,
0x03, 0x88, 0xE0, 0x1E, 0x41, 0x04, 0x0B, 0x98, 0xB0, 0xC1, 0xC2, 0x88,
0xE0, 0xFE, 0x04, 0x08, 0x20, 0x40, 0x82, 0x04, 0x08, 0x20, 0x40, 0x38,
0x8A, 0x0C, 0x14, 0x47, 0x11, 0x41, 0x83, 0x8C, 0xE0, 0x38, 0x8A, 0x1C,
0x18, 0x68, 0xCE, 0x81, 0x04, 0x13, 0xC0, 0xF0, 0x0F, 0x6C, 0x00, 0xD2,
0xD2, 0x00, 0x03, 0x04, 0x18, 0x60, 0x60, 0x18, 0x04, 0x03, 0xFF, 0x80,
0x00, 0x1F, 0xF0, 0x40, 0x18, 0x03, 0x00, 0x60, 0x20, 0x60, 0xC0, 0x80,
0x3D, 0x84, 0x08, 0x30, 0xC2, 0x00, 0x00, 0x00, 0x30, 0x3C, 0x46, 0x82,
0x8E, 0xB2, 0xA2, 0xA2, 0x9F, 0x80, 0x80, 0x40, 0x3C, 0x3C, 0x01, 0x40,
0x28, 0x09, 0x01, 0x10, 0x42, 0x0F, 0xC1, 0x04, 0x40, 0x9E, 0x3C, 0xFE,
0x21, 0x90, 0x48, 0x67, 0xE2, 0x09, 0x02, 0x81, 0x41, 0xFF, 0x80, 0x3E,
0xB0, 0xF0, 0x30, 0x08, 0x04, 0x02, 0x00, 0x80, 0x60, 0x8F, 0x80, 0xFE,
0x21, 0x90, 0x68, 0x14, 0x0A, 0x05, 0x02, 0x83, 0x43, 0x7F, 0x00, 0xFF,
0x20, 0x90, 0x08, 0x87, 0xC2, 0x21, 0x00, 0x81, 0x40, 0xFF, 0xC0, 0xFF,
0xA0, 0x50, 0x08, 0x87, 0xC2, 0x21, 0x00, 0x80, 0x40, 0x78, 0x00, 0x1E,
0x98, 0x6C, 0x0A, 0x00, 0x80, 0x20, 0xF8, 0x0B, 0x02, 0x60, 0x87, 0xC0,
0xE3, 0xA0, 0x90, 0x48, 0x27, 0xF2, 0x09, 0x04, 0x82, 0x41, 0x71, 0xC0,
0xF9, 0x08, 0x42, 0x10, 0x84, 0x27, 0xC0, 0x1F, 0x02, 0x02, 0x02, 0x02,
0x02, 0x82, 0x82, 0xC6, 0x78, 0xE3, 0xA1, 0x11, 0x09, 0x05, 0x83, 0x21,
0x08, 0x84, 0x41, 0x70, 0xC0, 0xE0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x41,
0x41, 0x41, 0xFF, 0xE0, 0xEC, 0x19, 0x45, 0x28, 0xA4, 0xA4, 0x94, 0x91,
0x12, 0x02, 0x40, 0x5C, 0x1C, 0xC3, 0xB0, 0x94, 0x4A, 0x24, 0x92, 0x49,
0x14, 0x8A, 0x43, 0x70, 0x80, 0x1E, 0x31, 0x90, 0x50, 0x18, 0x0C, 0x06,
0x02, 0x82, 0x63, 0x0F, 0x00, 0xFE, 0x43, 0x41, 0x41, 0x42, 0x7C, 0x40,
0x40, 0x40, 0xF0, 0x1C, 0x31, 0x90, 0x50, 0x18, 0x0C, 0x06, 0x02, 0x82,
0x63, 0x1F, 0x04, 0x07, 0x92, 0x30, 0xFE, 0x21, 0x90, 0x48, 0x24, 0x23,
0xE1, 0x10, 0x84, 0x41, 0x70, 0xC0, 0x3A, 0xCD, 0x0A, 0x03, 0x01, 0x80,
0xC1, 0xC7, 0x78, 0xFF, 0xC4, 0x62, 0x21, 0x00, 0x80, 0x40, 0x20, 0x10,
0x08, 0x1F, 0x00, 0xE3, 0xA0, 0x90, 0x48, 0x24, 0x12, 0x09, 0x04, 0x82,
0x22, 0x0E, 0x00, 0xF1, 0xE8, 0x10, 0x82, 0x10, 0x42, 0x10, 0x22, 0x04,
0x80, 0x50, 0x0C, 0x00, 0x80, 0xF1, 0xE8, 0x09, 0x11, 0x25, 0x44, 0xA8,
0x55, 0x0C, 0xA1, 0x8C, 0x31, 0x84, 0x30, 0xE3, 0xA0, 0x88, 0x82, 0x80,
0x80, 0xC0, 0x90, 0x44, 0x41, 0x71, 0xC0, 0xE3, 0xA0, 0x88, 0x82, 0x81,
0x40, 0x40, 0x20, 0x10, 0x08, 0x1F, 0x00, 0xFD, 0x0A, 0x20, 0x81, 0x04,
0x10, 0x21, 0x83, 0xFC, 0xEA, 0xAA, 0xAA, 0xC0, 0x80, 0x81, 0x03, 0x02,
0x04, 0x04, 0x08, 0x08, 0x10, 0x10, 0x20, 0x20, 0xD5, 0x55, 0x55, 0xC0,
0x10, 0x51, 0x22, 0x28, 0x20, 0xFF, 0xE0, 0x88, 0x80, 0x7E, 0x00, 0x80,
0x47, 0xEC, 0x14, 0x0A, 0x0C, 0xFB, 0xC0, 0x20, 0x10, 0x0B, 0xC6, 0x12,
0x05, 0x02, 0x81, 0x40, 0xB0, 0xB7, 0x80, 0x3A, 0x8E, 0x0C, 0x08, 0x10,
0x10, 0x9E, 0x03, 0x00, 0x80, 0x47, 0xA4, 0x34, 0x0A, 0x05, 0x02, 0x81,
0x21, 0x8F, 0x60, 0x3C, 0x43, 0x81, 0xFF, 0x80, 0x80, 0x61, 0x3E, 0x3D,
0x04, 0x3E, 0x41, 0x04, 0x10, 0x41, 0x0F, 0x80, 0x3D, 0xA1, 0xA0, 0x50,
0x28, 0x14, 0x09, 0x0C, 0x7A, 0x01, 0x01, 0x87, 0x80, 0xC0, 0x20, 0x10,
0x0B, 0xC6, 0x32, 0x09, 0x04, 0x82, 0x41, 0x20, 0xB8, 0xE0, 0x10, 0x01,
0xC0, 0x81, 0x02, 0x04, 0x08, 0x11, 0xFC, 0x10, 0x3E, 0x10, 0x84, 0x21,
0x08, 0x42, 0x3F, 0x00, 0xC0, 0x40, 0x40, 0x4F, 0x44, 0x58, 0x70, 0x48,
0x44, 0x42, 0xC7, 0x70, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10, 0x23,
0xF8, 0xB7, 0x64, 0x62, 0x31, 0x18, 0x8C, 0x46, 0x23, 0x91, 0x5E, 0x31,
0x90, 0x48, 0x24, 0x12, 0x09, 0x05, 0xC7, 0x3E, 0x31, 0xA0, 0x30, 0x18,
0x0C, 0x05, 0x8C, 0x7C, 0xDE, 0x30, 0x90, 0x28, 0x14, 0x0A, 0x05, 0x84,
0xBC, 0x40, 0x20, 0x38, 0x00, 0x3D, 0xA1, 0xA0, 0x50, 0x28, 0x14, 0x09,
0x0C, 0x7A, 0x01, 0x00, 0x80, 0xE0, 0xCE, 0xA1, 0x82, 0x04, 0x08, 0x10,
0x7C, 0x3A, 0x8D, 0x0B, 0x80, 0xF0, 0x70, 0xDE, 0x40, 0x40, 0xFC, 0x40,
0x40, 0x40, 0x40, 0x40, 0x41, 0x3E, 0xC3, 0x41, 0x41, 0x41, 0x41, 0x41,
0x43, 0x3D, 0xE3, 0xA0, 0x90, 0x84, 0x42, 0x20, 0xA0, 0x50, 0x10, 0xE3,
0xC0, 0x92, 0x4B, 0x25, 0x92, 0xA9, 0x98, 0x44, 0xE3, 0x31, 0x05, 0x01,
0x01, 0x41, 0x11, 0x05, 0xC7, 0xE3, 0xA0, 0x90, 0x84, 0x42, 0x40, 0xA0,
0x60, 0x10, 0x10, 0x08, 0x3E, 0x00, 0xFD, 0x08, 0x20, 0x82, 0x08, 0x10,
0xBF, 0x29, 0x24, 0xA2, 0x49, 0x26, 0xFF, 0xF8, 0x89, 0x24, 0x8A, 0x49,
0x2C, 0x61, 0x24, 0x30 };
const GFXglyph FreeMono9pt7bGlyphs[] PROGMEM = {
{ 0, 0, 0, 11, 0, 1 }, // 0x20 ' '
{ 0, 2, 11, 11, 4, -10 }, // 0x21 '!'
{ 3, 6, 5, 11, 2, -10 }, // 0x22 '"'
{ 7, 7, 12, 11, 2, -10 }, // 0x23 '#'
{ 18, 8, 12, 11, 1, -10 }, // 0x24 '$'
{ 30, 7, 11, 11, 2, -10 }, // 0x25 '%'
{ 40, 7, 10, 11, 2, -9 }, // 0x26 '&'
{ 49, 3, 5, 11, 4, -10 }, // 0x27 '''
{ 51, 2, 13, 11, 5, -10 }, // 0x28 '('
{ 55, 2, 13, 11, 4, -10 }, // 0x29 ')'
{ 59, 7, 7, 11, 2, -10 }, // 0x2A '*'
{ 66, 7, 7, 11, 2, -8 }, // 0x2B '+'
{ 73, 3, 5, 11, 2, -1 }, // 0x2C ','
{ 75, 9, 1, 11, 1, -5 }, // 0x2D '-'
{ 77, 2, 2, 11, 4, -1 }, // 0x2E '.'
{ 78, 7, 13, 11, 2, -11 }, // 0x2F '/'
{ 90, 7, 11, 11, 2, -10 }, // 0x30 '0'
{ 100, 5, 11, 11, 3, -10 }, // 0x31 '1'
{ 107, 7, 11, 11, 2, -10 }, // 0x32 '2'
{ 117, 8, 11, 11, 1, -10 }, // 0x33 '3'
{ 128, 6, 11, 11, 3, -10 }, // 0x34 '4'
{ 137, 7, 11, 11, 2, -10 }, // 0x35 '5'
{ 147, 7, 11, 11, 2, -10 }, // 0x36 '6'
{ 157, 7, 11, 11, 2, -10 }, // 0x37 '7'
{ 167, 7, 11, 11, 2, -10 }, // 0x38 '8'
{ 177, 7, 11, 11, 2, -10 }, // 0x39 '9'
{ 187, 2, 8, 11, 4, -7 }, // 0x3A ':'
{ 189, 3, 11, 11, 3, -7 }, // 0x3B ';'
{ 194, 8, 8, 11, 1, -8 }, // 0x3C '<'
{ 202, 9, 4, 11, 1, -6 }, // 0x3D '='
{ 207, 9, 8, 11, 1, -8 }, // 0x3E '>'
{ 216, 7, 10, 11, 2, -9 }, // 0x3F '?'
{ 225, 8, 12, 11, 2, -10 }, // 0x40 '@'
{ 237, 11, 10, 11, 0, -9 }, // 0x41 'A'
{ 251, 9, 10, 11, 1, -9 }, // 0x42 'B'
{ 263, 9, 10, 11, 1, -9 }, // 0x43 'C'
{ 275, 9, 10, 11, 1, -9 }, // 0x44 'D'
{ 287, 9, 10, 11, 1, -9 }, // 0x45 'E'
{ 299, 9, 10, 11, 1, -9 }, // 0x46 'F'
{ 311, 10, 10, 11, 1, -9 }, // 0x47 'G'
{ 324, 9, 10, 11, 1, -9 }, // 0x48 'H'
{ 336, 5, 10, 11, 3, -9 }, // 0x49 'I'
{ 343, 8, 10, 11, 2, -9 }, // 0x4A 'J'
{ 353, 9, 10, 11, 1, -9 }, // 0x4B 'K'
{ 365, 8, 10, 11, 2, -9 }, // 0x4C 'L'
{ 375, 11, 10, 11, 0, -9 }, // 0x4D 'M'
{ 389, 9, 10, 11, 1, -9 }, // 0x4E 'N'
{ 401, 9, 10, 11, 1, -9 }, // 0x4F 'O'
{ 413, 8, 10, 11, 1, -9 }, // 0x50 'P'
{ 423, 9, 13, 11, 1, -9 }, // 0x51 'Q'
{ 438, 9, 10, 11, 1, -9 }, // 0x52 'R'
{ 450, 7, 10, 11, 2, -9 }, // 0x53 'S'
{ 459, 9, 10, 11, 1, -9 }, // 0x54 'T'
{ 471, 9, 10, 11, 1, -9 }, // 0x55 'U'
{ 483, 11, 10, 11, 0, -9 }, // 0x56 'V'
{ 497, 11, 10, 11, 0, -9 }, // 0x57 'W'
{ 511, 9, 10, 11, 1, -9 }, // 0x58 'X'
{ 523, 9, 10, 11, 1, -9 }, // 0x59 'Y'
{ 535, 7, 10, 11, 2, -9 }, // 0x5A 'Z'
{ 544, 2, 13, 11, 5, -10 }, // 0x5B '['
{ 548, 7, 13, 11, 2, -11 }, // 0x5C '\'
{ 560, 2, 13, 11, 4, -10 }, // 0x5D ']'
{ 564, 7, 5, 11, 2, -10 }, // 0x5E '^'
{ 569, 11, 1, 11, 0, 2 }, // 0x5F '_'
{ 571, 3, 3, 11, 3, -11 }, // 0x60 '`'
{ 573, 9, 8, 11, 1, -7 }, // 0x61 'a'
{ 582, 9, 11, 11, 1, -10 }, // 0x62 'b'
{ 595, 7, 8, 11, 2, -7 }, // 0x63 'c'
{ 602, 9, 11, 11, 1, -10 }, // 0x64 'd'
{ 615, 8, 8, 11, 1, -7 }, // 0x65 'e'
{ 623, 6, 11, 11, 3, -10 }, // 0x66 'f'
{ 632, 9, 11, 11, 1, -7 }, // 0x67 'g'
{ 645, 9, 11, 11, 1, -10 }, // 0x68 'h'
{ 658, 7, 10, 11, 2, -9 }, // 0x69 'i'
{ 667, 5, 13, 11, 3, -9 }, // 0x6A 'j'
{ 676, 8, 11, 11, 2, -10 }, // 0x6B 'k'
{ 687, 7, 11, 11, 2, -10 }, // 0x6C 'l'
{ 697, 9, 8, 11, 1, -7 }, // 0x6D 'm'
{ 706, 9, 8, 11, 1, -7 }, // 0x6E 'n'
{ 715, 9, 8, 11, 1, -7 }, // 0x6F 'o'
{ 724, 9, 11, 11, 1, -7 }, // 0x70 'p'
{ 737, 9, 11, 11, 1, -7 }, // 0x71 'q'
{ 750, 7, 8, 11, 3, -7 }, // 0x72 'r'
{ 757, 7, 8, 11, 2, -7 }, // 0x73 's'
{ 764, 8, 10, 11, 2, -9 }, // 0x74 't'
{ 774, 8, 8, 11, 1, -7 }, // 0x75 'u'
{ 782, 9, 8, 11, 1, -7 }, // 0x76 'v'
{ 791, 9, 8, 11, 1, -7 }, // 0x77 'w'
{ 800, 9, 8, 11, 1, -7 }, // 0x78 'x'
{ 809, 9, 11, 11, 1, -7 }, // 0x79 'y'
{ 822, 7, 8, 11, 2, -7 }, // 0x7A 'z'
{ 829, 3, 13, 11, 4, -10 }, // 0x7B '{'
{ 834, 1, 13, 11, 5, -10 }, // 0x7C '|'
{ 836, 3, 13, 11, 4, -10 }, // 0x7D '}'
{ 841, 7, 3, 11, 2, -6 } }; // 0x7E '~'
const GFXfont FreeMono9pt7b PROGMEM = {
(uint8_t *)FreeMono9pt7bBitmaps,
(GFXglyph *)FreeMono9pt7bGlyphs,
0x20, 0x7E, 18 };
// Approx. 1516 bytes

View File

@ -0,0 +1,270 @@
const uint8_t FreeSans12pt7bBitmaps[] PROGMEM = {
0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xCF, 0x3C, 0xF3, 0x8A, 0x20, 0x06, 0x30,
0x31, 0x03, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x03, 0x18, 0x18,
0xC7, 0xFF, 0xBF, 0xFC, 0x31, 0x01, 0x18, 0x18, 0xC0, 0xC6, 0x06, 0x30,
0x04, 0x03, 0xE1, 0xFF, 0x72, 0x6C, 0x47, 0x88, 0xF1, 0x07, 0x20, 0x7E,
0x03, 0xF0, 0x17, 0x02, 0x3C, 0x47, 0x88, 0xF1, 0x1B, 0x26, 0x7F, 0xC3,
0xE0, 0x10, 0x02, 0x00, 0x00, 0x06, 0x03, 0xC0, 0x40, 0x7E, 0x0C, 0x0E,
0x70, 0x80, 0xC3, 0x18, 0x0C, 0x31, 0x00, 0xE7, 0x30, 0x07, 0xE6, 0x00,
0x3C, 0x40, 0x00, 0x0C, 0x7C, 0x00, 0x8F, 0xE0, 0x19, 0xC7, 0x01, 0x18,
0x30, 0x31, 0x83, 0x02, 0x1C, 0x70, 0x40, 0xFE, 0x04, 0x07, 0xC0, 0x0F,
0x00, 0x7E, 0x03, 0x9C, 0x0C, 0x30, 0x30, 0xC0, 0xE7, 0x01, 0xF8, 0x03,
0x80, 0x3E, 0x01, 0xCC, 0x6E, 0x19, 0xB0, 0x7C, 0xC0, 0xF3, 0x03, 0xCE,
0x1F, 0x9F, 0xE6, 0x1E, 0x1C, 0xFF, 0xA0, 0x08, 0x8C, 0x66, 0x31, 0x98,
0xC6, 0x31, 0x8C, 0x63, 0x08, 0x63, 0x08, 0x61, 0x0C, 0x20, 0x82, 0x18,
0xC3, 0x18, 0xC3, 0x18, 0xC6, 0x31, 0x8C, 0x62, 0x31, 0x88, 0xC4, 0x62,
0x00, 0x10, 0x23, 0x5B, 0xE3, 0x8D, 0x91, 0x00, 0x0C, 0x03, 0x00, 0xC0,
0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0xF5, 0x60,
0xFF, 0xF0, 0xF0, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20,
0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x00, 0x1F, 0x07, 0xF1, 0xC7, 0x30,
0x6E, 0x0F, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C,
0x0E, 0xC1, 0x9C, 0x71, 0xFC, 0x1F, 0x00, 0x08, 0xCF, 0xFF, 0x8C, 0x63,
0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0x1F, 0x0F, 0xF9, 0x87, 0x60, 0x7C,
0x06, 0x00, 0xC0, 0x18, 0x07, 0x01, 0xC0, 0xF0, 0x78, 0x1C, 0x06, 0x00,
0x80, 0x30, 0x07, 0xFF, 0xFF, 0xE0, 0x3F, 0x0F, 0xF3, 0x87, 0x60, 0x6C,
0x0C, 0x01, 0x80, 0x70, 0x7C, 0x0F, 0x80, 0x18, 0x01, 0x80, 0x3C, 0x07,
0x80, 0xD8, 0x73, 0xFC, 0x1F, 0x00, 0x01, 0x80, 0x70, 0x0E, 0x03, 0xC0,
0xD8, 0x1B, 0x06, 0x61, 0x8C, 0x21, 0x8C, 0x33, 0x06, 0x7F, 0xFF, 0xFE,
0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x3F, 0xCF, 0xF9, 0x80, 0x30, 0x06,
0x00, 0xDE, 0x1F, 0xE7, 0x0E, 0x00, 0xE0, 0x0C, 0x01, 0x80, 0x30, 0x07,
0x81, 0xF8, 0x73, 0xFC, 0x1F, 0x00, 0x0F, 0x07, 0xF9, 0xC3, 0x30, 0x74,
0x01, 0x80, 0x33, 0xC7, 0xFE, 0xF0, 0xDC, 0x1F, 0x01, 0xE0, 0x3C, 0x06,
0xC1, 0xDC, 0x71, 0xFC, 0x1F, 0x00, 0xFF, 0xFF, 0xFC, 0x01, 0x00, 0x60,
0x18, 0x02, 0x00, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x04, 0x01, 0x80,
0x30, 0x06, 0x01, 0x80, 0x30, 0x00, 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x66,
0x0C, 0xC1, 0x8C, 0x61, 0xFC, 0x3F, 0x8E, 0x3B, 0x01, 0xE0, 0x3C, 0x07,
0x80, 0xD8, 0x31, 0xFC, 0x1F, 0x00, 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x6C,
0x07, 0x80, 0xF0, 0x1E, 0x07, 0x61, 0xEF, 0xFC, 0x79, 0x80, 0x30, 0x05,
0x81, 0x98, 0x73, 0xFC, 0x1E, 0x00, 0xF0, 0x00, 0x03, 0xC0, 0xF0, 0x00,
0x0F, 0x56, 0x00, 0x00, 0x07, 0x01, 0xE0, 0xF8, 0x3C, 0x0F, 0x00, 0xE0,
0x07, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x01, 0xFF, 0xFF, 0xFF, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x0E, 0x00, 0x78, 0x01, 0xF0, 0x07,
0xC0, 0x0F, 0x00, 0x70, 0x1E, 0x0F, 0x03, 0xC0, 0xF0, 0x08, 0x00, 0x1F,
0x1F, 0xEE, 0x1B, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x06, 0x03, 0x81, 0xC0,
0xE0, 0x30, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0xFE,
0x00, 0x0F, 0xFE, 0x00, 0xF0, 0x3E, 0x07, 0x00, 0x3C, 0x38, 0x00, 0x30,
0xC1, 0xE0, 0x66, 0x0F, 0xD9, 0xD8, 0x61, 0xC3, 0xC3, 0x07, 0x0F, 0x1C,
0x1C, 0x3C, 0x60, 0x60, 0xF1, 0x81, 0x83, 0xC6, 0x06, 0x1B, 0x18, 0x38,
0xEE, 0x71, 0xE7, 0x18, 0xFD, 0xF8, 0x71, 0xE7, 0xC0, 0xE0, 0x00, 0x01,
0xE0, 0x00, 0x01, 0xFF, 0xC0, 0x01, 0xFC, 0x00, 0x03, 0xC0, 0x03, 0xC0,
0x03, 0xC0, 0x07, 0xE0, 0x06, 0x60, 0x06, 0x60, 0x0E, 0x70, 0x0C, 0x30,
0x0C, 0x30, 0x1C, 0x38, 0x18, 0x18, 0x1F, 0xF8, 0x3F, 0xFC, 0x30, 0x1C,
0x30, 0x0C, 0x70, 0x0E, 0x60, 0x06, 0x60, 0x06, 0xFF, 0xC7, 0xFF, 0x30,
0x19, 0x80, 0x6C, 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x0C, 0xFF, 0xC7, 0xFF,
0x30, 0x0D, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x06, 0xFF, 0xF7,
0xFE, 0x00, 0x07, 0xE0, 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x66, 0x00, 0x6C,
0x00, 0x30, 0x00, 0x60, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x06, 0x00,
0x06, 0x00, 0x6C, 0x00, 0xDC, 0x03, 0x1E, 0x0E, 0x1F, 0xF8, 0x0F, 0xC0,
0xFF, 0x83, 0xFF, 0x8C, 0x07, 0x30, 0x0E, 0xC0, 0x1B, 0x00, 0x7C, 0x00,
0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x1F, 0x00,
0x6C, 0x03, 0xB0, 0x1C, 0xFF, 0xE3, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xC0,
0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0,
0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xFF, 0xDF,
0xFB, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x00,
0x07, 0xF0, 0x1F, 0xFC, 0x3C, 0x1E, 0x70, 0x06, 0x60, 0x03, 0xE0, 0x00,
0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xC0, 0x7F, 0xC0, 0x03, 0xC0, 0x03,
0x60, 0x03, 0x60, 0x07, 0x30, 0x0F, 0x3C, 0x1F, 0x1F, 0xFB, 0x07, 0xE1,
0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78,
0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00,
0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x01,
0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60,
0x3C, 0x1E, 0x0F, 0x07, 0xC7, 0x7F, 0x1F, 0x00, 0xC0, 0x3B, 0x01, 0xCC,
0x0E, 0x30, 0x70, 0xC3, 0x83, 0x1C, 0x0C, 0xE0, 0x33, 0x80, 0xDE, 0x03,
0xDC, 0x0E, 0x38, 0x30, 0x60, 0xC1, 0xC3, 0x03, 0x8C, 0x06, 0x30, 0x1C,
0xC0, 0x3B, 0x00, 0x60, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C,
0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00,
0xFF, 0xFF, 0xF0, 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xD0,
0x0F, 0xD8, 0x1B, 0xD8, 0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC,
0x33, 0xC6, 0x63, 0xC6, 0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC1, 0x83, 0xE0, 0x1F, 0x00, 0xFC, 0x07, 0xE0, 0x3D, 0x81, 0xEE,
0x0F, 0x30, 0x79, 0xC3, 0xC6, 0x1E, 0x18, 0xF0, 0xE7, 0x83, 0x3C, 0x1D,
0xE0, 0x6F, 0x01, 0xF8, 0x0F, 0xC0, 0x3E, 0x01, 0xC0, 0x03, 0xE0, 0x0F,
0xFC, 0x0F, 0x07, 0x86, 0x00, 0xC6, 0x00, 0x33, 0x00, 0x1B, 0x00, 0x07,
0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x36, 0x00,
0x33, 0x00, 0x18, 0xC0, 0x18, 0x78, 0x3C, 0x1F, 0xFC, 0x03, 0xF8, 0x00,
0xFF, 0x8F, 0xFE, 0xC0, 0x6C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x07,
0xFF, 0xEF, 0xFC, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00,
0xC0, 0x0C, 0x00, 0x03, 0xE0, 0x0F, 0xFC, 0x0F, 0x07, 0x86, 0x00, 0xC6,
0x00, 0x33, 0x00, 0x1B, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00,
0xF0, 0x00, 0x78, 0x00, 0x36, 0x00, 0x33, 0x01, 0x98, 0xC0, 0xFC, 0x78,
0x3C, 0x1F, 0xFF, 0x03, 0xF9, 0x80, 0x00, 0x40, 0xFF, 0xC3, 0xFF, 0xCC,
0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x0C, 0xFF, 0xE3,
0xFF, 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xB0, 0x06,
0xC0, 0x1B, 0x00, 0x70, 0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x9C, 0x07, 0x60,
0x0D, 0x80, 0x06, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F, 0x80,
0x07, 0xC0, 0x0F, 0x00, 0x3E, 0x00, 0xDE, 0x0E, 0x3F, 0xF0, 0x3F, 0x80,
0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60,
0x06, 0x00, 0x60, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0,
0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01,
0xE0, 0x0F, 0x80, 0xEE, 0x0E, 0x3F, 0xE0, 0x7C, 0x00, 0x60, 0x06, 0xC0,
0x1D, 0xC0, 0x31, 0x80, 0x63, 0x01, 0xC7, 0x03, 0x06, 0x06, 0x0C, 0x1C,
0x1C, 0x30, 0x18, 0x60, 0x31, 0xC0, 0x73, 0x00, 0x66, 0x00, 0xDC, 0x01,
0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0xE0, 0x30, 0x1D, 0x80, 0xE0,
0x76, 0x07, 0x81, 0xD8, 0x1E, 0x06, 0x70, 0x7C, 0x18, 0xC1, 0xB0, 0xE3,
0x0C, 0xC3, 0x8C, 0x33, 0x0C, 0x38, 0xC6, 0x30, 0x67, 0x18, 0xC1, 0x98,
0x67, 0x06, 0x61, 0xD8, 0x1D, 0x83, 0x60, 0x3C, 0x0D, 0x80, 0xF0, 0x3E,
0x03, 0xC0, 0x70, 0x0F, 0x01, 0xC0, 0x18, 0x07, 0x00, 0x70, 0x0E, 0x60,
0x38, 0xE0, 0x60, 0xE1, 0xC0, 0xC3, 0x01, 0xCC, 0x01, 0xF8, 0x01, 0xE0,
0x03, 0x80, 0x07, 0x80, 0x1F, 0x00, 0x33, 0x00, 0xE7, 0x03, 0x86, 0x06,
0x0E, 0x1C, 0x0E, 0x70, 0x0C, 0xC0, 0x1C, 0x60, 0x06, 0x70, 0x0E, 0x30,
0x1C, 0x38, 0x18, 0x1C, 0x38, 0x0C, 0x30, 0x0E, 0x70, 0x06, 0x60, 0x03,
0xC0, 0x03, 0xC0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0xFF, 0xFF, 0xFF, 0xC0, 0x0E,
0x00, 0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x30, 0x03,
0x80, 0x38, 0x03, 0x80, 0x18, 0x01, 0xC0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF,
0xC0, 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF,
0xF0, 0x81, 0x81, 0x02, 0x06, 0x04, 0x08, 0x18, 0x10, 0x20, 0x60, 0x40,
0x81, 0x81, 0x02, 0x06, 0x04, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
0x33, 0x33, 0x33, 0x3F, 0xF0, 0x0C, 0x0E, 0x05, 0x86, 0xC3, 0x21, 0x19,
0x8C, 0x83, 0xC1, 0x80, 0xFF, 0xFE, 0xE3, 0x8C, 0x30, 0x3F, 0x07, 0xF8,
0xE1, 0xCC, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xCF, 0x8C, 0xC0, 0xCC, 0x0C,
0xE3, 0xC7, 0xEF, 0x3C, 0x70, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0,
0x0C, 0xF8, 0xDF, 0xCF, 0x0E, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0,
0x3C, 0x03, 0xE0, 0x6F, 0x0E, 0xDF, 0xCC, 0xF8, 0x1F, 0x0F, 0xE7, 0x1B,
0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x37, 0x1C, 0xFE, 0x1F,
0x00, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x3C, 0xCF, 0xFB, 0x8F,
0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8F, 0x3F,
0x63, 0xCC, 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x3C, 0x07, 0xFF, 0xFF, 0xFE,
0x00, 0xC0, 0x1C, 0x0D, 0xC3, 0x1F, 0xE1, 0xF0, 0x3B, 0xD8, 0xC6, 0x7F,
0xEC, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x00, 0x1E, 0x67, 0xFD, 0xC7,
0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x9F,
0xB1, 0xE6, 0x00, 0xC0, 0x3E, 0x0E, 0x7F, 0xC7, 0xE0, 0xC0, 0x30, 0x0C,
0x03, 0x00, 0xC0, 0x33, 0xCD, 0xFB, 0xC7, 0xE0, 0xF0, 0x3C, 0x0F, 0x03,
0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30, 0xF0, 0x3F, 0xFF, 0xFF,
0xF0, 0x33, 0x00, 0x03, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F,
0xE0, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x83, 0x30, 0xC6, 0x30,
0xCC, 0x1B, 0x83, 0xF0, 0x77, 0x0C, 0x61, 0x8E, 0x30, 0xE6, 0x0C, 0xC1,
0xD8, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xCF, 0x1F, 0x6F, 0xDF, 0xFC,
0x78, 0xFC, 0x18, 0x3C, 0x0C, 0x1E, 0x06, 0x0F, 0x03, 0x07, 0x81, 0x83,
0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78, 0x18, 0x3C, 0x0C, 0x18, 0xCF,
0x37, 0xEF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C,
0x0F, 0x03, 0xC0, 0xC0, 0x1F, 0x07, 0xF1, 0xC7, 0x70, 0x7C, 0x07, 0x80,
0xF0, 0x1E, 0x03, 0xC0, 0x7C, 0x1D, 0xC7, 0x1F, 0xC1, 0xF0, 0xCF, 0x8D,
0xFC, 0xF0, 0xEE, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E,
0x07, 0xF0, 0xEF, 0xFC, 0xCF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x00,
0x1E, 0x67, 0xFD, 0xC7, 0xF0, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0,
0x7C, 0x1D, 0xC7, 0x9F, 0xF1, 0xE6, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60,
0xCF, 0x7F, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, 0x3E, 0x1F,
0xEE, 0x1B, 0x00, 0xC0, 0x3C, 0x07, 0xF0, 0x3E, 0x01, 0xF0, 0x3E, 0x1D,
0xFE, 0x3E, 0x00, 0x63, 0x19, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0xC6, 0x31,
0xE7, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0,
0xF0, 0x7E, 0x3D, 0xFB, 0x3C, 0xC0, 0xE0, 0x66, 0x06, 0x60, 0x67, 0x0C,
0x30, 0xC3, 0x0C, 0x39, 0x81, 0x98, 0x19, 0x81, 0xF0, 0x0F, 0x00, 0xE0,
0x0E, 0x00, 0xC1, 0xC1, 0xB0, 0xE1, 0xD8, 0x70, 0xCC, 0x2C, 0x66, 0x36,
0x31, 0x9B, 0x18, 0xCD, 0x98, 0x64, 0x6C, 0x16, 0x36, 0x0F, 0x1A, 0x07,
0x8F, 0x03, 0x83, 0x80, 0xC1, 0xC0, 0x60, 0xEE, 0x18, 0xC6, 0x0C, 0xC1,
0xF0, 0x1C, 0x01, 0x80, 0x78, 0x1B, 0x03, 0x30, 0xC7, 0x30, 0x66, 0x06,
0xE0, 0x6C, 0x0D, 0x83, 0x38, 0x63, 0x0C, 0x63, 0x0E, 0x60, 0xCC, 0x1B,
0x03, 0x60, 0x3C, 0x07, 0x00, 0xE0, 0x18, 0x03, 0x00, 0xE0, 0x78, 0x0E,
0x00, 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0x03, 0x81, 0xC0, 0x60, 0x30,
0x18, 0x0E, 0x03, 0xFF, 0xFF, 0xC0, 0x19, 0xCC, 0x63, 0x18, 0xC6, 0x31,
0x99, 0x86, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x1C, 0x60, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFC, 0xC7, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x0C, 0x33, 0x31,
0x8C, 0x63, 0x18, 0xC6, 0x73, 0x00, 0x70, 0x3E, 0x09, 0xE4, 0x1F, 0x03,
0x80 };
const GFXglyph FreeSans12pt7bGlyphs[] PROGMEM = {
{ 0, 0, 0, 6, 0, 1 }, // 0x20 ' '
{ 0, 2, 18, 8, 3, -17 }, // 0x21 '!'
{ 5, 6, 6, 8, 1, -16 }, // 0x22 '"'
{ 10, 13, 16, 13, 0, -15 }, // 0x23 '#'
{ 36, 11, 20, 13, 1, -17 }, // 0x24 '$'
{ 64, 20, 17, 21, 1, -16 }, // 0x25 '%'
{ 107, 14, 17, 16, 1, -16 }, // 0x26 '&'
{ 137, 2, 6, 5, 1, -16 }, // 0x27 '''
{ 139, 5, 23, 8, 2, -17 }, // 0x28 '('
{ 154, 5, 23, 8, 1, -17 }, // 0x29 ')'
{ 169, 7, 7, 9, 1, -17 }, // 0x2A '*'
{ 176, 10, 11, 14, 2, -10 }, // 0x2B '+'
{ 190, 2, 6, 7, 2, -1 }, // 0x2C ','
{ 192, 6, 2, 8, 1, -7 }, // 0x2D '-'
{ 194, 2, 2, 6, 2, -1 }, // 0x2E '.'
{ 195, 7, 18, 7, 0, -17 }, // 0x2F '/'
{ 211, 11, 17, 13, 1, -16 }, // 0x30 '0'
{ 235, 5, 17, 13, 3, -16 }, // 0x31 '1'
{ 246, 11, 17, 13, 1, -16 }, // 0x32 '2'
{ 270, 11, 17, 13, 1, -16 }, // 0x33 '3'
{ 294, 11, 17, 13, 1, -16 }, // 0x34 '4'
{ 318, 11, 17, 13, 1, -16 }, // 0x35 '5'
{ 342, 11, 17, 13, 1, -16 }, // 0x36 '6'
{ 366, 11, 17, 13, 1, -16 }, // 0x37 '7'
{ 390, 11, 17, 13, 1, -16 }, // 0x38 '8'
{ 414, 11, 17, 13, 1, -16 }, // 0x39 '9'
{ 438, 2, 13, 6, 2, -12 }, // 0x3A ':'
{ 442, 2, 16, 6, 2, -11 }, // 0x3B ';'
{ 446, 12, 12, 14, 1, -11 }, // 0x3C '<'
{ 464, 12, 6, 14, 1, -8 }, // 0x3D '='
{ 473, 12, 12, 14, 1, -11 }, // 0x3E '>'
{ 491, 10, 18, 13, 2, -17 }, // 0x3F '?'
{ 514, 22, 21, 24, 1, -17 }, // 0x40 '@'
{ 572, 16, 18, 16, 0, -17 }, // 0x41 'A'
{ 608, 13, 18, 16, 2, -17 }, // 0x42 'B'
{ 638, 15, 18, 17, 1, -17 }, // 0x43 'C'
{ 672, 14, 18, 17, 2, -17 }, // 0x44 'D'
{ 704, 12, 18, 15, 2, -17 }, // 0x45 'E'
{ 731, 11, 18, 14, 2, -17 }, // 0x46 'F'
{ 756, 16, 18, 18, 1, -17 }, // 0x47 'G'
{ 792, 13, 18, 17, 2, -17 }, // 0x48 'H'
{ 822, 2, 18, 7, 2, -17 }, // 0x49 'I'
{ 827, 9, 18, 13, 1, -17 }, // 0x4A 'J'
{ 848, 14, 18, 16, 2, -17 }, // 0x4B 'K'
{ 880, 10, 18, 14, 2, -17 }, // 0x4C 'L'
{ 903, 16, 18, 20, 2, -17 }, // 0x4D 'M'
{ 939, 13, 18, 18, 2, -17 }, // 0x4E 'N'
{ 969, 17, 18, 19, 1, -17 }, // 0x4F 'O'
{ 1008, 12, 18, 16, 2, -17 }, // 0x50 'P'
{ 1035, 17, 19, 19, 1, -17 }, // 0x51 'Q'
{ 1076, 14, 18, 17, 2, -17 }, // 0x52 'R'
{ 1108, 14, 18, 16, 1, -17 }, // 0x53 'S'
{ 1140, 12, 18, 15, 1, -17 }, // 0x54 'T'
{ 1167, 13, 18, 17, 2, -17 }, // 0x55 'U'
{ 1197, 15, 18, 15, 0, -17 }, // 0x56 'V'
{ 1231, 22, 18, 22, 0, -17 }, // 0x57 'W'
{ 1281, 15, 18, 16, 0, -17 }, // 0x58 'X'
{ 1315, 16, 18, 16, 0, -17 }, // 0x59 'Y'
{ 1351, 13, 18, 15, 1, -17 }, // 0x5A 'Z'
{ 1381, 4, 23, 7, 2, -17 }, // 0x5B '['
{ 1393, 7, 18, 7, 0, -17 }, // 0x5C '\'
{ 1409, 4, 23, 7, 1, -17 }, // 0x5D ']'
{ 1421, 9, 9, 11, 1, -16 }, // 0x5E '^'
{ 1432, 15, 1, 13, -1, 4 }, // 0x5F '_'
{ 1434, 5, 4, 6, 1, -17 }, // 0x60 '`'
{ 1437, 12, 13, 13, 1, -12 }, // 0x61 'a'
{ 1457, 12, 18, 13, 1, -17 }, // 0x62 'b'
{ 1484, 10, 13, 12, 1, -12 }, // 0x63 'c'
{ 1501, 11, 18, 13, 1, -17 }, // 0x64 'd'
{ 1526, 11, 13, 13, 1, -12 }, // 0x65 'e'
{ 1544, 5, 18, 7, 1, -17 }, // 0x66 'f'
{ 1556, 11, 18, 13, 1, -12 }, // 0x67 'g'
{ 1581, 10, 18, 13, 1, -17 }, // 0x68 'h'
{ 1604, 2, 18, 5, 2, -17 }, // 0x69 'i'
{ 1609, 4, 23, 6, 0, -17 }, // 0x6A 'j'
{ 1621, 11, 18, 12, 1, -17 }, // 0x6B 'k'
{ 1646, 2, 18, 5, 1, -17 }, // 0x6C 'l'
{ 1651, 17, 13, 19, 1, -12 }, // 0x6D 'm'
{ 1679, 10, 13, 13, 1, -12 }, // 0x6E 'n'
{ 1696, 11, 13, 13, 1, -12 }, // 0x6F 'o'
{ 1714, 12, 17, 13, 1, -12 }, // 0x70 'p'
{ 1740, 11, 17, 13, 1, -12 }, // 0x71 'q'
{ 1764, 6, 13, 8, 1, -12 }, // 0x72 'r'
{ 1774, 10, 13, 12, 1, -12 }, // 0x73 's'
{ 1791, 5, 16, 7, 1, -15 }, // 0x74 't'
{ 1801, 10, 13, 13, 1, -12 }, // 0x75 'u'
{ 1818, 12, 13, 12, 0, -12 }, // 0x76 'v'
{ 1838, 17, 13, 17, 0, -12 }, // 0x77 'w'
{ 1866, 11, 13, 11, 0, -12 }, // 0x78 'x'
{ 1884, 11, 18, 11, 0, -12 }, // 0x79 'y'
{ 1909, 10, 13, 12, 1, -12 }, // 0x7A 'z'
{ 1926, 5, 23, 8, 1, -17 }, // 0x7B '{'
{ 1941, 2, 23, 6, 2, -17 }, // 0x7C '|'
{ 1947, 5, 23, 8, 2, -17 }, // 0x7D '}'
{ 1962, 10, 5, 12, 1, -10 } }; // 0x7E '~'
const GFXfont FreeSans12pt7b PROGMEM = {
(uint8_t *)FreeSans12pt7bBitmaps,
(GFXglyph *)FreeSans12pt7bGlyphs,
0x20, 0x7E, 29 };
// Approx. 2641 bytes

View File

@ -0,0 +1,201 @@
const uint8_t FreeSans9pt7bBitmaps[] PROGMEM = {
0xFF, 0xFF, 0xF8, 0xC0, 0xDE, 0xF7, 0x20, 0x09, 0x86, 0x41, 0x91, 0xFF,
0x13, 0x04, 0xC3, 0x20, 0xC8, 0xFF, 0x89, 0x82, 0x61, 0x90, 0x10, 0x1F,
0x14, 0xDA, 0x3D, 0x1E, 0x83, 0x40, 0x78, 0x17, 0x08, 0xF4, 0x7A, 0x35,
0x33, 0xF0, 0x40, 0x20, 0x38, 0x10, 0xEC, 0x20, 0xC6, 0x20, 0xC6, 0x40,
0xC6, 0x40, 0x6C, 0x80, 0x39, 0x00, 0x01, 0x3C, 0x02, 0x77, 0x02, 0x63,
0x04, 0x63, 0x04, 0x77, 0x08, 0x3C, 0x0E, 0x06, 0x60, 0xCC, 0x19, 0x81,
0xE0, 0x18, 0x0F, 0x03, 0x36, 0xC2, 0xD8, 0x73, 0x06, 0x31, 0xE3, 0xC4,
0xFE, 0x13, 0x26, 0x6C, 0xCC, 0xCC, 0xC4, 0x66, 0x23, 0x10, 0x8C, 0x46,
0x63, 0x33, 0x33, 0x32, 0x66, 0x4C, 0x80, 0x25, 0x7E, 0xA5, 0x00, 0x30,
0xC3, 0x3F, 0x30, 0xC3, 0x0C, 0xD6, 0xF0, 0xC0, 0x08, 0x44, 0x21, 0x10,
0x84, 0x42, 0x11, 0x08, 0x00, 0x3C, 0x66, 0x42, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC3, 0x42, 0x66, 0x3C, 0x11, 0x3F, 0x33, 0x33, 0x33, 0x33,
0x30, 0x3E, 0x31, 0xB0, 0x78, 0x30, 0x18, 0x1C, 0x1C, 0x1C, 0x18, 0x18,
0x10, 0x08, 0x07, 0xF8, 0x3C, 0x66, 0xC3, 0xC3, 0x03, 0x06, 0x1C, 0x07,
0x03, 0xC3, 0xC3, 0x66, 0x3C, 0x0C, 0x18, 0x71, 0x62, 0xC9, 0xA3, 0x46,
0xFE, 0x18, 0x30, 0x60, 0xC0, 0x7F, 0x20, 0x10, 0x08, 0x08, 0x07, 0xF3,
0x8C, 0x03, 0x01, 0x80, 0xF0, 0x6C, 0x63, 0xE0, 0x1E, 0x31, 0x98, 0x78,
0x0C, 0x06, 0xF3, 0x8D, 0x83, 0xC1, 0xE0, 0xD0, 0x6C, 0x63, 0xE0, 0xFF,
0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x18, 0x18, 0x18, 0x10, 0x30, 0x30,
0x3E, 0x31, 0xB0, 0x78, 0x3C, 0x1B, 0x18, 0xF8, 0xC6, 0xC1, 0xE0, 0xF0,
0x6C, 0x63, 0xE0, 0x3C, 0x66, 0xC2, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x03,
0x03, 0xC2, 0x66, 0x3C, 0xC0, 0x00, 0x30, 0xC0, 0x00, 0x00, 0x64, 0xA0,
0x00, 0x81, 0xC7, 0x8E, 0x0C, 0x07, 0x80, 0x70, 0x0E, 0x01, 0x80, 0xFF,
0x80, 0x00, 0x1F, 0xF0, 0x00, 0x70, 0x0E, 0x01, 0xC0, 0x18, 0x38, 0x71,
0xC0, 0x80, 0x00, 0x3E, 0x31, 0xB0, 0x78, 0x30, 0x18, 0x18, 0x38, 0x18,
0x18, 0x0C, 0x00, 0x00, 0x01, 0x80, 0x03, 0xF0, 0x06, 0x0E, 0x06, 0x01,
0x86, 0x00, 0x66, 0x1D, 0xBB, 0x31, 0xCF, 0x18, 0xC7, 0x98, 0x63, 0xCC,
0x31, 0xE6, 0x11, 0xB3, 0x99, 0xCC, 0xF7, 0x86, 0x00, 0x01, 0x80, 0x00,
0x70, 0x40, 0x0F, 0xE0, 0x06, 0x00, 0xF0, 0x0F, 0x00, 0x90, 0x19, 0x81,
0x98, 0x10, 0x83, 0x0C, 0x3F, 0xC2, 0x04, 0x60, 0x66, 0x06, 0xC0, 0x30,
0xFF, 0x18, 0x33, 0x03, 0x60, 0x6C, 0x0D, 0x83, 0x3F, 0xC6, 0x06, 0xC0,
0x78, 0x0F, 0x01, 0xE0, 0x6F, 0xF8, 0x1F, 0x86, 0x19, 0x81, 0xA0, 0x3C,
0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x68, 0x0D, 0x83, 0x18, 0x61, 0xF0,
0xFF, 0x18, 0x33, 0x03, 0x60, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0,
0x78, 0x0F, 0x03, 0x60, 0xCF, 0xF0, 0xFF, 0xE0, 0x30, 0x18, 0x0C, 0x06,
0x03, 0xFD, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0F, 0xF8, 0xFF, 0xC0, 0xC0,
0xC0, 0xC0, 0xC0, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x0F, 0x83,
0x0E, 0x60, 0x66, 0x03, 0xC0, 0x0C, 0x00, 0xC1, 0xFC, 0x03, 0xC0, 0x36,
0x03, 0x60, 0x73, 0x0F, 0x0F, 0x10, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C,
0x07, 0x80, 0xFF, 0xFE, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x06,
0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x07,
0x8F, 0x1E, 0x27, 0x80, 0xC0, 0xD8, 0x33, 0x0C, 0x63, 0x0C, 0xC1, 0xB8,
0x3F, 0x07, 0x30, 0xC3, 0x18, 0x63, 0x06, 0x60, 0x6C, 0x0C, 0xC0, 0xC0,
0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF, 0xE0,
0x3F, 0x01, 0xFC, 0x1F, 0xE0, 0xFD, 0x05, 0xEC, 0x6F, 0x63, 0x79, 0x13,
0xCD, 0x9E, 0x6C, 0xF1, 0x47, 0x8E, 0x3C, 0x71, 0x80, 0xE0, 0x7C, 0x0F,
0xC1, 0xE8, 0x3D, 0x87, 0x98, 0xF1, 0x1E, 0x33, 0xC3, 0x78, 0x6F, 0x07,
0xE0, 0x7C, 0x0E, 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0, 0x6C, 0x01, 0xE0,
0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x0C, 0x60, 0xC0, 0xF8,
0x00, 0xFF, 0x30, 0x6C, 0x0F, 0x03, 0xC0, 0xF0, 0x6F, 0xF3, 0x00, 0xC0,
0x30, 0x0C, 0x03, 0x00, 0xC0, 0x00, 0x0F, 0x81, 0x83, 0x18, 0x0C, 0xC0,
0x6C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x01, 0x98, 0x6C,
0x60, 0xC0, 0xFB, 0x00, 0x08, 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0,
0x6C, 0x0C, 0xFF, 0x8C, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x6C, 0x06, 0xC0,
0x70, 0x3F, 0x18, 0x6C, 0x0F, 0x03, 0xC0, 0x1E, 0x01, 0xF0, 0x0E, 0x00,
0xF0, 0x3C, 0x0D, 0x86, 0x3F, 0x00, 0xFF, 0x86, 0x03, 0x01, 0x80, 0xC0,
0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0xC0, 0x78, 0x0F,
0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01,
0xB0, 0x61, 0xF0, 0xC0, 0x6C, 0x0D, 0x81, 0x10, 0x63, 0x0C, 0x61, 0x04,
0x60, 0xCC, 0x19, 0x01, 0x60, 0x3C, 0x07, 0x00, 0x60, 0xC1, 0x81, 0x30,
0xE1, 0x98, 0x70, 0xCC, 0x28, 0x66, 0x26, 0x21, 0x13, 0x30, 0xC8, 0x98,
0x6C, 0x4C, 0x14, 0x34, 0x0A, 0x1A, 0x07, 0x07, 0x03, 0x03, 0x80, 0x81,
0x80, 0x60, 0x63, 0x0C, 0x30, 0xC1, 0x98, 0x0F, 0x00, 0xE0, 0x06, 0x00,
0xF0, 0x19, 0x01, 0x98, 0x30, 0xC6, 0x0E, 0x60, 0x60, 0xC0, 0x36, 0x06,
0x30, 0xC3, 0x0C, 0x19, 0x81, 0xD8, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60,
0x06, 0x00, 0x60, 0x06, 0x00, 0xFF, 0xC0, 0x60, 0x30, 0x0C, 0x06, 0x03,
0x01, 0xC0, 0x60, 0x30, 0x18, 0x06, 0x03, 0x00, 0xFF, 0xC0, 0xFB, 0x6D,
0xB6, 0xDB, 0x6D, 0xB6, 0xE0, 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84,
0x10, 0x80, 0xED, 0xB6, 0xDB, 0x6D, 0xB6, 0xDB, 0xE0, 0x30, 0x60, 0xA2,
0x44, 0xD8, 0xA1, 0x80, 0xFF, 0xC0, 0xC6, 0x30, 0x7E, 0x71, 0xB0, 0xC0,
0x60, 0xF3, 0xDB, 0x0D, 0x86, 0xC7, 0x3D, 0xC0, 0xC0, 0x60, 0x30, 0x1B,
0xCE, 0x36, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x7C, 0x6D, 0xE0, 0x3C,
0x66, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x66, 0x3C, 0x03, 0x03, 0x03,
0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67, 0x3B, 0x3C, 0x66,
0xC3, 0xC3, 0xFF, 0xC0, 0xC0, 0xC3, 0x66, 0x3C, 0x36, 0x6F, 0x66, 0x66,
0x66, 0x66, 0x60, 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x67,
0x3B, 0x03, 0x03, 0xC6, 0x7C, 0xC0, 0xC0, 0xC0, 0xDE, 0xE3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xC0, 0x30, 0x03,
0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xE0, 0xC0, 0x60, 0x30, 0x18, 0x4C,
0x46, 0x63, 0x61, 0xF0, 0xEC, 0x62, 0x31, 0x98, 0x6C, 0x30, 0xFF, 0xFF,
0xFF, 0xC0, 0xDE, 0xF7, 0x1C, 0xF0, 0xC7, 0x86, 0x3C, 0x31, 0xE1, 0x8F,
0x0C, 0x78, 0x63, 0xC3, 0x1E, 0x18, 0xC0, 0xDE, 0xE3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x3C, 0x66, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0x66, 0x3C, 0xDE, 0x71, 0xB0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x83,
0xE3, 0x6F, 0x30, 0x18, 0x0C, 0x00, 0x3B, 0x67, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0x67, 0x3B, 0x03, 0x03, 0x03, 0xDF, 0x31, 0x8C, 0x63, 0x18,
0xC6, 0x00, 0x3E, 0xE3, 0xC0, 0xC0, 0xE0, 0x3C, 0x07, 0xC3, 0xE3, 0x7E,
0x66, 0xF6, 0x66, 0x66, 0x66, 0x67, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC7, 0x7B, 0xC1, 0xA0, 0x98, 0xCC, 0x42, 0x21, 0xB0, 0xD0,
0x28, 0x1C, 0x0C, 0x00, 0xC6, 0x1E, 0x38, 0x91, 0xC4, 0xCA, 0x66, 0xD3,
0x16, 0xD0, 0xA6, 0x87, 0x1C, 0x38, 0xC0, 0xC6, 0x00, 0x43, 0x62, 0x36,
0x1C, 0x18, 0x1C, 0x3C, 0x26, 0x62, 0x43, 0xC1, 0x21, 0x98, 0xCC, 0x42,
0x61, 0xB0, 0xD0, 0x38, 0x1C, 0x0C, 0x06, 0x03, 0x01, 0x03, 0x00, 0xFE,
0x0C, 0x30, 0xC1, 0x86, 0x18, 0x20, 0xC1, 0xFC, 0x36, 0x66, 0x66, 0x6E,
0xCE, 0x66, 0x66, 0x66, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xC6, 0x66,
0x66, 0x67, 0x37, 0x66, 0x66, 0x66, 0xC0, 0x61, 0x24, 0x38 };
const GFXglyph FreeSans9pt7bGlyphs[] PROGMEM = {
{ 0, 0, 0, 5, 0, 1 }, // 0x20 ' '
{ 0, 2, 13, 6, 2, -12 }, // 0x21 '!'
{ 4, 5, 4, 6, 1, -12 }, // 0x22 '"'
{ 7, 10, 12, 10, 0, -11 }, // 0x23 '#'
{ 22, 9, 16, 10, 1, -13 }, // 0x24 '$'
{ 40, 16, 13, 16, 1, -12 }, // 0x25 '%'
{ 66, 11, 13, 12, 1, -12 }, // 0x26 '&'
{ 84, 2, 4, 4, 1, -12 }, // 0x27 '''
{ 85, 4, 17, 6, 1, -12 }, // 0x28 '('
{ 94, 4, 17, 6, 1, -12 }, // 0x29 ')'
{ 103, 5, 5, 7, 1, -12 }, // 0x2A '*'
{ 107, 6, 8, 11, 3, -7 }, // 0x2B '+'
{ 113, 2, 4, 5, 2, 0 }, // 0x2C ','
{ 114, 4, 1, 6, 1, -4 }, // 0x2D '-'
{ 115, 2, 1, 5, 1, 0 }, // 0x2E '.'
{ 116, 5, 13, 5, 0, -12 }, // 0x2F '/'
{ 125, 8, 13, 10, 1, -12 }, // 0x30 '0'
{ 138, 4, 13, 10, 3, -12 }, // 0x31 '1'
{ 145, 9, 13, 10, 1, -12 }, // 0x32 '2'
{ 160, 8, 13, 10, 1, -12 }, // 0x33 '3'
{ 173, 7, 13, 10, 2, -12 }, // 0x34 '4'
{ 185, 9, 13, 10, 1, -12 }, // 0x35 '5'
{ 200, 9, 13, 10, 1, -12 }, // 0x36 '6'
{ 215, 8, 13, 10, 0, -12 }, // 0x37 '7'
{ 228, 9, 13, 10, 1, -12 }, // 0x38 '8'
{ 243, 8, 13, 10, 1, -12 }, // 0x39 '9'
{ 256, 2, 10, 5, 1, -9 }, // 0x3A ':'
{ 259, 3, 12, 5, 1, -8 }, // 0x3B ';'
{ 264, 9, 9, 11, 1, -8 }, // 0x3C '<'
{ 275, 9, 4, 11, 1, -5 }, // 0x3D '='
{ 280, 9, 9, 11, 1, -8 }, // 0x3E '>'
{ 291, 9, 13, 10, 1, -12 }, // 0x3F '?'
{ 306, 17, 16, 18, 1, -12 }, // 0x40 '@'
{ 340, 12, 13, 12, 0, -12 }, // 0x41 'A'
{ 360, 11, 13, 12, 1, -12 }, // 0x42 'B'
{ 378, 11, 13, 13, 1, -12 }, // 0x43 'C'
{ 396, 11, 13, 13, 1, -12 }, // 0x44 'D'
{ 414, 9, 13, 11, 1, -12 }, // 0x45 'E'
{ 429, 8, 13, 11, 1, -12 }, // 0x46 'F'
{ 442, 12, 13, 14, 1, -12 }, // 0x47 'G'
{ 462, 11, 13, 13, 1, -12 }, // 0x48 'H'
{ 480, 2, 13, 5, 2, -12 }, // 0x49 'I'
{ 484, 7, 13, 10, 1, -12 }, // 0x4A 'J'
{ 496, 11, 13, 12, 1, -12 }, // 0x4B 'K'
{ 514, 8, 13, 10, 1, -12 }, // 0x4C 'L'
{ 527, 13, 13, 15, 1, -12 }, // 0x4D 'M'
{ 549, 11, 13, 13, 1, -12 }, // 0x4E 'N'
{ 567, 13, 13, 14, 1, -12 }, // 0x4F 'O'
{ 589, 10, 13, 12, 1, -12 }, // 0x50 'P'
{ 606, 13, 14, 14, 1, -12 }, // 0x51 'Q'
{ 629, 12, 13, 13, 1, -12 }, // 0x52 'R'
{ 649, 10, 13, 12, 1, -12 }, // 0x53 'S'
{ 666, 9, 13, 11, 1, -12 }, // 0x54 'T'
{ 681, 11, 13, 13, 1, -12 }, // 0x55 'U'
{ 699, 11, 13, 12, 0, -12 }, // 0x56 'V'
{ 717, 17, 13, 17, 0, -12 }, // 0x57 'W'
{ 745, 12, 13, 12, 0, -12 }, // 0x58 'X'
{ 765, 12, 13, 12, 0, -12 }, // 0x59 'Y'
{ 785, 10, 13, 11, 1, -12 }, // 0x5A 'Z'
{ 802, 3, 17, 5, 1, -12 }, // 0x5B '['
{ 809, 5, 13, 5, 0, -12 }, // 0x5C '\'
{ 818, 3, 17, 5, 0, -12 }, // 0x5D ']'
{ 825, 7, 7, 8, 1, -12 }, // 0x5E '^'
{ 832, 10, 1, 10, 0, 3 }, // 0x5F '_'
{ 834, 4, 3, 5, 0, -12 }, // 0x60 '`'
{ 836, 9, 10, 10, 1, -9 }, // 0x61 'a'
{ 848, 9, 13, 10, 1, -12 }, // 0x62 'b'
{ 863, 8, 10, 9, 1, -9 }, // 0x63 'c'
{ 873, 8, 13, 10, 1, -12 }, // 0x64 'd'
{ 886, 8, 10, 10, 1, -9 }, // 0x65 'e'
{ 896, 4, 13, 5, 1, -12 }, // 0x66 'f'
{ 903, 8, 14, 10, 1, -9 }, // 0x67 'g'
{ 917, 8, 13, 10, 1, -12 }, // 0x68 'h'
{ 930, 2, 13, 4, 1, -12 }, // 0x69 'i'
{ 934, 4, 17, 4, 0, -12 }, // 0x6A 'j'
{ 943, 9, 13, 9, 1, -12 }, // 0x6B 'k'
{ 958, 2, 13, 4, 1, -12 }, // 0x6C 'l'
{ 962, 13, 10, 15, 1, -9 }, // 0x6D 'm'
{ 979, 8, 10, 10, 1, -9 }, // 0x6E 'n'
{ 989, 8, 10, 10, 1, -9 }, // 0x6F 'o'
{ 999, 9, 13, 10, 1, -9 }, // 0x70 'p'
{ 1014, 8, 13, 10, 1, -9 }, // 0x71 'q'
{ 1027, 5, 10, 6, 1, -9 }, // 0x72 'r'
{ 1034, 8, 10, 9, 1, -9 }, // 0x73 's'
{ 1044, 4, 12, 5, 1, -11 }, // 0x74 't'
{ 1050, 8, 10, 10, 1, -9 }, // 0x75 'u'
{ 1060, 9, 10, 9, 0, -9 }, // 0x76 'v'
{ 1072, 13, 10, 13, 0, -9 }, // 0x77 'w'
{ 1089, 8, 10, 9, 0, -9 }, // 0x78 'x'
{ 1099, 9, 14, 9, 0, -9 }, // 0x79 'y'
{ 1115, 7, 10, 9, 1, -9 }, // 0x7A 'z'
{ 1124, 4, 17, 6, 1, -12 }, // 0x7B '{'
{ 1133, 2, 17, 4, 2, -12 }, // 0x7C '|'
{ 1138, 4, 17, 6, 1, -12 }, // 0x7D '}'
{ 1147, 7, 3, 9, 1, -7 } }; // 0x7E '~'
const GFXfont FreeSans9pt7b PROGMEM = {
(uint8_t *)FreeSans9pt7bBitmaps,
(GFXglyph *)FreeSans9pt7bGlyphs,
0x20, 0x7E, 22 };
// Approx. 1822 bytes

View File

@ -0,0 +1,123 @@
// Picopixel by Sebastian Weber. A tiny font
// with all characters within a 6 pixel height.
const uint8_t PicopixelBitmaps[] PROGMEM = {
0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A,
0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60,
0xE0, 0x80, 0x25, 0x48, 0x56, 0xD4, 0x75, 0x40, 0xC5, 0x4E, 0xC5, 0x1C,
0x97, 0x92, 0xF3, 0x1C, 0x53, 0x54, 0xE5, 0x48, 0x55, 0x54, 0x55, 0x94,
0xA0, 0x46, 0x64, 0xE3, 0x80, 0x98, 0xC5, 0x04, 0x56, 0xC6, 0x57, 0xDA,
0xD7, 0x5C, 0x72, 0x46, 0xD6, 0xDC, 0xF3, 0xCE, 0xF3, 0x48, 0x72, 0xD4,
0xB7, 0xDA, 0xF8, 0x24, 0xD4, 0xBB, 0x5A, 0x92, 0x4E, 0x8E, 0xEB, 0x58,
0x80, 0x9D, 0xB9, 0x90, 0x56, 0xD4, 0xD7, 0x48, 0x56, 0xD4, 0x40, 0xD7,
0x5A, 0x71, 0x1C, 0xE9, 0x24, 0xB6, 0xD4, 0xB6, 0xA4, 0x8C, 0x6B, 0x55,
0x00, 0xB5, 0x5A, 0xB5, 0x24, 0xE5, 0x4E, 0xEA, 0xC0, 0x91, 0x12, 0xD5,
0xC0, 0x54, 0xF0, 0x90, 0xC7, 0xF0, 0x93, 0x5E, 0x71, 0x80, 0x25, 0xDE,
0x5E, 0x30, 0x6E, 0x80, 0x77, 0x9C, 0x93, 0x5A, 0xB8, 0x45, 0x60, 0x92,
0xEA, 0xAA, 0x40, 0xD5, 0x6A, 0xD6, 0x80, 0x55, 0x00, 0xD7, 0x40, 0x75,
0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54,
0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A };
const GFXglyph PicopixelGlyphs[] PROGMEM = {
{ 0, 0, 0, 2, 0, 1 }, // 0x20 ' '
{ 0, 1, 5, 2, 0, -4 }, // 0x21 '!'
{ 1, 3, 2, 4, 0, -4 }, // 0x22 '"'
{ 2, 5, 5, 6, 0, -4 }, // 0x23 '#'
{ 6, 3, 6, 4, 0, -4 }, // 0x24 '$'
{ 9, 3, 5, 4, 0, -4 }, // 0x25 '%'
{ 11, 4, 5, 5, 0, -4 }, // 0x26 '&'
{ 14, 1, 2, 2, 0, -4 }, // 0x27 '''
{ 15, 2, 5, 3, 0, -4 }, // 0x28 '('
{ 17, 2, 5, 3, 0, -4 }, // 0x29 ')'
{ 19, 3, 3, 4, 0, -3 }, // 0x2A '*'
{ 21, 3, 3, 4, 0, -3 }, // 0x2B '+'
{ 23, 2, 2, 3, 0, 0 }, // 0x2C ','
{ 24, 3, 1, 4, 0, -2 }, // 0x2D '-'
{ 25, 1, 1, 2, 0, 0 }, // 0x2E '.'
{ 26, 3, 5, 4, 0, -4 }, // 0x2F '/'
{ 28, 3, 5, 4, 0, -4 }, // 0x30 '0'
{ 30, 2, 5, 3, 0, -4 }, // 0x31 '1'
{ 32, 3, 5, 4, 0, -4 }, // 0x32 '2'
{ 34, 3, 5, 4, 0, -4 }, // 0x33 '3'
{ 36, 3, 5, 4, 0, -4 }, // 0x34 '4'
{ 38, 3, 5, 4, 0, -4 }, // 0x35 '5'
{ 40, 3, 5, 4, 0, -4 }, // 0x36 '6'
{ 42, 3, 5, 4, 0, -4 }, // 0x37 '7'
{ 44, 3, 5, 4, 0, -4 }, // 0x38 '8'
{ 46, 3, 5, 4, 0, -4 }, // 0x39 '9'
{ 48, 1, 3, 2, 0, -3 }, // 0x3A ':'
{ 49, 2, 4, 3, 0, -3 }, // 0x3B ';'
{ 50, 2, 3, 3, 0, -3 }, // 0x3C '<'
{ 51, 3, 3, 4, 0, -3 }, // 0x3D '='
{ 53, 2, 3, 3, 0, -3 }, // 0x3E '>'
{ 54, 3, 5, 4, 0, -4 }, // 0x3F '?'
{ 56, 3, 5, 4, 0, -4 }, // 0x40 '@'
{ 58, 3, 5, 4, 0, -4 }, // 0x41 'A'
{ 60, 3, 5, 4, 0, -4 }, // 0x42 'B'
{ 62, 3, 5, 4, 0, -4 }, // 0x43 'C'
{ 64, 3, 5, 4, 0, -4 }, // 0x44 'D'
{ 66, 3, 5, 4, 0, -4 }, // 0x45 'E'
{ 68, 3, 5, 4, 0, -4 }, // 0x46 'F'
{ 70, 3, 5, 4, 0, -4 }, // 0x47 'G'
{ 72, 3, 5, 4, 0, -4 }, // 0x48 'H'
{ 74, 1, 5, 2, 0, -4 }, // 0x49 'I'
{ 75, 3, 5, 4, 0, -4 }, // 0x4A 'J'
{ 77, 3, 5, 4, 0, -4 }, // 0x4B 'K'
{ 79, 3, 5, 4, 0, -4 }, // 0x4C 'L'
{ 81, 5, 5, 6, 0, -4 }, // 0x4D 'M'
{ 85, 4, 5, 5, 0, -4 }, // 0x4E 'N'
{ 88, 3, 5, 4, 0, -4 }, // 0x4F 'O'
{ 90, 3, 5, 4, 0, -4 }, // 0x50 'P'
{ 92, 3, 6, 4, 0, -4 }, // 0x51 'Q'
{ 95, 3, 5, 4, 0, -4 }, // 0x52 'R'
{ 97, 3, 5, 4, 0, -4 }, // 0x53 'S'
{ 99, 3, 5, 4, 0, -4 }, // 0x54 'T'
{ 101, 3, 5, 4, 0, -4 }, // 0x55 'U'
{ 103, 3, 5, 4, 0, -4 }, // 0x56 'V'
{ 105, 5, 5, 6, 0, -4 }, // 0x57 'W'
{ 109, 3, 5, 4, 0, -4 }, // 0x58 'X'
{ 111, 3, 5, 4, 0, -4 }, // 0x59 'Y'
{ 113, 3, 5, 4, 0, -4 }, // 0x5A 'Z'
{ 115, 2, 5, 3, 0, -4 }, // 0x5B '['
{ 117, 3, 5, 4, 0, -4 }, // 0x5C '\'
{ 119, 2, 5, 3, 0, -4 }, // 0x5D ']'
{ 121, 3, 2, 4, 0, -4 }, // 0x5E '^'
{ 122, 4, 1, 4, 0, 1 }, // 0x5F '_'
{ 123, 2, 2, 3, 0, -4 }, // 0x60 '`'
{ 124, 3, 4, 4, 0, -3 }, // 0x61 'a'
{ 126, 3, 5, 4, 0, -4 }, // 0x62 'b'
{ 128, 3, 3, 4, 0, -2 }, // 0x63 'c'
{ 130, 3, 5, 4, 0, -4 }, // 0x64 'd'
{ 132, 3, 4, 4, 0, -3 }, // 0x65 'e'
{ 134, 2, 5, 3, 0, -4 }, // 0x66 'f'
{ 136, 3, 5, 4, 0, -3 }, // 0x67 'g'
{ 138, 3, 5, 4, 0, -4 }, // 0x68 'h'
{ 140, 1, 5, 2, 0, -4 }, // 0x69 'i'
{ 141, 2, 6, 3, 0, -4 }, // 0x6A 'j'
{ 143, 3, 5, 4, 0, -4 }, // 0x6B 'k'
{ 145, 2, 5, 3, 0, -4 }, // 0x6C 'l'
{ 147, 5, 3, 6, 0, -2 }, // 0x6D 'm'
{ 149, 3, 3, 4, 0, -2 }, // 0x6E 'n'
{ 151, 3, 3, 4, 0, -2 }, // 0x6F 'o'
{ 153, 3, 4, 4, 0, -2 }, // 0x70 'p'
{ 155, 3, 4, 4, 0, -2 }, // 0x71 'q'
{ 157, 2, 3, 3, 0, -2 }, // 0x72 'r'
{ 158, 3, 4, 4, 0, -3 }, // 0x73 's'
{ 160, 2, 5, 3, 0, -4 }, // 0x74 't'
{ 162, 3, 3, 4, 0, -2 }, // 0x75 'u'
{ 164, 3, 3, 4, 0, -2 }, // 0x76 'v'
{ 166, 5, 3, 6, 0, -2 }, // 0x77 'w'
{ 168, 3, 3, 4, 0, -2 }, // 0x78 'x'
{ 170, 3, 4, 4, 0, -2 }, // 0x79 'y'
{ 172, 3, 4, 4, 0, -3 }, // 0x7A 'z'
{ 174, 3, 5, 4, 0, -4 }, // 0x7B '{'
{ 176, 1, 6, 2, 0, -4 }, // 0x7C '|'
{ 177, 3, 5, 4, 0, -4 }, // 0x7D '}'
{ 179, 4, 2, 5, 0, -3 } }; // 0x7E '~'
const GFXfont Picopixel PROGMEM = {
(uint8_t *)PicopixelBitmaps,
(GFXglyph *)PicopixelGlyphs,
0x20, 0x7E, 7 };
// Approx. 852 bytes

230
libraries/SondeLib/geteph.cpp Executable file
View File

@ -0,0 +1,230 @@
#include "time.h"
#include "geteph.h"
#include <SPIFFS.h>
#include <WiFi.h>
#include <rom/miniz.h>
#include <inttypes.h>
#include <WiFi.h>
#include "Display.h"
extern WiFiClient client;
static const char *ftpserver = "www.ngs.noaa.gov";
char outbuf[128];
uint8_t getreply() {
String s = client.readStringUntil('\n');
Serial.println(s);
const char *str = s.c_str();
if(strlen(str)<4) return 255; // something unusual...
if(str[3]=='-') { // multi-line resonse...
String s2;
const char *str2;
do {
s2 = client.readStringUntil('\n');
Serial.println(s2);
str2 = s2.c_str();
if(strlen(str2)<4) return 255; // something is wrong
} while( str[0]!=str2[0] || str[1]!=str2[1] || str[2]!=str2[2] || str2[3]!=' ' );
}
return str[0];
}
void writeFully(File &file, uint8_t *buf, size_t len)
{
size_t olen;
while(len) {
olen = file.write(buf, len);
Serial.printf("written: %d of %d\n", olen, len);
len -= olen;
buf += olen;
}
}
void geteph() {
// Set current time via network...
struct tm tinfo;
configTime(0, 0, "pool.ntp.org");
bool ok = getLocalTime(&tinfo, 2000); // wait max 2 seconds to get current time via ntp
if(!ok) {
Serial.println("Failed to get current date/time");
return;
}
// Check time of last update
int year = tinfo.tm_year + 1900;
int day = tinfo.tm_yday + 1;
Serial.printf("year %d, day %d\n", year, day);
char nowstr[20];
snprintf(nowstr, 20, "%04d%03d%02d", year, day, tinfo.tm_hour);
File status = SPIFFS.open("/brdc.time", "r");
if(status) {
String ts = status.readStringUntil('\n');
const char *tsstr = ts.c_str();
if(tsstr && strlen(tsstr)>=9) {
if(strcmp(nowstr, ts.c_str())<=0) {
Serial.println("local brdc is up to date\n");
return;
}
}
Serial.printf("now: %s, existing: %s => updating\n", nowstr, tsstr);
}
status.close();
disp.rdis->clear();
disp.rdis->setFont(FONT_SMALL);
disp.rdis->drawString(0, 0, "FTP ngs.noaa.gov");
// fetch rinex from server
File fh = SPIFFS.open("/brdc.gz","w");
if(!fh) {
Serial.println("cannot open file\n");
return;
}
char buf[252];
snprintf(buf, 128, "/cors/rinex/%04d/%03d/brdc%03d0.%02dn.gz", year, day, day, year-2000);
Serial.println("running geteph\n");
disp.rdis->drawString(0, 1, buf+21);
if(!client.connect(ftpserver, 21)) {
Serial.println("FTP connection to www.ngs.noaa.gov failed");
return;
}
#if 0
while(!client.available()) delay(1);
while(client.available()) {
String s = client.readStringUntil('\n');
Serial.println(s);
}
#endif
if(getreply()>='4') { Serial.println("connected failed"); return; }
client.print("USER anonymous\r\n");
if(getreply()>='4') { Serial.println("USER failed"); return; }
client.print("PASS anonymous\r\n");
if(getreply()>='4') { Serial.println("PASS failed"); return; }
client.print("TYPE I\r\n");
if(getreply()>='4') { Serial.println("TYPE I failed"); return; }
client.print("PASV\r\n");
String s = client.readStringUntil('\n');
Serial.println(s);
if(s.c_str()[0]>='4') { Serial.println("PASV failed"); return; }
int array_pasv[6];
char *tStr = strtok((char *)s.c_str(), "(,");
for(int i=0; i<6; i++) {
tStr = strtok(NULL, "(,");
if(tStr==NULL) {
Serial.println("strange response to PASV");
return;
}
array_pasv[i] = atoi(tStr);
Serial.println(array_pasv[i]);
}
uint16_t port = (array_pasv[4]<<8) | (array_pasv[5]&0xff);
WiFiClient dclient;
Serial.printf("connecting to %s:%d\n", ftpserver,port);
dclient.connect(ftpserver, port);
if(!dclient) {
Serial.println("data connection failed");
return;
}
client.print("RETR ");
Serial.printf("fetching %s with FTP...\n", buf);
client.println(buf);
s = client.readStringUntil('\n');
Serial.println(s);
if(s.c_str()[0]>='4') { Serial.println("RETR failed"); return; }
int len=0;
while(dclient.connected()) {
while(dclient.available()) {
int c = dclient.read();
if(c==-1) {
Serial.println("dclient.read() returned -1 inspite of available() being true?!");
} else {
fh.write(c);
len++;
}
}
}
Serial.printf("fetched %d bytes\n", len);
fh.close();
snprintf(buf, 16, "Fetched %d B ",len);
buf[16]=0;
disp.rdis->drawString(0,2,buf);
disp.rdis->drawString(0,4,"Decompressing...");
// decompression
tinfl_decompressor *decomp = (tinfl_decompressor *)malloc(sizeof(tinfl_decompressor));
tinfl_init(decomp);
File file = SPIFFS.open("/brdc.gz","r");
if(!file) {
Serial.println("cannot open file\n");
return;
}
File ofile = SPIFFS.open("/brdc", "w");
if(!ofile) {
Serial.println("cannot open file /brdc for writing");
return;
}
file.readBytes(buf, 10); // skip gzip header
char flags = buf[3];
if(flags&0x07) {
Serial.println("Unsupported flags in gzip header, may or may not cause a problem");
}
if(flags&0x08) { // skip file name extra header
do {
int res=file.readBytes(buf, 1);
if(res!=1) return;
} while(*buf);
}
if(flags&0x10) { // skip file name extra header
do {
int res=file.readBytes(buf, 1);
if(res!=1) return;
} while(*buf);
}
int opos = 0;
int total = 0;
Serial.println("Decompressing ephemeris data...\n");
char *obuf =(char *)malloc(32768);
char *ibuf =(char *)malloc(8192);
while(file.available()) {
size_t len = file.readBytes(ibuf, 8192);
size_t inofs = 0;
size_t inlen = len;
while(inofs<len) {
size_t outlen=32768-opos;
int res = tinfl_decompress(decomp, (const mz_uint8 *)ibuf+inofs, &inlen, (uint8_t *)obuf, (mz_uint8 *)obuf+opos, &outlen, TINFL_FLAG_HAS_MORE_INPUT);
if(res<0) break;
if(outlen==0) break;
Serial.printf("... (res=%d) decompressed %d into %d bytes\n", res, inlen, outlen);
inofs += inlen;
inlen = len - inofs;
//size_t retv = ofile.write((uint8_t *)(obuf+opos), outlen);
//Serial.printf("write %d bytes\n", retv);
writeFully(ofile, (uint8_t *)(obuf+opos), outlen);
//Serial.write((uint8_t *)(obuf+opos), outlen);
total += outlen;
opos += outlen;
if(res==0) break; // done indication
if(opos>=32768) {
Serial.printf("... decompressed %d bytes\n", total);
opos=0;
}
}
}
// maybe todo: check crc?!?
Serial.printf("done extracing content (total length: %d)\n", total);
status = SPIFFS.open("/brdc.time","w");
status.println(nowstr);
status.close();
snprintf(buf, 16, "Done: %d B ",total);
buf[16]=0;
disp.rdis->drawString(0,5,buf);
delay(1000);
free(obuf);
free(ibuf);
free(decomp);
file.close();
ofile.close();
}

3
libraries/SondeLib/geteph.h Executable file
View File

@ -0,0 +1,3 @@
void geteph();

26
libraries/SondeLib/gfxfont.h Executable file
View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-3.0
// original source: https://github.com/Nkawu/TFT_22_ILI9225
// Font structures like Adafruit_GFX (1.1 and later).
// Example fonts are included in 'fonts' directory.
// To use a font in your Arduino sketch, #include the corresponding .h
// file and pass address of GFXfont struct to setFont().
#ifndef _GFFFONT_H_
#define _GFFFONT_H_
typedef struct { // Data stored PER GLYPH
uint16_t bitmapOffset; // Pointer into GFXfont->bitmap
uint8_t width, height; // Bitmap dimensions in pixels
uint8_t xAdvance; // Distance to advance cursor (x axis)
int8_t xOffset, yOffset; // Dist from cursor pos to UL corner
} GFXglyph;
typedef struct { // Data stored for FONT AS A WHOLE:
uint8_t *bitmap; // Glyph bitmaps, concatenated
GFXglyph *glyph; // Glyph array
uint8_t first, last; // ASCII extents
uint8_t yAdvance; // Newline distance (y axis)
} GFXfont;
#endif // _GFFFONT_H_

1866
libraries/SondeLib/nav_gps_vel.cpp Executable file

File diff suppressed because it is too large Load Diff

148
libraries/SondeLib/nav_gps_vel.h Executable file
View File

@ -0,0 +1,148 @@
typedef struct {
uint16_t prn;
uint16_t week;
uint32_t toa;
char epoch[20];
double toe;
double toc;
double e;
double delta_n;
double delta_i;
double i0;
double OmegaDot;
double sqrta;
double Omega0;
double w;
double M0;
double tgd;
double idot;
double cuc;
double cus;
double crc;
double crs;
double cic;
double cis;
double af0;
double af1;
double af2;
int gpsweek;
uint16_t svn;
uint8_t ura;
uint8_t health;
uint8_t conf;
} EPHEM_t;
typedef struct {
uint32_t t;
double pseudorange;
double pseudorate;
double clock_corr;
double clock_drift;
double X;
double Y;
double Z;
double vX;
double vY;
double vZ;
int ephhr;
double PR;
double ephtime;
int prn;
} SAT_t;
typedef struct {double X; double Y; double Z;} LOC_t;
typedef struct {double X; double Y; double Z;
double vX; double vY; double vZ;} VEL_t;
double dist(double X1, double Y1, double Z1, double X2, double Y2, double Z2);
void GPS_SatelliteClockCorrection(
const unsigned short transmission_gpsweek, // GPS week when signal was transmit (0-1024+) [weeks]
const double transmission_gpstow, // GPS time of week when signal was transmit [s]
const unsigned short ephem_week, // ephemeris: GPS week (0-1024+) [weeks]
const double toe, // ephemeris: time of week [s]
const double toc, // ephemeris: clock reference time of week [s]
const double af0, // ephemeris: polynomial clock correction coefficient [s],
const double af1, // ephemeris: polynomial clock correction coefficient [s/s],
const double af2, // ephemeris: polynomial clock correction coefficient [s/s^2]
const double ecc, // ephemeris: eccentricity of satellite orbit []
const double sqrta, // ephemeris: square root of the semi-major axis of orbit [m^(1/2)]
const double delta_n, // ephemeris: mean motion difference from computed value [rad]
const double m0, // ephemeris: mean anomaly at reference time [rad]
const double tgd, // ephemeris: group delay differential between L1 and L2 [s]
double* clock_correction );
void GPS_SatellitePosition_Ephem(
const unsigned short gpsweek, // gps week of signal transmission (0-1024+) [week]
const double gpstow, // time of week of signal transmission (gpstow-psr/c) [s]
EPHEM_t ephem,
double* clock_correction, // clock correction for this satellite for this epoch [m]
double* satX, // satellite X position WGS84 ECEF [m]
double* satY, // satellite Y position WGS84 ECEF [m]
double* satZ // satellite Z position WGS84 ECEF [m]
);
void GPS_SatelliteClockDriftCorrection(
const unsigned short transmission_gpsweek, // GPS week when signal was transmit (0-1024+) [weeks]
const double transmission_gpstow, // GPS time of week when signal was transmit [s]
const unsigned short ephem_week, // ephemeris: GPS week (0-1024+) [weeks]
const double toe, // ephemeris: time of week [s]
const double toc, // ephemeris: clock reference time of week [s]
const double af0, // ephemeris: polynomial clock correction coefficient [s],
const double af1, // ephemeris: polynomial clock correction coefficient [s/s],
const double af2, // ephemeris: polynomial clock correction coefficient [s/s^2]
const double ecc, // ephemeris: eccentricity of satellite orbit []
const double sqrta, // ephemeris: square root of the semi-major axis of orbit [m^(1/2)]
const double delta_n, // ephemeris: mean motion difference from computed value [rad]
const double m0, // ephemeris: mean anomaly at reference time [rad]
const double tgd, // ephemeris: group delay differential between L1 and L2 [s]
double* clock_correction, // ephemeris: satellite clock correction [m]
double* clock_drift ) ;
void GPS_SatellitePositionVelocity_Ephem(
const unsigned short gpsweek, // gps week of signal transmission (0-1024+) [week]
const double gpstow, // time of week of signal transmission (gpstow-psr/c) [s]
EPHEM_t ephem,
double* clock_correction, // clock correction for this satellite for this epoch [m]
double* clock_drift, // clock correction for this satellite for this epoch [m]
double* satX, // satellite X position WGS84 ECEF [m]
double* satY, // satellite Y position WGS84 ECEF [m]
double* satZ, // satellite Z position WGS84 ECEF [m]
double* satvX, // satellite X velocity WGS84 ECEF [m]
double* satvY, // satellite Y velocity WGS84 ECEF [m]
double* satvZ // satellite Z velocity WGS84 ECEF [m]
);
int NAV_ClosedFormSolution_FromPseudorange(
SAT_t sats[4], // input: satellite position and pseudorange
double* latitude, // output: ellipsoid latitude [rad]
double* longitude, // ellipsoid longitude [rad]
double* height, // ellipsoid height [m]
double* rx_clock_bias, // receiver clock bias [m]
double pos_ecef[3] );
int calc_DOPn(int n, SAT_t satss[], double pos_ecef[3], double DOP[4]);
int NAV_LinP(int N, SAT_t satv[], double pos_ecef[3], double dt,
double dpos_ecef[3], double *cc);
void ecef2elli(double X, double Y, double Z, double *lat, double *lon, double *alt);
int NAV_LinP(int N, SAT_t satv[], double pos_ecef[3], double dt,
double dpos_ecef[3], double *cc);
int NAV_LinV(int N, SAT_t satv[], double pos_ecef[3],
double vel_ecef[3], double dt,
double dvel_ecef[3], double *cc);
int NAV_bancroft1(int N, SAT_t sats[], double pos_ecef[3], double *cc);
EPHEM_t *read_RNXpephs(const char *file);

1735
libraries/SondeLib/rs92gps.cpp Executable file

File diff suppressed because it is too large Load Diff

22
libraries/SondeLib/rs92gps.h Executable file
View File

@ -0,0 +1,22 @@
typedef struct {
int frnr;
char id[11];
int week; int gpssec;
int jahr; int monat; int tag;
int wday;
int std; int min; float sek;
double lat; double lon; double alt;
double vH; double vD; double vU;
int k;
int sats[4];
double dop;
int freq;
unsigned short aux[4];
double diter;
} gpx_t;
extern gpx_t gpx;
void print_frame(uint8_t *data, int len);
void get_eph(const char *file);

32
libraries/SondeLib/rsc.cpp Executable file
View File

@ -0,0 +1,32 @@
/*
* dxlAPRS toolchain
*
* Copyright (C) Christian Rabler <oe5dxl@oevsv.at>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef inttypes_h
#include <inttypes.h>
#endif
#define N 255
#define R 24
#define K (N-R)
void *init_rs_char(int symsize,int gfpoly,int fcr,int prim,int nroots,int pad);
int decode_rs_char(void *arg,
unsigned char *data, int *eras_pos, int no_eras);
void *rs;
void initrsc()
{
rs = init_rs_char( 8, 0x11d, 0, 1, R, 0);
}
int decodersc(char *data, uint32_t *eras_pos, uint32_t no_eras)
{
return decode_rs_char(rs, (unsigned char *)data, (int *)eras_pos, no_eras);
}

17
libraries/SondeLib/rsc.h Executable file
View File

@ -0,0 +1,17 @@
/*
* dxlAPRS toolchain
*
* Copyright (C) Christian Rabler <oe5dxl@oevsv.at>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef rsc_H_
#define rsc_H_
long decodersc(char [], uint32_t [], uint32_t);
void initrsc(void);
#endif /* rsc_H_ */

436
libraries/SondeLib/rsc_decode.cpp Executable file
View File

@ -0,0 +1,436 @@
/*
* Copyright 2016 Hannes Schmelzer, OE5HPM
* doing several cleanups and architecture changes, no functional change yet
*
* General purpose Reed-Solomon decoder for 8-bit symbols or less
* Copyright 2003 Phil Karn, KA9Q
* May be used under the terms of the GNU Lesser General Public License (LGPL)
*
* The guts of the Reed-Solomon decoder, meant to be #included
* into a function body with the following typedefs, macros and variables supplied
* according to the code parameters:
* data_t - a typedef for the data symbol
* data_t data[] - array of rs->nn data and parity symbols to be corrected in place
* retval - an integer lvalue into which the decoder's return code is written
* NROOTS - the number of roots in the RS code generator polynomial,
* which is the same as the number of parity symbols in a block.
Integer variable or literal.
* rs->nn - the total number of symbols in a RS block. Integer variable or literal.
* rs->pad - the number of pad symbols in a block. Integer variable or literal.
* rs->alpha_to - The address of an array of rs->nn elements to convert Galois field
* elements in index (log) form to polynomial form. Read only.
* rs->index_of - The address of an array of rs->nn elements to convert Galois field
* elements in polynomial form to index (log) form. Read only.
* MODNN - a function to reduce its argument modulo rs->nn. May be inline or a macro.
* rs->fcr - An integer literal or variable specifying the first consecutive root of the
* Reed-Solomon generator polynomial. Integer variable or literal.
* rs->prim - The primitive root of the generator poly. Integer variable or literal.
* DEBUG - If set to 1 or more, do various internal consistency checking. Leave this
* undefined for production code
* The memset(), memmove(), and memcpy() functions are used. The appropriate header
* file declaring these functions (usually <string.h>) must be included by the calling
* program.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct rs {
unsigned int magic; /* struct magic */
int mm; /* Bits per symbol */
int nn; /* Symbols per block (= (1<<mm)-1) */
unsigned char *alpha_to; /* log lookup table */
unsigned char *index_of; /* Antilog lookup table */
unsigned char *genpoly; /* Generator polynomial */
int nroots; /*
* Number of generator
* roots = number of parity symbols
*/
int fcr; /* First consecutive root, index form */
int prim; /* Primitive element, index form */
int iprim; /* prim-th root of 1, index form */
int pad; /* Padding bytes in shortened block */
};
static inline int modnn(struct rs *rs,int x)
{
while (x >= rs->nn) {
x -= rs->nn;
x = (x >> rs->mm) + (x & rs->nn);
}
return x;
}
#define MODNN(x) modnn(rs, x)
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAGIC 0xABCD6722
void free_rs_char(void *arg)
{
struct rs *rs = (struct rs *)arg;
if (rs == NULL)
return;
if (rs->magic != MAGIC)
return;
if (rs->alpha_to != NULL)
free(rs->alpha_to);
if (rs->index_of != NULL)
free(rs->index_of);
if (rs->genpoly != NULL)
free(rs->genpoly);
free(rs);
}
/* Initialize a Reed-Solomon codec
* symsize = symbol size, bits
* gfpoly = Field generator polynomial coefficients
* fcr = first root of RS code generator polynomial, index form
* prim = primitive element to generate polynomial roots
* nroots = RS code generator polynomial degree (number of roots)
* pad = padding bytes at front of shortened block
*/
void *init_rs_char(int symsize, int gfpoly, int fcr, int prim,
int nroots, int pad)
{
struct rs *rs;
int i, j, sr,root,iprim;
/* Check parameter ranges */
if (symsize < 0 || symsize > 8*sizeof(unsigned char))
return NULL;
if (fcr < 0 || fcr >= (1<<symsize))
return NULL;
if (prim <= 0 || prim >= (1<<symsize))
return NULL;
if (nroots < 0 || nroots >= (1<<symsize))
return NULL;
if (pad < 0 || pad >= ((1<<symsize) -1 - nroots))
return NULL;
rs = (struct rs*)malloc(sizeof(*rs));
if (rs == NULL) {
printf("%s: cannot allocate memory!\n", __func__);
return NULL;
}
memset(rs, 0, sizeof(*rs));
rs->magic = MAGIC;
rs->mm = symsize;
rs->nn = (1<<symsize)-1;
rs->pad = pad;
rs->alpha_to = (unsigned char *)malloc(sizeof(unsigned char)*(rs->nn+1));
if (rs->alpha_to == NULL) {
free(rs);
return NULL;
}
rs->index_of = (unsigned char *)malloc(sizeof(unsigned char)*(rs->nn+1));
if (rs->index_of == NULL) {
free(rs->alpha_to);
free(rs);
return NULL;
}
/* Generate Galois field lookup tables */
rs->index_of[0] = rs->nn; /* log(zero) = -inf */
rs->alpha_to[rs->nn] = 0; /* alpha**-inf = 0 */
sr = 1;
for (i = 0; i < rs->nn; i++) {
rs->index_of[sr] = i;
rs->alpha_to[i] = sr;
sr <<= 1;
if (sr & (1<<symsize))
sr ^= gfpoly;
sr &= rs->nn;
}
if (sr != 1) {
/* field generator polynomial is not primitive! */
free(rs->alpha_to);
free(rs->index_of);
free(rs);
return NULL;
}
/* Form RS code generator polynomial from its roots */
rs->genpoly = (unsigned char *)malloc(sizeof(unsigned char)*(nroots+1));
if(rs->genpoly == NULL) {
free(rs->alpha_to);
free(rs->index_of);
free(rs);
return NULL;
}
rs->fcr = fcr;
rs->prim = prim;
rs->nroots = nroots;
/* Find prim-th root of 1, used in decoding */
for (iprim = 1; (iprim % prim) != 0; iprim += rs->nn)
;
rs->iprim = iprim / prim;
rs->genpoly[0] = 1;
for (i = 0, root = fcr*prim; i < nroots; i++, root += prim) {
rs->genpoly[i+1] = 1;
/* Multiply rs->genpoly[] by @**(root + x) */
for (j = i; j > 0; j--) {
if (rs->genpoly[j] != 0)
rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)];
else
rs->genpoly[j] = rs->genpoly[j-1];
}
/* rs->genpoly[0] can never be zero */
rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)];
}
/* convert rs->genpoly[] to index form for quicker encoding */
for (i = 0; i <= nroots; i++)
rs->genpoly[i] = rs->index_of[rs->genpoly[i]];
return rs;
}
int decode_rs_char(void *arg,
unsigned char *data, int *eras_pos, int no_eras)
{
struct rs *rs = (struct rs *)arg;
if (rs == NULL)
return -1;
if (rs->magic != MAGIC)
return -1;
int retval;
int deg_lambda, el, deg_omega;
int i, j, r,k;
unsigned char u,q,tmp,num1,num2,den,discr_r;
unsigned char lambda[rs->nroots+1], s[rs->nroots]; /* Err+Eras Locator poly
* and syndrome poly */
unsigned char b[rs->nroots+1], t[rs->nroots+1], omega[rs->nroots+1];
unsigned char root[rs->nroots], reg[rs->nroots+1], loc[rs->nroots];
int syn_error, count;
/* form the syndromes; i.e., evaluate data(x) at roots of g(x) */
for (i = 0; i < rs->nroots; i++)
s[i] = data[0];
for (j = 1; j < rs->nn-rs->pad; j++) {
for(i=0;i<rs->nroots;i++) {
if(s[i] == 0) {
s[i] = data[j];
} else {
s[i] = data[j] ^ rs->alpha_to[MODNN(rs->index_of[s[i]] + (rs->fcr+i)*rs->prim)];
}
}
}
/* Convert syndromes to index form, checking for nonzero condition */
syn_error = 0;
for (i = 0; i < rs->nroots; i++) {
syn_error |= s[i];
s[i] = rs->index_of[s[i]];
}
if (!syn_error) {
/* if syndrome is zero, data[] is a codeword and there are no
* errors to correct. So return data[] unmodified
*/
count = 0;
goto finish;
}
memset(&lambda[1], 0, rs->nroots*sizeof(lambda[0]));
lambda[0] = 1;
if (no_eras > 0) {
/* Init lambda to be the erasure locator polynomial */
lambda[1] = rs->alpha_to[MODNN(rs->prim*(rs->nn-1-eras_pos[0]))];
for (i = 1; i < no_eras; i++) {
u = MODNN(rs->prim*(rs->nn-1-eras_pos[i]));
for (j = i+1; j > 0; j--) {
tmp = rs->index_of[lambda[j - 1]];
if(tmp != rs->nn)
lambda[j] ^= rs->alpha_to[MODNN(u + tmp)];
}
}
#if DEBUG >= 1
/* Test code that verifies the erasure locator polynomial just constructed
Needed only for decoder debugging. */
/* find roots of the erasure location polynomial */
for(i=1;i<=no_eras;i++)
reg[i] = rs->index_of[lambda[i]];
count = 0;
for (i = 1,k=rs->iprim-1; i <= rs->nn; i++,k = MODNN(k+rs->iprim)) {
q = 1;
for (j = 1; j <= no_eras; j++)
if (reg[j] != rs->nn) {
reg[j] = MODNN(reg[j] + j);
q ^= rs->alpha_to[reg[j]];
}
if (q != 0)
continue;
/* store root and error location number indices */
root[count] = i;
loc[count] = k;
count++;
}
if (count != no_eras) {
printf("count = %d no_eras = %d\n lambda(x) is WRONG\n",count,no_eras);
count = -1;
goto finish;
}
#if DEBUG >= 2
printf("\n Erasure positions as determined by roots of Eras Loc Poly:\n");
for (i = 0; i < count; i++)
printf("%d ", loc[i]);
printf("\n");
#endif
#endif
}
for (i = 0; i < rs->nroots+1; i++)
b[i] = rs->index_of[lambda[i]];
/*
* Begin Berlekamp-Massey algorithm to determine error+erasure
* locator polynomial
*/
r = no_eras;
el = no_eras;
while (++r <= rs->nroots) { /* r is the step number */
/* Compute discrepancy at the r-th step in poly-form */
discr_r = 0;
for (i = 0; i < r; i++) {
if ((lambda[i] != 0) && (s[r-i-1] != rs->nn)) {
discr_r ^= rs->alpha_to[MODNN(rs->index_of[lambda[i]] + s[r-i-1])];
}
}
discr_r = rs->index_of[discr_r]; /* Index form */
if (discr_r == rs->nn) {
/* 2 lines below: B(x) <-- x*B(x) */
memmove(&b[1],b,rs->nroots*sizeof(b[0]));
b[0] = rs->nn;
} else {
/* 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) */
t[0] = lambda[0];
for (i = 0 ; i < rs->nroots; i++) {
if(b[i] != rs->nn)
t[i+1] = lambda[i+1] ^ rs->alpha_to[MODNN(discr_r + b[i])];
else
t[i+1] = lambda[i+1];
}
if (2 * el <= r + no_eras - 1) {
el = r + no_eras - el;
/*
* 2 lines below: B(x) <-- inv(discr_r) *
* lambda(x)
*/
for (i = 0; i <= rs->nroots; i++)
b[i] = (lambda[i] == 0) ? rs->nn : MODNN(rs->index_of[lambda[i]] - discr_r + rs->nn);
} else {
/* 2 lines below: B(x) <-- x*B(x) */
memmove(&b[1],b,rs->nroots*sizeof(b[0]));
b[0] = rs->nn;
}
memcpy(lambda,t,(rs->nroots+1)*sizeof(t[0]));
}
}
/* Convert lambda to index form and compute deg(lambda(x)) */
deg_lambda = 0;
for (i = 0;i < rs->nroots+1; i++){
lambda[i] = rs->index_of[lambda[i]];
if(lambda[i] != rs->nn)
deg_lambda = i;
}
/* Find roots of the error+erasure locator polynomial by Chien search */
memcpy(&reg[1], &lambda[1], rs->nroots*sizeof(reg[0]));
count = 0; /* Number of roots of lambda(x) */
for (i = 1,k=rs->iprim-1; i <= rs->nn; i++,k = MODNN(k+rs->iprim)) {
q = 1; /* lambda[0] is always 0 */
for (j = deg_lambda; j > 0; j--) {
if (reg[j] != rs->nn) {
reg[j] = MODNN(reg[j] + j);
q ^= rs->alpha_to[reg[j]];
}
}
if (q != 0)
continue; /* Not a root */
/* store root (index-form) and error location number */
#if DEBUG>=2
printf("count %d root %d loc %d\n",count,i,k);
#endif
root[count] = i;
loc[count] = k;
/* If we've already found max possible roots,
* abort the search to save time
*/
if(++count == deg_lambda)
break;
}
if (deg_lambda != count) {
/*
* deg(lambda) unequal to number of roots => uncorrectable
* error detected
*/
count = -1;
goto finish;
}
/*
* Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo
* x**rs->nroots). in index form. Also find deg(omega).
*/
deg_omega = deg_lambda-1;
for (i = 0; i <= deg_omega;i++) {
tmp = 0;
for (j = i; j >= 0; j--) {
if ((s[i - j] != rs->nn) && (lambda[j] != rs->nn))
tmp ^= rs->alpha_to[MODNN(s[i - j] + lambda[j])];
}
omega[i] = rs->index_of[tmp];
}
/*
* Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
* inv(X(l))**(rs->fcr-1) and den = lambda_pr(inv(X(l))) all in poly-form
*/
for (j = count-1; j >=0; j--) {
num1 = 0;
for (i = deg_omega; i >= 0; i--) {
if (omega[i] != rs->nn)
num1 ^= rs->alpha_to[MODNN(omega[i] + i * root[j])];
}
num2 = rs->alpha_to[MODNN(root[j] * (rs->fcr - 1) + rs->nn)];
den = 0;
/* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */
for (i = MIN(deg_lambda, rs->nroots-1) & ~1; i >= 0; i -=2) {
if(lambda[i+1] != rs->nn)
den ^= rs->alpha_to[MODNN(lambda[i+1] + i * root[j])];
}
#if DEBUG >= 1
if (den == 0) {
printf("\n ERROR: denominator = 0\n");
count = -1;
goto finish;
}
#endif
/* Apply error to data */
if (num1 != 0 && loc[j] >= rs->pad) {
data[loc[j]-rs->pad] ^= rs->alpha_to[MODNN(rs->index_of[num1] + rs->index_of[num2] + rs->nn - rs->index_of[den])];
}
}
finish:
if(eras_pos != NULL) {
for (i = 0; i < count; i++)
eras_pos[i] = loc[i];
}
retval = count;
return retval;
}

36
libraries/SondeLib/spiffs_sd.h Executable file
View File

@ -0,0 +1,36 @@
#include <FS.h>
#include <SPIFFS.h>
#include <SD_MMC.h> // (or SD_MMC.h)
void transfert_sd() {
if (!SD_MMC.begin()) {
Serial.println("Carte SD introuvable");
}
else{
Serial.println("Carte SD détectée");
sonde.clearDisplay();
disp.rdis->drawString(0, 2, "Carte SD On");
disp.rdis->drawString(0, 4, "Transfert Data");
File sourceFile = SPIFFS.open("/data.csv", "r");
File destFile = SD_MMC.open("/data.csv","w");
static uint8_t tanpon[512];
while( sourceFile.read( tanpon, 512) ) {
destFile.write( tanpon, 512 );
}
destFile.close();
sourceFile.close();
delay(1000);
//sonde.updateDisplay();
sonde.clearDisplay();
disp.rdis->drawString(0, 2, "Sortir SD");
disp.rdis->drawString(0, 4, "Reboot 5s");
delay(5000);
ESP.restart();
}
}

BIN
tools/ESP32FS/tool/esp32fs.jar Executable file

Binary file not shown.