Als ik in mijn c-programma voor een avr een array een dynamische index geef:

int index = 0;

command = Testprotocol[index] 

dan krijgt command niet de juiste waarde.

Maar als ik nu handmatig index de waarde 0 geef:

command = Testprotocol[0] 

dan krijg ik wel de juiste waarde.

Hoe kan het dat het bij de eerste method mis gaat?

Testprotocol is een array in het progmem:

const uint16_t Testprotocol[] PROGMEM = {

TC(Test_ID, VERSION_1_0),  // alles gedefinieerd in het progmem 
TC(TESTNAME, testroutine),
&SUB_A

};

progmem variabelen moet je lezen met pgm_read_xxx macros:

http://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html

Dat command = Testprotocol[0] wel werkt lijkt me een optimalisatie van de compiler vermits progmem variabelen altijd read-only en dus constant zijn.

Precies wat pimpelmees zegt.

Als je een variabele als index gebruikt, is er niks te optimizen. Je leest hoe dan ook uit RAM terwijl je data in flash staat. Als er optimized kan worden (en dat kan, je array staat in flash en is daarmee min of meer read-only), trekt die optimizer per ongeluk je programmeermisser recht.

Zet je optimization uit, en je hebt een aardige kans dat beide methoden niet werken. Tenzij er toevallig in RAM op dat address staat wat ook in flash staat...

De AVR is een zogenaamde harvard architectuur: Er zijn twee geheugenlocaties met "adres 0", maar dus ook met adres "Testprotocol". De ene is "datageheugen" de andere "programmageheugen". Bij de AVR is er alleen "flash" bij het programmageheugen, maar striktgenomen hoeft dat niet. (De ESP8266 bijvoorbeeld heeft stukken bedoeld voor programmageheugen, die gewoon RAM zijn).

Ik gebruik inderdaad:

commando = pgm_read_word_near (testprotocol [index])

Als ik dit in m'n mail programma gebruik werkt het wel, maar als ik dit in een functie uitvoor dan werkt het niet. bijvoorbeeld: waarde = read (index);


Uint16_t read (int index)
{  uint16_t commando;
   commando = pgm_read_word_near (testprotocol [index]);

Return (commando);
}

Hoe kan ik dit wel op de juiste manier uitvoeren?

Je hebt als parameter indx. Je leest vanaf index. Blijkbaar is dat een global. Die vast wel een bepaalde waarde heeft.

Maar eh... dat is toch redelijk basic C??


commando = pgm_read_word_near (testprotocol [index])

Werkt inderdaad niet.

pgm_read_word_near verwacht een adress als parameter. Hier krijgt hij echter de waarde uit de tabel.

Om het adress van die positie door te geven moet er een & tekentje bij:


commando = pgm_read_word_near (&testprotocol [index]);

Waarop Kees me er nog even fijntjes op attendeert dat dat ampersantje ontbreekt - ook basic C :+

Tja, inderdaad standaard C.
Maar is ook gemakkelijk overheen te lezen.
Puntcomma op het eind stond er ook niet.

Tsja, een tijdje terug ook aan het klooien geweest met een tabel van structures in het prog-mem. Er zaten ook nog functie pointers in om het even nog "makkelijker" te maken.

Eigenlijk bedroevend dat de compiler dit niet voor je oplost, gaat nergens over. :'(
Je hebt je array/structures al als progmem gedeclareerd en dan nog moet je zelf de kolen uit het vuur halen. Dit kan zo in het topic met design blunders.

Ik heb nu 2 arrays in het progmem gezet:

De eerste declareert 3 waardes die je d.m.v. een functie in kunt voeren

naam en waardes geven aan de array variablen:

IDENT(TEST, 6, 7); 
IDENT(TEST2, 8,9);

De tweede array zet het address van de id_name in een array:

const uint16_t Testprotocol[] PROGMEM = {

&TEST,
&Test2

};

Nu wil ik op deze manier het id_number opvragen van TEST2, welke de waarde 8 heft:

uint16_t k;
	uint16_t i;

	k= pgm_read_word_near(&Testprotocol[1]);
	
	i = pgm_read_word_near(k+2);

	
	
	
	
	return(i);

Is dit de juiste methode? Ik krijg nu wel de juiste waardes eruit

[Bericht gewijzigd door Huub12345 op (12%)]

Op 14 november 2015 23:05:21 schreef henri62:
Eigenlijk bedroevend dat de compiler dit niet voor je oplost, gaat nergens over. :'(
Je hebt je array/structures al als progmem gedeclareerd en dan nog moet je zelf de kolen uit het vuur halen. Dit kan zo in het topic met design blunders.

De compiler kan het niet voor je oplossen, want de taal zit nu eenmaal zo in elkaar. Als de compiler dit zou oplossen zou het geen C compiler zijn.

Dat het verrekte onhandig is, ja, dat ben ik absoluut met je eens.

De compiler LOST het voor je op. Alle strings en constanten worden gewoon naar RAM gecopieerd bij het opstarten van de CPU.

't is wat inefficient.... 't is bijvoorbeeld inderdaad vervelend dat als je veel "strings" te printen hebt, je RAM best wel snel vol zit.

Alleen als je aangeeft dat je weet waar je mee bezig bent en kreten als "PROGMEM" gaat gebruiken, moet je zelf ... ehh... weten waar je mee bezig bent.

Als je opschrijft:

char* replies[] PROGMEM = {
"Hallo ",
"Wereld",
"Goed  ",
"Fout  "};

Dan wil je (waarschijnlijk) dat die strings in je flash komen, en niet alleen de pointers zelf.

En dat lost de compiler niet voor je op.

Uitleg

Als jij

char* replies[] = {
"Hallo ",
"Wereld",
"Goed  ",
"Fout  "};

opschrijft werkt alles gewoon. Jij kan iedere array element benaderen volgens de standaard C-regels.

Iemand had kommentaar op dat het lastig was en dat de compiler dit soort problemen zou moeten oplossen. Mijn reactie is: Als je niets doet, werkt de standaard-C stuff gewoon. En als je met PROGMEM gaat werken, tja, dan moet je weten wat je doet. Dus niet klagen als je het verkeerd doet.

De compiler kan het niet voor je oplossen, want de taal zit nu eenmaal zo in elkaar. Als de compiler dit zou oplossen zou het geen C compiler zijn.

Je en nee :-) Enerzijds weet de compiler wat in RAM zit en wat in flash zit, dus zou gepaste code kunnen genereren. Anderzijds bestaat in avr een "near" en "far" adresmodel voor flash geheugen, mag en/of kan de compiler zelf kiezen ?

Mijn persoonlijke mening is: Uiteindelijk maakt het allemaal niet zoveel uit. Belangrijk is dat je weet welke code en data de compiler genereert en dat je dit kan bijsturen indien nodig; met gcc lukt dat helemaal vrij goed.

Ik ben het met een aantal stellingen hier niet eens. Als je een variable in het PROGMEM zit weet de compiler dat die de data in het const(initialized) data segment moet plaatsen.
De linker zal het const data segment in de flash zetten.

Als je niets doet zal de data (strings) in het flash gezet worden maar copieerd de c-startup code alle (initialized) data naar RAM, net voordat je main aangeroepen wordt.
Dan werkt het inderdaad "gewoon" zoals 'rew' zegt alleen ga je HEEL snel door je ram heen bij wat kleinere devices en past de code dan niet meer.

Wat een compiler wel kan doen is code genereren met je juiste assembly instructies (indirect reads uit het progmem) als de code daarvan een reference doet in het progmem. Je moet dat wel de array's en data correct definieren met #pragma's op PROGMEM etc prefixes.
Dat is wel degelijk oplosbaar in de compiler.

Helaas is de avr compiler gemaakt van een gcc versie uit het verleden en heeft men dit nooit fatsoenlijk opgelost. Want heel de gcc kent het concept van verschillende segment adressering niet.

Dat is ook een van de redenen waarom een dedicated compiler meestal efficienter is dan een generieke geporte compiler als de avc-gcc.

Wat dat betreft vind ik het heel jammer dat de sdcc compiler variant voor de AVR serie jaren geleden is gestopt.

Eigenlijk is het volgens mij eerder een blunder van Atmel, bij het ontwerp van de AVR instruktie set. Ze claimen een processor gemaakt te hebben die is geoptimaliseerd voor het runnen van C code. Dan moet je de gebruikers niet opzadelen met zo een dual adress space.

Ik weet hoe het werkt en ik kan ermee omgaan, maar toch is het telkens weer vervelend.

PS,
Je kunt wel een hele tabel in zijn geheel in flash plaatsen. Gaat goed zolang je niet met pointers werkt. Zoiets bijvoorbeeld:



struct MenuLine
{  char       Text[20 + 1];               // One extra for zero terminator,
   uint8_t    (* pFunction)(uint32_t p);  // Entry point for Menu function.
};

   
static const MenuLine TimerMenu[] PROGMEM =
{  {  "---- Timer Menu ----", DisplayTimer        },
   {  "1. Set Timer        ", DoSetTimer          },
   {  "2. Start Timer      ", DoTimerStart        },
   {  "3. Stop Timer       ", DoTimerStop         },
   {  "4. Continue Timer   ", DoTimerContinue     },
   {  "5. Exit             ", MenuExit            },
};

Op 16 november 2015 19:19:37 schreef henri62:

Wat een compiler wel kan doen is code genereren met je juiste assembly instructies (indirect reads uit het progmem) als de code daarvan een reference doet in het progmem.

Nee, het KAN niet.

Je hebt natuurlijk OOK een functie: "send_to_serialport (char *s)".
Geef die een string in "progmem" en hij moet "readprogmem" instructies gebruiken. Geef hem een 'char buf[32]; sprintf (buf, "t=%d\n", t);' en dan oet ie weer GEEN readprogmem instructies hebben. Dan zou je iets kunnen doen met "het hoogste bitje van de 16 bits pointers geeft progmem aan". Best, maar dan moet je voor iedere pointer-referentie code gaan genereren die kiest of de pointer naar data-geheugen wijst of naar programma geheugen, en afhankelijk van de uitslag de juiste dingen doet. Dat wordt run-time erg duur. Ze hebben de keuze gemaakt om dan maar alle const-data toch in RAM te zetten. Dat is nodig om de normale C-eigenschappen van pointers en zo te krijgen. Prima. Er was een afweging en er is een keuze gemaakt. Als je het er niet mee eens bent, mag je gerust je eigen compiler gaan schrijven. Succes!

@deKees: Die tabel van jou, bevat pointers. Pointers naar "char". De strings zet ie voor jou dus in RAM, zodat de "send_to_serialport" functie er mee kan werken. Dat zei blurp ook al

@Rew
Niet waar. Dat zijn geen pointers naar char maar embedded char arrays. De hele tabel staat als 1 geheel in flash. Hier wordt niks gecopieerd naar SRAM buffers. Data kan ik gewoon lezen met pgm_read... funkties.

@Huub12345
Dat laatste fragment is te kort om er iets zinnigs over te zeggen. Ik snap niet goed waar je naartoe wilt. Hangt erg af van hoe IDENT() gedefinieerd is. Maar zo op het eerst gezicht lijkt me dit niet goed.

Zoiets als jij gemaakt heb (deKees) met je "struct menuline" had ik ook.
Alleen waren bij mij de ipv de strings er een paar functie-codes.
Alles staat in flash, net zoals jouw voorbeeld.

Wat de compiler wel zou kunnen is als je alles goed declareert ook de juiste code kunt genereren.
(Eigenlijk is de "const" al voldoende om de var in progmem te kunnen zetten)

Als je de struct zo zou kunnen declareren:


struct MenuLine
{  PROGMEM const char *Text;
   PROGMEM uint8_t (* pFunction)(uint32_t p);
};

static const MenuLine TimerMenu[] PROGMEM =
{  {  "---- Timer Menu ----", DisplayTimer        },
   {  "1. Set Timer"        , DoSetTimer          },
   {  "2. Start Timer"      , DoTimerStart        },
   {  "3. Stop Timer"       , DoTimerStop         },
   {  "4. Continue Timer"   , DoTimerContinue     },
   {  "5. Exit"             , MenuExit            },
};

Zou je er mee weg kunnen komen.

Verder zijn er ook compilers die slim instructies locaties reserveren om de uiteindelijke code door de linker tussen te voegen (ik weet niet meer welke compiler dit doet maar ik heb ze gezien).

Stel (dit is fictief als voorbeeld): als een progmem_read bijvoorbeeld 4 bytes kost en een normale read 3 bytes, dan wordt in de object code 4 bytes gereserveerd voor de uiteindelijke code.
De linker is zo slim dat die weet waar de uiteindelijke (data) segmenten liggen en zet de juiste opcode er in met eventueel een opvul NOP.

Ik heb zelfs compilers gezien die de code zelfs packen en opnieuw de jump adressen relocaten.

Hi Henri62,

Inderdaad, iedere entry in de tabel heeft een stuk text (vaste lengte 20+terminator) en een pointer naar een funtie. Alles wat binnen de structure valt komt automatisch in Flash tgv de PROGMEM achter TimerMenu.

In jou voorbeeld heb je echter een pointer naar text. Ik weet niet of dat gaat werken, maar ik denk het niet. Dan gaat wel de pointer naar flash, maar niet de eigenlijke tekst, zoals je kunt zien in de avr-gcc documentatie.

En volgens mij zou de compiler het wel op kunnen lossen, maar dat wordt wel erg ingewikkeld en geeft ook veel extra code, zoals rew ook al zei. Het is maar de vraag of je dat wilt in zo een klein controllertje als een AVR.

Het is ook een voorbeeld hoe het zou kunnen want daar ging het laatste stukje van deze discussie over!
Daarom staat er bij de pointer naar de text ook PROGMEM zodat de compiler zou moeten beslissen dat die ook in het progmem terecht moet komen.

Het klopt inderdaad dat je extra pointer overhead in de struct hebt die wijzen naar de strings, maar die hebben nu een minimale lengte.
Verder is in jouw code de string een vaste lengte, daar heb je ook wat overtollige spaties in staan. Is een beetje lood om oud ijzer wat efficiënter is.

Een ander probleem wat dan ook nog opgelost zou moeten worden is dat pointers naar het progmem ook als zodanig gedeclareerd moeten worden.
Zie het voorbeeld wat 'rew' aanhaalt met het meest beroerde issue: printf(). Voor eigen gemaakte functies is dat niet zo'n groot probleem, maar voor de standaard library wel een groot probleem.

Een beetje een zelfde soort issue was er vroeger met de compiler/assembler voor MSDOS je had 4 verschillende memory modellen: small/medium/large/huge. Ieder met zijn eigen variant van de libraries want de pointers moesten allemaal anders zijn in de verschillende gevallen.

Maar het blijft lastig en nooit volledig oplosbaar. Helaas kun je nu dingen in progmem zetten waar de compiler niet over klaagt en het werkt gewoon helemaal niet @runtime. Dat is het meest slordige ervan.

Eigenlijk is het volgens mij eerder een blunder van Atmel, bij het ontwerp van de AVR instruktie set. Ze claimen een processor gemaakt te hebben die is geoptimaliseerd voor het runnen van C code. Dan moet je de gebruikers niet opzadelen met zo een dual adress space.

Dat is natuurlijk onzin. Een dual address space is inherent aan een Harvard architectuur. Als je kijkt naar een 8051: die heeft 3 address spaces. Of het handig is, is een andere discussie...

Ik heb geen recente ervaring met andere C compilers voor een AVR. Ik weet wel dat SDCC voor een 8051 het probleem deels probeerde op te lossen. Echter, ook daar moest je goed nadenken over wat je deed. Een void * bijvoorbeeld kon wel eens heel erg duur worden. De compiler wist niet in welke memory space dat ding zat, dus werd er runtime code gegenereerd om dat on-the-fly recht te trekken. En in 1x is de performance van die code zoek... Maar de compiler heeft het functioneel wel netjes opgelost, dat wel...
Overigens is dat de situatie die rew beschrijft. En het werd duuuuuur...

Stel (dit is fictief als voorbeeld): als een progmem_read bijvoorbeeld 4 bytes kost en een normale read 3 bytes, dan wordt in de object code 4 bytes gereserveerd voor de uiteindelijke code.
De linker is zo slim dat die weet waar de uiteindelijke (data) segmenten liggen en zet de juiste opcode er in met eventueel een opvul NOP.

Dat klopt. 8086 ed. doen dat bijvoorbeeld met near & far jumps.

(Eigenlijk is de "const" al voldoende om de var in progmem te kunnen zetten)

Als een read uit RAM en een read uit ROM even 'duur' zouden zijn, dan wel ja.

Een beetje een zelfde soort issue was er vroeger met de compiler/assembler voor MSDOS je had 4 verschillende memory modellen: small/medium/large/huge. Ieder met zijn eigen variant van de libraries want de pointers moesten allemaal anders zijn in de verschillende gevallen.

Oh yeah! :) (en eh... met een beetje pech, zeurde er niemand als je de verkeerde versie te pakken had. Draaien deed het ook niet...)

Helaas kun je nu dingen in progmem zetten waar de compiler niet over klaagt en het werkt gewoon helemaal niet @runtime. Dat is het meest slordige ervan.

Eens. Daar had die compiler wel een warning op mogen geven.

Ik heb de dekees code uitgebreid totdat het compileert en naar de gegenereerde assembly code zitten kijken.

"I stand corrected": Het lijkt er op dat de strings (tegenwoordig?) WEL in progmem komen. Ik heb daar ooit truuken voor moeten doen. (ik denk dat het uitmaakt of je "char *menustring" of "char menustring[21]" schrijft. In het eerste geval moet je moeilijk doen en bespaar je een paar bytes omdat je niet ruimte voor de langste string hoeft te reserveren.)

Maar

int main (void)
{
 int i;
   for (i=0;i<5;i++)
      send_to_serial (TimerMenu[i].Text);
}

daar wordt "send_to_serial" aangeroepen en die krijgt als argument gewoon een 16 bit pointer mee met geen enkele manier om te weten dat het een pointer naar program memory is.

[Bericht gewijzigd door rew op (17%)]