Skocz do zawartości

Czytak do ebooków (czyli epub to speech)


Pomocna odpowiedź

(edytowany)

A więc jestem już po przesłuchaniu jednego rozdziału z książki, efekty są całkiem zachęcające. Na razie:

Muszę dodać jakiś filterek RC między wyjściem DAC a słuchawkami (szumy kwantyzacji są niestety słyszalne). Na szczęście użyteczne pasmo jest ograniczone do 8 kHz, z prób wyszło mi 10Ω i 4.7µF. Niestety akurat mam same jakieś wielgaśne elektrolity, a nie będę jednego kondensatora zamawiać na Allegro... poczekam aż się zbierze coś więcej (a już się zbiera).

Kwestia akcentowania jednosylabowych zaimków na końcu zdania pozostaje wciąż otwarta, na razie działa warunek, że zaimek jest akcentowany gdy poprzedzająca sylaba nie  była akcentowana i poprzedzający wyraz nie jest czasownikiem, lub gdy poprzedzający czasownik jest akcentowany co najniej na trzeciej sylabie od końca. Zobaczymy w praniu, co z tego wyjdzie.

Muszę dodać możliwość zmiany przez (interfejs www) tematów (np. "do 10 Dywizji" ma być "do dziesiątej dywizji" a nie "do dziesięć dywizji", temat "wojsko" to załatwia tylko trzeba mieć możliwość zaznaczenia, że takiego trzeba użyć). Przy okazji zrobić to samo z językami (wymowa zwrotów obcojęzycznych).

Kilka drobnych błędów (w tym jeden śmieszny - program mylił tytuły przy uploadzie więcej niż jednej książki w sesji - po prostu zapomniałem o czyszczeniu tablicy "mainTitle" przed rozpoczęciem przetwarzania; a śmieszny dlatego że dosłownie zgłupiałem kiedy czytak stwierdził, że "Zaginiony symbol" napisała Marinina) udało mi się poprawić od ręki.

Natomiast bardzo zadowolony jestem z klawiatury, i dlatego dzisiaj właśnie o niej.

Układ okazał się intuicyjny. Z jednej strony łatwo wymacać odpowiedni klawisz, z drugiej zastosowanie typowych tact switchy wymaga zdecydowanego wciśnięcia, co dość skutecznie eliminuje możliwość przypadkowego wduszenia nie tego co trzeba. Czyli tu nie ma co zmieniać - może najwyżej przycisk "play" w jakiś sposób wyraźniej oznaczę (np. będzie o milimetr wyższy), ale znając mój zapał do rozbierania całego urządzenia (niestety, inaczej nie da się wymienić klawisza) będzie to gdzieś w okolicy świętego Nevera.

No, ale do rzeczy.

Założenia (tym razem techniczne) są następujące:

  • Klawiatura matrycowa 3x3, bez możliwości wciśnięcia więcej niż jednego klawisza jednocześnie[1];
  • Odczyt klawiatury w przerwaniu zegarowym, eventy klawiatury ustawiane w kolejce
  • Każdy klawisz może pracować w następujących trybach)
  1. Standard - po naciśnięciu generowany jest event CLICKED, po puszczeniu nie jest generowany żaden event.
  2. Long - po naciśnięciu nie jest generowany żaden event, po puszczeniu wcześniej niż po 500 msec generowany jest event CLICKED. Jeśli klawisz jest przytrzymany dłużej, po upływie 500 msec generowany jest event LONG i wtedy po puszczeniu niue jest generowany żaden event
  3. Repeat - po naciśnięciu generowany jest event CLICKED, generowany jest również okresowo w trakcie przytrzymania klawisza. Po puszczeniu generowany jest event RELEASED
  4. Combo - po naciśnięciu nic nie jest generowane. Po puszczeniu wcześniej niż po upływie 500 msec generowany jest event CLICKED. Po upływie 500 msec i później okresowo generowany jest event REPEAT, a po puszczeniu event RELEASED

W trybie Combo i Repeat event zawiera numer powtórzenia, począwszy od 1.
W każdej chwili można zmienić tryb pracy dowolnego klawisza.

Ponieważ to typowa klawiatura matrycowa, zacząłem od funkcji skanowania matrycy. Funkcja zwraca wartość int z ustawionym jednym bitem odpowiadającym klawiszowi, lub 0 w przypadku gdy żaden klawisz nie jest naciśnięty lub naciśnięto więcej niż jeden klawisz. Ponieważ funkcja wywoływana jest z przerwania co 10 msec, kompilator umieszcza jej kod w pamięci RAM aby nie odczytywać bez potrzeby pamięci FLASH/PSRAM (które i tak są często-gęsto odczytywane).

static uint16_t IRAM_ATTR scanKeys(void)
{
    int r, c;
    for (r=0; r<3; r++) {
        pinMode(rowPins[r],INPUT_PULLUP);
    }
    uint16_t mapa=0;
    int n=0;
    for (c=0; c<3; c++) {
        pinMode(colPins[c],OUTPUT);
        digitalWrite(colPins[c], LOW);
        for (byte r=0; r<3; r++) {
            if (!digitalRead(rowPins[r])) {
                mapa |= 1 << (3*c+r);
                n++;
            }
        }
        digitalWrite(colPins[c],HIGH);
        pinMode(colPins[c],INPUT);
    }
    // jeśli jest więcej niż jeden bit  pewnie wciśnięto dwa klawisze
    if (n>1) return 0;
    return mapa;
}

Oczywiście należało zdefiniować wcześniej kody klawiszy i kody eventów, tak że w pliku nagłówkowym mam:

// kody klawiszy
#define BKEY_PLAY 0x08
#define BKEY_LOUX 0x02
#define BKEY_SILX 0x01
#define BKEY_FAST 0x80
#define BKEY_SLOW 0x40
#define BKEY_CLOCK 0x04
#define BKEY_PREV 0x10
#define BKEY_NEXT 0x20
#define BKEY_WAI 0x100

// kody eventów
enum {
    KEYMSG_NONE = 0,
    KEYMSG_CLICK,
    KEYMSG_REPEAT,
    KEYMSG_RELEASED,
    KEYMSG_LONG,
    KEYMSG_MAX
};

// struktura eventu
extern volatile struct keyMessage {
    int key;
    int msg;
    int repc;
} keyMessage;

Jak widać, keyMessage jest zmienną globalną; pozwala to na odczyt ostatniego eventu z różnych miejsc programu.
Dodatkowo, trzy zmienne statyczne zwierają informację (maska bitowa), czy dany klawisz pracuje w tym trybie. Ponieważ procedura odczytu klawiatory jest oedczytywania w przerwaniu zegarowym, postanowiłem skorzystać (nie pierwszy raz zresztą) z biblioteki ESP32TimerInterrupt. Tak więc początek pliku z kodem do klawiatury wygląda tak:
 

#include <Arduino.h>
#include <ESP32_TimerInterrupt.h>
#include "czytak.h"

static ESP32Timer ITimer1(1); // timer do przerwania zegarowego

static int rowPins[] = {5,18,19}; // piny klawiatury
static int colPins[] = {21,22,23};

static volatile uint16_t xmask_long, // maska dla trybu Long
                        xmask_combo, // maska dla trybu Combo
                        xmask_repeat; // maska dla trybu Repeat

// makra do sprawdzenia trybu pracy klawisza

#define _isKeyCombo(m) (xmask_combo & (m))
#define _isKeyLong(m) (xmask_long & (m))
#define _isKeyRepeat(m) (xmask_repeat & (m))

volatile struct keyMessage keyMessage; // to chyba najlepsze miejsce dla tej zmiennej


portMUX_TYPE mux2 = portMUX_INITIALIZER_UNLOCKED; // mutex dla zapisu/odczytu

Jak wspomniałem, konieczne jest przyporządkowanie klawiszy do odpowiedniego trybu pracy zależnie od stanu urządzenia. Ponieważ z punktu widzenia klawiatury możliwe są cztery stany (czytanie, pauza, lista książek, serwer) mamy tu dalej odpowiednie maski dla każdego trybu:

static const uint16_t amask_long[] = {
    BKEY_WAI | BKEY_CLOCK,
    BKEY_WAI | BKEY_CLOCK | BKEY_PLAY,
    BKEY_WAI | BKEY_CLOCK | BKEY_PLAY,
    BKEY_WAI | BKEY_CLOCK};

static const uint16_t amask_combo[] = {
    BKEY_PREV | BKEY_NEXT,
    BKEY_PREV | BKEY_NEXT,
    0,
    0};

static const uint16_t amask_repeat[] = {
    BKEY_SILX | BKEY_LOUX,
    BKEY_SILX | BKEY_LOUX,
    BKEY_SILX | BKEY_LOUX,
    0};

Jako że eventy są ustawiane w kolejkę, stworzyłem bufor kołowy oraz funkcję, która wprowadza di owego bufora kolejne eventy:

volatile struct keyMessage keyboardRing[8];
volatile uint8_t keyringPos=0, keyringLen=0;

static void pushKeyMsg(int key, int message, int repeat)
{
    if (keyringLen == 8) keyringPos=(keyringPos+1) & 7;
    else keyringLen ++;
    int n = (keyringPos + keyringLen-1) & 7;
    keyboardRing[n].key = key;
    keyboardRing[n].msg = message;
    keyboardRing[n].repc = repeat;    
}

Teraz potrzebna była funkcja, zmieniająca tryby pracy klawiszy. Funkcja oprócz owej zmiany robi jeszcze jedną ważną rzecz: blokuje wszelkie eventy pochodzące od klawisza aż do jego puszczenia. Przydaje się to, gdy np. wciśnięty klawisz zmienia tryb ze Standard na inny - wtedy mimo że np. klawisz jest w trybie Repeat, nie generuje niepotrzebnych eventów.

static volatile bool ignoreRelease;
static volatile bool ignoreKeep;

// zmiana trybu pracy klawiatury, wywoływana z programu

void setKeyMaskType(int param)
{
    portENTER_CRITICAL(&mux2);
    xmask_long = amask_long[param];
    xmask_combo = amask_combo[param];
    xmask_repeat  = amask_repeat[param];
    ignoreRelease = true;
    ignoreKeep = true;
    keyringLen=0;
    portEXIT_CRITICAL(&mux2);
}    

Pozostało teraz zebrać to wszystko w całość i stworzyć funkcję, która zamieni informacje o wciśniętych klawiszach na serię eventów. Kilka dodatkowych zmiennych służy do przechowywania informacji między kolejnymi wywołaniami funkcji:
 

static uint16_t lastKey; // poprzedni odczyt
static bool keyDebounce; // czy w stanie debouncingu?
static uint32_t keyMs; // czas ostatniej operacji
static bool lastRepKey; // klawisz w trybie powtarzania
static int reptCtl; // numer powtórzenia


static void IRAM_ATTR keyboardIntI(void)
{
    int mask = scanKeys();
    uint32_t msec = millis();
    if (keyDebounce) {
        if (millis() - keyMs < 50) return; // najprostszy debouncing - odczekanie 50 msec
        keyDebounce = false;
    }
    if (!mask) { // żaden klawisz nie jest wciśnięty
        if (!lastKey) return; // jeśli wcześniej też nie był, nie mamy nic do roboty
        ignoreKeep = false;
        keyDebounce = true;
        keyMs = millis();
        if (ignoreRelease) {
            ignoreRelease = false; // nic nie robimy jeśli trzeba zignorować release
        }
        else if (_isKeyCombo(lastKey)) {
            pushKeyMsg(lastKey, lastRepKey ? KEYMSG_RELEASED: KEYMSG_CLICK, reptCtl);
        }
        else if (_isKeyLong(lastKey)) {
            pushKeyMsg(lastKey, KEYMSG_CLICK, 0);
        }
        else if (_isKeyRepeat(lastKey)) {
            pushKeyMsg(lastKey, KEYMSG_RELEASED, 0);
        }
        lastKey = 0;
        lastRepKey=0;
        return;
    }
    if (mask == lastKey) { // przytrzymany klawisz
        if (_isKeyLong(mask)) {
            if (!ignoreKeep && msec - keyMs >= 500UL) { // klawisz przytrzymany powyżej 500 msec
                ignoreRelease = true;
                ignoreKeep = true;
                pushKeyMsg(lastKey, KEYMSG_LONG, 0);
            }
        }
        else if (_isKeyRepeat(mask)) {            
            if (!ignoreKeep && msec - keyMs >= 500UL) {
                keyMs = msec-320; // ustalone doświadczalnie
                pushKeyMsg(lastKey, KEYMSG_CLICK, ++reptCtl);
            }
        }
        else if (_isKeyCombo(mask)) {
            if (!ignoreKeep && msec - keyMs >= 500UL) {
                keyMs = msec-320;
                lastRepKey = true;
                pushKeyMsg(lastKey, KEYMSG_REPEAT, ++reptCtl);
            }
        }
        return;
    }

    // wciśnięcie klawisza
    keyDebounce = true;
    keyMs = msec;
    lastRepKey = false;
    ignoreKeep = false;
    ignoreRelease = false;
    if (lastKey) { // dla combo zawsze musi być release
        if (_isKeyCombo(lastKey)) {
            pushKeyMsg(lastKey, KEYMSG_RELEASED, reptCtl);
        }
        lastKey = 0; // w sumie niepotrzebne, ale kto wie jak się funkcja rozbuduje
    }
    reptCtl = 0;
    lastKey = mask;
    if (_isKeyCombo(mask) || _isKeyLong(mask)) {
        return;
    }
    if (!_isKeyRepeat(mask)) ignoreRelease = true;
    pushKeyMsg(lastKey, KEYMSG_CLICK, 0);
}

// można to było zrobić w poprzedniej funkcji, ale lubię
// jak każdemu ENTER odpowiada dokładnie jeden EXIT

static void IRAM_ATTR keyboardInt(void)
{
    portENTER_CRITICAL_ISR(&mux2);
    keyboardIntI();
    portEXIT_CRITICAL_ISR(&mux2);
}

// no i inicjalizacja wywoływana z setup()

void initKeyboardSys(void)
{
    ITimer1.attachInterrupt(10000, keyboardInt);
}

Pozostała tylko funkcja wywoływana w głównej pętli odbierająca eventy z kolejki, jak widać, funkcja zwraca true jesli odebrano event:

bool getKeyMessage(void)
{
    bool rc;
    portENTER_CRITICAL(&mux2);
    if (!keyringLen) {
        keyMessage.key = 0;
        keyMessage.msg = 0;
        rc = false;
    }
    else {
        keyMessage = keyboardRing[keyringPos];
        keyringPos = (keyringPos+1) & 7;
        keyringLen--;
        rc=true;
    }
    portEXIT_CRITICAL(&mux2);
    return rc;
}

A przykładowy kod w pętli głównej:

void loop(void)
{
    if (getKeyMessage()) { // od tego momentu ważna jest zawartość zmiennej keyMessage
        obsluga_klawiatury();
        return;
    }
    /*
    reszta kodu
    */
}

Mam nadzieję, że kod jest na tyle prosty, że nie wymaga dalszego komentarza.
W rzeczywistości jest tego tam trochę więcej, ale opuściłem fragmenty związane stricte z czytakiem.

Być może kod stanie się inspiracją dla kogoś, kto potrzebuje podobnej funkcjonalności - o ile wiem żadna znana biblioteka takiej nie zapewnia.

I jeszcze jedno: opisałem tu pierwszą (oczywiście działającą) wersję kodu. Doczekała się ona kilku ważnych przeróbek, ale o tym następnym razem.

Stay tuned!

---

[1] Z tym "jednocześnie" to nie tak do końca - w rzeczywistości można bez problemu wykryć niektóre kombinacje - o tym jednak potem.

Edytowano przez ethanak
  • Lubię! 1
Link do komentarza
Share on other sites

52 minuty temu, ethanak napisał:

Muszę dodać jakiś filterek RC między wyjściem DAC a słuchawkami (szumy kwantyzacji są niestety słyszalne). Na szczęście użyteczne pasmo jest ograniczone do 8 kHz, z prób wyszło mi 10Ω i 4.7µF.

Myślisz, że szum kwantyzacji wynika z 13bitowych próbek, czy z samego DACa? Jakiego DACa używasz? Może też szum z zasilania - tu bramki Schmitta mogą pomóc. Kiedyś jak robiłem analizator widma na ATMega'dze (jeszcze na studiach, które ponoć nic nie uczą) to używałem aktywnych filtrów na przełączanych kondensatorach. Wtedy to była nowość i Maxim mi wysłał sample na uczelnie. Jak się tak bawisz audio możesz spróbować, może wyślą Ci tez sampla:  https://www.maximintegrated.com/en/products/parametric/search.html?fam=filt. Taki filtr piątego rzędu pomógł mi się pozbyć harmonicznych z zegara ATMega'i, które ciągle pojawiały się na widmie.

Link do komentarza
Share on other sites

41 minut temu, pmochocki napisał:

Myślisz, że szum kwantyzacji wynika z 13bitowych próbek

W tym przypadku akurat mam uczciwe 16-bitowe próbki (mój WROVER ma 16 MB Flash, czyli nie muszę oszczędzać).

42 minuty temu, pmochocki napisał:

Jakiego DACa używasz

Konkretnie taki moduł.

Podejrzewam, że tu sumują się:

  • Niska częstotliwość próbkowania (16 kHz);
  • Średnia (to taki eufemizm) jakość owych próbek (innych nie ma);
  • Bezpośrednie podłączenie słuchawek do wzmacniacza klasy D przeznaczonego do współpracy z głośnikiem i który nie ma żadnych filtrów na wyjściu;
  • Sama zasada działania algorytmu Cośtam-OLA, który robi bardzo dziwne patentowane rzeczy z próbkami (na szczęście patenty wygasły).

W związku z tym słuchawki łapią cały syf powyżej 8 kHz. Szum przeszkadza co prawda tylko wtedy, kiedy w otoczeniu jest dokładnie cicho (noc w pokoju), ale jednak przeszkadza. Nie mam zamiaru jednak bawić się w jakieś wymyślne filtry, jeśli prosty RC skutkuje, a przy okazji trochę tłumi wyższe częstotliwości z użytecznego pasma (nie muszę się bawić w cyfrowe filtry). Zresztą - mam jutro wpaść do kumpla elektronika i zajumać mu delikatnie kondensator 🙂

Przy okazji: zakłócenia od zasilania raczej nie wchodzą w grę - jeśli Mbrola odtwarza akurat pauzę, to w słuchawkach jest cisza.

 

  • Lubię! 1
Link do komentarza
Share on other sites

1 godzinę temu, ethanak napisał:

Konkretnie taki moduł.

Też niedawno kupiłem, ale nie znalazłem jeszcze czasu na zabawę.

1 godzinę temu, ethanak napisał:

Nie mam zamiaru jednak bawić się w jakieś wymyślne filtry, jeśli prosty RC skutkuje, a przy okazji trochę tłumi wyższe częstotliwości z użytecznego pasma (nie muszę się bawić w cyfrowe filtry). Zresztą - mam jutro wpaść do kumpla elektronika i zajumać mu delikatnie kondensator 🙂

Oczywiście jak nie ma potrzeby to nie ma co komplikować. Daj znać jak zadziała ten filtr pierwszego rzędu. Trzymam kciuki.

 

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

12 godzin temu, pmochocki napisał:

Też niedawno kupiłem, ale nie znalazłem jeszcze czasu na zabawę.

Na razie nie stosowałem do niczego więcej poza syntezą mowy (RPi Zero W, ESP32), ale tu się sprawdza znakomicie.

A tak w ogóle to dobrze, że postanowiłem zrobić te tematy i języki - okazało się, że przygotowując dane omyłkowo wywaliłem temat wojsko zamiast (nieużywanego) scifi - gdybym tego nie zauważył długo bym się pewnie zastanawiał, dlaczego tej nieszczęsnej 10 Dywizji nie chce odmieniać 🙂

Link do komentarza
Share on other sites

Dnia 18.10.2021 o 21:37, pmochocki napisał:

Daj znać jak zadziała ten filtr pierwszego rzędu.

Dopiero dzisiaj mogłem to sprawdzić - na razie mam nie taki kondensator jak chciałem ale to i tak prowizorka, a ładnie tnie jakieś 90% artefaktów.

  • Lubię! 1
Link do komentarza
Share on other sites

Ech... dalej problemy z akcentowaniem "się" na końcu frazy.

Na razie wymyślony naprędce algorytm: jednosylabowy zaimek osobowy i "się" na końcu frazy - akcentowany zaimek i nieakcentowane "się".

Przy okazji znalazłem kolejnego babola w translacji fonetycznej - "niezidentyfikowany" ma być wymawiane przez "z" a nie "ź"...

Słucham sobie dalej...

 

  • Lubię! 1
Link do komentarza
Share on other sites

Usunąłem warunek końca frazy z tym nieszczęsnym "się" - na razie jest nieźle.

Teraz czekam na dostawę elementów, trzeba będzie tylko sprawdzić ten kondensator (zamówiłem 4.7 i 10 uF, zobaczymy co z tego wyjdzie), no i projekt docelowej płytki...

Najgorsze jest to, że DS3231 luzem kosztuje 50PLN w tme, a moduł z eepromem i koszykiem na baterię 15 PLN na alledrogo... chyba trzeba sobie przypomnieć do czego służy hotair 🙂

 

  • Lubię! 1
Link do komentarza
Share on other sites

4 godziny temu, ethanak napisał:

Najgorsze jest to, że DS3231 luzem kosztuje 50PLN w tme, a moduł z eepromem i koszykiem na baterię 15 PLN na alledrogo... chyba trzeba sobie przypomnieć do czego służy hotair 🙂

Aż sprawdziłem, bo nie mogłem uwierzyć, że tyle sobie liczą...

MCP7940N za 5zł?
https://pl.farnell.com/microchip/mcp7940n-i-ms/real-time-clock-calendar-64b-i2c/dp/2774923
Krótkie podsumowanie:

  • podtrzymanie bateryjne z poborem prądu około 1uA
  • alarm
  • I2C
  • dzień/miesiąc/rok hh:mm:ss
  • zasilanie 1.8-5.5V
  • mały - 8 nóżek
  • minus - potrzebny zewnętrzny kwarc

Hotair też jest opcją - chciałem tylko pokazać alternatywę...

EDIT: Dopiero sprawdziłem, że DS3231 ma dokładność 3ppm - więc Hotair to nie jest zła opcja 🙂

Edytowano przez pmochocki
Link do komentarza
Share on other sites

Hej.

Trochę posłuchałem, wynotowałem poprawki i przy okazji poprawiania poprawek postanowiłem zrobić trochę porządku w kodzie.

Na początek połączenie z WiFi (ile można trzymać w kodzie hasło do domowego WiFi?).

Przygotowaną strukturę (oraz miejsce w EEPROM-ie) miałem już wcześniej, teraz trzeba było tylko zaimplementować funkcję połączenia. A więc mamy coś takiego (wypełniane z serwera WWW, ale to trywialne):

#define CRED_FLAG_DHCP 1
#define CRED_FLAG_ACTIVE 2

extern struct WiFiCredentials {
    uint32_t addr;
    uint32_t gateway;
    uint32_t netmask;
    uint32_t nameserver;
    uint8_t flags;
    char name[15];
    char ssid[32];
    char pass[32];
} WiFiCred[2];

// funkcja odczytu z EEPROM-u
extern void getCredentials(void);

I funkcja realizująca połączenie:

#include <WiFi.h>
#include <ESPmDNS.h>
//#include <DNSServer.h>

//DNSServer dnsServer;

extern bool pleaseSetClock;

void initWiFi(bool apmode)
{
    if (!apmode) {
        int n;
        getCredentials();
        for (n=0; n<2; n++) if (WiFiCred[n].flags & CRED_FLAG_ACTIVE) {
            // sayfu - taki printf z wyjściem głosowym, działający w UTF-8
            // czyli dodatkowo konwertuje wynikowy string ze ssprintfa na ISO-2
            sayfu("łączę z siecią %s", WiFiCred[n].name);
            WiFi.mode(WIFI_STA);
            WiFi.begin(WiFiCred[n].ssid, WiFiCred[n].pass);
            if (!(WiFiCred[n].flags & CRED_FLAG_DHCP)) WiFi.config(
                WiFiCred[n].addr,
                WiFiCred[n].gateway,
                WiFiCred[n].netmask,
                WiFiCred[n].nameserver);
            Serial.println("\nConnecting");
            int i;
            for (i=0; i<20 && WiFi.status() != WL_CONNECTED;i++) {
                Serial.print(".");
                delay(500);
            }
            Serial.println();
            if (WiFi.status() == WL_CONNECTED) {
                Serial.println(WiFi.localIP());
                String s = WiFi.localIP().toString();
                //sayf - też taki printf, ale w ISO-2
                sayf("Adres IP serwera: %s", s.c_str());
                //MDNS.begin("czytak");
                pleaseSetClock=true;
                break;
            }
        }
    }
    if (apmode || WiFi.status() != WL_CONNECTED) {
        // say działa w ISO-2. Pisownia fonetyczna z różnych przyczyn
        if (apmode) say("Uruchamiam tryb Akces Pojnta");
        else say("Po\263\261czenie nieudane, uruchamiam Akces Pojnta");
        WiFi.mode(WIFI_AP);
        WiFi.softAP("czytak","mojetajnehaslo");
  //      dnsServer.start(53,"*",WiFi.softAPIP());
        printf("AP started\n");
    }
    // dodane po stwierdzeniu że nie działa:
    MDNS.begin("czytak");
}

Jak widać chciałem dobrze: jeśli uda się połączyć z siecią ma wystartować mDNS (znajduję sobie adres przez avahi, bo komórką z domu raczej się nie łączę), w trybie AP ma ruszyć dnsServer (właśnie ze względu na Androida). Tyle że na chceniu się skończyło - włączenie kodu dnsServera nawet bez uruchamiania kończyło się tym, że mDNS miał mnie gdzieś.

Może ktoś coś na ten temat?

I jeszcze ciekawostka.

Otóż oryginalne próbki dla Mbroli są w 16 kHz. Mbrola robi z tego jakiś misz-masz patentowanymi algorytmami, wychodzi z tego dźwięk w 16 kHz. Robię prosty upsampling (średnia arytmetyczna) na 32 kHz i wrzuczm to w DAC-a. Dźwięk jest niezły, ale czasami gdy tego mbrolowego miszmaszu jest za dużo słychać jakieś delikatne brzęknięcie (operuję na floatach a nie double z różnych przyczyn).

Postanowiłem to na próbę zmienić: próbki w czasie ładowania są od razu resamplowane na 32 kHz, Mbrola pracuje wewnętrznie na 32 kHz i wynik wrzucam bezpośrednio w DAC-a. Efekt? Mocno słyszalny szum w tle...

Ciekawe dlaczego...

 

  • Lubię! 1
Link do komentarza
Share on other sites

(edytowany)

Cóż - na razie prace stoją, program działa, a nie mam pomysłu na płytkę (jak nie próbuję tak trochę się nie mieści w obudowie). Na razie więc działam na prowizorce, a na wszelki wypadek załączam kompletny kod wraz z danymi.Czytak.zip

Gdzieś jeszcze jest jakiś błąd w CSS, ale na razie mam co innego na głowie 😞

Nie dołączam schematu, bo tam praktycznie nic nie ma ciekawego, a wszystko można wywnioskować z plików nagłówkowych.

 

Edytowano przez ethanak
  • Lubię! 1
Link do komentarza
Share on other sites

Dopiero teraz wylazł jeszcze jeden błąd. Gdzieś w programie do tworzenia binarnych danych w jednym z plików zginęła sobie jedna linijka. Efejt: program zatrzymał się w połowie dwudziestego rozdziału "Atlasu" Chaneya i nie dał się ruszyć... okazało się, że nie wie co zrobić ze znakiem %.

Na szczęście wyjście proste, drobna poprawka w nylena_data.h:

const char *spell_chars="\134\327\367\244`_~^#|\247\260<>/&*=+$&@%";
const char *const spell_values[23]={
   "bekslesz","razy","dzielone","waluta","gravis","podkre\266l","tylda","daszek",
   "hasz","pipa","paragraf","stopie\361","mniejsze","wi\352ksze","przez","and",
   "gwiazdka","r\363wne","plus","dolar","end","ma\263pa","procent"};

Ciekawe, ile jeszcze takich wylezie...

  • Lubię! 2
Link do komentarza
Share on other sites

(edytowany)

...i jeden wylazł. A właściwie to nie wylazł, ale się przypomniał.

Otóż przy wczytywaniu książki co prawda ładnie anonsował "Wczytuję: cośtam cośtam", ale nmie uwzględniał słowników i przełączników wyrazów obcojęzycznych. Efekt: nazwisko autora Chaney (tylko w tym miejscu) było wymawiane jako "chanej" zamiast prawidłowego "czejny".  Bliższe przyjrzenie się pozwoliło stwierdzić, że udało mi się w dwóch linijkach wsadzić dwa błędy 🙂

  1. najpierw była wywoływana funkcja anonsowania wczytywanego tytułu, a potem wczytywany był słownik. To raczej nie miało prawa działać.
  2. a nawet po poprawieniu babola z punktu 1 i tak by nie działało, bo zamiast funkcji uwzględniającej słowniki użyłem funkcji, która ich nie uwzględniała.

czyli fragment funkcji openBook zamiast:

    sayf("Wczytuj\352: %s", currentBookMap->title); // do komunikatów technicznych, nie używa słowników
    currentBookDic = openBookDic(namebuf);

powinien wyglądać tak:

    currentBookDic = openBookDic(namebuf);
    sayfl("Wczytuj\352: %s", currentBookMap->title); // używa słowników i przełączników wymowy 

Przy okazji dodałem jednostkę Nm (fonetycznie niutonometr) - co prawda nigdy się z nią w literaturze nie spotkałem, ale z procentem też pierwszy raz 🙂

Powoli trzeba będzie kończyć te poprawki... ale może jeszcze coś wylezie 😞

Aha, zaraz ktoś spyta "skąd ten błąd ze słownikami, jakieś niedbalstwo?" - uprzedzając pytanie wyjaśniam, że gdy pisałem kod funkcji openBook funcja sayfl po prostu jeszcze nie istniała, a potem jakoś tak się zapomniało...

Edytowano przez ethanak
  • Lubię! 2
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.