This shows you the differences between two versions of the page.
pm:prj2025:cmoarcas:gheorghe.petrica [2025/05/07 01:13] gheorghe.petrica |
pm:prj2025:cmoarcas:gheorghe.petrica [2025/05/30 09:55] (current) gheorghe.petrica [Rezultate Obţinute] |
||
---|---|---|---|
Line 37: | Line 37: | ||
=== Lista de piese === | === Lista de piese === | ||
- | ^ Nr. Crt. ^ Denumire Componenta ^ Cantitate ^ Descriere / Specificatii ^ | + | ^ Nr. Crt. ^ Denumire Componenta ^ Cantitate ^ Descriere / Specificatii ^ Link + DataSheet ^ |
- | | 1 | ESP32 DevKit v1 | 1 | Microcontroller cu WiFi + Bluetooth, interfete SPI/I2C/I2S | | + | | 1 | ESP32 DevKit v1 | 1 | Microcontroller cu WiFi + Bluetooth, interfete SPI/I2C/I2S |https://www.electronicmarket.ro/ro/product/esp32-wroom-32-placa-de-dezvoltare-38-pini?gad_campaignid=21513542058 https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32_datasheet_en.pdf | |
- | | 2 | Arduino Uno R3 | 1 | Pentru comunicarea SPI cu casetofonul (emulare CD changer) | | + | | 2 | Arduino Uno R3 | 1 | Pentru comunicarea SPI cu casetofonul (emulare CD changer) | https://ardushop.ro/ro/plci-de-dezvoltare/2282-placa-de-dezvoltare-uno-r3-compatibil-arduino-6427854027122.html?gad_campaignid=22058879462 https://ww1.microchip.com/downloads/en/devicedoc/doc8161.pdf | |
- | | 3 | Modul microfon I2S (ex: INMP441) | 1 | Microfon digital pentru transmitere voce (handsfree) | | + | | 3 | Modul microfon I2S (INMP441) | 1 | Microfon digital pentru transmitere voce (handsfree) | https://www.emag.ro/microfon-digital-electroweb-omnidirectional-mems-inmp441-3-f-060/pd/DL3NPBYBM/ https://www.invensense.com/wp-content/uploads/2015/02/INMP441.pdf | |
| 4 | Modul cititor microSD pe SPI | 1 | Pentru redare muzica de pe card | | | 4 | Modul cititor microSD pe SPI | 1 | Pentru redare muzica de pe card | | ||
- | | 5 | Modul ecran OLED 0.96" SPI | 1 | Afisare melodie curenta, status conexiune, etc. | | + | | 5 | Modul ecran TFT 1.44" SPI | 1 | Afisare melodie curenta, status conexiune, etc. | https://www.optimusdigital.ro/en/lcds/2167-144-lcd-for-stc-stm32-and-arduino-boards.html?gad_campaignid=19615979487 https://cdn-shop.adafruit.com/datasheets/ST7735.pdf | |
| 6 | Butoane tactile (tip pushbutton) | 5 | Play/Pause, Next, Prev, Accept Call, Reject Call | | | 6 | Butoane tactile (tip pushbutton) | 5 | Play/Pause, Next, Prev, Accept Call, Reject Call | | ||
- | | 7 | Amplificator audio clasa D (ex: TPA3116D2) | 1 | Suporta pana la 100W pe canal, alimentare 12–24V | | + | | 7 | Amplificator audio clasa D (TPA3118) | 1 | Suporta pana la 60W pe canal, alimentare 12–24V | https://sigmanortec.ro/modul-amplificator-audio-1-canal-60w-4-8ohm-tpa3118-8-24vdc-digital?SubmitCurrency=1&id_currency=2&gad_campaignid=22174019478 https://www.ti.com/lit/ds/symlink/tpa3118d2.pdf | |
- | | 8 | Difuzor JBL 5 inch, 4 Ohmi, 175W max | 1 | Pentru testarea audio (simulare casetofon) | | + | | 8 | Difuzor 5 inch, 4 Ohmi | 1 | Pentru testarea audio (simulare casetofon) | |
- | | 9 | Sursa de alimentare 24V, minim 5A | 1 | Pentru alimentarea amplificatorului si difuzorului | | + | | 9 | Sursa de alimentare 24V | 1 | Pentru alimentarea amplificatorului si difuzorului | https://www.a2t.ro/default-category/sursa-de-alimentare-24v-10-in-comutatie-stabilizata?gad_campaignid=18574987832 | |
- | | 10 | Sursa step-down (ex: LM2596) | 1 | Pentru a obtine 5V/3.3V pentru ESP32 si periferice | | + | | 10 | Sursa step-down (LM2596) | 1 | Pentru a obtine 5V/3.3V pentru ESP32 si periferice | https://www.emag.ro/modul-coborare-tensiune-lm2596-tri252/pd/D9XL5VBBM/?ref=history-shopping_423138763_34366_1 https://www.ti.com/lit/ds/symlink/lm2596.pdf | |
- | | 11 | Conector jack 3.5mm stereo | 1 | Input alternativ audio auxiliar | | + | | 11 | Modul DAC audio CS4344 | 1 | Pentru a genera semnal analogic | https://ardushop.ro/ro/module/729-modul-dac-audio-cs4344-cu-filtru-trece-jos-si-port-i2s-compatibil-cu-arduino-6427854009272.html https://www.cirrus.com/products/cs4344/ | |
| 12 | Breadboard + jumper wires | 1 set | Pentru prototipare si conectare module | | | 12 | Breadboard + jumper wires | 1 set | Pentru prototipare si conectare module | | ||
- | | 13 | Rezistente pull-up/pull-down | n/a | Pentru stabilizarea semnalelor GPIO (la butoane) | | ||
=== Diagrama Bloc pentru casetofon === | === Diagrama Bloc pentru casetofon === | ||
- | {{:pm:prj2025:cmoarcas:arduino_uno.png?200|}} | + | {{:pm:prj2025:cmoarcas:ImperiumBT.png?700|}} |
+ | |||
+ | === Schema electrica === | ||
+ | {{:pm:prj2025:cmoarcas:adapter-esp32.png?700|}} | ||
===== Software Design ===== | ===== Software Design ===== | ||
+ | == CDC Emulator == | ||
- | <note tip> | + | Pentru a putea trimite orice fel de semnal audio catre casetofonul auto, vom folosi un Arduino Uno, iar cu ajutorul interfetei SPI vom trimite o secvente de date catre casetofon. In lipsa acestei secvente, casetofonul in momentul in care este aleasa optiunea de redare de pe CD, va genera o eroare de tipul **NO CD CHANGER** chiar daca pe inputul audio este trimis semnal. |
- | Descrierea codului aplicaţiei (firmware): | + | |
- | * mediu de dezvoltare (if any) (e.g. AVR Studio, CodeVisionAVR) | + | |
- | * librării şi surse 3rd-party (e.g. Procyon AVRlib) | + | |
- | * algoritmi şi structuri pe care plănuiţi să le implementaţi | + | |
- | * (etapa 3) surse şi funcţii implementate | + | |
- | </note> | + | |
- | ===== Rezultate Obţinute ===== | + | Folosim urmatoarele define-uri pentru a indica fiecare comanda suportata de casetofon: |
+ | #define CDC_PREFIX1 0x53 | ||
+ | #define CDC_PREFIX2 0x2C | ||
+ | #define CDC_END_CMD 0x14 | ||
+ | #define CDC_PLAY 0xE4 | ||
+ | #define CDC_STOP 0x10 | ||
+ | #define CDC_NEXT 0xF8 | ||
+ | #define CDC_PREV 0x78 | ||
+ | #define CDC_SEEK_FWD 0xD8 | ||
+ | #define CDC_SEEK_RWD 0x58 | ||
+ | #define CDC_CD1 0x0C | ||
+ | #define CDC_CD2 0x8C | ||
+ | #define CDC_CD3 0x4C | ||
+ | #define CDC_CD4 0xCC | ||
+ | #define CDC_CD5 0x2C | ||
+ | #define CDC_CD6 0xAC | ||
+ | #define CDC_SCAN 0xA0 | ||
+ | #define CDC_SFL 0x60 | ||
+ | #define CDC_PLAY_NORMAL 0x08 | ||
+ | #define MODE_PLAY 0xFF | ||
+ | #define MODE_SHFFL 0x55 | ||
+ | #define MODE_SCAN 0x00 | ||
+ | uint8_t cd, tr, mode; | ||
+ | unsigned long prevMillis = 0; | ||
+ | |||
+ | Functia urmatoare **send_package()** trimite un pachet de cate 8 octeti pe SPI, cu intarziere pe fiecare transfer: | ||
+ | void send_package(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3, uint8_t c4, uint8_t c5, uint8_t c6, uint8_t c7) { | ||
+ | uint8_t data[8] = {c0, c1, c2, c3, c4, c5, c6, c7}; | ||
+ | for (int i = 0; i < 8; i++) { | ||
+ | SPDR = data[i]; | ||
+ | while (!(SPSR & (1 << SPIF))); | ||
+ | delayMicroseconds(874); | ||
+ | } | ||
+ | } | ||
- | <note tip> | + | Initializam SPI, dupa cum urmeaza, astfel incat sa ruleze cu o viteza de transfer de 62.5kHz |
- | Care au fost rezultatele obţinute în urma realizării proiectului vostru. | + | void spi_init() { |
- | </note> | + | DDRB |= (1 << PB3) | (1 << PB5); |
+ | DDRB &= ~(1 << PB4); | ||
+ | SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0); | ||
+ | SPSR &= ~(1 << SPI2X); | ||
+ | } | ||
- | ===== Concluzii ===== | + | In functia **setup()** initializam valorile initiale pentru disc, mod de redare, piesa, precum si interfata SPI. Dupa care trimitem o secventa de comenzi pentru //load// si //idle// |
+ | void setup() { | ||
+ | cd = 1; | ||
+ | tr = 1; | ||
+ | mode = MODE_PLAY; | ||
+ | #ifdef DEBUG | ||
+ | Serial.begin(9600); | ||
+ | #endif | ||
+ | delay(1000); | ||
+ | spi_init(); | ||
+ | send_package(0x74, 0xBE, 0xFE, 0xFF, 0xFF, 0xFF, 0x8F, 0x7C); // idle | ||
+ | delayMicroseconds(10000); | ||
+ | send_package(0x34, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0xFA, 0x3C); // load disc | ||
+ | delayMicroseconds(100000); | ||
+ | send_package(0x74, 0xBE, 0xFE, 0xFF, 0xFF, 0xFF, 0x8F, 0x7C); // idle | ||
+ | delayMicroseconds(10000); | ||
+ | #ifdef DEBUG | ||
+ | Serial.println("Sent idle/load/idle commands"); | ||
+ | #endif | ||
+ | } | ||
- | ===== Download ===== | + | In continuare tot pe Arduino, in functia **loop()** vom trimite la fiecare 50ms cate un pachet care contine discul, piesa si modul de redare pentru a mentine conexiunea activa cu casetofonul auto ales: |
+ | void loop() { | ||
+ | if ((millis() - prevMillis) > 50) { | ||
+ | send_package(0x34, 0xBF ^ cd, 0xFF ^ tr, 0xFF, 0xFF, mode, 0xCF, 0x3C); | ||
+ | prevMillis = millis(); | ||
- | <note warning> | + | #ifdef DEBUG |
- | 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ă ;-). | + | Serial.println("Sent packet"); |
+ | #endif | ||
+ | } | ||
+ | } | ||
- | 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**. | + | == Bluetooth Receiver == |
- | </note> | + | |
- | ===== Jurnal ===== | + | In urmatoarele bucati de cod voi descrie cum am folosit un **ESP32** pentru a deveni un receiver audio prin interfata Bluetooth a acestuia si sa redirectionez semnalul audio prin interfata I2S catre DAC extern (e.g. CS4344) |
+ | |||
+ | Definim in continuare pinii folositi pentru interfata I2S, butoanele de play/pause pe care le vom folosi, pinii SPI folositi pentru ecranul TFT, care va folosi biblioteca Adafruit_ST7735, precum si variabilele folosite pentru debounce: | ||
+ | #define I2S_BCLK 26 | ||
+ | #define I2S_LRCK 25 | ||
+ | #define I2S_DATA 22 | ||
+ | #define I2S_MCLK 3 // sau I2S_PIN_NO_CHANGE | ||
+ | |||
+ | // === Butoane === | ||
+ | #define BTN_PLAY 12 | ||
+ | #define BTN_PAUSE 14 // redenumit din NEXT | ||
+ | |||
+ | // === Ecran ST7735 === | ||
+ | #define TFT_CS 5 | ||
+ | #define TFT_RST 4 | ||
+ | #define TFT_DC 16 | ||
+ | |||
+ | Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); | ||
+ | BluetoothA2DPSink a2dp_sink; | ||
+ | |||
+ | String current_song = "Fara melodie"; | ||
+ | String last_displayed_song = ""; | ||
+ | |||
+ | volatile bool playPressed = false; | ||
+ | volatile bool pausePressed = false; | ||
+ | |||
+ | unsigned long lastDebouncePlay = 0; | ||
+ | unsigned long lastDebouncePause = 0; | ||
+ | const unsigned long debounceDelay = 150; | ||
+ | |||
+ | unsigned long lastPlayPressTime = 0; | ||
+ | bool waitingForSecondClick = false; | ||
+ | |||
+ | Functia urmatoare **metadata_callback()** va fi folosita pentru trimiterea de text pe ecranul TFT: | ||
+ | void metadata_callback(uint8_t id, const uint8_t *text) { | ||
+ | if (id == 0x01) { | ||
+ | current_song = String((char*)text); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | In functia **update_display()** vom scrie momentan numele melodiei curente: | ||
+ | void update_display() { | ||
+ | if (current_song != last_displayed_song) { | ||
+ | tft.fillScreen(ST77XX_BLACK); | ||
+ | tft.setTextColor(ST77XX_WHITE); | ||
+ | tft.setTextSize(1); | ||
+ | tft.setCursor(2, 10); | ||
+ | tft.println("Redare Bluetooth:"); | ||
+ | tft.setCursor(2, 30); | ||
+ | tft.setTextWrap(true); | ||
+ | tft.setTextSize(1); | ||
+ | tft.println(current_song); | ||
+ | tft.setCursor(2, 60); | ||
+ | tft.setTextSize(1); | ||
+ | tft.println("Play=D12 | Pause=D14"); | ||
+ | last_displayed_song = current_song; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | Definim urmatoarele 2 functii care vor declansa intreruperile corespunzatoare cand unul din cele 2 butoane este selectat: | ||
+ | void IRAM_ATTR isr_play() { | ||
+ | if ((millis() - lastDebouncePlay) > debounceDelay) { | ||
+ | playPressed = true; | ||
+ | lastDebouncePlay = millis(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void IRAM_ATTR isr_pause() { | ||
+ | if ((millis() - lastDebouncePause) > debounceDelay) { | ||
+ | pausePressed = true; | ||
+ | lastDebouncePause = millis(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | In functia **setup()** initializam interfata I2S cu un sample rate de 44.1 Mhz si un MCLK de 11.2896 Mhz, precum si pinii pe care ii folosim pentr DAC-ul CS4344. Mai apoi pornim receiver-ul Bluetooth A2DP si initializam o conexiune 'ESP32_Speaker'. In final setam butoanele D12 si D14 ca intrari cu rezistenta de pull-up interna, pe care atasam 2 intreruperi. | ||
+ | void setup() { | ||
+ | Serial.begin(115200); | ||
+ | i2s_config_t i2s_config = { | ||
+ | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), | ||
+ | .sample_rate = 44100, | ||
+ | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, | ||
+ | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, | ||
+ | .communication_format = I2S_COMM_FORMAT_STAND_I2S, | ||
+ | .intr_alloc_flags = 0, | ||
+ | .dma_buf_count = 8, | ||
+ | .dma_buf_len = 64, | ||
+ | .use_apll = true, | ||
+ | .tx_desc_auto_clear = true, | ||
+ | .fixed_mclk = 11289600 | ||
+ | }; | ||
+ | i2s_pin_config_t pin_config = { | ||
+ | .mck_io_num = I2S_MCLK, | ||
+ | .bck_io_num = 26, | ||
+ | .ws_io_num = 25, | ||
+ | .data_out_num = 22, | ||
+ | .data_in_num = I2S_PIN_NO_CHANGE | ||
+ | }; | ||
+ | a2dp_sink.set_pin_config(pin_config); | ||
+ | a2dp_sink.set_avrc_metadata_callback(metadata_callback); | ||
+ | a2dp_sink.start("ESP32_Speaker"); | ||
+ | tft.initR(INITR_BLACKTAB); | ||
+ | tft.setRotation(1); | ||
+ | update_display(); | ||
+ | pinMode(BTN_PLAY, INPUT_PULLUP); | ||
+ | pinMode(BTN_PAUSE, INPUT_PULLUP); | ||
+ | attachInterrupt(digitalPinToInterrupt(BTN_PLAY), isr_play, FALLING); | ||
+ | attachInterrupt(digitalPinToInterrupt(BTN_PAUSE), isr_pause, FALLING); | ||
+ | } | ||
+ | |||
+ | In functia **loop()** se executa continuu si verifica daca butoanele D12 si D14 sunt apasate, precum actualizeaza si ecranul TFT: | ||
+ | void loop() { | ||
+ | unsigned long now = millis(); | ||
+ | if (playPressed) { | ||
+ | playPressed = false; | ||
+ | if (waitingForSecondClick && (now - lastPlayPressTime < doubleClickDelay)) { | ||
+ | a2dp_sink.next(); | ||
+ | waitingForSecondClick = false; | ||
+ | } else { | ||
+ | lastPlayPressTime = now; | ||
+ | waitingForSecondClick = true; | ||
+ | } | ||
+ | } | ||
+ | if (waitingForSecondClick && (now - lastPlayPressTime > doubleClickDelay)) { | ||
+ | a2dp_sink.play(); | ||
+ | waitingForSecondClick = false; | ||
+ | } | ||
+ | if (pausePressed) { | ||
+ | pausePressed = false; | ||
+ | a2dp_sink.pause(); | ||
+ | } | ||
+ | update_display(); | ||
+ | delay(50); | ||
+ | } | ||
+ | |||
+ | ===== Rezultate Obţinute ===== | ||
<note tip> | <note tip> | ||
- | Puteți avea și o secțiune de jurnal în care să poată urmări asistentul de proiect progresul proiectului. | + | Rezultatele obtinute au fost destul de satisfacatoare, am intampinat cateva probleme pe care nu am putut sa le rezolv din cauza DAC-ului ales, deoarece acesta are nevoie de un master clock extern care sa fie sincron cu celelalte semnale de pe ele, iar ESP-ul nu este capabil de a oferi corect un astfel de semnal, generand destul de mult zgomot (cred ca un MAX98357 ar fi rezolvat problema zgomotului). Rezultatul final poate fi observat in urmatorul link: |
+ | https://drive.google.com/file/d/1erp1HS01shDhwGrpYLM5Av1lJeFgSRbM/view?usp=sharing | ||
</note> | </note> | ||
===== Bibliografie/Resurse ===== | ===== Bibliografie/Resurse ===== | ||
- | <note> | + | https://github.com/NullString1/VWCDC |
- | Listă cu documente, datasheet-uri, resurse Internet folosite, eventual grupate pe **Resurse Software** şi **Resurse Hardware**. | + | https://schuett.io/2013/09/avr-raspberry-pi-vw-beta-vag-cdc-faker/ |
- | </note> | + | https://itohi.com/acoustics/esp32-as-bluetooth-audio/ |
- | + | ||
- | <html><a class="media mediafile mf_pdf" href="?do=export_pdf">Export to PDF</a></html> | + | |