Programová záhada

Raspberry, Arduino, Mini-PC a další

Moderátor: Moderátoři

Zpráva
Autor
Uživatelský avatar
Celeron
Příspěvky: 16140
Registrován: 02 dub 2011, 02:00
Bydliště: Nový Bydžov

Programová záhada

#1 Příspěvek od Celeron »

Mám tady řídící prográmek na Arduino Pro mini na řízení generátoru Si5351. Zobrazení na OLED 1,3" I₂C, knihovna pro řadič SH1106G.
V Setup mám skok do podprogramu void statup_text(), kterej zobrazí úvodní kecy:

Kód: Vybrat vše

void statup_text() {                        // úvodní výpis
  display.setTextSize(1);
  display.setCursor(4, 5);
//  display.print("Si5351");
//  display.setCursor(4, 20);
  display.print("VFO / RF Gen");
// display.setCursor(4, 35);
//  display.print("10kHz - 225MHz");
//  display.setCursor(4, 50);
//  display.print("V 14");
  display.display();
  delay(3000);
  display.clearDisplay();

  display.setCursor(4, 5);                  // výpis vstupů
  display.print("Out:");
  display.setCursor(4, 20);
  display.print("CLK0-Adj");
  display.setCursor(4, 35);
  display.print("CLK1-10MHz");
  display.setCursor(4, 50);
  display.print("CLK2-100kHz");
  display.display();
  delay(3000);
  display.clearDisplay();
}
Výpisová prkotina, takhle je to funkční. Ale pokud aktivuju komentovaný řádky, tak se zobrazí "úvodní výpis", pak se počká 3 sekundy, smaže displej, vypíše "výpis vstupů" ale už se neprovede výmaz displeje na konci a zůstane to zakouslý nejspíš v delay(3000).
Když zruším ve Void Setup() skok do Void Startup_text(), tak řízení generátoru za tímhle Startup_textem vše maká jak má.
Ovšem nyní začíná záhada. Pokud jsou odkomentované řádky viz výše, tak se to nekouše. Stačí ale abych jeden výpis přidal a už se to zase kouše. No a nejzajímavější je, že stačí do kódu nahoře přidat do textu pár znaků navíc a zase se to kouše.
Měl jsem podezření, jestli není chyba na nějaký adrese flešky v 328P ale jiná deska to dělá taky. Po kompilaci je ještě 6KB ve flešce volno a proměnný je ještě 1300 byte volno.
Zkusil jsem obsah Void Startup_text zkopírovat do Void Setup() aby se vyloučil problém se zásobníkem a vůbec žádna změna.
Netuším, v čem může být problém. Připadá mi, že pokud se má vypsat víc, než určitej počet znaků, tak se to hryže. Nějaká chyba v knihovně? Divný je, že grafickej test displeje s touhle knihovnou jede bez problémů mimo jiný plnej displej ASCII znaků.
Jirka

Proč mi nemůže všechno chodit hned ?!!

Uživatelský avatar
Cust
Příspěvky: 5553
Registrován: 17 led 2007, 01:00
Bydliště: Husinec-Řež

#2 Příspěvek od Cust »

Mrknul bych se jestli pro tohle nemají knihovny seeeduino. Někde jsem slyšel, že na nejaké OLED měli lepší knihovny...

Uživatelský avatar
Celeron
Příspěvky: 16140
Registrován: 02 dub 2011, 02:00
Bydliště: Nový Bydžov

#3 Příspěvek od Celeron »

Jo, o knihovně u8glib od Oli Krause vím, je tam definice pro tenhle řadič:
U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_NONE); // I2C / TWI
Ale je to monstrum 13KB, co zabere půlku flešky 328P a navíc proti Adafruit OLED knihovnám dost šílený ovládání. Ale v nejhorším zkusím. Díky!
Jirka

Proč mi nemůže všechno chodit hned ?!!

Uživatelský avatar
ondraN
Příspěvky: 209
Registrován: 16 srp 2022, 02:00
Bydliště: Roztoky

#4 Příspěvek od ondraN »

Já bych řekl, že to je problém s přetečením stacku nebo heapu (podle toho, jak knihovna alokuje paměť). Přesun z jedné funkce do jiné nemá skoro žádný vliv. Protože je práce s řadičem dost pomalá, asi by mohlo pomoci, vložit mezi výpisy menší delay, aby se stihl přesun zchroustaných dat do řadiče. Taky by mohlo pomoci použít ve výpisech makro "F", aby se řetězce ukládaly do flešky a nezabíraly míso v RAMce. Jinak bez možnosti debugování se tenhle problém bude hledat dost blbě.

Uživatelský avatar
Valdano
Příspěvky: 695
Registrován: 01 led 2023, 01:00
Bydliště: Česká Lípa

#5 Příspěvek od Valdano »

ondraN píše:Já bych řekl, že to je problém s přetečením stacku nebo heapu...
Jsem stejného názoru.

Uživatelský avatar
Celeron
Příspěvky: 16140
Registrován: 02 dub 2011, 02:00
Bydliště: Nový Bydžov

#6 Příspěvek od Celeron »

Takže se asi zbouchne proti sobě stack, co ukládá směrem dolů a heap, co ukládá směrem nahoru? Nebo datovka vleze do heap?
Jde nějak nastavit velikosti stack a heap?
Koukal jsem na net, co s tím. Vyházet globální definice a nahradit je lokálníma, kouknout jestli int nepůjde předefinovat na char.
Potom jsem ještě četl něco o Static a Dynamic json.Buffer ale vůbec netuším, vo co go.
Jirka

Proč mi nemůže všechno chodit hned ?!!

Uživatelský avatar
ondraN
Příspěvky: 209
Registrován: 16 srp 2022, 02:00
Bydliště: Roztoky

#7 Příspěvek od ondraN »

Můžeš si to představit tak, že dolní oblast RAM je alokována pro globální proměnné a její velikost se určí v okamžiku překladu a je neměnná. Nad ní začíná heap, ze kterého se přiděluje RAM dynamicky za chodu ,třeba funkcí malloc, new, a přiděluje se taky proměnným v nově vytvářených objektech. Stack začíná úplně nahoře a jede směrem dolů. Přes něj se předávají data při volání funkcí a vytvářejí se jejich lokální proměnné. Taky na rozdíl od heapu, má stack hardware registr v MCU a mohou s ním manipulovat přímo instrukce assembleru, protože se tam strkají i různé data při přerušeních. Správa heapu bývá většinou v kompetenci OS, nebo u MCU ji řeší překladač.
Definicí static řekneš kompiléru, že ta proměnná bude alokována permanentně v dolní cásti paměti. To se hodí třeba u funkcí, kde je třeba znát stav z minulého volání, což přes hodnoty na zásobníku není možné. Globální proměnné jsou všechny static.
Tvůj problém asi bude v nedostatku paměti MCU. S knihovnou nic neuděláš, takže musíš skrouhnout svoje nároky. Použitm makra F v printech, donutíš kompilátor, aby ty textové řetězce uložil do flash paměti, místo do RAM a tak získáš nějakou RAM (výměnnou za výkon). Pokud je práce s řadičem přes I2C pomalá, můžeš vložením delay nechat knihovnu ať zpracuje aktuální data a neprodlužuje dynamické buffery příjmem dalších řetězců, atd...

Uživatelský avatar
Valdano
Příspěvky: 695
Registrován: 01 led 2023, 01:00
Bydliště: Česká Lípa

#8 Příspěvek od Valdano »

Nejprve vyzkoušejte co zde navrhoval ondraN tj. řetězce ukládat do flešky a ne do RAMky.

Takže například namísto
display.print("Si5351");
použijte
display.print(F("Si5351"));

a stejně tak u dalších volání display.print s různými řetězci textu.

Arduino Pro mini, které zmiňujete má 2kB RAM (tj. pouze 2048 bajtů), a to není mnoho.

Vývojové prostředí Arduino IDE vypisuje využití rozsahu globálních alokací paměti při překladu projektu.

Pokud je to blízko hranice těch 2048 bajtů pak máte v projektu příliš globálních alokací, které bývají v části na počátku zdrojáku, kde bývají deklarovány různé proměnné, pole atd. a z části to může být ukryto i v knihovnách zejména pokud se používají různé objekty jako třeba pro řízení displeje apod.

S knihovnami asi nim moc nenaděláte jak zmínil ondraN leda, že byste použil třeba pro řízení toho displeje nějakou jinou variantu knihovny, která by byla méně náročná na paměť (pokud pro daný účel existuje).

Pokud máte v programu deklarovány nějaké globální pole, které byste mohl zkrátit tak je udělejte pokud možno kratší.

Jakou spotřebu paměti vám při překladu toho projektu vypisuje překladač?

Příklad výpisu viz tento odkaz a různé možnosti optimalizace využití paměti viz tento odkaz.

Uživatelský avatar
Celeron
Příspěvky: 16140
Registrován: 02 dub 2011, 02:00
Bydliště: Nový Bydžov

#9 Příspěvek od Celeron »

Valdano píše: Jakou spotřebu paměti vám při překladu toho projektu vypisuje překladač?
Píšu to hned v úvodním příspěvku, proměnný mají 1300 byte volných. Pole nejsou použitý, stringy taky ne. Právě proto je mi divný, co by mělo zapatlat RAMku. Navíc tyhle výpisy jsou hned na začátku programu ve Void setup(). Přerušení pro n-codér se spouští až po tomhle výpisu. Tady je začátek toho mýho projektu:

Kód: Vybrat vše

//******************************
#include <Wire.h>                
#include <Rotary.h>             
#include <si5351.h>               
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>  
#include <EEPROM.h>
//----------------------------------------------------
#define IF  0                            //IF frekvence, např.: 455 = 455 kHz
                                              // + přidá a - odečte IF offset.
#define FREQ_INIT  10000000  //počáteční frekvence při spuštění
#define XT_CAL_F   145350     //Kalibrační faktor Si5351
#define tunestep   A0              //pin použitý tlačítkem kodéru.
#define disPin        A1             //pin na zhasínání displeje
//---------------------------------------------------------------------
Rotary r = Rotary(2, 3);
Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire, -1); 

Si5351 si5351;

unsigned long freq;                         // 4
unsigned long freqold, fstep;           //  4 + 4
long interfreq = IF;                         //  4
long cal = XT_CAL_F;                     //  4
byte encoder = 1;                          //  1
byte stp, n = 1;                             //   1 + 1 
byte value;                                    //   1
unsigned long lastActivityTime = 0;  //  4
int stpAdr = 10;                            //  2
int freqAdr =  20;                          //  2
//------------------------------------------------
void setup() {
  Wire.begin();
  display.begin(0x3C,true);                      // nastavení OLED displeje
  display.clearDisplay();                           // vymazání displeje
  display.setTextColor(SH110X_WHITE);  // nastav bílou
  display.display();                                 // vypiš

  pinMode(2, INPUT_PULLUP);                     // nastavení vstupních pinů
  pinMode(3, INPUT_PULLUP);
  pinMode(tunestep, INPUT_PULLUP);
  pinMode(disPin, INPUT_PULLUP); 

  statup_text();                          // v něm se to hryže
  
 
Takže globální proměnný zabírají nějakých 32 byte. Určitě to bude víc, ale víc jich jinde definováno opravdu není.
Zkusil jsem přestěhovat texty v startup_text do flešky a je to naprosto stejný. Jakmile je poslaných víc znaků na displej, hryzne se to. Pokud před texty dám string, tak se to zase chová stejně. Stačí ubrat stejnej počet znaků a maká, pár jich přidám a zase se žvejkne.
Podle mě je problém v knihovně.
Jirka

Proč mi nemůže všechno chodit hned ?!!

Uživatelský avatar
Mahoney
Příspěvky: 347
Registrován: 26 říj 2019, 02:00

#10 Příspěvek od Mahoney »

Asi přetečení proměnný, a chyba v knihovně být klidně může.

Jinak je pěkný že se ti to hryže ve volání

Kód: Vybrat vše

statup_text();
, ale kde se máme dočíst, co tam je? (a nemá to být "startup_text();" ?)

Uživatelský avatar
Celeron
Příspěvky: 16140
Registrován: 02 dub 2011, 02:00
Bydliště: Nový Bydžov

#11 Příspěvek od Celeron »

Mahoney píše: Jinak je pěkný že se ti to hryže ve volání

Kód: Vybrat vše

statup_text();
, ale kde se máme dočíst, co tam je? (a nemá to být "startup_text();" ?)
Je vypsán jako code v prvním příspěvku.
Jirka

Proč mi nemůže všechno chodit hned ?!!

Uživatelský avatar
Valdano
Příspěvky: 695
Registrován: 01 led 2023, 01:00
Bydliště: Česká Lípa

#12 Příspěvek od Valdano »

Chyba může být v knihovně Adafruit SH110X. Když to někam hrábne tak to pak může a nemusí padat v závislosti na tom co a kde to při tom hrábnutí přepsalo. Někdy se může chyba zdánlivě ukrýt tím, že se změní zdroják a po překladu se to v paměti trochu jinak uspořádá a chyba se přestane projevovat což neznamená, že by tam nebyla dál, ale hrabe někam do místa kde to prostě shodou okolností nevadí a tak se to neprojevuje. Takové chyby se velmi špatně hledají. Seznam různých verzí zmíněné knihovny v čase je viz tento odkaz. Pokud máte starší verzi té knihovny tak zkuste použít tu nejnovější verzi 2.1.8. Jestli to bude dělat i s ní.

Uživatelský avatar
Mahoney
Příspěvky: 347
Registrován: 26 říj 2019, 02:00

#13 Příspěvek od Mahoney »

Celeron píše:Je vypsán jako code v prvním příspěvku.
Aha tak to se omlouvám. Já to sice četl, ale přehlédl jsem že to je zrovna ono.

Jinak mám ty Adafruit knihovny otevřené ve vedlejších tabech, byl jsem odhodlán že se na to mrknu, ale Valdano má pravdu, jakou verzi té knihovny jsi použil? Resp můžeš zkust novější a dát vědět… (a ještě můžeš zkusit proházet mezi sebou ty include na začátku programu, i když by to teoreticky mělo být jedno, někdy to může zabrat… pokud by to přestalo, pořád by to odpovídalo tomu co napsal Valdano - "chyba sice je, ale hrábne někam kde to nevadí", a pořád se bude blbě hledat :) )

Uživatelský avatar
Celeron
Příspěvky: 16140
Registrován: 02 dub 2011, 02:00
Bydliště: Nový Bydžov

#14 Příspěvek od Celeron »

Koukal jsem do manažéru knihoven a mám poslední 2.1.18. GFX, která s displejem taky souvisí mám poslední, 1.11.5. Takže buď zkusit starší nebo nevím...
Ale je to zajímavější. Vyměnil jsem ten 1,3" OLED za 0,96" s SSD1306. Má to knihovnu SSD1306 a stejnou GFX. No a co by jste řekli? Je to stejný!!! Tak buď je bordel v GFX a nebo je v těch knihovnách stejná chyba. Mají stejný příkazy, nejspíš stejnej autor a obojí Adafruit.
Ještě to zkusím přepsat na U8GLIB ale to je na delší dobu, jinak se s ní pracuje.
Když to bude neřešitelný, tak se na ten úvodní výpis prostě vykašlu. S další částí zobrazení v obsluze VF generu Si5351 žádnej problém není. A to tam je i grafika, několik velikostí fontu a pod. Ale je to opravdu divný, taková celkem jednoduchá věc jako blbej výpis na displej jedním typem fontu.
Ještě mě napadlo vyházet z obsluhy toho SI5351 některý části, co nejsou vyloženě prioritně potřeba , třeba ukládání do EEprom, zobrazování grafiky, dát kmitočtovej krok natvrdo a zkrátit tak o pár KB program. Jestli se něco změní na těch výpisech.
Jirka

Proč mi nemůže všechno chodit hned ?!!

Uživatelský avatar
Valdano
Příspěvky: 695
Registrován: 01 led 2023, 01:00
Bydliště: Česká Lípa

#15 Příspěvek od Valdano »

Celeron píše:Píšu to hned v úvodním příspěvku, proměnný mají 1300 byte volných.
Problém je v tom, že překladač vám vypíše jen spotřebu paměti globálních pevně deklarovaných proměnných, ale pokud knihovna alokuje paměť z haldy dynamicky tak to se samozřejmě děje až za běhu programu a tuto spotřebu vám překladač při překladu nezobrazí.

Díval jsem se do základní knihovny Adafruit_GFX, a ta si alokuje pro celou kreslící plochu paměťový bafr (tj. pole) dynamicky funkcí malloc v konstruktoru tj. ještě před voláním těch funkcí pro výpisy textu

Kód: Vybrat vše

velikost v bajtech = ((sirka + 7) / 8) * vyska;
Takže pokud máte pro ten displej deklarovánu šířku 128 a výšku 64 pixelů, tak se funkcí malloc alokuje dynamicky datový bafr o velikosti

Kód: Vybrat vše

1024 = ((128 + 7) / 8) * 64;
Když odečteme ten překladačem oznámený zbytek 1300 - 1024 tak najednou zbývá už jen 276 bajtů, a pokud jsou tam ještě nějaké další dynamické alokace třeba v knihovně Wire tak může paměť na haldě lehce dojít a pak se nelze divit tomu, že se někam hrábne a program jak se říká "lehne" a nebo se začne chovat nevyspytatelně. :wink:

Pro tyto knihovny by to zkrátka asi chtělo verzi Arduina, která má k dispozici víc paměti RAM tj. zvážit použit namísto Arduino Pro Mini, které má jen 2kB RAM třeba nové Arduino Nano Every, které má 6kB RAM a navíc ještě výkonnější procesor. Pokud máte doma Arduino Mega 2560 tak to zkuste s ním jen na vyzkoušení. Pokud je ten problém způsoben tím, že díky dynamickým alokacím dochází paměť haldy při velikost RAM jen 2kB tak při velikosti 8kB, které má verze Mega 2560 by se ty problémy neměly projevovat vůbec.

Odpovědět

Zpět na „Miniaturní počítače (Arduino, Raspberry a další)“