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);
}
- 648 megtekintés
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).
- A hozzászóláshoz be kell jelentkezni
Köszönöm. Beteszek egy DMA buffer ellenőrzést, i2s_is_empty(). Tudnék valahogy gyorsítani a cikluson?
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
Miért nem rakod táblázatba? Elfér 201 bájtban.
- A hozzászóláshoz be kell jelentkezni
nem is tudtam... az esp32 támogatja az Excelt?
- A hozzászóláshoz be kell jelentkezni
a szmályli nem fért bele az ekceledbe?
"Normális ember már nem kommentel sehol." (c) Poli
- A hozzászóláshoz be kell jelentkezni
Az esp32 a Lotus 123-at tudja. ;)
- A hozzászóláshoz be kell jelentkezni
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 hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
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...
- A hozzászóláshoz be kell jelentkezni
É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
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
Ez miért ennyire bonyolult? A TrackNumber() szerintem egyetlen return némi argumentummal.
pow(2,track)
Már úgy érted 1 << track, ugye? ;)
(~AmpSB)+1
Tehát (uint16_t) -AmpSB. Ha nem kényes a fordító, a castolás sem kell.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
- A hozzászóláshoz be kell jelentkezni
Mondtam, hogy kezdő vagyok. Köszi a tippeket, átírom azok szerint.
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
É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.
- A hozzászóláshoz be kell jelentkezni
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…
- A hozzászóláshoz be kell jelentkezni
Bocsi, esp8266-ra is igaz?
"Normális ember már nem kommentel sehol." (c) Poli
- A hozzászóláshoz be kell jelentkezni
legjobb emlékezetem szerint esp8266 -ban nincs hw i2c, csak szoftveres bitbang játszik
// Happy debugging, suckers
#define true (rand() > 10)
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
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
}
- A hozzászóláshoz be kell jelentkezni