Sziasztok!
Próbálok összerakni egy I2S jelgenerátort Wemos D1 minivel. A cél az lenne, hogy 1024 bit amplitudójú szinuszjelet generáljon 440 Hz-en, amit egy DAC-ba vezetnék. A loop-ba tettem egy for ciklust, ami 1 másodperc alatt, azaz 44100 WS periódus alatt kellene, hogy végigmenjen, kiszámolja a szinusz amplitudót, átalakítja kettes komplemensre, és kiírja a DATA kimenetre, ami megy a DAC-ra. Nem világos azonban, hogy a for ciklus lefutása után mi történik. Azt szeretném, hogy kezdje újból a loop-ot, de mintha nem azonnal kezdené újra a for ciklust. A GPIO3 a DATA (SD), a D1,D2,D5,D6 kapcsolókkal beállítható "track number" 0-tól 15-ig. WS a GPIO2 (D4), SCK a GPIO15 (D8). Egy másik általam írt I2S kódban a TrackNumber és az i2s_write_lr jól működik. Azt nem értem, hogy az I2S sampling rate (44100 Hz) hogyan viszonyul a loop lefutási idejéhez. A loop-on belül szeretnék adatot kiírni 44100-szor, és ezt ismételni késedelem nélkül. Elnézést, kezdő vagyok, valamit elnézhettem, és a kódon is lehetne optimalizálni.
#include <I2S.h>
void setup() {
pinMode(3,OUTPUT);
pinMode(D1,INPUT);
pinMode(D2,INPUT);
pinMode(D5,INPUT);
pinMode(D6,INPUT);
digitalWrite(3,0);
i2s_begin();
i2s_set_rate(44100);
}
void loop() {
uint8_t track=TrackNumber();
uint16_t Freq = 440; // frequency in Hz
float Omega = 2*PI*Freq; // angular frequency in radians
for (uint16_t Pace=0; Pace < 44100; Pace++) {
float Cycle = (Pace*Omega)/44100;
float Ampnorm = ((sin(Cycle)+1)/2)*1023; // normalized amplitude 0...1023 float
uint16_t AmpOB = Ampnorm + 0.5; // normalized amplitude rounded integer 0...1023 (0...03FF) offset binary
uint16_t AmpOffset = pow(2,track)-512 + AmpOB; // amplitude DC shifted according to track number
uint16_t AmpSB = 0x8000 - AmpOffset; // amplitude signed binary
uint16_t Amp2C = (~AmpSB)+1; // amplitude 2s complement
i2s_write_lr(Amp2C, Amp2C);
}
}
uint8_t TrackNumber(void) {
uint8_t(var1);
uint8_t(var2);
uint8_t(var4);
uint8_t(var8);
uint8_t(track_number);
if (digitalRead(D1)) var1=1;
else var1=0;
if (digitalRead(D2)) var2=2;
else var2=0;
if (digitalRead(D5)) var4=4;
else var4=0;
if (digitalRead(D6)) var8=8;
else var8=0;
track_number = var8 + var4 + var2 + var1;
return (track_number);
}
Hozzászólások
Gyanítom, hogy az i2s_write_lr blokkol addig, amíg el kell küldeni a samplet. A headerben ez van:
/*
i2s_write_sample will block when you're sending data too quickly, so you can just
generate and push data as fast as you can and i2s_write_sample will regulate the
speed.*/
Úgyhogy ha elég gyors a ciklusod, akkor működik. Más esetben bufferbe kellene írni, majd DMA (i2s_write_buffer).
Köszönöm. Beteszek egy DMA buffer ellenőrzést, i2s_is_empty(). Tudnék valahogy gyorsítani a cikluson?
Ha egyesével írsz bele, minek az ellenőrzés? Ráadásul megvárni, míg tök üres lesz a FIFO...az írás megvárja, míg lesz szabad hely.
Gyorsítani? Mondjuk átírod integer aritmetikára float helyett.
Vagy kiszámolod a függvény első negyedét előre, beteszed egy tömbbe (ez 44100/4*2 byte, kb. 20 kB, elfér az ESP RAM-jában), és a loop()-ban csak kiolvasod onnan.
Miért nem rakod táblázatba? Elfér 201 bájtban.
nem is tudtam... az esp32 támogatja az Excelt?
a szmályli nem fért bele az ekceledbe?
"Normális ember már nem kommentel sehol." (c) Poli
Az esp32 a Lotus 123-at tudja. ;)
A Wemost akarod gyakorolni, vagy a jelgenerator a lenyeg? Utobbi esetben egy AD9850 vagy a csaladbol megfelelo eszkoz jo lehet. Arduinohoz meg lib is van, gondolom ESP-hez is. Ill. letezik breakout boardos valtozata, csak ossze kell dugdosni.
A strange game. The only winning move is not to play. How about a nice game of chess?
A jelgenerátor lenne a lényeg. Most mindent kitettem a loopon kívülre, csináltam egy tömböt, abba számoltattam ki a szinuszt. Még finomítani kell.
Egy androidos telefon kimenete nem jó erre a célra? Megannyi signal generátor app van, ha jó a dac, jó lesz a jelalak is..
https://play.google.com/store/apps/details?id=com.keuwl.functiongenerat…
"Nem akkor van baj amikor nincs baj, hanem amikor van!"
Népi bölcsesség
Jó, és nekem is pont ez a kedvenc generátorom, rendkívül alacsony a torzítása. Én éppen DAC teszteléséhez építettem ezt az ESP8266-os eszközt, amivel mindenféle extrém kondíciókat tudok beállítani, pl. hogy viselkedik a DAC amikor sok bit egyszerre vált (pl. 0000 1111 1111 1111 > 0001 0000 0000 0000), és ennek a hatása -60 dB amplitudójú szinuszjel torzítására, amit a nullátmenetnél fellépő linearitás-hiba okoz. Méricskélek, szórakozok...
Én vettem egy fizikai eszközt (jó az ha van :)) olcsón a banggood-ról, 60MHz-es, két csatornás cuccos..
"Nem akkor van baj amikor nincs baj, hanem amikor van!"
Népi bölcsesség
Nincs veletlenul fenn az ESP a halozaton? Ha korabban mentettel bele autoconnectes beallitast, akkor azt szepen "tolja belul". A loop() hivasok kozott ilyenkor tuti, hogy van valamennyi housekeeping, hogy a halozaton aktiv maradhasson.
Mezei Arduinon is van holtjáték a loop egyes futásai között.
Megfigyelésem szerint a loop() az nem a main(), "csak" egy függvény, ami rendszeresen meghívódik.
"Normális ember már nem kommentel sehol." (c) Poli
Ez miért ennyire bonyolult? A TrackNumber() szerintem egyetlen return némi argumentummal.
Már úgy érted 1 << track, ugye? ;)
Tehát (uint16_t) -AmpSB. Ha nem kényes a fordító, a castolás sem kell.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
Mondtam, hogy kezdő vagyok. Köszi a tippeket, átírom azok szerint.
Ez a kód így nem az, ami jó. Valahogy úgy csinálmám, hogy felizgatnék egy timer-t és egy DMA kontrollert. A timer 44100 Hz-cel triggerelné a DMA kontrollert, a DMA forrása egy buffer, célja az I2S periféria. A buffert két félbufferre bontanám, s amikor elértük a felső felét, megszakítás keletkezne, s feltöltenénk FIFO-ból az alját, amikor a felső felének végéhez értünk, s az alsó felének lejátszása következik, akkor pedig FIFO-ból feltöltjük a felső felét. A FIFO-t a 440 Hz-es mintákkal lehet tölteni alapszintről addig, amíg megtelik a buffer. Ha legközelebb lesz benne üres hely, tesszük bele az újabb mintákat, persze folytatólagosan, nem elfelejtve, hogy az Omega * t fázishelyzet hol tartott.
Mert ennek most nem is tudom, mi végzi az időzítését. Ha az i2s_write_lr() blokkol, akkor utána marha gyorsan kell adni neki a következő mintát, amiről, ha lemaradunk, meg fog nyúlni az egész, recsegni, kopogni fog a hang. Semmi rugalmasság nem lesz a rendszerben, még egy elég gyors MCU is kevés lesz hozzá, ráadásul semmi mást nem tud majd csinálni, kommunikálni, LED-et villogtatni, más taskokat futtatni. Ellenben, ha úgy csinálod, ahogy írtam, csak átlagos sebességben kell tudni kellő sebességgel etetni a FIFO-t.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
Én azzal kezdeném, hogy ennél az I2S-nél a végső órajel (ami alapján a DAC működik) miből áll elő, és hogy az I2S lib mit csinál? Az I2S jelforma elég egyszerű, a Wiki találat alapján is implementálható lenne talán. Eleve kérdés, hogy szoftveresen, vagy hardveresen állítja-e elő a csipped? És a másik kérdés, hogy milyen időzítési pontosságot igényet, tehát hogy van-e bufferelve a másik végén, és hogy miből áll végül elő a kimeneti órajel? Úgy érzékelem a leírása alapján, hogy a WS vonal egyben a kimenet órajele is lehetne.
(Régi megfigyelésem, hogy az Arduino libek nem jól hordozhatóak platformok között - tehát csak néhány csipen működik jól, vagy csak bizonyos körülmények között működnek, vagy nem stabilak, stb. Nem lehet megúszni, hogy belenézz, hogy mit csinál és adaptáld a saját igényeidhez.)
Ha nincs hardveres I2S támogatás a csipben, akkor én úgy valósítanám meg, hogy egy PWM-re felprogramozható timer adná a WS jelet pontosan az elvárt frekvenciára hangolva, és ezzel párhuzamosan egy interruptot is adna, amivel triggerelnék egy SPI átvitelt. Mert úgy tűnik, hogy egy okosan beállított SPI (ami minden csipen van), plusz a vele szinkronban járó WS jel adja ki az I2S protokoll.
Az SPI részét meg lehet csinálni teljesen szoftveresen is persze.
ESP32-ben van hardveres I2S, neked annyi a feladatod, hogy felprogramozd, majd a FIFO-t olyan gyorsan etesd, hogy ne ürüljön ki, ami az egyszerű blokkolós függvényekkel annyira nem egyszerű, mert ezek megvárják, amíg a DMA buffer kiürül:
https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-referen…
Bocsi, esp8266-ra is igaz?
"Normális ember már nem kommentel sehol." (c) Poli
legjobb emlékezetem szerint esp8266 -ban nincs hw i2c, csak szoftveres bitbang játszik
// Happy debugging, suckers
#define true (rand() > 10)
Adatlapja szerint van rajta HW I2S:
ESP8266EX has one I2S data input interface and one I2S data output interface, and
supports the linked list DMA.
Érdemes a datasheetet átböngészni, ha az ember egy új MCU-val ismerkedik.
Győzelem! Biztosan nem a legszebb kód, de működik. Oszcilloszkópon nézve azt kapom, amit elvártam. Köszönöm mindenkinek a segítséget. Ilyen lett: (track_number csak 9 és felette használom)
#include <I2S.h>
float Cycle[2205];
uint16_t Freq; // frequency in Hz divided by 20
float Omega; // angular frequency in radians
float Ampnorm[2205];
void setup() {
pinMode(3,OUTPUT);
pinMode(D1,INPUT);
pinMode(D2,INPUT);
pinMode(D5,INPUT);
pinMode(D6,INPUT);
digitalWrite(3,0);
i2s_begin();
i2s_set_rate(44100);
Freq = 22;
Omega = 2*PI*Freq;
for (uint16_t i=0; i < 2205; i++) {
Cycle[i]=(i*Omega)/2205;
Ampnorm[i] = ((sin(Cycle[i])+1)/2)*1023; // normalized amplitude 0...1023 float
}
}
void loop() {
uint16_t TrackNumber();
for (uint16_t j=0; j < 2205; j++) {
uint16_t AmpOB = Ampnorm[j] + 0.5; // normalized amplitude rounded integer 0...1023 (0...03FF) offset binary
uint16_t AmpOffset = TrackNumber()-512 + AmpOB; // amplitude DC shifted according to track number
uint16_t Amp2C = 0x8000 xor AmpOffset; // first bit swapped (XOR)
i2s_write_lr(Amp2C, Amp2C);
}
}
uint16_t TrackNumber(void) {
uint8_t(var1);
uint8_t(var2);
uint8_t(var4);
uint8_t(var8);
uint8_t(track_number);
if (digitalRead(D1)) var1=1;
else var1=0;
if (digitalRead(D2)) var2=2;
else var2=0;
if (digitalRead(D5)) var4=4;
else var4=0;
if (digitalRead(D6)) var8=8;
else var8=0;
track_number = var8 + var4 + var2 + var1; // track 0 ... 15
return (1<<track_number); // 1, 2, 4, 8, ... 32768
}