This shows you the differences between two versions of the page.
| pm:prj2023:gpatru:retropm [2023/05/07 23:05] irina_cristina.nita created | pm:prj2023:gpatru:retropm [2023/05/30 12:09] (current) irina_cristina.nita [Download] | ||
|---|---|---|---|
| Line 4: | Line 4: | ||
| Proiectul constă în implementarea unui "PC" inspirat de cele old school pentru care interfața cu utilizatorul se rezuma la introducerea unor comenzi în terminal. O sa îl numesc RetroPM. | Proiectul constă în implementarea unui "PC" inspirat de cele old school pentru care interfața cu utilizatorul se rezuma la introducerea unor comenzi în terminal. O sa îl numesc RetroPM. | ||
| - | RetroPM va sta intr-o stare de "oprit" (LED-ul pentru afișarea stării va fi roșu) până când utilizatorul va apăsa butonul de "ON" pentru a porni execuția programului principal (Butonul se va afla atât pe tastatura cât și pe ). Utilizatorul va fi întâmpinat cu un prompt pentru logare și o melodie redata de buzzer. By default, user-ul o sa fie "root". Sunt luate în considerare privilegiile pentru că utilizatorii vor avea acces și la un sistem de fișiere stocat într-un microSD pe care pot sa îl modifice prin comenzi. (text/imagini tip bitmap) | + | Prin acest proiect, mi-am propus sa replic un sistem ce poate reproduce într-un mod minimal gestionarea de fișiere și utilizatori. | 
| ===== Descriere generală ===== | ===== Descriere generală ===== | ||
| - | Cateva feature-uri: | + | RetroPM se bazează în principal pe comunicarea dintre 3 microcontrollere cu scopuri diferite: | 
| - | * La pornire, se va reda o melodie prin intermediul buzzer-ului. | + | * ESP32: Se ocupa de afișarea pe 2 display-uri a input-ului si output-ului pentru utilizator (comenzile în timp ce se tastează, un prompt cu utilizatorul curent etc.) și cu conectarea la un server NTP pentru a afișa ora actuala pe display.  | 
| - | * Utilizatorii și privilegiile lor vor fi stocați în memoria EEPROM. | + | * STM32F103: Se ocupa de logica principala (FSM-ul) a sistemului. Acesta este responsabil de procesarea input-ului, de a decide dacă o comanda este invalida, ce privilegii are un user când când dorește sa modifice un fișier etc. | 
| - | * Prin intermediul terminalului, se pot scrie și salva fișiere text în memoria din microSD. | + | * Raspberry Pi Pico: Se ocupa de prelucrarea input-ului de la utilizator (tastele sunt procesate în caractere ASCII) pentru a le comunica către STM32. | 
| - | * ESP32 va fi folosit pentru conectarea la anumite servere pentru a expanda funcționalități fără sa fie nevoie de hardware suplimentar (de ex. la un server NTP pentru redarea timpului actual) | + | |
| - | * Tastatura este conceputa pentru a evita ghosting-ul și a suporta 2 taste apăsate simultan. | + | |
| - | * Combinatii de taste de tip Shift+(Key) pentru Uppercase sau "ANSI escape sequences" pentru a controla pozitia cursorului vor fi implementate. | + | |
| - | + | ||
| - | {{:pm:prj2023:gpatru:irina_retropm.png?900|}} | + | |
| + | {{pm:prj2023:gpatru:retro-pm-diagr.png?800}} | ||
| ===== Hardware Design ===== | ===== Hardware Design ===== | ||
| Lista componente: | Lista componente: | ||
| - | * Arduino Uno | + | * STM32F103 | 
| + | * Raspberry Pi Pico | ||
| * ESP32 | * ESP32 | ||
| - | * Modul microSD + microSD 16GB | + | * Modul microSD + microSD 32GB | 
| * Shift registers | * Shift registers | ||
| - | * Diode, Resistori.. | + | * Diode, Resistori | 
| * Pushbuttons | * Pushbuttons | ||
| - | * Buzzer | + | * Memorie EEPROM | 
| - | * RGB LED | + | |
| - | * Memorii EEPROM | + | |
| * Ecran OLED | * Ecran OLED | ||
| + | * Ecran LCD 16x2 | ||
| * Sursa alimentare | * Sursa alimentare | ||
| * Level shifter | * Level shifter | ||
| - | <note tip> | + | Schema pentru Raspberry Pi Pico (partea de "tastatura"): | 
| - | Aici puneţi tot ce ţine de hardware design: | + | |
| - | * listă de piese | + | {{:pm:prj2023:gpatru:keyboard.png?770|}} | 
| - | * scheme electrice (se pot lua şi de pe Internet şi din datasheet-uri, e.g. http://www.captain.at/electronic-atmega16-mmc-schematic.png) | + | |
| - | * diagrame de semnal  | + | Schema pentru ESP32 (celelalte periferice: display, microSD): | 
| - | * rezultatele simulării | + | |
| - | </note> | + | {{:pm:prj2023:gpatru:eps32.png?500|}} | 
| + | |||
| + | (Notita: Am uitat sa pun in schema I2C-ul pentru ESP32, are rezistenta de pull-up ca STM32 si foloseste un singur bus pentru ambele display-uri) | ||
| + | |||
| + | Schema pentru STM32 (microcontroller principal): | ||
| + | |||
| + | {{:pm:prj2023:gpatru:bluepill.png?500|}} | ||
| ===== Software Design ===== | ===== Software Design ===== | ||
| + | Pentru STM32 și Raspeberry Pi Pico am folosit Rust, cu biblioteci de Hardware Layer Abstraction. Pentru ESP32, am folosit Arduino IDE și C++. | ||
| + | Lista bibliotecilor folosite: | ||
| + | * C++ | ||
| + | * SPI.h, SD.h pentru modulul de microSD | ||
| + | * Adafruit_GFX, Adafruit_SH110X, hd44780 pentru cele doua display-uri | ||
| + | * Wire pentru I2C | ||
| + | * HardwareSerial pentru UART | ||
| + | * Rust | ||
| + | * rp2040_hal - pentru Pico | ||
| + | * stm32f1xx_hal, eeprom34x (pentru a scrie/citi din IC-ul de memorie) - pentru STM32F103 | ||
| + | Pentru partea de procesare de tastatura, a trebuit în primul rând sa implementez o funcție asemănătoare cu cea de shiftOut din Arduino.h (nu am găsit-o în vreu crate de Rust). Folosind 2 shift registeri ca output-uri (fiecare cu 5 coloane din matricea de butoane asignate), și 4 coloane ca input-uri pentru a reduce numărul de pini folosiți pe placa, am folosit o tehnica de matrix scanning pentru a decide ce buton a fost apăsat de utilizator. Astfel am putut sa scanez mai multe butoane în același ciclu de loop. (Lucrul acesta a fost folosit pentru a reproduce efectul de shift + lowercase = uppercase). | ||
| - | <note tip> | + | Code snippet: | 
| - | Descrierea codului aplicaţiei (firmware): | + | Prima parte din loop se ocupa cu scanarea și stocarea intr-o matrice de tip bool dacă tasta de la poziția i,j a fost apăsată. | 
| - | * mediu de dezvoltare (if any) (e.g. AVR Studio, CodeVisionAVR) | + | <code c> | 
| - | * librării şi surse 3rd-party (e.g. Procyon AVRlib) | + | // Bits 9:5 for first shift register | 
| - | * algoritmi şi structuri pe care plănuiţi să le implementaţi | + | // Bits 4:0 for second shift register | 
| - | * (etapa 3) surse şi funcţii implementate | + | let mut bits = 0x0200u16; // 0000_0010_0000_0000 | 
| - | </note> | + | |
| - | ===== Rezultate Obţinute ===== | + | // Mask for second shift register -> 0000_0000_0001_1111 | 
| + | let mask = 0x001Fu16; | ||
| - | <note tip> | + | for col in (0u8..10u8).rev() { | 
| - | Care au fost rezultatele obţinute în urma realizării proiectului vostru. | + | // Byte to serial input for first shift register. | 
| - | </note> | + | let byte_higher = !((bits >> 5).to_be_bytes()[1]); | 
| - | ===== Concluzii ===== | + | // Byte to serial input for second shift register. | 
| + | let byte_lower = !((bits & mask).to_be_bytes()[1]); | ||
| - | ===== Download ===== | + | latch_pin_1.set_low().unwrap(); | 
| + | latch_pin_2.set_low().unwrap(); | ||
| + | // Shift out value from register | ||
| + | kb_lib::shift_out(&byte_lower, &mut data_pin_2, &mut clock_pin_2, &mut delay, kb_lib::BitOrder::LSBFIRST); | ||
| + | kb_lib::shift_out(&byte_higher, &mut data_pin_1, &mut clock_pin_1, &mut delay, kb_lib::BitOrder::LSBFIRST); | ||
| - | <note warning> | + | // Read from the input pins for scanning: | 
| - | O arhivă (sau mai multe dacă este cazul) cu fişierele obţinute în urma realizării proiectului: surse, scheme, etc. Un fişier README, un ChangeLog, un script de compilare şi copiere automată pe uC crează întotdeauna o impresie bună ;-). | + | let idx = if col == 9 { 9usize } else { 8 - col as usize }; | 
| - | Fişierele se încarcă pe wiki folosind facilitatea **Add Images or other files**. Namespace-ul în care se încarcă fişierele este de tipul **:pm:prj20??:c?** sau **:pm:prj20??:c?:nume_student** (dacă este cazul). **Exemplu:** Dumitru Alin, 331CC -> **:pm:prj2009:cc:dumitru_alin**. | + | if rows0.is_low().unwrap() { | 
| - | </note> | + | keyState[0][idx] = true; | 
| + | } else { | ||
| + | keyState[0][idx] = false; | ||
| + | } | ||
| - | ===== Jurnal ===== | + | if rows1.is_low().unwrap() { | 
| + | keyState[1][idx] = true; | ||
| + | } else { | ||
| + | keyState[1][idx] = false; | ||
| + | } | ||
| - | <note tip> | + | if rows2.is_low().unwrap() { | 
| - | Puteți avea și o secțiune de jurnal în care să poată urmări asistentul de proiect progresul proiectului. | + | if idx == 0 { | 
| - | </note> | + | is_shift_pressed = true; | 
| + | } else { | ||
| + | keyState[2][idx] = true; | ||
| + | } | ||
| + | } else { | ||
| + | keyState[2][idx] = false; | ||
| + | } | ||
| - | ===== Bibliografie/Resurse ===== | + | if rows3.is_low().unwrap() { | 
| + | keyState[3][idx] = true; | ||
| + | } else { | ||
| + | keyState[3][idx] = false; | ||
| + | } | ||
| + | |||
| + | bits >>= 1; | ||
| + | latch_pin_1.set_high().unwrap(); | ||
| + | latch_pin_2.set_high().unwrap(); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | A doua parte se ocupa cu comunicarea către STM32: | ||
| + | |||
| + | <code c> | ||
| + | for i in 0..4 { | ||
| + | for j in 0..10 { | ||
| + | if keyState[i][j] { | ||
| + | |||
| + | if is_shift_pressed { | ||
| + | last_key_pressed = kb_lib::SHIFT_KEYS[i][j]; | ||
| + | is_shift_pressed = !is_shift_pressed; | ||
| + | } else { | ||
| + | last_key_pressed = KEYS[i][j]; | ||
| + | } | ||
| + | delay.delay_ms(100); | ||
| + | write!(uart,"{}", &last_key_pressed).unwrap(); | ||
| + | delay.delay_ms(100); | ||
| + | break; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Pentru partea de logica principala, atât în STM32, cât și în ESP32 am folosit logica unor FSM-uri. | ||
| + | |||
| + | ESP32 are FSM-uri separate pentru procesarea de SSID/Parola (pentru conectarea cu serverul NTP), procesarea comenzilor speciale pentru SD, rulării comenzilor: | ||
| + | |||
| + | <code c> | ||
| + | enum State { | ||
| + | MW_LOADING, | ||
| + | MW_RUNNING, | ||
| + | MW_RUNNING_SETUP | ||
| + | }; | ||
| + | |||
| + | enum CredentialsState { | ||
| + | CRED_NOT_PROV, | ||
| + | CRED_PROV, | ||
| + | CRED_NO_CHECK | ||
| + | }; | ||
| + | |||
| + | enum CredentialsProvStatus { | ||
| + | NONE, | ||
| + | SSID, | ||
| + | BOTH | ||
| + | }; | ||
| + | |||
| + | enum NTPState { | ||
| + | NTP_NOT_CONN, | ||
| + | NTP_CONN | ||
| + | }; | ||
| + | |||
| + | enum SDState { | ||
| + | SD_PROC_B, | ||
| + | SD_NO_PROC_B, | ||
| + | SD_PROC_FN | ||
| + | }; | ||
| + | |||
| + | enum SDCommand { | ||
| + | SD_COMM_NONE, | ||
| + | SD_COMM_WR, | ||
| + | SD_COMM_RD, | ||
| + | SD_COMM_AP, | ||
| + | SD_COMM_CR, | ||
| + | SD_COMM_LS, | ||
| + | SD_COMM_RM | ||
| + | }; | ||
| + | </code> | ||
| + | |||
| + | FSM-ul pentru STM32 se ocupa de inițializare, iar apoi intra într-un loop Procesare Comanda <-> Afișare Status. | ||
| + | |||
| + | <code Rust> | ||
| + | pub enum State { | ||
| + | StateGetSsid, | ||
| + | StateGetSsidPwd, | ||
| + | StateInit, | ||
| + | StateLoadCmd, | ||
| + | StateDoneLoadCmd | ||
| + | } | ||
| + | </code> | ||
| <note> | <note> | ||
| - | Listă cu documente, datasheet-uri, resurse Internet folosite, eventual grupate pe **Resurse Software** şi **Resurse Hardware**. | + | Comunicarea dintre ESP32 și STM32 a fost cea mai "tricky" parte din software. Fiind doar 2 canale de comunicare seriale (unul prin care ESP32 trebuia sa proceseze o comanda legată de sistemul de fișiere, și unul pentru afișarea pe display a input-ului), și pentru ca a evita procesarea comenzilor la nivel de string de fiecare data (și pentru a mapa mai ușor la numărul de argumente așteptate), am folosit un byte de start pentru a determina tipul unei comenzi. | 
| + | |||
| + | Code snippet ca exemplu: | ||
| + | <code c> | ||
| + | match processed_cmd.cmd_type { | ||
| + | CommandType::CmdLs => { | ||
| + | first_byte = '5' as u8; | ||
| + | } | ||
| + | |||
| + | CommandType::CmdAp => { | ||
| + | first_byte = '2' as u8; | ||
| + | } | ||
| + | |||
| + | CommandType::CmdRd => { | ||
| + | first_byte = '1' as u8; | ||
| + | } | ||
| + | |||
| + | CommandType::CmdRm => { | ||
| + | first_byte = '6' as u8; | ||
| + | } | ||
| + | |||
| + | CommandType::CmdWr => { | ||
| + | first_byte = '3' as u8; | ||
| + | } | ||
| + | |||
| + | CommandType::CmdCr => { | ||
| + | first_byte = '4' as u8; | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| </note> | </note> | ||
| + | ===== Rezultate Obţinute ===== | ||
| - | <html><a class="media mediafile mf_pdf" href="?do=export_pdf">Export to PDF</a></html> | + | Ferris facandu-si prezenta simitita Ła power-up: | 
| + | |||
| + | {{ pm:prj2023:gpatru:ferris_startup.jpeg?500 }} | ||
| + | |||
| + | Cum arata circuitul final (utilizatorul logat default este root, se vede ca LCD-ul 16x2 este folosit pentru ca utilizatorul sa vadă ce tastează, iar display-ul OLED este folosit pentru informații despre sistemul de fișiere + afișarea orei). | ||
| + | |||
| + | Comanda ls care implicit afișează numele și dimensiunea fișierelor din /: | ||
| + | |||
| + | {{ pm:prj2023:gpatru:retro-final.jpeg?500 }} {{ pm:prj2023:gpatru:ls_example1.jpeg?500 }} | ||
| + | |||
| + | Video: (just in case): | ||
| + | |||
| + | <html> | ||
| + | <iframe width="560" height="315" src="https://www.youtube.com/embed/11CuGCW_KQc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> | ||
| + | </html> | ||
| + | ===== Concluzii ===== | ||
| + | |||
| + | Mi-ar fi plăcut sa pot sa implementez alte funcționalități ( de ex. mutarea cursorului printr-o secvența de caractere apăsate etc.), dar am subestimat dificultatea proiectului. | ||
| + | |||
| + | Concluzie finala: o experienta placuta (mai ales cand am făcut câteva scurturi XD). | ||
| + | ===== Download ===== | ||
| + | |||
| + | {{:pm/prj2023/gpatru/source_code_retro_pm_2.zip|Download source code & .sch}} | ||
| + | |||
| + | |||
| + | |||
| + | ===== Bibliografie/Resurse ===== | ||
| + | * [[https://docs.rust-embedded.org/book/| The Embedded Rust Book]] | ||
| + | * [[http://www.openmusiclabs.com/learning/digital/input-matrix-scanning/index.html| Matrix Scanning]] | ||