This shows you the differences between two versions of the page.
pm:prj2023:gpatru:retropm [2023/05/29 20:01] irina_cristina.nita [Descriere generală] |
pm:prj2023:gpatru:retropm [2023/05/30 12:09] (current) irina_cristina.nita [Download] |
||
---|---|---|---|
Line 7: | Line 7: | ||
===== Descriere generală ===== | ===== Descriere generală ===== | ||
RetroPM se bazează în principal pe comunicarea dintre 3 microcontrollere cu scopuri diferite: | RetroPM se bazează în principal pe comunicarea dintre 3 microcontrollere cu scopuri diferite: | ||
- | * ESP32: Se ocupa de Afișarea pe 2 display-uri input-ul si output-ul 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. | + | * 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. |
* 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. | * 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. | ||
* 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. | * 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. | ||
+ | {{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]] |