This shows you the differences between two versions of the page.
|
pm:prj2026:theodor_ioan.buliga:adrian.vancea [2026/05/03 23:08] adrian.vancea |
pm:prj2026:theodor_ioan.buliga:adrian.vancea [2026/05/27 08:11] (current) adrian.vancea [Results] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ====== Portable MP3 Player ====== | + | {{:pm:prj2026:theodor_ioan.buliga:portable_mp3_player.zip|}}====== Portable MP3 Player ====== |
| ===== Introduction ===== | ===== Introduction ===== | ||
| Line 28: | Line 28: | ||
| - | {{:pm:prj2026:theodor_ioan.buliga:block_diagram.png?600|}} | + | {{:pm:prj2026:theodor_ioan.buliga:block_diagram.png?550|}} |
| <note> | <note> | ||
| Line 54: | Line 54: | ||
| ==== Schematics ==== | ==== Schematics ==== | ||
| + | The electrical schematic was designed in KiCad, ensuring a modular and portable design. | ||
| + | |||
| + | {{:pm:prj2026:theodor_ioan.buliga:diagrama_electrica.png?800|}} | ||
| + | |||
| + | <note> | ||
| + | **Hardware Details:** | ||
| + | * **Power:** Managed by the TP4056 module. The battery provides ~3.7V, which powers the Arduino via the VIN pin. | ||
| + | * **Audio:** DAC_R and DAC_L pins from DFPlayer are connected to the Audio Jack for stereo output. | ||
| + | * **Protection:** A 1kOhm resistor is placed on the UART line between Nano (TX) and DFPlayer (RX) to prevent noise and protect the 3.3V logic of the player. | ||
| + | </note> | ||
| + | |||
| + | ==== Pin Mapping ==== | ||
| + | |||
| + | ^ Component ^ Arduino Pin ^ Connection Type ^ | ||
| + | | OLED Display | A4 (SDA), A5 (SCL) | I2C (Hardware TWI) | | ||
| + | | DFPlayer Mini | D8 (TX), D9 (RX) | UART (9600 bps) | | ||
| + | | Buttons | D2, D3, D4 | Digital Input (Interrupts) | | ||
| + | | Audio Jack | DAC_R, DAC_L, GND | Analog Audio | | ||
| + | | Power Switch | VIN | Power Control | | ||
| + | |||
| + | |||
| + | === Exemplu de functionare: === | ||
| + | |||
| + | {{:pm:prj2026:theodor_ioan.buliga:poza_2.jpeg?600|}} | ||
| + | |||
| + | {{:pm:prj2026:theodor_ioan.buliga:poza_1.jpeg?600|}} | ||
| ===== Software Design ===== | ===== Software Design ===== | ||
| Line 76: | Line 102: | ||
| * **Laboratory 7 (I2C/TWI):** Control of the OLED screen via the I2C protocol, using the hardware TWI module of the microcontroller. | * **Laboratory 7 (I2C/TWI):** Control of the OLED screen via the I2C protocol, using the hardware TWI module of the microcontroller. | ||
| </note> | </note> | ||
| + | |||
| + | ===== Software Design ===== | ||
| + | |||
| + | ==== Overview ==== | ||
| + | |||
| + | The firmware is written entirely in bare-metal C with no external Arduino libraries. | ||
| + | All peripheral control is done through direct manipulation of ATmega328P hardware | ||
| + | registers (TWBR, TWCR, TWDR, DDRD, PORTD, PIND). The project is built with | ||
| + | PlatformIO using the AVR-GCC toolchain. | ||
| + | |||
| + | **Key metrics:** | ||
| + | * ~300 lines of non-trivial, original code (''src/main.cpp'') | ||
| + | * RAM usage: 54.8% (1123 / 2048 bytes) | ||
| + | * Flash usage: 11.4% (3502 / 30720 bytes) | ||
| + | |||
| + | ==== Laboratory Concepts Applied ==== | ||
| + | |||
| + | <note tip> | ||
| + | * **Laboratory 0 (GPIO):** Buttons on PD4/PD5/PD6 configured as digital inputs with internal pull-ups via DDRD/PORTD registers. State is polled each loop iteration directly from the PIND register. DFPlayer communication pins (PD2, PD3) configured as output/input via the SOFT_DDR register. | ||
| + | * **Laboratory 1 (UART):** Software UART (bit-banging at 9600 baud, 104µs bit period) implemented from scratch for bidirectional communication with the DFPlayer Mini. TX sends 10-byte command packets; RX reads 10-byte response packets with a timeout. No hardware USART registers are used — the serial protocol is reproduced entirely in software. | ||
| + | * **Laboratory 6 (I2C/TWI):** Hardware TWI module used to drive the SSD1306 OLED display. Direct register access: TWBR=72 (100kHz clock), TWCR for START/STOP/ACK control, TWDR for data bytes. A full 1024-byte framebuffer is flushed to the display every frame via 32-byte I2C transactions. | ||
| + | </note> | ||
| + | |||
| + | ==== Pin Mapping (Firmware) ==== | ||
| + | |||
| + | ^ Component ^ Arduino Pin ^ Register ^ Type ^ | ||
| + | | OLED Display | A4 (SDA), A5 (SCL) | TWBR,TWCR,TWDR | I2C Hardware TWI | | ||
| + | | DFPlayer RX | D2 (PD2) | PORTD bit 2 | Software UART TX (bit-bang) | | ||
| + | | DFPlayer TX | D3 (PD3) | PIND bit 3 | Software UART RX (bit-bang) | | ||
| + | | Button PREV/VOL- | D4 (PD4) | PIND bit 4 | Digital Input, pull-up | | ||
| + | | Button PLAY/PAUSE | D5 (PD5) | PIND bit 5 | Digital Input, pull-up | | ||
| + | | Button NEXT/VOL+ | D6 (PD6) | PIND bit 6 | Digital Input, pull-up | | ||
| + | | Audio Jack | DAC_R, DAC_L | — | Analog (DFPlayer direct) | | ||
| + | |||
| + | ==== State Machine ==== | ||
| + | |||
| + | The main loop (~110ms/iteration) drives a three-state UI machine: | ||
| + | |||
| + | ^ State ^ ui_mode ^ Enter trigger ^ Exit trigger ^ | ||
| + | | Track Mode | 0 | Default / PLAY in vol / timeout | — | | ||
| + | | Volume Mode | 1 | Long-press PREV or NEXT (>880ms) | Short PLAY or 5s inactivity | | ||
| + | | Track Select | 2 | Long-press PLAY | Short/long PLAY confirms track | | ||
| + | |||
| + | In each state the OLED shows different content. Transitions are triggered exclusively | ||
| + | by button events or a countdown timer (vol_timer), with no hardware interrupts — | ||
| + | the entire logic is polling-based inside the main while(1) loop. | ||
| + | |||
| + | ==== Software UART — DFPlayer Communication ==== | ||
| + | |||
| + | The DFPlayer Mini communicates at 9600 baud using 10-byte packets: | ||
| + | |||
| + | 0x7E 0xFF 0x06 CMD 0x00 P1 P2 CHK_H CHK_L 0xEF | ||
| + | |||
| + | A bit-bang TX function drives the PD2 line manually, respecting the 104µs bit period: | ||
| + | |||
| + | <code c> | ||
| + | static void suart_tx_byte(uint8_t b) { | ||
| + | SOFT_PORT &= ~(1 << SOFT_TX_PIN); // start bit | ||
| + | _delay_us(104); | ||
| + | for (uint8_t i = 0; i < 8; i++) { | ||
| + | if (b & 0x01) SOFT_PORT |= (1 << SOFT_TX_PIN); | ||
| + | else SOFT_PORT &= ~(1 << SOFT_TX_PIN); | ||
| + | _delay_us(104); | ||
| + | b >>= 1; | ||
| + | } | ||
| + | SOFT_PORT |= (1 << SOFT_TX_PIN); // stop bit | ||
| + | _delay_us(104); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | A matching RX function reads PD3 with a timeout, used at startup to query the | ||
| + | total number of tracks on the SD card (command 0x48). If the DFPlayer does not | ||
| + | respond, the firmware falls back to a compile-time constant (MAX_TRACKS_FALLBACK = 6). | ||
| + | |||
| + | Commands used at runtime: | ||
| + | |||
| + | ^ Command ^ Hex ^ Description ^ | ||
| + | | Reset | 0x0C | Hardware reset on startup | | ||
| + | | Volume | 0x06 | Set volume level (0–30) | | ||
| + | | Play | 0x03 | Play specific track by number | | ||
| + | | Pause | 0x0E | Pause playback | | ||
| + | | Resume | 0x0D | Resume playback | | ||
| + | | Query | 0x48 | Read total file count from SD | | ||
| + | |||
| + | ==== I2C / OLED Display System ==== | ||
| + | |||
| + | A 1024-byte framebuffer (''oled_buf[1024]'') mirrors the 128×64 display in RAM | ||
| + | (8 pages × 128 columns, 1 bit per pixel). Each frame follows this sequence: | ||
| + | |||
| + | - ''memset(oled_buf, 0, 1024)'' — clear the buffer | ||
| + | - ''draw_ui()'' — render current UI state into the buffer | ||
| + | - ''oled_flush()'' — push the full buffer to the OLED via I2C | ||
| + | |||
| + | Two rendering primitives exist with an important constraint: | ||
| + | |||
| + | * ''oled_char(col, page, c)'' — writes a 5×8 font glyph using byte assignment (''='') | ||
| + | * ''draw_pixel(x, y, color)'' — sets/clears one pixel using bitwise OR ('' |= '') | ||
| + | |||
| + | <note warning> | ||
| + | ''oled_char'' uses ''='' which overwrites the entire page byte, erasing any pixels | ||
| + | set by ''draw_pixel'' in the same column. The screen layout is designed so that | ||
| + | each page is used exclusively by text OR by pixels, never both simultaneously. | ||
| + | </note> | ||
| + | |||
| + | The font is a 42-entry 5×8 bitmap array stored in PROGMEM (Flash), covering | ||
| + | digits 0–9, letters A–Z, and special glyphs for '':'', ''-'', ''%'', ▶ (\\x01), ⏸ (\\x02). | ||
| + | |||
| + | ==== Big Character Rendering ==== | ||
| + | |||
| + | To make the track number and volume level visually prominent, a ''draw_big_char()'' | ||
| + | function scales every 5×8 glyph to 10×16 pixels by replacing each source pixel | ||
| + | with a 2×2 block: | ||
| + | |||
| + | <code c> | ||
| + | static void draw_big_char(uint8_t x, uint8_t y_top, char c) { | ||
| + | // resolve font index for c ... | ||
| + | for (uint8_t col = 0; col < 5; col++) { | ||
| + | uint8_t bits = pgm_read_byte(&font5x8[idx][col]); | ||
| + | for (uint8_t row = 0; row < 8; row++) { | ||
| + | if (bits & (1 << row)) { | ||
| + | draw_pixel(x + col*2, y_top + row*2, 1); | ||
| + | draw_pixel(x + col*2 + 1, y_top + row*2, 1); | ||
| + | draw_pixel(x + col*2, y_top + row*2 + 1, 1); | ||
| + | draw_pixel(x + col*2 + 1, y_top + row*2 + 1, 1); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | In track mode this renders ''⏸ 01'' (icon + two digits) centered in the top 16px. | ||
| + | In volume mode it renders the current level ''05'' centered in the same area. | ||
| + | |||
| + | ==== Spectrum Bar Animation ==== | ||
| + | |||
| + | 15 bars are animated each frame using a smooth target-following algorithm: | ||
| + | |||
| + | * 20% chance per frame: pick a new random target height for that bar (3–28px) | ||
| + | * Attack: bar grows +2px/frame toward its target | ||
| + | * Decay: bar shrinks −1px/frame toward its target | ||
| + | * On pause: all bars and targets are instantly set to 0 → flat baseline line | ||
| + | |||
| + | This produces a natural-looking equalizer effect without any real audio analysis. | ||
| + | |||
| + | ==== Button Logic ==== | ||
| + | |||
| + | Each button uses a hold counter incremented every ~110ms loop iteration: | ||
| + | |||
| + | * **Short press** (released with counter 1–8): change track / adjust volume / play-pause | ||
| + | * **Long press** (counter exceeds 8, ~880ms): open volume menu (PREV/NEXT) or track select menu (PLAY) | ||
| + | |||
| + | A ''prev_long'' / ''next_long'' flag is set on long-press activation to prevent | ||
| + | the short-press action from also firing when the button is released. | ||
| + | |||
| + | Volume is stored as an integer 0–10. The DFPlayer always receives ''vol_level × 3'' | ||
| + | (mapping 0–10 → 0–30, which is the DFPlayer's native 0–30 range). | ||
| + | |||
| + | ==== Screen Layout ==== | ||
| + | |||
| + | <code> | ||
| + | Track Mode: | ||
| + | y=0–15 (pages 0–1): ⏸/▶ 01 ← 16px big characters (draw_pixel only) | ||
| + | y=16: ──────── ← separator line | ||
| + | y=17–47 (pages 2–5): spectrum ← 15 bars × 5px wide, 7px spacing | ||
| + | y=47: ──────── ← baseline | ||
| + | y=48–55 (page 6): 00:23 ← elapsed time (oled_char only) | ||
| + | y=62–63 (page 7): ▓▓▓░░░ ← 2px volume fill bar (draw_pixel only) | ||
| + | |||
| + | Volume Mode: | ||
| + | y=0–15 (pages 0–1): 05 ← big vol number (draw_pixel only) | ||
| + | y=16–39 (pages 2–4): [▓▓▓░░░] ← horizontal fill bar with border | ||
| + | y=40–47 (page 5): VOLUME ← label (oled_char only) | ||
| + | y=62–63 (page 7): ▓▓▓░░░ ← same 2px volume indicator | ||
| + | |||
| + | Track Select: | ||
| + | page 0: SELECT TRACK | ||
| + | pages 1–6: ▶ 01 / 02 / ... / 06 ← cursor + track number list | ||
| + | page 7: PLAY=CONFIRM | ||
| + | </code> | ||
| ===== Results ===== | ===== Results ===== | ||
| + | |||
| + | The final product is a functional, standalone portable MP3 player built around | ||
| + | the Arduino Nano and DFPlayer Mini, with a custom OLED user interface written | ||
| + | entirely without external libraries. | ||
| + | |||
| + | {{:pm:prj2026:theodor_ioan.buliga:whatsapp_image_2026-05-27_at_08.06.10.jpeg?500|}} | ||
| + | {{:pm:prj2026:theodor_ioan.buliga:whatsapp_image_2026-05-27_at_08.08.02.jpeg?500|}} | ||
| + | {{:pm:prj2026:theodor_ioan.buliga:whatsapp_image_2026-05-27_at_08.05.58.jpeg?500|}} | ||
| + | **Functional features achieved:** | ||
| + | * Playback of MP3 files from a microSD card with Play / Pause / Next / Previous control | ||
| + | * 3-mode user interface (Track Mode, Volume Mode, Track Select) managed by a state machine | ||
| + | * OLED display showing a 16px scaled track number and play/pause icon, a spectrum bar animation, elapsed time, and a volume indicator bar | ||
| + | * Volume control (0–10 steps) via long-press + short-press button interaction | ||
| + | * Track select menu listing all available tracks (auto-detected from SD card at startup) | ||
| + | * Spectrum animation that instantly flattens to a straight line on pause | ||
| + | * Portable power supply: 3.7V Li-Po battery with TP4056 charging and protection circuit | ||
| + | * Stereo audio output via 3.5mm jack | ||
| + | |||
| + | **Performance measurements:** | ||
| + | * Main loop iteration: ~110ms (10ms active delay + ~100ms I2C framebuffer flush) | ||
| + | * Firmware RAM usage: 54.8% (1123 / 2048 bytes) — stable, no stack overflow observed | ||
| + | * Firmware Flash usage: 11.4% (3502 / 30720 bytes) — significant headroom remaining | ||
| + | * Button response latency: max 110ms (one loop iteration) — imperceptible in practice | ||
| + | * Volume range: 0–30 on DFPlayer (mapped from 0–10 user-facing steps) | ||
| + | |||
| + | **Hypothesis validation:** \\ | ||
| + | The initial hypothesis was confirmed. Offloading audio decoding to the DFPlayer Mini | ||
| + | allowed the ATmega328P to dedicate its full cycle budget to UI rendering and button | ||
| + | polling, resulting in a smooth 9 FPS display refresh with no audio glitches. | ||
| ===== Conclusions ===== | ===== Conclusions ===== | ||
| - | ===== Download ===== | + | The project successfully demonstrates a balanced embedded system where dedicated |
| + | hardware (DFPlayer) handles the computationally expensive task (MP3 decoding) while | ||
| + | the microcontroller focuses on user interaction and visual feedback. | ||
| + | |||
| + | **What worked well:** | ||
| + | * The bare-metal approach (no libraries) gave full control over timing and memory, | ||
| + | which was essential given the 2KB RAM constraint of the ATmega328P. | ||
| + | * The software UART bit-bang implementation proved reliable for DFPlayer communication | ||
| + | and also enabled reading the track count response at startup. | ||
| + | * The framebuffer rendering approach (draw everything into RAM, flush once) eliminated | ||
| + | partial-update artifacts on the OLED. | ||
| + | * The hold-counter button logic (short press vs. long press on the same button) reduced | ||
| + | the required number of physical buttons from 5 to 3 without sacrificing usability. | ||
| + | |||
| + | **Challenges encountered:** | ||
| + | * The ''oled_char'' (uses ''='') vs. ''draw_pixel'' (uses ''|='') page conflict required | ||
| + | careful layout planning to avoid text erasing pixel graphics in the same page byte. | ||
| + | * DFPlayer communication is sensitive to timing — the soft UART bit period (104µs) | ||
| + | had to be exact, and any I2C activity during transmission caused corruption. | ||
| + | * Power stability: the Li-Po voltage drop under load initially caused resets; | ||
| + | resolved by adding a decoupling capacitor near the Arduino VIN pin. | ||
| + | |||
| + | **Future improvements:** | ||
| + | * Replace the simulated spectrum with a real VU meter using the ADC and a signal | ||
| + | tap from the audio line. | ||
| + | * Add a progress bar using DFPlayer command 0x28 (query current playback position). | ||
| + | * Design a custom PCB to reduce size and improve reliability over the prototype board. | ||
| ===== Journal ===== | ===== Journal ===== | ||
| - | * **May 3:** Created the Wiki documentation page and the Block Diagram. | + | * **May 3**: Created the Wiki documentation page and the Block Diagram. Defined project scope and selected components. |
| + | * **May 10**: Hardware milestone: developed the full system schematic in KiCad including power management (TP4056) and audio routing. Verified I2C and UART pin mapping on breadboard. Confirmed stable communication between Arduino, OLED, and DFPlayer. | ||
| + | * **May 15**: Implemented bare-metal I2C driver (TWI registers) and basic OLED framebuffer rendering. First text displayed on screen. | ||
| + | * **May 18**: Implemented software UART TX for DFPlayer control. First successful track playback triggered from firmware. Volume and play/pause commands verified. | ||
| + | * **May 20**: Added software UART RX and ''dfp_query_tracks()'' to auto-detect SD card track count at startup. Verified fallback behavior when DFPlayer does not respond. | ||
| + | * **May 22**: Implemented spectrum bar animation with smooth attack/decay algorithm. Added instant flatten-to-line behavior on pause. | ||
| + | * **May 24**: Designed and implemented the 3-mode state machine (Track / Volume / Track Select). Rewrote button logic to support short press vs. long press on the same button using hold counters. | ||
| + | * **May 26**: Added ''draw_big_char()'' function for 2× scaled 16px characters. Redesigned the OLED layout: big icon + track number in top 16px, spectrum in the middle, time and volume bar at the bottom. | ||
| + | * **May 27**: Final firmware build verified: RAM 54.8%, Flash 11.4%, no compilation errors. Assembled final prototype on PCB board. Completed Wiki documentation. | ||
| ===== Bibliography/Resources ===== | ===== Bibliography/Resources ===== | ||
| Line 92: | Line 361: | ||
| * PM Laboratories (UART, I2C, Timers) | * PM Laboratories (UART, I2C, Timers) | ||
| * SSD1306 OLED Tutorials | * SSD1306 OLED Tutorials | ||
| + | * Project Schematic: {{:pm:prj2026:theodor_ioan.buliga:proiect_pm_v2.pdf|}} | ||
| + | * Project Code : {{:pm:prj2026:theodor_ioan.buliga:portable_mp3_player.zip|}} | ||
| <html><a class="media mediafile mf_pdf" href="?do=export_pdf">Export to PDF</a></html> | <html><a class="media mediafile mf_pdf" href="?do=export_pdf">Export to PDF</a></html> | ||