From da837ffbb4ff1f1974e884d102f86349fffd7c8e Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Mon, 16 Oct 2023 22:16:47 +0100 Subject: [PATCH 1/4] RFID: Update comment refering to -ID When a tag leaves, we don't negate the ID which would be implied by writing -ID. Clarify that we set the sign bit instead. --- include/RFID.h | 4 ++-- src/main.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/RFID.h b/include/RFID.h index bcec1d8..48fa1c6 100644 --- a/include/RFID.h +++ b/include/RFID.h @@ -23,7 +23,7 @@ struct uidNode { MFRC522Constants::Uid uid{}; uidNode* next = nullptr; - // IDs should be positive integers < 127 as the sign is used for direction. + // IDs should be positive integers < 127 as the sign bit is used for direction. int8_t tag_ID = 0; }; @@ -36,7 +36,7 @@ public: * at a time, another call would be needed to get eventual concurrent events. * This updates the respective lists and returns the ID with the direction * of the change. - * @return 0 if no change, +ID if new tag detected, -ID if the tag left. + * @return 0 if no change, ID if new tag detected, ID & sign bit if the tag left. */ int8_t checkTags(); private: diff --git a/src/main.cpp b/src/main.cpp index ddebea6..c19b04c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -37,7 +37,7 @@ __attribute__((noreturn)) int main() { */ Serial.begin(115200); - Com::sendComment("# System is powered up, running set-up."); + Com::sendComment("System is powered up, running set-up."); /* TODO: Setups once module structure is up. */ RFID rfid; From 6c5a6703668f9e8a46e323ce2bdcd817dc992ee0 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sat, 21 Oct 2023 16:00:08 +0100 Subject: [PATCH 2/4] IDs: Introduce ID handling functions and data IDs will be needed by multiple subsystems, so introduce the needed variables and handling functions. --- include/IDs.h | 37 +++++++++++++++++++++++++++++++++++++ include/RFID.h | 1 + src/IDs.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/RFID.cpp | 2 +- src/main.cpp | 2 +- 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 include/IDs.h create mode 100644 src/IDs.cpp diff --git a/include/IDs.h b/include/IDs.h new file mode 100644 index 0000000..783e2d0 --- /dev/null +++ b/include/IDs.h @@ -0,0 +1,37 @@ +// +// Created by Teo-CD on 19/10/23. +// + +#ifndef JIN_BARBAPAPA_IDS_H +#define JIN_BARBAPAPA_IDS_H + +#include + +/*** + * Convenience functions and variables to handle IDs and data that they link to. + */ + +constexpr uint8_t idMaskGone = 0b10000000; +constexpr uint8_t idMaskChar = 0b01000000; + +bool isIdGone(int8_t ID); +bool isIdCharacter(int8_t ID); +/*** + * Get the ID without the control bits : direction and character/promotion bit. + * @param ID to check, with all bits + * @return The ID with only index bits. + */ +uint8_t getRawId(int8_t ID); +/*** + * Checks if the ID is known to the firmware, either as a character or promotion. + * @param ID to check, with all bits + * @return True if valid as a character or promotion, false otherwise. + */ +bool isValidId(int8_t ID); + +static constexpr uint8_t dirStrSize = 10; +extern const char promoDirs[][dirStrSize]; +extern const char charDirs[][dirStrSize]; + + +#endif //JIN_BARBAPAPA_IDS_H diff --git a/include/RFID.h b/include/RFID.h index 48fa1c6..88d4c65 100644 --- a/include/RFID.h +++ b/include/RFID.h @@ -15,6 +15,7 @@ #include #include +#include "IDs.h" #include "Pinout.h" /*** diff --git a/src/IDs.cpp b/src/IDs.cpp new file mode 100644 index 0000000..8a3ae9a --- /dev/null +++ b/src/IDs.cpp @@ -0,0 +1,45 @@ +// +// Created by Teo-CD on 19/10/23. +// + +#include "IDs.h" + +/* IDs are greater than one, offset the array to make it easier to use. */ +constexpr char promoDirs[][dirStrSize] = { + "INVALIDE", + "RANDONNEE", + "RESTO", + "AUTOBUS", + "MAYO", + "MADO", + "COOP", + "OURS", + "BAGAR", + "INCAPABLE", + "MYSTERE" +}; +constexpr char charDirs[][dirStrSize] = { + "INVALIDE", + "GUILLAUME", + "MICHEL" +}; + +bool isIdGone(int8_t ID) { + return ID & idMaskGone; +} + +bool isIdCharacter(int8_t ID) { + return ID & idMaskChar; +} + +uint8_t getRawId(int8_t ID) { + return ID & ~(idMaskGone | idMaskChar); +} + +bool isValidId(int8_t ID) { + uint8_t rawId = getRawId(ID); + if (isIdCharacter(ID)) + return rawId < sizeof(charDirs) / dirStrSize; + else + return rawId < sizeof(promoDirs) / dirStrSize; +} diff --git a/src/RFID.cpp b/src/RFID.cpp index 0ba5e1b..22c69fc 100644 --- a/src/RFID.cpp +++ b/src/RFID.cpp @@ -39,7 +39,7 @@ int8_t RFID::checkTags() { removeActiveTag(node, previousNode); // Return the ID with the sign bit set to signal it has gone. // If other tags are gone we can check on the next loop. - return node->tag_ID | 1<<7; + return node->tag_ID | idMaskGone; } } } diff --git a/src/main.cpp b/src/main.cpp index c19b04c..14c990f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,8 +2,8 @@ #include "Pinout.h" -#include "RFID.h" #include "Com.h" +#include "RFID.h" __attribute__((noreturn)) int main() { pinMode(pin_DBG_LED_1, OUTPUT); From e21123538b088a0df36297dddb19faa501590377 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sat, 21 Oct 2023 21:55:54 +0100 Subject: [PATCH 3/4] Utils: Introduce utils and animation helpers The next commit implements the LCD and animation support. To enable an easy way to create animations for it, add details in the README regarding the way the SD card is used and how the files need to be formatted. Create the Utils directory and add a Rust project to do the bit flipping and a script that automates all the steps described in the README. Add a README for Utils explaining how to use them. --- README.md | 97 +++++++++++++++++++++++++++++++++ Utils/.gitignore | 2 + Utils/README.md | 56 +++++++++++++++++++ Utils/bitmap_helper/.gitignore | 1 + Utils/bitmap_helper/Cargo.lock | 7 +++ Utils/bitmap_helper/Cargo.toml | 8 +++ Utils/bitmap_helper/src/main.rs | 51 +++++++++++++++++ Utils/convertAnimGif.sh | 52 ++++++++++++++++++ 8 files changed, 274 insertions(+) create mode 100644 Utils/.gitignore create mode 100644 Utils/README.md create mode 100644 Utils/bitmap_helper/.gitignore create mode 100644 Utils/bitmap_helper/Cargo.lock create mode 100644 Utils/bitmap_helper/Cargo.toml create mode 100644 Utils/bitmap_helper/src/main.rs create mode 100755 Utils/convertAnimGif.sh diff --git a/README.md b/README.md index 3a52e6f..2df5da7 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ de la carte électronique : - Jouer des sons - Contrôle du gain +Les données utilisées pour l'affichage sur le LCD ou pour la sortie audio doivent +être chargés depuis une carte SD insérée dans la carte microcontrôleur. + En plus de contrôler la carte, le firmware doit pouvoir intéragir avec une application développée sur Unity. En premier lieu, il doit renvoyer des identifiants correspondants aux tags RFID @@ -69,6 +72,100 @@ mise à jour et suivi des dépendances. La structure du code assume une carte Teensy. En particulier, il est conçu pour la [Teensy 4.1](https://www.pjrc.com/store/teensy41.html). +# Carte SD et données + +La carte SD est utilisée pour lire les animations et les sons, lors de l'utilisation +des figurines ou pour des cas génériques (démarrage, batterie faible…). +Le firmware attend les fichiers dans certains endroits et sous certaines formes, +détaillés ici. + +## Structure de fichiers + +Les fichiers sur la carte SD sont structurés comme suit : +``` +/ +├── RANDONNEE/ +│ ├── ANIM/ +│ │ ├── 00 +│ │ ├── 01 +│ │ ├── 02 +│ │ ├── .. +│ │ └── XX +│ └── audio.wav +├── RESTO/ +│ ├── ANIM/ +│ │ └── .. +│ └── audio.wav +├── .../ +└── SYS/ + ├── ANIM*/ + ├── ../ + └── *.wav +``` + +Tous les fichiers liés à une figurine en particulier doivent être placés dans un +dossier associé au nom de la figurine. +Les noms attendus sont ceux des tableaux définis dans `src/IDs.cpp`. +Les différentes images d'une même animation doivent être dans un même dossier, +numérotés dans l'ordre avec deux chiffres et sans extension, les fichiers audio +doivent garder leur extension. + +Le dossier SYS sera utilisé pour toute interaction qui n'est pas liée à une figurine. + +## Structure des données +### Audio + +Le format audio supporté est assez précis : les fichiers doivent être des WAV +16 bit PCM, 44100 Hz. +Mono ou stéréo n'est pas important : la carte ne supporte qu'un haut-parleur, +les deux canaux stéréo seront moyennés `(Gauche+Droit)/2`. + +### Animations + +Le format des animations est d'autant plus précis et particulier et demandera +probablement du traitement pour qu'elles soient correctes. + +Tout d'abord, chaque image de l'animation doit être dans son propre fichier, un +seul fichier pour l'animation (comme un gif) n'est pas possible. + +L'écran LCD choisi est un module pour écran Nokia 5110 de 84x48 pixels monochromes, +les images de l'animation doivent donc être à ce format et monochromes. +L'image attendue est exclusivement une bitmap d'un bit par pixel, sans aucune +autre information. +De plus, il est probable que les bits attendus soient inversés par rapport à ceux +produits "logiquement" depuis un programme normal. Par exemple `0b11001100` devrait +en réalité être `0b00110011`. + +Je n'ai pas connaissance d'un format connu qui peut être généré depuis un logiciel +et répondre à ces conditions, cependant il est possible d'y arriver automatiquement +avec quelques opérations de transformation. +Pour la facilité, il est donc possible d'arriver aux fichiers individuels requis +à partir d'un simple gif aux proportions correctes. + +À l'aide d'[ImageMagick](https://imagemagick.org/), on peut découper le gif en +ses images individuelles, et les convertir en un format de bitmap presque correct +(aux bits inversés) avec la commande suivante : + +```shell +convert -resize 84x48 -coalesce .gif -threshold 50% %02d.mono +``` + +Il peut être nécessaire d'inverser les bits comme mentionné plus haut et je n'ai +pas trouvé de façon existante de le faire. +Vous trouverez donc dans `Utils/bitmap_helper` un projet Rust qui permet de +réaliser cette opération sur les fichiers produits par la commande ci-dessus. +Une fois compilé, vous pouvez convertir les fichiers `.mono` dans le format adapté +(avec une extension `.bin` en les passant à l'utilitaire : +```shell +bitmap_helper *.mono +``` + +N'oubliez pas de retirer l'extension et de les placer dans un dossier `ANIM` sur +la carte SD, comme décrit ci-dessus. + +Un script bash est aussi fourni pour automatiser tout ce processus : +`Utils/convertAnimGif.sh`. + # Interfaçage avec l'application Unity Afin de permettre la détection automatique de l'objet, la communication se fait diff --git a/Utils/.gitignore b/Utils/.gitignore new file mode 100644 index 0000000..977a0f9 --- /dev/null +++ b/Utils/.gitignore @@ -0,0 +1,2 @@ +*.gif +ANIM*/ diff --git a/Utils/README.md b/Utils/README.md new file mode 100644 index 0000000..d27866d --- /dev/null +++ b/Utils/README.md @@ -0,0 +1,56 @@ +# Utilitaires + +Ce sous-dossier rassemble divers utilitaires liés au projet. + +# Conversion des animations + +Deux outils sont disponnibles pour aider à la conversion : un pour faire l'inversion +de bit nécessaire, et un script pour automatiser tout le protocole. + +Pour des détails sur le format de sortie, référez-vous au README principal. + +## bitmap_helper + +Un projet Rust a été créé pour permettre la conversion d'une bitmap MONO gérée +par ImageMagick en une bitmap correcte pour le LCD, en inversant les bits de chaque +octet. + +Il est possible qu'une version compilée soit disponible dans les versions sur +Github. + +### Compilation + +Pour le compiler, il suffit d'utiliser Cargo, qui peut être installé via +[rustup](https://rustup.rs/) par exemple. +Une fois Rust et Cargo disponibles, la compilation peut être faite depuis le +dossier `bitmap_helper` avec la commande suivante : +```shell +cargo build +``` +L'exécutable produit est alors `bitmap_helper/target/debug/bitmap_helper`. + +### Utilisation + +L'utilitaire peut convertir plusieurs fichiers `.mono` à la fois, passés directement +sur la ligne de commande. +```shell +bitmap_helper/target/debug/bitmap_helper .mono .mono ... +``` +Tout autre format de fichier ne sera pas traité. +⚠️ La détection du format n'est basée que sur les extensions ! + +## convertAnimGif.sh + +Ce script permet d'automatiser tout le protocole de conversion d'un ou plusieurs +gifs en suites de bitmaps utilisables par le projet. + +Il prend en argument un ou plusieurs fichier(s) `.gif` et produit les bitmap +individuelles correctement numérotées dans un dossier portant le nom du fichier +original. + +```shell +./convertAnimGif.sh .gif .gif ... +``` + +Il suffit ensuite de déposer les dossiers sur la carte SD, sous les dossiers correspondants +(voir noms dans `src/IDs.cpp`) et les renommer pour ne laisser que `ANIM`. diff --git a/Utils/bitmap_helper/.gitignore b/Utils/bitmap_helper/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/Utils/bitmap_helper/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Utils/bitmap_helper/Cargo.lock b/Utils/bitmap_helper/Cargo.lock new file mode 100644 index 0000000..ef6ca90 --- /dev/null +++ b/Utils/bitmap_helper/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitmap_helper" +version = "0.1.0" diff --git a/Utils/bitmap_helper/Cargo.toml b/Utils/bitmap_helper/Cargo.toml new file mode 100644 index 0000000..e3a07c7 --- /dev/null +++ b/Utils/bitmap_helper/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "bitmap_helper" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/Utils/bitmap_helper/src/main.rs b/Utils/bitmap_helper/src/main.rs new file mode 100644 index 0000000..a96029a --- /dev/null +++ b/Utils/bitmap_helper/src/main.rs @@ -0,0 +1,51 @@ +use std::fs::File; +use std::fs::write; +use std::env; +use std::io::Read; + +fn main() { + let args: Vec = env::args().collect(); + + for arg in &args[1..] { + println!("Processing : {}", arg); + if !arg.ends_with(".mono") { + eprintln!("{} : unsupported file type.", arg); + continue; + } + let file_r = File::open(arg); + if file_r.is_err() { + eprintln!("{} : could not open file.", arg); + continue; + } + let mut image_data = vec![]; + if file_r.unwrap().read_to_end(&mut image_data).is_err() { + eprintln!("{} : could not read file.", arg); + continue; + } + + let out_file = arg.replace(".mono", ".bin"); + flip_bits(&mut image_data); + if write(&out_file, &image_data).is_err() { + eprintln!("Failed to write bit flipped bitmap to file : {}", out_file); + } + println!("Processing successful, flipped bits and wrote to {}", out_file); + } +} + +/// Flips the bits of each individual byte (except zeroes). +/// This would transform 0b11001100 in 0b00110011. +fn flip_bits(data: &mut Vec) { + for source_id in 0..data.len() { + let mut new_int = 0; + for i in 0..8 { + if data[source_id] == 0 { break; } + new_int |= ((data[source_id] & (1 << 7 - i)) >> 7 - i) << i; + } + data[source_id] = new_int; + } + // for i in 0..data.len() { + // print!("{:2X}", data[i]); + // if i > 0 && i%16 == 0 { println!(); } + // } + // println!(); +} diff --git a/Utils/convertAnimGif.sh b/Utils/convertAnimGif.sh new file mode 100755 index 0000000..d8d33d1 --- /dev/null +++ b/Utils/convertAnimGif.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -euo pipefail + +if [[ $# -eq 0 ]]; then + echo "This script expects the name of the files to convert as arguments" + exit 1 +fi + +if [[ ! $(command -v convert) ]]; then + echo "Could not find 'convert' utility, cannot proceed." + exit 1 +fi + +# Get the absolute path to the source of the script +scriptSrcDir="$(dirname -- "$0")" +pushd "$scriptSrcDir" +scriptSrcDir="$(pwd)" +popd + +if [[ $(command -v bitmap_helper) ]]; then + bmp_help="$(command -v bitmap_helper)" +elif [[ $(command -v "$scriptSrcDir"/bitmap_helper/target/debug/bitmap_helper) ]]; then + bmp_help="$scriptSrcDir"/bitmap_helper/target/debug/bitmap_helper +elif [[ $(command -v "$scriptSrcDir"/bitmap_helper) ]]; then + bmp_help="$scriptSrcDir"/bitmap_helper +else + echo "Could not find 'bitmap_helper'." + echo "Have you compiled it or placed it in the same directory as the script ?" + exit 1 +fi + +for gifToConvert in "$@"; do + if [[ -z "$(echo "$gifToConvert" | grep -e 'gif')" ]]; then + echo "Cannot convert $gifToConvert : not a gif." + continue + fi + + animDir=ANIM_"${gifToConvert//.gif//}" + mkdir "$animDir" + pushd "$animDir" + + convert -resize 84x48 -coalesce ../"$gifToConvert" -threshold 50% %02d.mono + "$bmp_help" ./*.mono + # Clean up and remove the extension + rm -f ./*.mono + for f in ./*.bin; do + mv "$f" "${f//.bin/}" + done + + popd +done From 69909851c217f0a18df8184f3848e4ee115a643d Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sat, 21 Oct 2023 22:01:10 +0100 Subject: [PATCH 4/4] LCD: Introduce LCD display and animations Add support for the LCD and using it for displaying animations. Animations are automatically played if available for the ID of the detected tag. The code should support adding other kind of animations, for tags being removed or for other UI interactions for example. --- README.md | 4 ++-- include/LCD.h | 46 ++++++++++++++++++++++++++++++++++++ src/LCD.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 15 +++++++++++- 4 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 include/LCD.h create mode 100644 src/LCD.cpp diff --git a/README.md b/README.md index 2df5da7..2362bbd 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,8 @@ se synchroniser avec le firmware et potentiellement le mettre à jour. - [ ] Contrôle des modules - [x] RFID (base) - [ ] RFID (avancé, écriture des tags) - - [ ] LCD (base) - - [ ] LCD (animations) + - [x] LCD (base) + - [x] LCD (animations) - [ ] LCD (UI) - [ ] Audio (sons de figurines) - [ ] Audio (sons d'UI) diff --git a/include/LCD.h b/include/LCD.h new file mode 100644 index 0000000..b3111a1 --- /dev/null +++ b/include/LCD.h @@ -0,0 +1,46 @@ +// +// Created by Teo-CD on 17/10/23. +// + +#ifndef JIN_BARBAPAPA_LCD_H +#define JIN_BARBAPAPA_LCD_H + +#include +#include +#include + +#include + +#include "Com.h" +#include "IDs.h" +#include "Pinout.h" + +class LCD { +public: + LCD() : sd(SD) {}; + void init(); + /*** + * Check if time has passed and we should display a new frame. + * @return True if a new frame was displayed. + */ + bool checkAndDisplay(); + void startNewAnim(int8_t ID); +private: + Adafruit_PCD8544 lcd{pin_LCD_DC, pin_LCD_CS, pin_LCD_RST}; + + SDClass &sd; + /* Directory where the current animation frames are stored. */ + File animDir; + + /*** + * Array storing the bitmap that will be passed to the LCD. + * The LCD is 84x48 so that should be 504 bytes, but the row length (84) + * is not byte aligned. Thus, the last byte is padded, giving 88x48 = 528. + */ + static uint8_t bitmap[528]; + uint32_t lastFrameTime = 0; + /* Delay between two frames, in milliseconds. Above 10 FPS is mushy, avoid. */ + static constexpr uint32_t frameTarget = 1000 / 7; +}; + +#endif //JIN_BARBAPAPA_LCD_H diff --git a/src/LCD.cpp b/src/LCD.cpp new file mode 100644 index 0000000..0b9e478 --- /dev/null +++ b/src/LCD.cpp @@ -0,0 +1,65 @@ +// +// Created by Teo-CD on 17/10/23. +// + +#include "LCD.h" + +uint8_t LCD::bitmap[528] = {}; + +void LCD::init() { + lcd.begin(60, 4); + lcd.clearDisplay(); + lastFrameTime = millis(); + /* Needed to keep the display clean at boot. */ + lcd.display(); + /* TODO: JIN splash welcome ? */ +} + +void LCD::startNewAnim(int8_t ID) { + char directoryPath[15]; + uint8_t rawID = getRawId(ID); + + if (!SD.mediaPresent()) { + Com::sendComment("SD Card not present, cannot play animation"); + return; + } + if (!isValidId(ID)) { + Com::sendComment("Unknown ID for animation : %d", rawID); + return; + } + + /* Interrupt previous running animation if there's one. */ + if (animDir) + animDir.close(); + + /* TODO: If adding remove animation, change path + check isIdGone */ + snprintf(directoryPath, 15, "%s/ANIM", + (isIdCharacter(ID) ? charDirs : promoDirs)[rawID]); + animDir = SD.open(directoryPath); + if (!animDir) { + Com::sendComment("Could not open directory %s for ID %d", directoryPath, rawID); + return; + } + checkAndDisplay(); +} + +bool LCD::checkAndDisplay() { + if (!animDir || millis() - lastFrameTime < frameTarget) + return false; + + File frame = animDir.openNextFile(); + if (!frame) { + /* There are no frames anymore, the animation is complete. */ + animDir.close(); + return false; + } + frame.read(bitmap, 528); + frame.close(); + + lcd.clearDisplay(); + lcd.drawBitmap(0, 0, bitmap, 84, 48, 1); + lcd.display(); + + lastFrameTime = millis(); + return true; +} diff --git a/src/main.cpp b/src/main.cpp index 14c990f..6615fef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ #include "Pinout.h" #include "Com.h" +#include "LCD.h" #include "RFID.h" __attribute__((noreturn)) int main() { @@ -44,15 +45,27 @@ __attribute__((noreturn)) int main() { digitalWrite(pin_NFC1_RST, HIGH); rfid.init(); + if (!SD.begin(BUILTIN_SDCARD)) + Com::sendComment("Could not use the SD Card."); + + LCD lcd; + digitalWrite(pin_LCD_RST, HIGH); + lcd.init(); + /* Main loop */ while (true) { int8_t tagEvent; + /* Display first, in case the RFID communication delays it too much. */ + lcd.checkAndDisplay(); + tagEvent = rfid.checkTags(); if (tagEvent) { Com::sendFigUpdate(tagEvent); + lcd.startNewAnim(tagEvent); } - delay(100); + /* TODO: Drop delay, WFE+timer interrupt(s) ? */ + delay(25); } }