Themabewertung:
  • 1 Bewertung(en) - 5 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
iRadioMini für ESP32
#1
Hallo zusammen,

vor einiger Zeit hatte ich hier https://radio-bastler.de/forum/showthread.php?tid=17414 Boards mit dem ESP32 vorgestellt, die sich vorzüglich für den Aufbau von Internetradios eignen. Was liegt näher, als unser Baukastensystem iRadio (https://github.com/BM45/iRadio) in einer abgespeckten (also dem ESP32 angepassten) Version zu übertragen?

Als Basis für das iRadioMini soll dieses schon beschreibene Board dienen:

[Bild: attachment.php?aid=92298]

[Bild: attachment.php?aid=92299]

Damit wir unser iRadio darauf laufen lassen können, benötigen wir zunächst eine Entwicklungsumgebung, also ein SDK. Dieses auf iRadioMiniESP32SDK-getaufte Stück Software ist im Prinzip nichts anderes als das offizielle ADF und IDF von Espressif, gesprickt mit ein paar GPIO_NUM-Änderungen, die der Boardhersteller hier https://github.com/Ai-Thinker-Open/ESP32-A1S-AudioKit beschreibt.

Man kann dieses iRadioMiniESP32SDK also sich direkt aus den Originalquellen per github clonen, ich habe es aber auch nochmal (bereits fertig gepatched) auf meiner github- Seite zusammengefasst.

https://github.com/BM45/esp-adf 
https://github.com/BM45/esp-idf   -> wird rekursiv beim Checkout des esp-adf mit auf die Entwicklungsmaschine kopiert !!!
https://github.com/BM45/ESP32-A1S-AudioKit

Wer kein Github nutzt, für den habe ich auch alles als gepacktes Archiv in eine "normale" Downloadquelle gelegt: https://mega.nz/file/WHIkVRBJ#hsu9_BR0VV...BsR8iYculo

Noch etwas, grundsätzlich werde ich mich ab dieser Stelle (wie auch beim iRadio) rein auf Linux oder Unix - Entwicklungssysteme und Umgebungen beziehen. Klar, auch mit einer Windows-Maschine kann man das alles bewältigen, aber dazu kann (und werde) ich keine Unterstützung geben. Windows-Nutzer sind bei der Einrichtung und bei der Nutzung der Tool-Chain auf sich selbst angewiesen wenn es Probleme gibt. 

Zunächst die Basis, dass Espressif IDF :  

Die Installation des IDF ist relativ einfach, nach dem Clonen des ADF von Github (das IDF wird rekusiv mitgeladen)

git clone --recursive https://github.com/BM45/esp-adf

wird die Installation durch das Script install.sh eingleitet.

Die Toolchain benötigt einige zusätzliche Softwareprodukte, die auf gängigen Desktop-Distributionen vorinstalliert sein sollten, zudem ist Sie sehr Python-lastig.

Welche Vorvoraussetzungen nötig sind, sieht man hier https://docs.espressif.com/projects/esp-...setup.html

Bei Debian/Ubuntu-Systemen ist man mit

sudo apt-get install git wget flex bison gperf python3 python3-pip python3-setuptools cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0

also gut bedient. Ältere Installationen werden hiermit bedient:

sudo apt-get install gcc git wget make libncurses-dev flex bison gperf python python-pip python-setuptools python-serial python-cryptography python-future libffi-dev libssl-dev

Sind die Voraussetzungen geschaffen, läuft install.sh des IDF problemlos durch. Das erkennt man, wenn man die Ausgaben auf der Console auch liest! Ich schreibe das Extra. In der Vergangenheit gab es ein paar Experten, die waren der Ansicht, sobald das Prompt erscheint ist alles erfolgreich installiert, dass die Console vorher schon Fehler ausgespuckt hat, wurde absolut ignoriert überlesen.  Undecided

Nachdem install.sh erfolgreich durchgelaufen ist, müssen wir bei jedem Öffnen eines Terminals (in unserem Projekt) noch das Export-Script ausführen:

. PfadzumSDK/esp-adf/esp-idf/export.sh

Das Ergebnis:

Adding ESP-IDF tools to PATH...
Checking if Python packages are up to date...
...

Done! You can now compile ESP-IDF projects.
Go to the project directory and run:
  idf.py build

IDF-seitig sind wir damit einsatzbereit.

Nun wird das Espressif-ADF "installiert". Im Prinzip erstreckt sich die Installation nur auf das setzen einiger Umgebungspfade. Bitte lest dazu Step 3 und 4 auf meiner Seite durch: https://github.com/BM45/ESP32-A1S-AudioKit

       

Damit wären wir durch.

Step 5 ESP32 + ES8388 audio chips sofern Ihr das SDK von meiner Github-Seite habt, ist das auch schon erledigt!

Die Änderungen für ESP32 + AC101 audio chips  sind bei mir nicht durchgeführt, da Boards in dieser Konfiguration mit dem Status status:halt production gekennzeichnet sind. Sollte jemand so ein Board noch besitzen, dann sind diese aufgeführten Schritte noch durchzuführen, was aber kein Beinbruch ist.

Smiley34 An dieser Stelle haben wir das iRadioMiniESP32SDK eingerichtet. Smiley34 

Wollen wir nun noch mit einer kleinen Testanwendnung überprüfen, ob unser Board und unser SDK auch funktionieren.

Zunächst verbinden wir unser ESP32 Audio Board per USB mit dem Entwicklungsrechner. Am Board stecken wir das USB-Kabel in den mit UART gekennzeichneten Anschluss. Auf dem Board werden ein paar LEDs anfangen zu blinken und zu leuchten. Wir öffnen jetzt ein Terminalfenster und geben den Befehl dmesg ein.

Als Antwort müssten wir, neben vielen anderen Meldungen, auch so etwas oder ähnliches erhalten:

...
[ 8324.704018] usb 7-2: new full-speed USB device number 2 using uhci_hcd
[ 8324.933349] usb 7-2: New USB device found, idVendor=10c4, idProduct=ea60
[ 8324.933351] usb 7-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 8324.933352] usb 7-2: Product: CP2102 USB to UART Bridge Controller
[ 8324.933354] usb 7-2: Manufacturer: Silicon Labs
[ 8324.933355] usb 7-2: SerialNumber: 0001
[ 8325.793652] usbcore: registered new interface driver usbserial_generic
[ 8325.793995] usbserial: USB Serial support registered for generic
[ 8325.818152] usbcore: registered new interface driver cp210x
[ 8325.818169] usbserial: USB Serial support registered for cp210x
[ 8325.818204] cp210x 7-2:1.0: cp210x converter detected
[ 8325.823404] usb 7-2: cp210x converter now attached to ttyUSB0

Wir sehen das unser Audioboard nun unter ttyUSB0 erreichbar ist. Über diese (o.ä.) Portbezeichnungen schicken wir unsere Programme zum Board und lesen Debugmeldungen aus. Ist im Konsolenfenster nicht zu lesen, dass wir eine UART Bridge eingerichtet haben und auch keine Portbezeichnung reserviert wurde, dürften wir ein Problem mit dem USB-Kabel haben. Das ist auch der Fall, wenn dennoch auf dem Board LEDs leuchten! Das Kabel ist dann nicht für die Übertragung von Programmen zum Board geeignet, wir müssen es tauschen und erneut mit dmesg prüfen, ob das Austauschkabel geeignet ist. ...

Nun laden wir uns folgende Testanwendung auf unsere Entwicklungsmaschine:


.zip   demoapp.zip (Größe: 9,97 KB / Downloads: 9)

Nach dem Entpacken in unser lokal eingerichtetes Arbeitsverzeichnis öffnen wir ein Terminal.

Wir wechseln mit dem cd Kommando in das Verzeichnis des Demoprogramms.
Wir setzen noch die IDF-Pfade wie oben beschrieben durch den Aufruf von

. PfadzumSDK/esp-adf/esp-idf/export.sh

Abschließend rufen wir mit

idf.py menuconfig

unser Configtool auf, und wählen unter Audio HAL das (unser) ESP32-Lyrat V4.3 Board aus.

[Bild: menuconfig_8388.png]

(Wer ein anderes Board hat, muss natürlich sein Board auswählen).

Zusätzlich könnte man über dieses Tool auch gleich eine WiFi-Verbindung einrichten, was wir hier aber nicht wollen, denn das machen wir direkt einmal "hart-codiert" im Quellcode.
Anschließend speichern wir die Konfiguration durch die <save> Option im Configtool, dabei belassen wir den vorgeschlagenen Dateinamen. Nach dem Speichern, verlassen wir das Configtool über <Exit>, womit wir wieder zur Console kommen.

Jetzt wollen wir den Quelltext unserer Demoanwendung bearbeiten.
Dazu nehmen wir einen Texteditor unserer Wahl (nano, vi, emacs, eclipse, ...) und öffnen die Datei play_http_mp3_example.c aus dem main-Ordner unserer Demoapp.

Neben einem Haufen includes und Compilerdirektiven sehen wir, wie die Demoanwendung aufgebaut ist. Die Funktionsweise wird später, bei laufender Anwendung auch auf der Console verfolgbar sein, was durch ESP_LOGI geschieht.

Code:
   
...
    audio_pipeline_handle_t pipeline;
    audio_element_handle_t http_stream_reader, i2s_stream_writer, mp3_decoder;

    esp_log_level_set("*", ESP_LOG_WARN);
    esp_log_level_set(TAG, ESP_LOG_DEBUG);

    ESP_LOGI(TAG, "[ 1 ] Start audio codec chip");
    audio_board_handle_t board_handle = audio_board_init();
    audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START);

    ESP_LOGI(TAG, "[2.0] Create audio pipeline for playback");
    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    pipeline = audio_pipeline_init(&pipeline_cfg);
    mem_assert(pipeline);

    ESP_LOGI(TAG, "[2.1] Create http stream to read data");
    http_stream_cfg_t http_cfg = HTTP_STREAM_CFG_DEFAULT();
    http_stream_reader = http_stream_init(&http_cfg);

    ESP_LOGI(TAG, "[2.2] Create i2s stream to write data to codec chip");
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.type = AUDIO_STREAM_WRITER;
    i2s_stream_writer = i2s_stream_init(&i2s_cfg);

    ESP_LOGI(TAG, "[2.3] Create mp3 decoder to decode mp3 file");
    mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
    mp3_decoder = mp3_decoder_init(&mp3_cfg);

    ESP_LOGI(TAG, "[2.4] Register all elements to audio pipeline");
    audio_pipeline_register(pipeline, http_stream_reader, "http");
    audio_pipeline_register(pipeline, mp3_decoder,        "mp3");
    audio_pipeline_register(pipeline, i2s_stream_writer,  "i2s");

    ESP_LOGI(TAG, "[2.5] Link it together http_stream-->mp3_decoder-->i2s_stream-->[codec_chip]");
    const char *link_tag[3] = {"http", "mp3", "i2s"};
    audio_pipeline_link(pipeline, &link_tag[0], 3);

    ESP_LOGI(TAG, "[2.6] Set up  uri (http as http_stream, mp3 as mp3 decoder, and default output is i2s)");
    audio_element_set_uri(http_stream_reader, "https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.mp3");

    ESP_LOGI(TAG, "[ 3 ] Start and wait for Wi-Fi network");
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);
    periph_wifi_cfg_t wifi_cfg = {
        .ssid = CONFIG_WIFI_SSID,
        .password = CONFIG_WIFI_PASSWORD,
    };
    esp_periph_handle_t wifi_handle = periph_wifi_init(&wifi_cfg);
    esp_periph_start(set, wifi_handle);
    periph_wifi_wait_for_connected(wifi_handle, portMAX_DELAY);
   
    // Example of using an audio event -- START
    ESP_LOGI(TAG, "[ 4 ] Set up  event listener");
    audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
    audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);

    ESP_LOGI(TAG, "[4.1] Listening event from all elements of pipeline");
    audio_pipeline_set_listener(pipeline, evt);

    ESP_LOGI(TAG, "[4.2] Listening event from peripherals");
    audio_event_iface_set_listener(esp_periph_set_get_event_iface(set), evt);

    ESP_LOGI(TAG, "[ 5 ] Start audio_pipeline");
    audio_pipeline_run(pipeline);
...


Die Anwendung folgt dabei genau dem Codierungsprinzip für Audioanwendungen, welches Espressif im Design Guide und in der API-Reference https://docs.espressif.com/projects/esp-...index.html vorgibt.

[Bild: blockdiag-b5750d3b0e6ba8e4c0defe2aa18f89b5d0c2fe15.png]
[Bild: blockdiag-f03beae6c1799168b9ebfa6dc6a5e4da7b589363.png]
Im Quelltext gehen wir jetzt an folgende Stelle (um Zeile 99)
PHP-Code:
    ESP_LOGI(TAG"[ 3 ] Start and wait for Wi-Fi network");
    esp_periph_config_t periph_cfg DEFAULT_ESP_PERIPH_SET_CONFIG();
    esp_periph_set_handle_t set esp_periph_set_init(&periph_cfg);
    periph_wifi_cfg_t wifi_cfg = {
        .ssid CONFIG_WIFI_SSID,
        .password CONFIG_WIFI_PASSWORD,
    };
    esp_periph_handle_t wifi_handle periph_wifi_init(&wifi_cfg);
    esp_periph_start(setwifi_handle);
    periph_wifi_wait_for_connected(wifi_handleportMAX_DELAY); 

Die über unser Configtool belegbaren Werte CONFIG_WIFI_SSID und CONFIG_WIFI_PASSWORD, wollen wir jetzt hart mit den Zugangsdaten unseres Heimnetzwerkes belegen. Da diese Zeichenketten, also Strings sind, könnte das exemplarisch also so aussehen:

PHP-Code:
    periph_wifi_cfg_t wifi_cfg = {
        .ssid "Mein_Netzwerkname",
        .password "Mein_Netzwerk_Passwort",
    }; 

Im Codebereich der Zeile 96-97, also hier:

PHP-Code:
   ESP_LOGI(TAG"[2.6] Set up  uri (http as http_stream, mp3 as mp3 decoder, and default output is i2s)");
    audio_element_set_uri(http_stream_reader"https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.mp3"); 


könnten wir noch die URL zu einer MP3(!) Radiostation aus dem Netz austauschen. MP3 ist wichtig, da im Demoquellcode zunächst nur ein MP3-Decoder in der Pipeline verknüpft ist! Belassen wir für unseren Test erstmal die vorgegebene URL, der Chiphersteller bietet ja einen Demostream auf seinen Servern an.

Wir speichern den Quelltext in unserem Texteditor ab und gehen zurück zur Console.

In der Console wollen wir nun die Anwendung bauen, also compilieren lassen. Dazu geben wir ein:

idf.py build

Wir sehen jetzt, wie unsere Anwendung compiliert wird. Wenn wir keinen Syntaxfehler in Quellcode gemacht haben, wird der Compilerlauf mit einer Ausgabe ähnlich dieser beendet:


[ 98%] Built target ldgen
[ 92%] Built target ldgen
[ 98%] Built target bootloader.elf
[100%] Built target play_http_mp3.elf
[100%] Built target bootloader
[100%] Built target app
[100%] No install step for 'bootloader'
[100%] Completed 'bootloader'
[100%] Built target bootloader

Project build complete. To flash, run this command:
../esp-adf/esp-idf/components/esptool_py/esptool/esptool.py -p (PORT) -b 460800 --after hard_reset write_flash --flash_mode dio --flash_size detect --flash_freq 40m 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/play_http_mp3.bin
or run 'idf.py -p (PORT) flash'

Jetzt müssen wir die Binärversion unseres Programms noch in den ESP32-Mikrocontroller flashen.


.png   what-you-need.png (Größe: 44,11 KB / Downloads: 1.203)

Dazu rufen wir wieder idf.py auf, aber diesmal mit der oben aus dmesg gewonnenen Portbezeichnung und dem Zusatzparameter flash

idf.py -p /dev/ttyUSB0 flash

In der Ausgabe sehen wir wie der Programmiervorgang abläuft:

Connecting.....
Detecting chip type... ESP32
Chip is ESP32D0WDQ5 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 8c:ce:4e:99:73:d4
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 460800
Changed.
Configuring flash size...
Compressed 25760 bytes to 15290...
Wrote 25760 bytes (15290 compressed) at 0x00001000 in 0.3 seconds (effective 599.1 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 83...
Wrote 3072 bytes (83 compressed) at 0x00008000 in 0.0 seconds (effective 6147.0 kbit/s)...
Hash of data verified.
Compressed 968704 bytes to 591767...
Wrote 968704 bytes (591767 compressed) at 0x00010000 in 13.5 seconds (effective 575.4 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
Done


Der Controller ist nun programmiert.
Verbinden wir uns mal mit der Consolen-/Debugausgabe des Controllers! Auch hier nehmen wir wieder das idf.py Programm, aber diesmal mit dem Aufrufparameter monitor .

idf.py -p /dev/ttyUSB0 monitor

Wir erhalten jetzt permanent sämtliche Consolenausgaben des ESP32:

I (0) cpu_start: App cpu up.
I (537) heap_init: Initializing. RAM available for dynamic allocation:
I (544) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (550) heap_init: At 3FFB9AD0 len 00026530 (153 KiB): DRAM
I (556) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (563) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (569) heap_init: At 40095F8C len 0000A074 (40 KiB): IRAM
I (575) cpu_start: Pro cpu start user code
I (258) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (320) HTTP_MP3_EXAMPLE: [ 1 ] Start audio codec chip
I (340) HTTP_MP3_EXAMPLE: [2.0] Create audio pipeline for playback
I (340) HTTP_MP3_EXAMPLE: [2.1] Create http stream to read data
I (350) HTTP_MP3_EXAMPLE: [2.2] Create i2s stream to write data to codec chip
I (360) HTTP_MP3_EXAMPLE: [2.3] Create mp3 decoder to decode mp3 file
I (360) HTTP_MP3_EXAMPLE: [2.4] Register all elements to audio pipeline
I (370) HTTP_MP3_EXAMPLE: [2.5] Link it together http_stream-->mp3_decoder-->i2s_stream-->[codec_chip]
I (380) HTTP_MP3_EXAMPLE: [2.6] Set up  uri (http as http_stream, mp3 as mp3 decoder, and default output is i2s)
I (390) HTTP_MP3_EXAMPLE: [ 3 ] Start and wait for Wi-Fi network
E (390) gpio: gpio_install_isr_service(412): GPIO isr service already installed
I (410) wifi:wifi driver task: 3ffcaf50, prio:23, stack:3584, core=0
I (430) wifi:wifi firmware version: 5f8804c
I (430) wifi:config NVS flash: enabled
I (430) wifi:config nano formating: disabled
I (430) wifi:Init dynamic tx buffer num: 32
I (440) wifi:Init data frame dynamic rx buffer num: 32
I (440) wifi:Init management frame dynamic rx buffer num: 32
I (450) wifi:Init management short buffer num: 32
I (450) wifi:Init static rx buffer size: 1600
I (460) wifi:Init static rx buffer num: 10
I (460) wifi:Init dynamic rx buffer num: 32
I (560) phy: phy_version: 4180, cb3948e, Sep 12 2019, 16:39:13, 0, 0
I (560) wifi:mode : sta (*********)
I (1290) wifi:new:<6,2>, old:<1,0>, ap:<255,255>, sta:<6,2>, prof:1
I (2280) wifiConfusedtate: init -> auth (b0)
I (2280) wifiConfusedtate: auth -> assoc (0)
I (2290) wifiConfusedtate: assoc -> run (10)
I (2300) wifi:connected with *******, aid = 1, channel 6, 40D, bssid = *******
I (2300) wifiConfusedecurity type: 3, phy: bgn, rssi: -43
I (2300) wifi:pm start, type: 1
I (2360) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (2820) HTTP_MP3_EXAMPLE: [ 4 ] Set up  event listener
I (2820) HTTP_MP3_EXAMPLE: [4.1] Listening event from all elements of pipeline
I (2820) HTTP_MP3_EXAMPLE: [4.2] Listening event from peripherals
I (2830) HTTP_MP3_EXAMPLE: [ 5 ] Start audio_pipeline
I (3970) HTTP_MP3_EXAMPLE: [ * ] Receive music info from mp3 decoder, sample_rates=44100, bits=16, ch=2
...

Die Ausgabe sagt es schon, wir sollten jetzt mal dem ESP32 Audio Board einen Kopfhörer oder Lautsprecher spendieren, denn unser erster Internetradio-Datenstrom wird empfangen, in Software dekodiert und zum DAC geleitet.  An dieser Stelle ist der Grundstein für unser iRadioMini gelegt, praktisch der Urknall für alles was als nächstes kommt.

------ Wie geht es weiter ? --------

Ich werde das iRadioMini für ESP32 nicht einfach als einen Haufen von Code hier auf einmal einstellen. Was wäre sonst das Resultat? Niemand würde verstehen wie es funktioniert, wie das System aufgebaut ist. Das ist nicht mein Ziel!

Wir werden hier zunächst ein einfaches iRadioMini-Grundsystem sehen, bestehend aus der Startsqeuenz (ähnlich der /etc/rc.local aus unserem großen iRadio). Darin werden die Zugangsdaten zum WLAN und eine Senderliste geladen. In dem Grundsystem wird es einen Playerprozess geben (ähnlich vlc). Nach dem Start wird dieser Playerprozess mit dem ersten Eintrag aus der Senderliste loslaufen. Wir werden im Grundsystem einen Steuerdaemonen (also gpiod) sehen, mit diesem kann man durch die Senderliste schreiten, analog unserem Beispiel gpiod vom iRadio.

Im weiteren Verlauf werden wir dann einzeln Displaydaemonen hinzunehmen. Dabei werde ich wieder versuchen, wie beim iRadio, den Code und die Funktionsweise zu erklären. Und dann schauen wir mal was der ESP32 so im Vergleich zum großen iRadio auf einem Pi noch schafft.  Das ganze iRadioMini-Projekt hat also auch den Charakter eines Programmierkurses für Fortgeschrittene. Schließlich soll das iRadioMini (wie auch sein großer Bruder) nur einen Rahmen für eigene Ideen vorgeben und dafür muss man das System (wenigstens in Ansätzen) verstanden haben!

Eine bitte noch: Dieser Thread soll bitte nur der Einführung, Demonstration des iRadioMini dienen!
Fragen, Probleme und andere Diskussionen bitte - wie beim iRadio- in einem getrennten FAQ stellen.
Hier der Link "FAQ zum iRadioMini"
https://radio-bastler.de/forum/showthrea...#pid219801

Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#2
Hallo zusammen,

gehen wir nach der Einrichtung des "iRadioMiniESP32SDK" einen kleinen Schritt weiter und schauen uns das minimale Grundgerüst unseres iRadioMini für ESP32 an.

Hier zum Download als ZIP-Archiv:
.zip   iRadioMini.zip (Größe: 1,35 MB / Downloads: 11)

Nach dem Download und Entpacken auf unserer Entwicklermaschine sehen wir im Hauptordner "iRadioMini":


.jpg   folder_iRadioMini.jpg (Größe: 10,19 KB / Downloads: 1.069)

folgende Struktur. Im Unterordner "main" liegt unserer iRadioMini mit seinen künftigen Daemonen und Modulen, im Ordner "datasheets" gibt es Entwicklerinfos zu den oben gezeigten ESP32 Audio Kit. Die restlichen Dateien sind (automatische) Arbeitsdateien für unser Entwicklungssystem, wir lassen (zunächst) unserer Finger davon.


.jpg   folder_main.jpg (Größe: 10,6 KB / Downloads: 1.069)

Im "main"-Ordner finden wir in der Datei iRadioMini.c unser Hauptprogramm.  Den Inhalt will ich hier mal zeigen:

Code:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "sdkconfig.h"

#include "board.h"
#include "modules/sdcard.h"

#include "modules/player.h"
#include "modules/messages.h"


#include "modules/gpiod.h"
#include "modules/gpiod_rotary.h"


#include "globals.h"

#if __has_include("esp_idf_version.h")
#include "esp_idf_version.h"
#else
#define ESP_IDF_VERSION_VAL(major, minor, patch) 1
#endif

#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
#include "esp_netif.h"
#else
#include "tcpip_adapter.h"
#endif

static const char *TAG = "IRADIOMINI";


void app_main(void)
{
   esp_log_level_set(TAG, ESP_LOG_INFO);

   // BOARDINITIALISIERUNGEN
   ESP_LOGI(TAG, "call startup procedures (aka rc.local)");
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
        // NVS partition was truncated and needs to be erased
        // Retry nvs_flash_init
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
    ESP_ERROR_CHECK(esp_netif_init());
#else
    tcpip_adapter_init();
#endif

    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    set = esp_periph_set_init(&periph_cfg);

    // Einlesen der wifi.txt und playlist.m3u von SD-Karte
    audio_board_sdcard_init(set, SD_MODE_1_LINE);
    wifi_cfg = readWifiConfigSDCard();
    readPlaylistSDCard();


    // Playerprozess starten
    ESP_LOGI(TAG, "prepare to start the player process");
    TaskHandle_t xPlayerTaskHandle = NULL;
    xTaskCreate( player, "player", 4096, NULL, tskIDLE_PRIORITY, &xPlayerTaskHandle );
    configASSERT(xPlayerTaskHandle);

    // Queues für Nachrichtenaustausch zwischen den Prozessen
    ESP_LOGI(TAG, "start IPC");
    // - control queue of player process
    xPlayerQueue = xQueueCreate( 10, sizeof( struct AMessage * ) );
    if (!xPlayerQueue)
      ESP_LOGE(TAG, "IPC:Player Message Queue failed");
  
    // Kontrollprozess für Player starten
    TaskHandle_t xPlayerControlTaskHandle = NULL;
    xTaskCreate( playerControlTask, "playerControlTask", 4096, NULL , tskIDLE_PRIORITY, &xPlayerControlTaskHandle );
    configASSERT(xPlayerControlTaskHandle);


    // Prozess gpiod starten
    TaskHandle_t xgpiodHandle = NULL;
    xTaskCreate( gpiod, "gpiod", 2048, NULL , tskIDLE_PRIORITY, &xgpiodHandle );
    configASSERT(xgpiodHandle);

    // Prozess gpiod_rotary starten
    // TaskHandle_t xgpiodHandle = NULL;
    // xTaskCreate( gpiod_rotary, "gpiod", 2048, NULL , tskIDLE_PRIORITY, &xgpiodHandle );
    // configASSERT(xgpiodHandle);

    // Daemonbetrieb - Hauptprozess
    while(1)
    {

      ESP_LOGI(TAG, "Heartbeat");
      vTaskDelay(1000/portTICK_PERIOD_MS );
        
    }

  
    ESP_LOGI(TAG, "cleanup");
    if(xPlayerTaskHandle)
      vTaskDelete(xPlayerTaskHandle);

    if(xPlayerControlTaskHandle)
      vTaskDelete(xPlayerControlTaskHandle);
 
    if(xgpiodHandle)
      vTaskDelete(xgpiodHandle);

    esp_periph_set_destroy(set);
}


Erklärung:
Zunächst haben wir die obligatorischen Includes und Compilerdirektiven. Dabei werden auch Headerfiles unserer Daemonen geladen. Die Hauptfunktion "app_main" ist zugleich der Hauptprozess, den das Betriebssystem FreeRTOS uns anbietet. Betrachten wir das einfach ganz abstrakt wie eine Laufzeitinstanz eines Raspberry-Pi Betriebssystems.

In diesem Hauptprozess initialisieren wir die Hardware des Audioboards.

Danach laden wir (analog der rc.local und vlcd beim iRadio) von einer SD-Karte die Dateien wifi.txt und playlist.m3u.  Diese Dateien kennen wir von unserem großen iRadio auf Raspberry-Basis! Sie können hier 1:1 übernommen werden und müssen auch so sorgsam angelegt sein. 

Sollten in der wifi.txt mehrere Netzwerkblöcke mit Zugangsdaten vorhanden sein, so wird auf dem ESP32 Audio Kit aber nur der erste Block verarbeitet!

Bei der Playlist.m3u, also der Senderliste, liegt im Gegensatz zum großen iRadio zur Zeit eine Beschränkung auf 100 Sender vor, was man aber im Quellcode leicht ändern kann.

Ebenfalls wichtig, die Dateien bleiben auf der SD-Karte und werden nicht in den ESP32 transferiert. Die SD-Karte muss bei jedem Systemstart also gesteckt sein.

Nachdem unsere WiFi-Verbindungsdaten und die Senderliste geladen wurden, starten wir wie in rc.local unsere ganzen Prozesse und Daemonen.

Zunächst starten wir einen Audioplayerprozess (quasi ein Schmalspur vlc) und übergeben ihn FreeRTOS zur Verwaltung.

Die Quellen dieses Players schauen wir uns später noch an!

Im nächsten Schritt wird ein Queue angelegt. Queues sind in FreeRTOS eine Art der Interprozesskommunikation. Über diesen Queue können wir später den Audioplayer von anderen Daemonen aus ansteuern, indem wir Ihn Nachrichten schicken. Analog TCP/IP Port 9294 bei unserem großen iRadio.

Und natürlich brauchen wir auch einen Daemonen (gpiod) der Steuerelemente (Tastendrücke, Drehencoderaktionen) ermöglicht und daraus Nachrichten für den Audioplayerprozess generiert.

Nachdem unsere iRadio-Prozesse und Daemonen rennen, erzeugen wir im Hauptprozess in der while-Schleife nur noch im Sekundentakt einen "Ich bin am Leben"-Heartbeat für die Console.

Sollte unser iRadio mal diese Schleife verlassen (also kontrolliert beendet werden), gibt es danach noch die üblichen Aufräumaktionen.

Schauen wir uns nun noch den "modules"-Ordner an:


.jpg   folder_modules.jpg (Größe: 14,19 KB / Downloads: 1.071)

In diesem Ordner liegen sämtliche Codebestandteile unserer Prozesse und Daemonen, sowie ein paar Hilfsfunktionen.

In sdcard.h und .c finden wir den Code für das Einlesen unserer Dateien wifi.txt und playlist.m3u .

In utils.h und .c werden kleine Helferfunktionen Platz finden .

In globals.h ist der Platz für ein paar globale Variablen und Datenstrukturen, so zum Beispiel die Datenstruktur unserer Playlist oder der Datensatz zur (SSID/Passwort) unseres WLANs.

Der "aufregendste" Prozess (eigentlich sind es 2) ist in player.h und c zu finden, unser Minimal-"vlc".

Hier wird die Audio-Pipeline zusammengesetzt, so wie es das ESP32-ADF Framework vorsieht.

[Bild: blockdiag-b5750d3b0e6ba8e4c0defe2aa18f89b5d0c2fe15.png]

[Bild: blockdiag-f03beae6c1799168b9ebfa6dc6a5e4da7b589363.png]


Damit folgt unser Minimal-vlc dem Prinzip der in #1 gezeigten Demoanwendung! Sie wurde nur dahingehend verändert, daß die Softwaredecoder fallweise ausgetauscht werden.

Neben einer einzelnen Dekoderinstanz (mp3_decoder, aac_decoder, flac_decoder, wav_decoder, ...) hat uns Espressif auch einen "Universaldekoder" ins SDK gepackt. Sein Name esp_decoder .

Dieser nimmt einen Datenstrom vom HTTP-Audioelement und schaut in diesen rein. Findet es passende Metadaten, dann entscheidet der esp_decoder automatisch, welchen Codec er für die Dekodierung des Datenstroms nehmen soll. Am Ausgang des esp_decoders liegt dann der Audiostrom vor, der an den DAC in der Pipeline geschickt wird.

Somit brauchen wir diese Aufgabe selbst nicht zu implementieren und uns zur Laufzeit für einen Codec zu entscheiden.

Man kann den esp_decoder übrigens selbst mit Codecs bestücken, die er für seine Analyse und Auswahl nehmen soll.

Und zwar hier in diesem Teil:

...

    ESP_LOGI(TAG, "[ 2.3 ] Create auto-audio-decoder ");

    audio_decoder_t auto_decode[] = {

        DEFAULT_ESP_MP3_DECODER_CONFIG(),
        DEFAULT_ESP_WAV_DECODER_CONFIG(),
        DEFAULT_ESP_AAC_DECODER_CONFIG(),
        DEFAULT_ESP_M4A_DECODER_CONFIG(),
        DEFAULT_ESP_TS_DECODER_CONFIG(),

    };



    esp_decoder_cfg_t decoder_cfg = DEFAULT_ESP_DECODER_CONFIG();
    esp_decoder = esp_decoder_init(&decoder_cfg, auto_decode, 5);

...


Man erzeugt eine Liste mit Codecs die man haben will und übergibt diese Liste bei der Initialisierung dem esp_decoder.

Die "5" in esp_decoder_init sagt, daß in der Liste 5 Codecs angegeben wurden. Ändert man die Liste, so ist dieser Wert mit anzupassen.

Welche Codecs gibt es? Antwort hier: https://github.com/espressif/esp-adf-lib...lude/codec

Man könnte also noch ogg, opus, ... hinzunehmen, wenn man das möchte und in der Endanwendung den Platz im ESP32 hat.

Zusätzlich finden wir in player.c (ziemlich am Ende der Datei) einen playerControlTask. Das ist der zweite Prozess unseres Mini-vlc's ! Hier werden die oben bereits erwähnten Nachrichten aus dem "xPlayerQueue" genommen und verarbeitet.

Um es zunächst einfach zu halten, habe ich nur das "Handling" für 2 Nachrichten angegeben. Über NEXT_PRG und PREV_PRG wird in unserer Sendeliste umgeschaltet.  Solche Nachrichten werden gewöhnlich von gpiods verschickt.

Kommen wir damit auch gleich zu unseren Steuerdaemon gpiod. In diesem Schritt habe ich wieder zwei unterschiedliche gpiod aufgenommen. Einen für Tasten (gpiod.c und .h) und einen für Drehencoder (gpiod_rotary.c und .h).

Schauen wir uns zunächst den gpiod für Tasten an:


Code:
#include "gpiod.h"

#include "board.h"

#include "messages.h"
#include "esp_log.h"

#define KEY_PRG_NEXT  GPIO_NUM_5
#define KEY_PRG_PREV  GPIO_NUM_18

static const char *TAG = "GPIOD";

void gpiod(void *pvParameters) {

  gpio_config_t io_conf;
  io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
  io_conf.mode = GPIO_MODE_INPUT;
  io_conf.pin_bit_mask = ( BIT(KEY_PRG_NEXT) | BIT(KEY_PRG_PREV) );
  io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
  io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
  gpio_config(&io_conf);

  struct AMessage *pxMessage;

  // Daemonbetrieb
  while (1) {
    if (!gpio_get_level(KEY_PRG_NEXT)) // If pin is switched on
     {
        ESP_LOGI(TAG, "prog+");
        xMessage.ucMessage = NEXT_PRG;
        pxMessage = &xMessage;
        xQueueSend( xPlayerQueue, ( void * ) &pxMessage, ( TickType_t ) 0 );
         }

     if (!gpio_get_level(KEY_PRG_PREV)) // If pin is switched on
    {
        ESP_LOGI(TAG, "prog-");
        xMessage.ucMessage = PREV_PRG;
        pxMessage = &xMessage;
        xQueueSend( xPlayerQueue, ( void * ) &pxMessage, ( TickType_t ) 0 );
        }

      vTaskDelay(500/portTICK_PERIOD_MS );
   }


}


Zunächst werden die GPIOs für die betroffenen Tasten definiert (findet man in den Unterlagen zu den Boards). 
Über gpio_config_t wird das Verhalten der IOs festgelegt: INPUT, PULLUP-Widerstände eingeschaltet.
Danach gehen wir mit der Endlosschleife in den Daemon-Betrieb, der eigentlich selbsterklärend ist.
Zyklisch [alle 500ms -> wegen vTaskDelay(500/portTICK_PERIOD_MS ); ] wird im Polling-Verfahren geschaut, ob eine der definierten Tasten gedrückt wurde. Ist das der Fall, wird eine Nachricht erstellt, die dann in den Queue mit Namen "xPlayerQueue" von FreeRTOS gestellt wird. Wir erinnern uns? Auf der anderen Seite von xPlayerQueue hört unsere Playersoftware auf diese Kommandos!

Schauen wir uns den gpiod für Drehencoder an:


Code:
#include "gpiod_rotary.h"

#include "board.h"

#include "messages.h"
#include "esp_log.h"

#define RO_A_PIN  GPIO_NUM_5
#define RO_B_PIN  GPIO_NUM_18

static const char *TAG = "GPIOD_ROTARY";

unsigned char flag;
unsigned char Last_RoB_Status;
unsigned char Current_RoB_Status;

struct AMessage *pxMessage;

void rotaryDeal(void)
{
    Last_RoB_Status = gpio_get_level(RO_B_PIN);

    while(!gpio_get_level(RO_A_PIN)){
        Current_RoB_Status = gpio_get_level(RO_B_PIN);
        flag = 1;
    }

    if(flag == 1){
        flag = 0;
        if((Last_RoB_Status == 0)&&(Current_RoB_Status == 1)){
            //system("echo \"next\" | nc 127.0.0.1 9294 -N");
            ESP_LOGI(TAG, "prog+");
            xMessage.ucMessage = NEXT_PRG;
                    pxMessage = &xMessage;
                    xQueueSend( xPlayerQueue, ( void * ) &pxMessage, ( TickType_t ) 0 );
        }
        if((Last_RoB_Status == 1)&&(Current_RoB_Status == 0)){
            //system("echo \"prev\" | nc 127.0.0.1 9294 -N");
            ESP_LOGI(TAG, "prog-");
            xMessage.ucMessage = PREV_PRG;
                    pxMessage = &xMessage;
                    xQueueSend( xPlayerQueue, ( void * ) &pxMessage, ( TickType_t ) 0 );
        }

    }
}


void gpiod_rotary(void *pvParameters) {

  gpio_config_t io_conf;
  io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
  io_conf.mode = GPIO_MODE_INPUT;
  io_conf.pin_bit_mask = ( BIT(RO_A_PIN) | BIT(RO_B_PIN) );
  io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
  io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
  gpio_config(&io_conf);


  // Daemonbetrieb
  while (1) {
      rotaryDeal();
      vTaskDelay(10/portTICK_PERIOD_MS );
   }


}

Hier habe ich praktisch eine 1:1 Funktionsübersetzung des Codes aus unserem großen iRadio gemacht: https://github.com/BM45/iRadio/blob/master/rotary.c

Das braucht eigentlich keine Erklärung mehr, oder?

Austausch und Start dieser gpiod's werden, wie oben gezeigt, zentral über iRadioMini.c gehandhabt, quasi unserer rc.local . Dort habe ich das als Beispiel schon angebenen:

PHP-Code:
// Prozess gpiod starten
    
TaskHandle_t xgpiodHandle NULL;
    
xTaskCreategpiod"gpiod"2048NULL tskIDLE_PRIORITY, &xgpiodHandle );
    
configASSERT(xgpiodHandle);

    
// Prozess gpiod_rotary starten
    // TaskHandle_t xgpiodHandle = NULL;
    // xTaskCreate( gpiod_rotary, "gpiod", 2048, NULL , tskIDLE_PRIORITY, &xgpiodHandle );
    // configASSERT(xgpiodHandle); 

Nach dem Umkommentieren (man kann natürlich auch mehrere Steuerelemente und Steuerdaemonen nutzen - ACHTUNG: auf Ressourcenkonflikte achten) baut man die Software für das Board (siehe #1) mit

idf.py build

Nach dem comilieren wird die Firmware zum ESP32 so transferiert:

idf.py flash

Und die Console (quasi eine SSH) startet man nach dem flashen mit

idf.py monitor

Und beim Start nicht die wifi.txt und playlist.m3u (analog dem iRadio) auf der SD-Karte vergessen. Danach hat man dann sein erstes Minimales Internetradio auf dem ESP32.

Im nächsten Schritt werden wir weitere Daemonen implementieren (displayds für verschiedene Displays, webd für eine Weboberfläche, ...) und hinzufügen.

Viel Spaß beim Experimentieren!

Eine bitte noch: Dieser Thread soll bitte nur der Einführung, Demonstration des iRadioMini dienen!
Fragen, Probleme und andere Diskussionen bitte - wie beim iRadio- in einem getrennten FAQ stellen.

Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#3
Hallo Bastler,

weiter geht es. Nach unserem Grundgerüst fügen wir nun stetig und langsam neue Daemonen und neue Hardwareunterstützung hinzu.

Hier das neue Archiv: https://mega.nz/file/2SY2Qbga#W_XsPCQHLF...zxAs5XJYzQ
Was ist neu hinzugekommen?

Im iRadioMini-Wurzelverzeichnis ist es primär ein Ordner mit dem Namen "components".


.jpg   folder_components.jpg (Größe: 18 KB / Downloads: 1.023)

In diesem Ordner werden wir einige Fremdbibliotheken ablegen, da wir den riesigen Funktionsumfang von mehreren Mannjahren Entwicklungszeit nicht selbst leisten können. Im hier gezeigten Schritt liegt darin die Bibliothek "u8g2" fix und fertig für unser iRadioMini für ESP32.


.jpg   folder_u8g2.jpg (Größe: 6,94 KB / Downloads: 1.022)

Was verbirgt sich hinter u8g2? Nun das ist eine riesige Bibliothek für zahlreiche monochrome Displays. Hier die Projektseite: https://github.com/olikraus/u8g2/wiki

Durch die Einbindung unterstützt unser iRadioMini auf einen Schlag Displaycontroller wie:

Zitat:Supported Display Controller: SSD1305, SSD1306, SSD1309, SSD1316, SSD1322, SSD1325, SSD1327, SSD1329, SSD1606, SSD1607, SH1106, SH1107, SH1108, SH1122, T6963, RA8835, LC7981, PCD8544, PCF8812, HX1230, UC1601, UC1604, UC1608, UC1610, UC1611, UC1617, UC1701, ST7511, ST7528, ST7565, ST7567, ST7571, ST7586, ST7588, ST75256, ST75320, NT7534, ST7920, IST3020, IST7920, LD7032, KS0108, KS0713, SED1520, SBN1661, IL3820, MAX7219

Die gesamte Liste findet man hier: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp

Wir sehen hier bekannte "Bastler-Displays" mit Controllern wie den SSD1306, also soetwas:


.jpg   SSD1306.jpg (Größe: 7,43 KB / Downloads: 1.020)

Und genau solche Displays mit I2C oder SPI - Schnittstelle eignen sich wunderbar für unser ESP32 Audio Kit, da diese Schnittstellentypen mit sehr wenigen GPIO-Pins auskommen. Davon haben wir auf dem ESP32 Audio Kit ja nicht allzu viele auf dem Pin-header nach außen geführt. Außerdem ist dieses Display eines der ersten Displays gewesen, welches wir auch in unserem großen iRadio eingeführt haben. Es bietet sich also an, beim iRadioMini genauso vorzugehen.


Wie oben schon geschrieben, kommen unsere iRadio-Daemonen ins Verzeichnis "modules".

Sämtliche displayd-Daemonen kommen ins Unterverzeichnis "display".


.jpg   folder_modules2.jpg (Größe: 12,36 KB / Downloads: 1.021)

Den Democode eines displayd für SSD1306-Displays habe ich hier eingefügt.


.jpg   folder_ssd1306.jpg (Größe: 5,35 KB / Downloads: 1.017)
.jpg   folder_ssd1306_cont.jpg (Größe: 8,56 KB / Downloads: 1.014)


Die Datei displayd_i2c.c ist quasi das Pendant zu unserer iRadio-Version hier https://github.com/BM45/iRadio/blob/mast...displayd.c

Im Bereich 

Code:
// SDA - PIN
#define PIN_SDA GPIO_NUM_21

// SCL - PIN
#define PIN_SCL GPIO_NUM_22

// I2C - Adresse
#define I2C_ADRESS 0x78


legen wir die GPIOs für unsere I2C Schnittstelle fest. Ebenso geben wir die I2C-Adresse des Displays auf dem Bus an.

Nach der folgenden Initialisierungen (u.a. der Schnittstelle und des Displays) gehen wir mit der Endlosschleife in den Daemonbetrieb über.

Im ersten Teil schicken wir über den xPlayerQueue eine Nachricht an unser "Mini-vlc", daß wir gern ein paar Infos über den aktuell eingestellten Sender haben wollen. Als Antwort von Playerprozess bekommen wir über den xDisplaydQueue die technischen Infos zurück, die unser displayd auf dem Display darstellen soll. Das ganze erfolgt zyklisch, jede Sekunde.

Code:
// Daemonbetrieb
   while(1)
    {
         xMessage.ucMessage = GET_CHANNEL_INFO;
         txMsg = &xMessage;
     xQueueSend( xPlayerQueue, ( void * ) &txMsg, ( TickType_t ) 0 );

     if( xQueueReceive( xDisplaydQueue, &( rxMsg ), ( TickType_t ) 10 )  )
          {
            if (rxMsg->ucMessage==GET_CHANNEL_INFO) {
        ESP_LOGI(TAG, "actual channel num: %i",rxMsg->uciChannelNum);
                ESP_LOGI(TAG, "music_info.sample_rates: %i",rxMsg->music_info.sample_rates);    
        ESP_LOGI(TAG, "music_info.channels: %i",rxMsg->music_info.channels);    
        ESP_LOGI(TAG, "music_info.bits: %i",rxMsg->music_info.bits);    
        ESP_LOGI(TAG, "music_info.bps: %i",rxMsg->music_info.bps);    
        ESP_LOGI(TAG, "music_info.duration: %i",rxMsg->music_info.duration);    
        ESP_LOGI(TAG, "music_info.uri: %s",rxMsg->ucURI);    
    
            }
          }


Im zweiten Teil bauen wir dann den Displayinhalt auf:

Code:
       ESP_LOGI(TAG, "u8g2_ClearBuffer");
      u8g2_ClearBuffer(&u8g2);
       
          ESP_LOGI(TAG, "u8g2_SetFont");
         
          // Kanalnummer
      u8g2_SetFont(&u8g2, u8g2_font_10x20_tr);
          u8g2_DrawStr(&u8g2, 2,17,"P");
          itoa(rxMsg->uciChannelNum,buffer,10);
      u8g2_DrawStr(&u8g2, 14,17,buffer);
         
          // URI
          u8g2_SetFont(&u8g2, u8g2_font_8x13_tr); // more fonts in /u8g2/tools/font/build/single_font_files
      u8g2_DrawStr(&u8g2, 2,30,rxMsg->ucURI);

          // BPS
          itoa(rxMsg->music_info.bps,buffer,10);
      u8g2_DrawStr(&u8g2, 2,45,"BPS:");
        u8g2_DrawStr(&u8g2, 60,45,buffer);
        
          // Samplerate
          itoa(rxMsg->music_info.sample_rates,buffer,10);
      u8g2_DrawStr(&u8g2, 2,60,"SR:");
        u8g2_DrawStr(&u8g2, 60,60,buffer);    
  

      u8g2_SendBuffer(&u8g2);
 
        ESP_LOGI(TAG, "Heartbeat");
        vTaskDelay(1000/portTICK_PERIOD_MS );
    }


Damit dieser displayd im FreeRTOS-Betriebssystem startet, müssen wir ihn dem Betriebssystem bekannt geben. Das erfolgt in unserer iRadioMini.c -Datei, analog rc.local aus dem großen iRadio, an dieser Stelle:

Code:
    // Prozess displayd starten
    TaskHandle_t xdisplaydHandle = NULL;
    xTaskCreate( displayd_i2c, "displayd", 4096, NULL , tskIDLE_PRIORITY, &xdisplaydHandle );
    configASSERT(xdisplaydHandle);

    // Daemonbetrieb - Hauptprozess
 
    while(1)
    {


Nach dem Compilieren und Flashen zum ESP32 Audio Kit läuft das Gesamtwerk dann los (wichtig: beim Start muss das Display schon richtig angeschlossen sein, sonst startet das System solange neu, bis er das Display findet).

   

Möchte man jetzt einen anderen Displaytypen aus der u8g2-Lib benutzen, so braucht man im Prinzip nur den displayd-Code an wenigen Stellen abändern!

Diese Stelle (der Funktionsname ggf. Parameter)

Code:
...
[b]u8g2_Setup_ssd1306_i2c_128x64_noname_f[/b](
    &u8g2,
    U8G2_R0,
    //u8x8_byte_sw_i2c,
    u8g2_esp32_i2c_byte_cb,
    u8g2_esp32_gpio_and_delay_cb);  // init u8g2 structure
...


ist durch den passenden Setup-Funktionsaufruf aus der u8g2.h (liegt in /iRadioMini/component/u8g2/csrc/) auszutauschen. Muss man Ausprobieren! Es gibt zu jedem Controller zig Displayvarianten! Danach kann man sich die Displayausgabe mit den Grafikprimitiven der u8g2-Lib so zusammenbauen, wie man es haben will. Der Democode des displayd ist wie immer nur ein Rahmen für eigenen Ideen und Umsetzungen. Im ssd1306LED-Ordner findet man zusätzlich noch die Datei SSD1306_SPI.c . In dieser Datei sieht man die exemplarische Ansteuerung des Controllers über SPI. Ich dachte das wäre toll dies im Vergleich zusätzlich zu haben, da man daran erkennen kann, wie einfach die Sache doch mit dieser Display-Bibliothek geht.


Viel Spaß beim Basteln
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#4
Nachtrag: Link in #3 zum Downloadarchiv nochmal korrigiert, jetzt sollte es klappen. Auch der Zuwachs der Dateigröße im Vergleich zu #2 ist korrekt! Wir haben mit der neuen Bibliothek auch einen Haufen Schriftarten für die Displays dabei.

Fragen/Diskussionen bitte hier stellen: https://radio-bastler.de/forum/showthrea...pid=219917
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#5
Hallo zusammen,

fügen wir dem iRadioMini für ESP32 noch einen weiteren Dienst hinzu.

Hier erst einmal der Link für das "neuste" iRadioMini-Archiv:

https://mega.nz/file/uLBVkAhZ#TN9UCeNeLU...8Vce-qh_WA


Was hat sich in diesem Tutorial-Schritt getan? Im Modules-Ordner finden wir zwei neue Dateien: httpd.c und h .
Wie der Name schon vermuten lässt, handelt es sich hier um einen kleinen Webserver-Daemon. Mit diesem kleinen und elementaren "httpd" können wir also über einen Webbrowser unser iRadioMini ansteuern! Eine Funktion, die Ihr bereits aus unserem großen iRadio kennt.

Schauen wir uns diese c-Datei mal stückweise an. Der Einstiegspunkt für das FreeRTOS-Betriebssystem, auf dem das iRadioMini läuft, ist

PHP-Code:
void httpd(void *pvParameters) { 
  httpd_handle_t hnd start_webserver();
 
  // Daemonbetrieb
  while (1) {
      ESP_LOGI(TAG"alive");
      vTaskDelay(1000/portTICK_PERIOD_MS );
   }

  stop_webserver(hnd);



Nichts aufregendes. Wir holen uns mit start_webserver() ein Handle und verschwinden sofort in der Whileschleife. Jede Sekunde gibt es auf der Console ein "Lebenszeichen". Sollten wir die Schleife doch mal mal terminieren müssen, stoppen wir mit dem Handle den Webserver wieder.

Gucken wir uns mal start_webserver() an:

PHP-Code:
/* Function for starting the webserver */
httpd_handle_t start_webserver(void)
{
    /* Generate default configuration */
    httpd_config_t config HTTPD_DEFAULT_CONFIG();

    /* Empty handle to esp_http_server */
    httpd_handle_t server NULL;

    /* Start the httpd server */
    if (httpd_start(&server, &config) == ESP_OK) {

        /* Register URI handlers */
        httpd_register_uri_handler(server, &uri_get);
        httpd_register_uri_handler(server, &uri_post);
    }
    /* If server failed to start, handle will be NULL */
    return server;



Mit httpd_config_t config = HTTPD_DEFAULT_CONFIG(); holen wir uns aus dem IDF-Headerfile esp_http_server.h eine Defaultkonfiguration für unseren zu startenden Webserver.

Diese Konfiguration sieht so aus

PHP-Code:
*/
#define HTTPD_DEFAULT_CONFIG() {                        \
        .task_priority      tskIDLE_PRIORITY+5,       \
        .stack_size         4096,                     \
        .core_id            tskNO_AFFINITY,           \
        .server_port        80,                       \
        .ctrl_port          32768,                    \
        .max_open_sockets   7,                        \
        .max_uri_handlers   8,                        \
        .max_resp_headers   8,                        \
        .backlog_conn       5,                        \
        .lru_purge_enable   false,                    \
        .recv_wait_timeout  5,                        \
        .send_wait_timeout  5,                        \
        .global_user_ctx NULL,                        \
        .global_user_ctx_free_fn NULL,                \
        .global_transport_ctx NULL,                   \
        .global_transport_ctx_free_fn NULL,           \
        .open_fn NULL,                                \
        .close_fn NULL,                               \
        .uri_match_fn NULL                            \


und kann selbstverständlich überschrieben bzw. angepasst werden.

Im nächsten Teil von start_webserver() holen wir uns das Handle, welches wir als Rückgabetyp nutzen, um damit die oben genannten Aufgaben in httpd erledigen zu können.

Durch die IDF-Funktion httpd_start, reichen wir den Wunsch einen Webserver mit unserer Konfiguration zu haben einfach an das IDF von Espressif weiter. Wenn das klappt, antwortet dieses mit ESP_OK. Danach registrieren wir zwei Callback- oder Handlerstrukturen auf diesem Serverhandle.

Schauen wir uns diese Strukturen einmal an:

PHP-Code:
/* URI handler structure for GET / */
httpd_uri_t uri_get = {
    .uri      "/",
    .method   HTTP_GET,
    .handler  get_handler,
    .user_ctx NULL
};

/* URI handler structure for POST / */
httpd_uri_t uri_post = {
    .uri      "/",
    .method   HTTP_POST,
    .handler  post_handler,
    .user_ctx NULL
}; 

In diesen Strukturen legen wir getrennt für GET und POST (hier eine kleine Erläuterung was GET/POST im HTTP-Dienst überhaupt ist: https://www.ionos.de/digitalguide/websit...t-vs-post/ ) eigene URIs, also Ressourcenbezeichner an. Diese URIs sind praktisch die Pfade für Webseiten, die hinter der Serveradresse bzw. Domain in der URL-Zeile eines Webbrowsers auftauchen. Zusätzlich bekommt jede Struktur eine eigene Callbackfunktion bekanntgegeben -> get_handler für GET und post_handler für POST.

Diese Callback-Funktionen werden immer dann angesprungen, wenn jemand in der URL-Zeile eines Webbrowsers die Adresse unseres Radios aufruft. Rein theoretisch kann jede Webseite einen eigenen getrennten Handler haben, wir aber haben erstmal nur eine Top-Seite. In diesen Funktionen müssen(!) wir auf die Anfragen von einem Webbrowser antworten. Entweder liefern wir das passende Dokument (Webseite) an den Client aus, oder wir schicken Ihm eine Meldung warum wir das zur Zeit nicht können. Sollte uns der aufrufende Webbrowser gleich noch ein paar Parameter für die Webseite auf dem Server mitschicken, können wir in diesen Funktionen die Parameter auslesen und weiterverarbeiten. -> Das ist quasi unser Kommandopromt im iRadioMini!

Mit return server; in start_webserver() wird das Handle für den Webserver an die aufrufende Funktion zurückgegeben.

Gut schauen wir uns einmal den get_handler() für GET REQUEST an. 

Wenn also jemand in einem Webbroser in der URL-Zeile soetwas angibt: http://IP_USERES_RADIO  und dann die Enter-Taste drückt, landen wir genau hier im Programm.

Mit

PHP-Code:
buf_len httpd_req_get_url_query_len(req) + 1

Schauen wir nach ob jemand nur die Seite aufgerufen hat, oder uns auch noch zusätzliche Parameter an die Adresse angehangen hat. Ist das der Fall, reservieren wir für diesen Teil in der URL erstmal einen Speicherplatz.

PHP-Code:
  if (buf_len 1) {
      buf = (char*)malloc(buf_len);
      if(!buf){
        httpd_resp_send_500(req);
        return ESP_FAIL;
      

Geht die Sache schief, bekommen wir also keinen freien Speicher mehr zugeteilt , dann müssen wir dem anfragenden Webbrowser mitteilen, daß unsere Bude leider gerade in Flammen steht, weil sprichwörtlich die "Kacke am dampfen" ist. Es bekommt einen 500 Internal Server Error als Antwort zurück. Ihr habt so etwas im Netz sicher schon einmal gesehen, wenn ein Forum oder eine Webseite nicht erreichbar ist.

Klappt das aber alles, dann belegen wir den gerade uns zugeteilten Speicher mit den Parametern aus der URL

PHP-Code:
      if (httpd_req_get_url_query_str(reqbufbuf_len) == ESP_OK) {
        if (httpd_query_key_value(buf"command"variablesizeof(variable)) == ESP_OK) { 

Gleichzeitig suchen wir in diesen Parametern nach dem Wort "command". Das ist quasi ein interes "Hallo Alexa? Hallo Google? Hallo Mercedes? ..." Also unser Weckruf.

Finden wir einen solchen Weckruf, gucken wir nach, was man eigentlich neben einer Website von uns noch will:

PHP-Code:
         if (!strcmp(variable,"Next")) {
               struct AMessage *pxMessage;
               xMessage.ucMessage NEXT_PRG;
    
       pxMessage = &xMessage
    
       xQueueSendxPlayerQueue, ( void * ) &pxMessage, ( TickType_t );
            }

            if (!strcmp(variable,"Prev")) {
               struct AMessage *pxMessage;
               xMessage.ucMessage PREV_PRG;
    
       pxMessage = &xMessage
    
       xQueueSendxPlayerQueue, ( void * ) &pxMessage, ( TickType_t );
            }

            if (!strcmp(variable,"Stop")) {
               struct AMessage *pxMessage;
               xMessage.ucMessage STOP;
    
       pxMessage = &xMessage
    
       xQueueSendxPlayerQueue, ( void * ) &pxMessage, ( TickType_t );
            }

            if (!strcmp(variable,"Play")) {
               struct AMessage *pxMessage;
               xMessage.ucMessage PLAY;
    
       pxMessage = &xMessage
    
       xQueueSendxPlayerQueue, ( void * ) &pxMessage, ( TickType_t );
            


Ihr erkennt die Nachrichten? Next, Prev, Stop, Play? Genau! Treffen wir auf diese Werte nach unserem Weckruf, dann basteln wir uns Nachrichten (gehe zum nächsten/vorherigen Sender, pausiere, spiele weiter) für unsere Abspielsoftware zurecht. Das sind praktisch die gleichen Nachrichten, die auch ein gpiod an die Abspielsoftware sendet. Und natürlich werden diese Nachrichten auch über xQueueSend an die Warteschlange xPlayerQueue gesendet. Alles so wie bei einem gpiod, nur diesmal macht das der httpd.

Nun sind wir aber noch nicht fertig. Der Webserver wartet immer noch auf eine Webseite von uns. Die liefern wir Ihm in HTML im nächsten Abschnitt der Funktion. Die gesamte Webseite liegt in der einen Zeichenkette

PHP-Code:
   /* Send a simple response */
   charHTML "<!DOCTYPE html>\
<html> \
<head> \
<style> \
... 


Diese Webseite wird dann mittels

PHP-Code:
   httpd_resp_send(reqHTMLstrlen(HTML));
    return ESP_OK

zurück an den anfragenden Webbrowser geschickt.

In dieser Webseite sind dabei HTML-Elemente eingebaut (hier als Beispiel Buttons) die bei Mausklick immer wieder erneut mittels des Webbrowsers eine Anfrage starten. Jeweils mit den Commands, auf die wir im Handler warten und die wir dann übersetzt  per Queue an die Abspielsoftware schicken.

Das heißt, mit jedem Knopfdruck auf ein HTML-Element der ausgelieferten Webseite, triggern wir wieder eine Anfrage zur Auslieferung eines neuen Exemplars dieser Webseite, nur das wir zwischendurch serverseitig (also im ESP32) dabei auch auf die Kommandos reagieren, die man uns an die Anfrage angehangen hat.

Eigentlich einfach, oder?

Und damit der httpd überhaupt beim Systemstart mit gestartet wird, muss er in unserer iRadioMini.c natürlich angegeben werden, so wie beim großen iRadio in der rc.local.

PHP-Code:
  
    
// Prozess httpd starten
    TaskHandle_t xhttpdHandle NULL;
    xTaskCreatehttpd"httpd"4096NULL tskIDLE_PRIORITY, &xhttpdHandle );
    configASSERT(xhttpdHandle); 


Das Ergebnis ist dann eine sehr einfache und elementare Webseite wie diese, die unser iRadioMini ausliefert.

   

Hier könnte man dann auch noch die Senderliste mit anzeigen lassen, bei der ein Klick auf einen Eintrag automatisch einen Programmwechsel triggert. Ebenso technische Infos zum aktuellen Programm oder weitere Steuerungen wie Laustärke, Höhen, Tiefen verwirklichen.  So wie beim großen iRadio und VLC.

Damit sind wir beim Problem. Ich gestehe kein großer Künstler zu sein. Ich kann HTML lesen und auch anwenden, aber ich kann keine guten Webseiten designen. Ich kann zwar verschiedene Pinsel kaufen und in der Hand halten, ich kann Farbdosen öffnen, aber dennoch bekomme ich kein ansehnliches Bild gemalt. Daher eine Bitte an potentielle Nachbauer, wer kann es besser? Wir müssen nicht VLC schlagen, aber vielleicht schaffen wir gemeinsam mehr als eine solche hier gezeigte minimale Webseite? Auch wenn das iRadioMini wie sein großes Pendant nur Democode und einen Rahmen für eigene Ideen bieten soll, darf hier auch gern zugearbeitet werden.

Wie immer Diskussionen und Fragen bitte im FAQ für iRadioMini für ESP32.

Vielen Dank und Grüße
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#6
Hallo zusammen,

für den heutigen Tutorial-Schritt habe ich unsere Servo-Anzeige vom iRadio ins iRadioMini rüber geholt.

Ihr erinnert Euch an Jupps VE301 Umbau mit dem iRadio?





Dieser Servo-displayd steht nun auch im iRadioMini bereit.

Dazu wurde in /iRadioMini/main/modules/display der Ordner servo mit den beiden Dateien servo.c und .h angelegt.

Schauen wir uns die servo.c in Einzelteilen an.

PHP-Code:
#define SERVO_PIN GPIO_NUM_23         //   PWM-Anschluß zum Servo
#define TRIGGER_SERVO_PIN GPIO_NUM_19 //   Freigabe der Versorgungsspannung Servo

#define GESAMT_PULSBREITE 1024        //  LEDC_TIMER_10_BIT->2^10 = 1024

#define MIN_SERVO_PULSBREITE 1                 // Zeigeranschlag links
#define MAX_SERVO_PULSBREITE GESAMT_PULSBREITE  // Zeigeranschlag rechts 


In den Defines legt man die GPIOs für das PWM-Signal des Servos fest und einen ggf. notwendigen Freigabepin für die Spannungsversorgung des Servos. Damit wird das Servo nur bei Positionsänderungen an die Betriebsspannung geklemmt (für 2 Sekunden). Ist das Servo auf Soll-Position, wird die Betriebsspannung wieder weggenommen. Das vermeidet ein Servo-Jitter (falls er auftreten sollte) und senkt den Stromverbrauch des Gesamtsystems.

Die Betriebsspannung des Servo (meist 5V) nehmt Ihr bitte aus einer eigenen Quelle und nicht vom Board! Der TRIGGER_SERVO_PIN läuft dann auf einen Transistor/Relais auf. Das Board kann den PWM-Eingang in der Regel noch selbst treiben.

PHP-Code:
void displayd_servo(void *pvParameters) {
   // Messagequeues 
   struct ADisplaydMessage *rxMsg// -> vom Playerprozess
   struct AMessage *txMsg// -> zum Playerprozess

   ledc_timer_config_t timer_conf;
   timer_conf.speed_mode LEDC_HIGH_SPEED_MODE;
   timer_conf.duty_resolution LEDC_TIMER_10_BIT;
   timer_conf.timer_num LEDC_TIMER_0;
   timer_conf.freq_hz 50;
   //timer_conf.clk_cfg = LEDC_AUTO_CLK;
   ledc_timer_config(&timer_conf);

   ledc_channel_config_t ledc_conf;
   ledc_conf.gpio_num SERVO_PIN;
   ledc_conf.speed_mode LEDC_HIGH_SPEED_MODE;
   ledc_conf.intr_type LEDC_INTR_DISABLE;
   ledc_conf.channel LEDC_CHANNEL_0;
   ledc_conf.timer_sel LEDC_TIMER_0;
   ledc_conf.duty MIN_SERVO_PULSBREITE;
   ledc_channel_config(&ledc_conf);

   gpio_config_t io_conf;
   io_conf.intr_type GPIO_PIN_INTR_DISABLE;
   io_conf.mode GPIO_MODE_OUTPUT;
   io_conf.pin_bit_mask BIT(TRIGGER_SERVO_PIN);
   io_conf.pull_down_en GPIO_PULLDOWN_DISABLE;
   io_conf.pull_up_en GPIO_PULLUP_DISABLE;
   gpio_config(&io_conf); 

Die Funktion displayd_servo(...) ist wie immer der Einstiegspunkt in den Prozess für das FreeRTOS-Betriebssystem.

Die Messages kennen wir schon vom obigen displayd.  Das sind quasi Zeiger auf die Nachrichten die wir zur Playersoftware schicken und vor dort als Antwort bekommen.

Das IDF von Espressif bietet uns bereits eine PWM-Unterstützung an. Dazu müssen zwei Strukturen ( ledc_timer_config_t und ledc_channel_config_t) gefüllt werden. Bitte wundert Euch nicht über die Bezeichnungen "LED...". Espressif hat mit der PWM-Signalerzeugung eigentlich die Dimmbarkeit von LEDs und nicht Servos im Kopf gehabt. Blinkende und Dimmbare LEDs werden heute halt öfters nachgefragt als Servos. Big Grin 

Wir bestücken also die Strukturen so, daß am Ausgang ein 50 Hz (alle 20ms) PWM mit einer Auflösung von 10 Bit (1024 Schritte) herauskommt.  Unmittelbar danach läuft das PWM-Signal auch schon los.

In der Struktur gpio_config_t legen wir die Eigenschaften unseres GPIOs als TRIGGER_SERVO_PIN fest, danach geht es in die Dauerschleife, bzw. den Daemon-Betrieb.


PHP-Code:
   // Daemonbetrieb
   while(1)
    {
       xMessage.ucMessage GET_CHANNEL_INFO;
       txMsg = &xMessage
       xQueueSendxPlayerQueue, ( void * ) &txMsg, ( TickType_t );

       if( xQueueReceivexDisplaydQueue, &( rxMsg ), ( TickType_t 10 )  )
          {
            if (rxMsg->ucMessage==GET_CHANNEL_INFO) {
        
ESP_LOGI(TAG"actual channel num: %i",rxMsg->uciChannelNum); 
                if (old_ch!=rxMsg->uciChannelNum) {
                   ledc_set_duty(LEDC_HIGH_SPEED_MODELEDC_CHANNEL_0MIN_SERVO_PULSBREITE+(rxMsg->uciChannelNum*SENDERABSTAND));
        
   ledc_update_duty(LEDC_HIGH_SPEED_MODELEDC_CHANNEL_0);
    
        
   gpio_set_level(TRIGGER_SERVO_PIN,1);
        
   vTaskDelay(2000/portTICK_PERIOD_MS);
        
   gpio_set_level(TRIGGER_SERVO_PIN,0);
                // if (old_ch!=rxMsg->uciChannelNum) {
     
       old_ch rxMsg->uciChannelNum;
            }
        }
            
        ESP_LOGI
(TAG"Heartbeat");
        vTaskDelay(1000/portTICK_PERIOD_MS ); 
    


Wir fragen hier zyklisch in der Playersoftware nach den Infos zum aktuell eingestellten Programm. Wenn wir die Antwort bekommen, interessiert uns dabei nur die Kanalnummer, denn mit dieser berechnen wir die Zeiger (Servo)-Position. Hat die sich im Vergleich zur vorherigen Anfrage geändert, ändern wir das PWM-Signal ab und geben dem Servo für 2 Sekunden lang eine Spannungsfreigabe. Mehr Hexerei ist nicht dabei.


Noch etwas. Da für jeden Schritt ein extra Download-Archiv nicht lohnt, habe ich die Sache für heute direkt in die neue Github-Seite des iRadioMini gestellt.

https://github.com/BM45/iRadioMini/


   

Von hieraus könnt Ihr Euch immer den aktuellsten Tutorialstand clonen und hier werde ich alle weiteren Schritte einchecken bis wir das volle iRadioMini-Paket erreicht haben. Ich denke das ist so einfacher.


Fragen und Diskussionen bitte nicht hier, sondern wie gehabt ins FAQ. Danke.

Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#7
Ich möchte hier jetzt zeigen, wie man für das iRadioMini im ADF ein eigenes Board anlegen kann.



Ziel ist es, mit ganz normalen ESP32-Modulen, in etwa solchen hier:



[Bild: ESP32.jpg]



eine Basis für das iRadioMini zu schaffen.



Ein fertig abgepacktes Universaldesign hier als Archiv zum Download: https://mega.nz/file/LbYkkJzS#-E-n0E6d6I...AfZ-_WMPQ0



Dieses Archiv wird so entpackt, das der Ordner my_board in esp-adf/components/audio_board zu liegen kommt. Die

Dateien CMakeLists.txt component.mk und Kconfig.projbuild, die bereits im ADF sind, werden dabei durch die neuen Versionen aus dem Archiv ersetzt.



Schauen wir uns das Boarddesign näher an:



Im Ordner my_board finden wir die vier Dateien



board.c und .h

board_def.h

board_pins_config.c .



In board_def.h



PHP-Code:
#ifndef _AUDIO_BOARD_DEFINITION_H_
#define _AUDIO_BOARD_DEFINITION_H_

#include "driver/touch_pad.h"

#define SDCARD_OPEN_FILE_NUM_MAX  5
#define SDCARD_INTR_GPIO          GPIO_NUM_34

// minimal ADF-board with no preselected audiocontrol keys
#define BUTTON_REC_ID             -1
#define BUTTON_MODE_ID            -1
#define BUTTON_SET_ID             -1
#define BUTTON_PLAY_ID            -1
#define BUTTON_VOLUP_ID           -1
#define BUTTON_VOLDOWN_ID         -1

#define AUXIN_DETECT_GPIO         -1
#define HEADPHONE_DETECT          -1
#define PA_ENABLE_GPIO            -1

#define GREEN_LED_GPIO            -1


#endif 


vergeben wir zunächst die Ressourcen (GPIO_Pins) für im ADF genutzte Defines. Da unser Universalboard (zunächst, vielleicht auch nie, bleibt ja jedem selbst überlassen) keine Tasten für REC, MODE, SET, PLAY, VOLUP .... hat, belegen wir diese mit dem Wert -1 .   



Das ist der gemeinsame Nenner in dieser Datei zum ADF. Hier können wir auch eigene Defines reinlegen, die nur für unser Board gültig sind. Bauen wir diese Bezeichner dann aber im iRadioMini direkt ein, können wir das iRadioMini nur noch für dieses Board hier compilieren, nicht mehr für andere Boards die das ADF unterstützt!



Die Datei board_pins_config.c bildet nun eine direkte Schnittstelle für das ADF!



Funktionen aus dem ADF fragen beim Boarddesign an, gibt mir deine i2c_pins, gib mit deine i2s_pins, gib mit deine spi_pins, gibt mir deinen Pin für pa_enable, gib mit deinen Pin für Taste VOL_UP usw. . Man kann es an der Implementierung in der Datei sehen. Für Schnittstellen wie i2c, i2s, spi, kann man hier Vorgaben für sein Board festlegen.



In der Datei board.h werden neben ADF- weite Initialisierungen auch eigenen Initialisierungen festgelegt, die dann in board.c implementiert werden.



Standardmäßig müssen alle Boards ein Handle auf sich selbst, die Initialisierung dieses, genauso wie die Deinitialisierung haben.

Benutzt das Design ein Audio Codec IC, so muss dieses hier auch angegeben und entsprechend initialisiert werden! -> audio_board_codec_init



Da unser Board an kein spezielles IC gebunden ist, geben wir erst mal ein NULL zurück. Wie so eine Initialisierung vorgenommen wird, habe ich aber in der audio_board_codec_init auskommentiert drin gelassen. Hier könnt Ihr Euch für Euer Board aus der gesamten Treiberwelt des ADF bedienen oder auch gänzlich neue ICs hinzufügen.



Für das ADF speziell nicht unbedingt wichtig, für unser iRadioMini aber schon, ist die Initialisierung eines SD-Kartenlesers. Das passiert hier in audio_board_sdcard_init welches vom iRadioMini aufgerufen wird. Wir brauchen diesen Teil! Schließlich liegen die WLAN-Zugangsdaten und die Senderliste bei jeder iRadioMini-Installation auf einer SD-Karte!



Mehr ist in einer eigenen Boarddefinition eigentlich nicht zu machen. Jetzt müssen wir diese Boarddefinition nur noch dem ADF bekannt machen. Das passiert halt durch die überschriebenen Versionen der Dateien CMakeLists.txt component.mk und Kconfig.projbuild.



In CMakeLists.txt durch diesen neuen Block



PHP-Code:
if (CONFIG_MY_BOARD)
message(STATUS "Current board name is " CONFIG_ESP_MY_BOARD)
list(
APPEND COMPONENT_ADD_INCLUDEDIRS ./my_board)
set(COMPONENT_SRCS
./my_board/board.c
./my_board/board_pins_config.c
)
endif() 




In der component.mk durch diesen neuen Block



PHP-Code:
ifdef CONFIG_ESP_MYBOARD
COMPONENT_ADD_INCLUDEDIRS 
+= ./my_board
COMPONENT_SRCDIRS 
+= ./my_board
endif 


und in der Kconfig.projbuild  durch





menu "Audio HAL"



choice AUDIO_BOARD

    prompt "Audio board"

    default ESP_LYRAT_V4_3_BOARD

    help

        Select an audio board to use with the ESP-ADF

config AUDIO_BOARD_CUSTOM

    bool "Custom audio board"

config ESP_LYRAT_V4_3_BOARD

    bool "ESP32-Lyrat V4.3"

config ESP_LYRAT_V4_2_BOARD

    bool "ESP32-Lyrat V4.2"

config ESP_LYRATD_MSC_V2_1_BOARD

    bool "ESP32-LyraTD-MSC V2.1"

config ESP_LYRATD_MSC_V2_2_BOARD

    bool "ESP32-LyraTD-MSC V2.2"

config ESP_LYRAT_MINI_V1_1_BOARD

    bool "ESP32-Lyrat-Mini V1.1"

config ESP32_KORVO_DU1906_BOARD

    bool "ESP32_KORVO_DU1906"

config ESP32_S2_KALUGA_1_V1_2_BOARD

    bool "ESP32-S2-Kaluga-1 v1.2"

config ESP32_S3_KORVO2_V3_BOARD

    bool "ESP32-S3-Korvo-2 v3.0"

config MY_BOARD

    bool "My ESP32 audio board"



endchoice





choice MY_BOARD

    prompt "Select play audio output"

    default USE_INTERNAL_AUDIODAC

    help

        Select play output method



config USE_INTERNAL_AUDIODAC

    bool "Enable internal DAC output"



config USE_EXTERNAL_AUDIODAC

    bool "Enable external DAC output"



endchoice







Das ist die Datei, die dafür sorgt, daß unser neues Boarddesign in idf.py menuconfig auch erscheint und ausgewählt werden kann. Aber nicht nur das, wir können in unserem Boarddesign per menuconfig auch auswählen, ob das Audio durch den internen I2S->DAC wiedergeben werden soll, oder durch einen externen DAC.  Bei letzteren haben wir diesen natürlich wie gerade eben gesagt in der audio_board_codec_init bekannt zu machen und zu initialisieren.




.jpg   1.jpg (Größe: 26,57 KB / Downloads: 857)
.jpg   2.jpg (Größe: 22,93 KB / Downloads: 856)
.jpg   3.jpg (Größe: 19,41 KB / Downloads: 856)
.jpg   4.jpg (Größe: 16,53 KB / Downloads: 857)



Nach dem Speichern der neuen Projektkonfiguration mit menuconfig, kann das Projekt (hier unser iRadioMini) ganz nochmal mit idf.py build gebaut werden. Wir können jederzeit mit menuconfig auch wieder das Board und Design wechseln und einen neuen Build der iRadioMini-Firmware vornehmen lassen. Der Wechsel zwischen LyraT-Board und eigenem Design geht also dann vollkommen transparent.



Die Änderungen findet man dann auch in der Config-Datei im iRadioMini-Projektordner (wenn nicht unbenannt, dann in sdkconfig) und kann sie dort im weiteren Verlauf direkt ändern.



Zitat:CONFIG_IDF_TARGET="esp32"
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000

#
# SDK tool configuration
#
CONFIG_TOOLPREFIX="xtensa-esp32-elf-"
CONFIG_MAKE_WARN_UNDEFINED_VARIABLES=y
CONFIG_APP_COMPILE_TIME_DATE=y
# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set
# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set
CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16
# CONFIG_AUDIO_BOARD_CUSTOM is not set
# CONFIG_ESP_LYRAT_V4_3_BOARD is not set
# CONFIG_ESP_LYRAT_V4_2_BOARD is not set
# CONFIG_ESP_LYRATD_MSC_V2_1_BOARD is not set
# CONFIG_ESP_LYRATD_MSC_V2_2_BOARD is not set
# CONFIG_ESP_LYRAT_MINI_V1_1_BOARD is not set
# CONFIG_ESP32_KORVO_DU1906_BOARD is not set
# CONFIG_ESP32_S2_KALUGA_1_V1_2_BOARD is not set
# CONFIG_ESP32_S3_KORVO2_V3_BOARD is not set
CONFIG_MY_BOARD=y
CONFIG_USE_INTERNAL_AUDIODAC=y
# CONFIG_USE_EXTERNAL_AUDIODAC is not set


Aber nicht die Datei "vermurksen", sonst wird das Projekt nicht richtig gebaut und auch menuconfig kann u.U. danach nicht nochmal richtig aufgerufen werden, deshalb ja auch die Warnung am Anfang der sdkconfig.



Bitte wie immer, hier keine Fragen stellen und Diskussionen führen! Danke.




Gruß

Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#8
Hallo zusammen!


Jetzt haben wir ja schon einiges gesehen und gelernt. Wir wissen wie das iRadio-Konzept vom Raspberry auf den ESP32 Microcontroller übertragen wurde.

Heute wollen wir weitere Displays ins Programm aufnehmen und dafür nur einen zentralen displayd benutzen.

Doch bevor es soweit ist, müssen wir uns erst mal um unsere Entwicklungsumgebung kümmern. Diese ist nämlich reichlich "angestaubt" und Espressif liefert mit dem ADF (eigentlich ADF2.0) nur ein IDF3.3 mit aus, welches gut 2 Jahre alt ist.

Schauen wir uns mal den Release-Stand des IDF an:

   

Das IDF3.3 befindet sich eigentlich in der Wartungsphase und wird noch dieses Jahr (im Frühling) den EOL (End-of-Life) Status erreichen. Für neue "Software"-Designs ist es eigentlich schon gar nicht mehr empfohlen. Warum also verpackt Espressif Systems noch so ein obsoletes IDF in das ADF? Wahrscheinlich um auch noch Rechnerinstallationen zu versorgen, deren Build-Werkzeuge ein paar Jahre älter sind, als das ADF/IDF selbst. Damit hat man eine gewisse breite "Grundversorgung", sobald aber neue Funktionen und Bibliotheken genutzt werden sollen, ist der Entwickler schnell auf ein neues IDF angewiesen. Zur Zeit ist dies das IDF 4.3 oder 4.4 und für aktuelle Designs empfohlen. Ein 5er ist wohl in der Mache (https://docs.espressif.com/projects/esp-...index.html) , aber das wird noch dauern und auch einige Änderungen mit sich bringen. Ich vermute wenn das IDF3.3 EOL ist, wird Espressif das ADF automatisch mit einem 4er IDF ausliefern.

Egal, bringen wir unser Entwicklungswerkzeug von ADF2/IDF3.3 auf ein ADF2/IDF4.3 oder ADF2/IDF4.4.
Alles was wir bisher gemacht haben, geht damit auch (sofern unsere Windows/Linux/Mac-Softwarekomponenten nicht zu alt sind), aber für das was ab jetzt kommt, benötigen wir ein Update.


Wie führen wir ein Update aus?

Wenn wir noch kein ADF/IDF installiert haben, machen wir das natürlich wie in #1 beschrieben. Danach wechseln wir in das Verzeichnis .../esp-adf/esp-idf/ und führen das Update so aus, wobei wir, wenn wir auf den ESP32 internen I2S->DAC angewiesen sind, eine v4.3 auschecken, sonst gleich das Neuste v4.4 .

cd $IDF_PATH
git fetch
git checkout vX.Y.Z
git submodule update --init --recursive

Siehe https://docs.espressif.com/projects/esp-...sions.html

Danach wird nochmal install.sh des IDF aufgerufen und nach einer Weile ist das Entwicklungssystem in der Neuzeit angekommen. Wir erkennen das erfolgreiche Update zum Beispiel auch am Neudesign des idf.py menuconfig. Falls vorher schon ein Build-Ordner im eigenen Projekt vorhanden ist, sollte dieser noch gelöscht werden und erst dann menuconfig aufgerufen werden.

   

Doch wofür brauchen wir diese Sache den nun? Wie gesagt, wollen wir unsere Displayunterstützung im iRadioMini massiv ausbauen und dabei den "Luxus" einer ziemlich mächtigen Grafikbibliothek genießen, die im Bereich Embedded Systems nicht gerade unbekannt ist. Es geht hier um die lvgl - Light and Versatile Embedded Graphics Library.

   

Damit sind wir im iRadioMini ungefähr auf der FLTK/SDL2 und X11 Ebene des großen iRadios angekommen. Wir können in dieser Grafikbibliothek nicht nur "Highlevel"-mäßig unterwegs sein, sondern uns auch im Bildspeicher "Lowlevel"-mäßig um jedes Pixel einzeln kümmern. Das wird bei Skalensimulationen und Spriteprogrammierung ein wichtiges Thema. Und nicht nur das, wir haben auch Unterstützung für JPEG, GIF, PNG, ... dabei.

Über das Treiberpaket steht uns dabei eine große Auswahl an Displays


.jpg   lvgl_disp.jpg (Größe: 43,95 KB / Downloads: 811)

und auch an Touch-Controllern

   

zur Verfügung. Wir sind also breit und bestens für alle Anwendungsfälle im Bereich Digitalradio aufgestellt.

Die Einstellungen zum Display (welcher Typ, welche Ausrichtung [Landscape,...], an welchen GPIOs angeschlossen, Bildspeicher, Schnittstellengeschwindigkeit, ...) werden dabei von idf.py menuconfig übernommen. Unser displayd-Code bleibt von solchen technischen Angaben der Hardwareschicht praktisch verschont.

                   

Und natürlich haben wir für diese Sache auch wieder einen minimalen, dafür verständlich und übersichtlichen displayd - Democode dabei.

Er liegt in display/lvgl zusammen mit einer README.txt, die nochmal das Update des ADF/IDF beschreibt, ebenso, wie im iRadioMini-Projekt (oder jedem Anderen) die lvgl, samt lvgl-Treiberpaket installiert wird und wie der displayd im iRadio zur Anwendung kommt.

   

Eine Umfangreiche Dokumentation zur lvgl (in der zum jetzigen Zeitpunkt V8) findet man hier: https://docs.lvgl.io/8.0/
Geht man durch die "Highlevel"-Grafikelemente dieser Lib https://docs.lvgl.io/8.0/widgets/index.html wird man sicher auf genügend Ideen für eine eigene GUI kommen. 

Übrigens sollte man sich den Weg der Installation der lvgl gut merken, genau so lassen sich nämlich zahlreiche weitere Bibliotheken für das IDF als Komponente in das eigenen Projekt einbinden.

Viel Spaß beim Basteln und Experimentieren!

Hier bitte keine Fragen/Diskussionen, denn dafür haben wir ja den FAQ-Thread! Danke!
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#9
Hallo Bastler,

kommen wir im nächsten Schritt zur "Königsdisziplin" des iRadios, welches wir auch auf das iRadioMini übertragen wollen - die Skalensimulation! Ich denke mit diesem Schritt dürfte ich sämtliche Zweifler überzeugt habe, die meinen, daß so etwas auf einem ESP32 nicht geht. Es geht nämlich und wer immer noch das Gegenteil behauptet, den kann man mit Beispielcode sowieso nicht überzeugen.

Als erste Skalensimulation möchte ich Jupps Umsetzung des Baby-Metz zeigen.

   

Der Quellcode dieser Skalensimulation liegt unter ../modules/display/lvglskalensim/...

Darin enthalten sind mit:

- displayd_lvgl_skale.c und .h die eigentliche Skalensimulation

- mit skale.c die übersetze Bilddatei skale.png ohne Alphakanal

- mit zeiger.c die übersetzte Bilddatei zeiger.png mit Alphakanal

- die Bilddateien skale.png und zeiger.png, die aber nur einmal benötigt werden und durch dieses LVGL- interne Tool https://lvgl.io/tools/imageconverter  übersetzt wurden. Diesen Konverter gibt es auch in einer Offlineversion wie man lesen kann. 

- README.txt erklärt die Installation, im Prinzip analog eines LVGL-displayd. 

Schauen wir uns mal ein kurzes Handyfilmchen zur Skalensimulation an. Der "Stern" kommt hier nur als Audioverstärker für den interen I2S-DAC des ESP32 zur Geltung.


Wir laufen hier nicht mal auf voller Auslastung, sondern bremsen die Displaybewegung noch ab. Umgeschaltet wird über das Webinterface und ich spiele den Ton nur kurz an, weil sonst bei Youtube wieder diese Auto-Rechteverletzung-Sperre zuschlägt.

Wie baut man nun seine eigene Simulation? Bildmäßig, also von Seiten der PNGs genauso wie beim großen iRadio. Das hatte ich hier beschrieben: https://radio-bastler.de/forum/showthrea...#pid142892

Danach werden die Bilder entsprechend mit dem LVGL eigenen Konverter umgewandelt. Diese c-Dateien enthalten praktisch das Bild pixelweise mit den Farbinfos + Alpha in einem Array oder so wie es später im Bildschirmspeicher stehen würde, mit allen Möglichkeiten die sich daraus ergeben.

Auch die Skalensimulation selbst https://github.com/BM45/iRadioMini/blob/...gl_skale.c enthält keine Hexerei.  Nach der ganzen Initialisierung der LVGL legen wir zwei Image-Widgets an.

PHP-Code:
  // Hintergrund - hier Skale
   LV_IMG_DECLARE(skale);
   lv_obj_t img lv_img_create(scr);
   lv_img_set_src(img, &skale);
   lv_obj_align(imgLV_ALIGN_TOP_LEFT00); 

   // Zeiger vor(!) der Skale
   LV_IMG_DECLARE(zeiger);
   lv_obj_t img_zeiger lv_img_create(scr);
   lv_img_set_src(img_zeiger, &zeiger);
   lv_obj_align(img_zeigerLV_ALIGN_TOP_LEFTZEIGERANSCHLAG_LINKS0); 


In der while-Schleife holen wir zyklisch die Kanalinfos per Queue vom PlayerTask und warten darauf bis eine Umschaltung erfolgt. Danach bewegen wir den Zeiger zum Ziel:


PHP-Code:
        // wurde umgeschaltet ?
                if (old_ch!=rxMsg->uciChannelNum) {
                  lv_label_set_text(label_url"\0");
 
        
  // bewege Skalenzeigen bis zur neuen Station
                  while (pos_zeiger_now != (ZEIGERANSCHLAG_LINKS SENDERABSTAND*rxMsg->uciChannelNum)) {

                     if (pos_zeiger_now < (ZEIGERANSCHLAG_LINKS SENDERABSTAND*rxMsg->uciChannelNum)) {
                       pos_zeiger_now++;
        
     }
        
                     if (pos_zeiger_now > (ZEIGERANSCHLAG_LINKS SENDERABSTAND*rxMsg->uciChannelNum)) {
                       pos_zeiger_now--;
        
     }    

    
             vTaskDelay((1000/FPS)/portTICK_PERIOD_MS ); 
    
             lv_obj_align(img_zeigerLV_ALIGN_TOP_LEFTpos_zeiger_now0);
        
   }

                   // URI-Anzeige aktualisieren
                   lv_label_set_text(label_urlrxMsg->ucURI);     
        
                
// if (old_ch!=rxMsg->uciChannelNum) {
     
      old_ch rxMsg->uciChannel 


fertig.



Auf "normalen" Ur-ESP32 Modulen ohne zusätzlichen PSRAM läuft die Skalensimulation auch, allerdings haben wir dann meist nur noch genug RAM für die Wiedergabe von mp3 Stationen frei. Haben wir aber ein ESP32-Modul mit PSRAM und aktivieren wir diesen per menuconfig für das IDF und für die LVGL, sieht die Welt wieder ganz anders aus. Skalensim + AAC, MP3, OGG rein in Software usw... sind dann praktisch wieder möglich, wie man im Demovideo auch hören kann.

So, meiner Meinung nach ist der Beweis erbracht, daß wir auch auf dem ESP32 gut mit einer Skalensimulation arbeiten können.  Also macht was draus!  ;-)

Viel Spaß beim Experimentieren
Gruß
Bernhard


Hier bitte keine Fragen/Diskussionen, denn dafür haben wir ja den FAQ-Thread! Danke!
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#10
klasse Arbeit Smiley14
Zitieren
#11
Und auch die Cassettensimulation vom iRadio gibt es nun auf dem iRadioMini.





Die Quellen dazu liegen in .../modules/display/lvgl_cassettensim

Da es sich wieder um eine LVGL-Simulation handelt, wird diese genauso eingerichtet wie die anderen LVGL-Daemonen.


Ein Hinweis noch! Wenn wir LVGL wie in den Readme's beschrieben ins iRadioMini auschecken, benutzen wir in der Regel ein 8er Release von LVGL. Beim Compilieren wird es zu einer "Fehlermeldung" kommen, die die Datei lvgl_helpers.c betrifft ->

‘SPI_HOST_MAX’ undeclared (first use in this function) ....


Ab der Version 8 von LVGL gibt es die Bezeichnung SPI_HOST_MAX nicht mehr, man kann also in lvgl_helpers.c im Treiberpaket die Zeile so auskommentieren:

Code:
  
    // assert((0 <= host) && (SPI_HOST_MAX > host));
    const char *spi_names[] = {
   
        "SPI1_HOST", "SPI2_HOST", "SPI3_HOST"
    };

Danach speichern und ganz normal das iRadioMini übersetzen, der Rest wird über idf.py menuconfig aktiviert und eingestellt!

Wer LVGL und LVGL_ESP32_DRIVERS von meinem Github zum Projekt auscheckt, der braucht diesen Bearbeitungsschritt nicht zu machen, den habe ich schon erledigt!

https://github.com/BM45/lvgl
https://github.com/BM45/lvgl_esp32_drivers



Gruß und viel Spaß beim Basteln
Bernhard

Hier bitte keine Fragen/Diskussionen, denn dafür haben wir ja den FAQ-Thread! Danke!
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#12
Mit dem letzten LVGL-Beispiel, der Simulation einer Rundskala, schließe ich das Thema "Skalensimulation im iRadioMini" erst mal ab.



   

Mit den gegebenen Beispielen sollte eigentlich jeder Bastler und Nachbauer zu seiner ganz persönlichen Skale oder Simulation kommen können. Wie beim iRadio helfen wir natürlich auch, wenn jemand etwas ganz Spezielles für sein Radio benötigt - einfach im FAQ-Thread zum iRadio/iRadioMini anfragen.

Fortsetzung folgt...
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#13
So ich habe heute nochmal ein paar Bibliotheken hinzugefügt.

- HD44780 -> LCD Modul
- PCF8574 und PCF8575 sind bekannte, preiswerte I2C-Portexpander, die man wunderbar kaskadieren kann, wie man hier sehen kann:


   

Über die Jumper können die Adressen der einzelnen Module eingestellt werden, mit 3 Jumper lassen sich so 8 Adressen setzen. Pro Modul gibt es 8 I/Os, macht also pro I2C-Bus mit diesen Modulen max. 64 GPIO-Pins. Eine gute Ausbeute, wenn man bedenkt, daß sich so 64 IOs mit nur zwei belegten Pins am Controller bedienen lassen!  An diese Pins könnte man nun Taster oder Encoder hängen oder auch Displays.

Als Demo habe ich ein HD44780 mit dem PCF8574 gekoppelt und damit einen Displayd erzeugt:

https://github.com/BM45/iRadioMini/blob/...4780_i2c.c

Wenn man ein Rotary-Encoder über I2C abfragen will, nutzt man nicht mehr einen solchen Befehl gpio_get_level(Pinnummer); sondern holt sich zyklisch oder Interrupt-getrieben von einem I2C-Expander, dessen Adresse am Bus ja bekannt ist, per i2c_dev_read() oder i2c_dev_read_reg() ein komplettes Byte ab, welches die jeweiligen I/O-Zustände P7...P0 beinhaltet. 
Über diesen Zahlenwert legt man eine AND-verknüpfte Bitmaske und kann so sehen, welches I/O gerade welchen Zustand hat.  Mit diesen Werten kann man ohne Probleme die bereits bestehenden gpiod's füttern.

Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#14
Hallo zusammen,

heute kommen wir zu einem weiteren Funktionsupdate des iRadioMini für ESP32.

Wie im großen iRadio auf Raspberry Pi, wollen wir eine Sendeunterstützung / Relais-Funktion auch für das iRadioMini haben.


Historisch haben wir im Forum damals mit den DDS ICs AD9832/35 - etwas später mit dem AD9850 - angefangen zu senden. Diese DDS-ICs sind auch heute bei vielen Bastlern in der Radioszene verbreitet, zum Beispiel im "Heimsenderlein" oder in diversen Selbstbau-Signalgeneratoren und Wobblern.

Die ICs gibt es für relativ wenig Geld bereits als Fertigmodul im DIP-Format bei Amazon und co. . Die ICs selbst sind sogar als kostenloses Sample beim Hersteller Analog beziehbar.

Die DDS-ICs lassen sich dabei entweder direkt AM-modulieren (zum Beispiel über FS-Adjust Pin https://www.b-kainka.de/Weblog/Logbuch070616.html oder Rset beim AD9850) oder als LOcarrier über einen zusätzlichen Mischer.

Was liegt also näher, als genau mit diesen ICs unsere Sendeunterstützung im iRadioMini zu starten?

   

Im iRadioMini werden alle Sender, egal ob als DDS-IC oder SDRTx-SW, als Treibermodule behandelt und liegen im Unterordner "tx" des Ordners "modules".

Alle diese "Sender" müssen für die Verwendung im iRadioMini nach außen hin zwei Funktionen implementieren:


void initTX(void);
void setTxFreq(uint32_t);



initTX() -> hier wird alles rein codiert, was für die Initialisierung des "Senders" notwendig ist.

setTXFreq() -> über diese Funktion wird die Sendefrequenz des "Senders" festgelegt oder geändert.

In den Implementierungen werden auch die Anschlußpins (sofern externes IC) festgelegt, Beispielsweise:

// pin configuration
#define FSYNC    GPIO_NUM_5
#define SCLK    GPIO_NUM_18
#define SDATA    GPIO_NUM_23



Das iRadioMini spricht die Sender zentral über einen Daemonen an. Das ist der transmitterd. Die Quellen dieses Daemons sind in den Dateien transmitterd.c und .h zu finden. Durch die Festlegung des Senders im Includezweig (beispielsweise #include "./AD9835/AD9835.h" ) werden die passenden initTX() und setTXFreq() Funktionen zum passenden Modul ausgewählt. Natürlich muss sich die Auswahl des Moduls auch in der
iRadioMini/main/CMakeLists.txt  wiederfinden!


...
            "./modules/display/servo/servo.c"

  "./modules/tx/transmitterd.c"
  "./modules/tx/AD9835/AD9835.c"
    )


Zum transmitterd (der als Endlos-FreeRTOS-Task läuft), gibt es einen Messagequeue mit Namen xTransmitterdQueue . Über diesen Nachrichtenkanal hört der Daemon auf Nachrichten des Typs ATransmitterdMessage, speziell auf das Kommando SET_FREQ .  (siehe messages.h)
In ATransmitterdMessage gibt es den Wert  uint32_t new_freq; über den eine neue Frequenz mitgesendet wird.
Empfängt der transmitterd nun diese Nachricht, übergibt er den Wert new_freq an die Funktion setTXFreq. Somit lässt sich die Sendefrequenz auch nach dem Start des iRadioMini bequem durch andere Prozesse im iRadioMini ändern!

Wenn die Firmware also mit Sendeunterstützung durch transmitterd gebaut und geflashed wurde, kann die Sendefunktion bequem über eine Datei mit Namen AM.txt im Wurzelverzeichnis der SD-Karte gesteuert werden. Wird so eine Datei beim Systemstart gefunden, wird der Sendeprozess aktiviert und der Sender auf die in der Datei angegebene Sendefrequenz (in kHz) eingestellt.  Die Datei AM.txt enthält also nichts anderes als den Wert der Sendefrequenz in kHz. Fehlt diese Datei beim Systemstart, bleibt die Sendefunktion deaktiviert.

Ein weiterer Daemon mit Namen inet2RFd befindet sich ebenfalls im tx-Ordner des iRadioMini. Dieser Daemon bildet dabei die gesamte Senderliste (playlist.m3u) auf ein frei wählbares Frequenzband (sofern dieses Band vom Sendemodul unterstützt wird) ab.


.jpg   inet2RDd.jpg (Größe: 36,83 KB / Downloads: 631)

Doch wie funktioniert das mit nur einem Sendemodul? Ganz einfach, wir hören ja auf einem Radio immer nur einen Sender, also benötigen wir auch nur ein Sendemodul mit einer eingestellten Frequenz! Damit das iRadioMini nun weiß, welche Empfangsfrequenz am (historischen) Radiogerät eingestellt ist, misst es durch den Daemon inet2RFd die Frequenz des LO am ersten Mischer. Dabei sind oft nicht mal Eingriffe am Gerät notwendig! Speziell bei alten Röhrengeräten, ist die Abstrahlung der Mischröhre so groß, daß meist eine Messsonde oder kleine Draht-Schleife ausreichend ist, um diese Abstrahlung außerhalb des Gerätes aufzufangen! Ich hatte es im Forum ja schon gezeigt und einige Bastler haben so auch alten Radios eine digitale Frequenzanzeige verpasst.

Denkt an Franks (Morningstars) Dabendorf https://www.radio-bastler.de/forum/showt...p?tid=9922 oder
Roberts und Opa.Wolles Nachrüstungen https://radio-bastler.de/forum/showthrea...2#pid93852

Genau dieses Prinzip wird hier im inet2RFd genutzt.

   

Dazu wird der im Daemon festgelegte Frequenzbereich gleichmäßig durch die Anzahl der Sender in der Senderliste (playlist.m3u) geteilt. Bewegt sich die am Radio eingestellte und gemessene Empfangsfrequenz in einem solchen Bereich, wird am Sendemodul der passende Träger eingestellt. Da als Modulationssignal natürlich auch die passende Internetradiostation aus der Playlist anliegen muss, wird auch dieser Umschaltbefehl an die Playersoftware gesendet.
Dreht man jetzt über das Frequenzband am Radio, wird immer ein neuer Sender mit Internetradiomusik erscheinen und zwischen den Sendern (sofern man es nicht mit der Playlist übertreibt) genügend "Wellenrauschen" für ein authentisches Abstimmgefühl sorgen.

Den Code für inet2RFd habe ich bewusst so einfach gehalten. Hier kann man auch andere "Umschaltstrategien" umsetzen oder die Trägerfrequenzen bestimmten Frequenzrastern zuweisen (%-Operation) wenn man das möchte.

Im Prinzip ist der inet2RFd nur ein weiterer gpiod. Dieser reagiert aber nicht auf Tastendrücke oder Drehencoder-Impulse, sondern auf eine gemessene LO-Frequenz am ESP32.


Ihr seht, es gibt viele viele Möglichkeiten für Spielereien.

Gruß
Bernhard

Hier bitte keine Fragen/Diskussionen, denn dafür haben wir ja den FAQ-Thread! Danke!
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#15
Hallo zusammen,

heute ein kleineres Funktionsupdate.

   

Wie das große iRadio auf Raspberry, so kann das iRadioMini nun auch als Mediaplayer benutzt werden. 

https://github.com/BM45/iRadioMini/blob/...n-sd-karte

Dazu wurde der Quellcode von player.c dahingehend erweitert, daß die Audiopipeline nun nicht nur von einem Netzwerkstream Daten empfangen kann (http_stream_reader), sondern auch von einem Dateisystem (fatfs_stream_reader).

Der Mediaplayer kann im Quellcode (idealerweise in einem gpiod) durch den Aufruf der Funktion start_mediaplayer_service/MSG ENABLE_MEDIAPLAYER gestartet werden. Dabei wird zunächst die gesteckte SD-Karte nach Mediendateien durchsucht und eine interne Playlist angelegt.

Im Quellcode wird zunächst nur mp3 und aac als Suchparameter angegeben, siehe:

sdcard_scan(sdcard_url_save_cb, "/sdcard", 0, (const char *[]) {"mp3", "aac"}, 2, sdcard_list_handle);

Die Liste kann jedoch durch weitere Formate ergänzt werden, wobei sämtliche Formate möglich sind, die der ESPDekoder aus dem Audio Developer Framework unterstützt.
Der Mediaplayer wird durch den Funktionsaufruf stop_mediaplayer_service/MSG ENABLE_INTERNETRADIO wieder beendet, worauf dann die Internetradiowiedergabe wieder anspringt.

Viel Spaß beim Basteln und Experimentieren
Bernhard

Hier bitte keine Fragen/Diskussionen, denn dafür haben wir ja den FAQ-Thread! Danke!
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#16
(26.04.2022, 14:39)Bernhard45 schrieb: Der Mediaplayer kann im Quellcode (idealerweise in einem gpiod) durch den Aufruf der Funktion start_mediaplayer_service/MSG ENABLE_MEDIAPLAYER gestartet werden.

Da die Frage "Wie?" aufkam, habe ich das mal demonstrativ im Standard-gpiod mit eingebaut: https://github.com/BM45/iRadioMini/blob/...es/gpiod.c


Die Umschaltung erfolgt genau an dieser Codestelle:

PHP-Code:
  if (!gpio_get_level(KEY_MEDIAPLAYER)) {
       ESP_LOGI(TAG"toggle playermodi (internet/mediaplayer)");

       if (MEDIAPLAYER_ENABLED
    
    xMessage.ucMessage ENABLE_INTERNETRADIO;
       else 
            xMessage.ucMessage ENABLE_MEDIAPLAYER;

       pxMessage = &xMessage
       xQueueSendxPlayerQueue, ( void * ) &pxMessage, ( TickType_t );
    


Es wird nach Tastendruck auf die Taste, die am Pin KEY_MEDIAPLAYER angeschlossen ist, die Nachricht ENABLE_MEDIAPLAYER oder  ENABLE_INTERNETRADIO  in die Nachrichtenwarteschlange des Players gestellt, je nachdem welcher Modi gerade aktiv ist. Den Rest macht für den Anwender dann der Playerprozess.

Ich hoffe die Sache ist jetzt klarer? Bei Fragen einfach direkt im FAQ-Thread schreiben https://radio-bastler.de/forum/showthread.php?tid=19199 , vielleicht stellen sich andere Nutzer auch gerade die gleiche Frage.

Viel Spaß beim Experimentieren.

Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#17
Vielen Dank. Das kommt wie gerufen!

Mit freundlichen Grüßen
BalticSea Ronald
Zitieren
#18
Ich habe mich dem Wunsch eines Neuanwenders hingegeben und ein paar Kleinigkeiten am iRadioMini geändert.

Mit Commit Nummer 100 geht es von


[Bild: attachment.php?aid=103087]

in Richtung

[Bild: support-periods.svg]



Wer das iRadioMini auf dem alten 3er und 4er IDF Zweig fortführen möchte checkt einen Codestand bis Jun 23, 2023 aus, die alten IDF/ADF Entwicklungsumgebungen dafür können nach wie vor vom BM45 git geclont werden. Da ändere ich auch nichts. Ab 4.4 und danach ist der aktuelle Codestand mit dem aktuellen IDF/ADF von Espressif.
Falls im 5er Zweig die Abwärtskompatibilität für FreeRTOS benötigt wird, kann das  über idf.py menuconfig unter <Component config> <FreeRTOS> <kernel> aktiviert werden.
 
Es sind seit damals einige neue Boards dazugekommen habe ich gesehen. Auch interessante Softwareerweiterungen.
Für den ES8388 braucht es aber immer noch den Patch von uns damit der DAC auf dem Lyra 4.3 richtig laut wird, das hat man noch nicht korrigiert. Wahrscheinlich bleibt das auch für immer so. Für den internen DAC wird von uns weiterhin vom Player auf den I2S-PDM-Mode geschaltet der einen besseren Klang bietet.
Die ganzen Sende und Relayfunktionen habe ich auch getestet, auch inet2RFd und der Mediaplayer funktionieren. Bei lvgl habe ich noch nicht nach einem Update geschaut. Wer es nutzen will müsste dort einmal selbst aktiv werden.
Zitieren


Möglicherweise verwandte Themen…
Thema Verfasser Antworten Ansichten Letzter Beitrag
  Karadio für ESP8266/ESP32 saarfranzose 22 7.784 23.04.2024, 08:55
Letzter Beitrag: 23pse
  Dementia III ESP32 Internetradio mit Simulation saarfranzose 16 3.095 08.04.2024, 14:34
Letzter Beitrag: saarfranzose
  MW-Sender mit ESP32 DrNeurosurg 23 4.809 06.02.2024, 14:56
Letzter Beitrag: navi
  Software Installer für den ESP32 pintel 18 1.074 29.12.2023, 18:29
Letzter Beitrag: Siegfried55
  Huth E72 Internetradio mit ESP32 saarfranzose 10 1.914 12.05.2023, 14:49
Letzter Beitrag: saarfranzose

Gehe zu: