Sida 12 av 13

Re: Jesses följetång om AVR programmering i C...

Postat: 23 juli 2010, 17:23:33
av jesse
verkar ju bökigt värre med funktionspekare.
Eftersom det handlar om ett fåtal funktioner så kanske en switch-sats är enklare!

tackar för bra länkar (särskilt den sista)!

Re: Jesses följetång om AVR programmering i C...

Postat: 23 juli 2010, 17:50:27
av stekern

Kod: Markera allt

#include <stdio.h>
struct item {
    void (*funktion)(int);
    int value;
    char text[12];
};
void funk1(int data)
{
    printf("funk1, data = %i\n",data);
}
void funk2(int data)
{
    printf("funk2, data = %i\n",data);
}
int main()
{
    struct item list1[3] = {
        { funk1, 1, "start"},
        { funk2, 25, "open"},
        { funk1, 0, "stop"}
    };
    list1[0].funktion(list1[0].value);
    list1[1].funktion(list1[1].value);
    list1[2].funktion(list1[2].value);
    return 0;
}
Där är ett litet exempel på det du ville åstadkomma.

utskriften blir:
funk1, data = 1
funk2, data = 25
funk1, data = 0

Re: Jesses följetång om AVR programmering i C...

Postat: 23 juli 2010, 17:55:29
av jesse
stekern: fantastiskt! Då var det inte så krångligt som länkarna ovan gjorde det till! Tackar så mycket! :P

Re: Jesses följetång om AVR programmering i C...

Postat: 23 juli 2010, 18:07:23
av stekern
Nä, det är inte så bökigt, den första länken ser det krångligt ut för att den behandlar just generiska funktionspekare.
Den andra länken är bra men är väldigt ingående, så det kanske kan vara lite mycket att smälta på en gång.

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 05:39:05
av jesse
Jag slutar aldrig att imponeras av olika smarta saker som AVR GCC gör för att optimera kod.

Jag hade skrivit en mass if-satser för att byta ut tecken i en ascii-tabell mot en annan:

Kod: Markera allt

		if (tecken > 127) { // ÅÄÖ åäö é \ $¤£$~ñ
			if (tecken == 'å') tecken = 0x83; // å
			if (tecken == 'ä') tecken = 0x84; // ä
			if (tecken == 'ö') tecken = 0x94; // ö
			if (tecken == 'Å') tecken = 0x8f; // Å
			if (tecken == 'Ä') tecken = 0x8e; // Ä
			if (tecken == 'Ö') tecken = 0x99; // Ö
			if (tecken == 'ü') tecken = 0x81; 
			if (tecken == 'Ü') tecken = 0x9a; 
			if (tecken == 'é') tecken = 0x82; 
		if (tecken == 0x84) tecken = 0x99; // <== krock här
			if (tecken == 'É') tecken = 0x90; 
			if (tecken == '£') tecken = 0xe5; 
			if (tecken == '~') tecken = 0xf8; // (divisionstecken ./.) 
			if (tecken == '§') tecken = 0x12;
			if (tecken == 'ñ') tecken = 0x9b; 
			if (tecken == 'Ñ') tecken = 0x9c; 
		}
...och jag slogs av att det här var ett farligt sätt att skriva koden på: om någon av ascii-tecken hade samma kod skulle det bli en "krock"... som exempel fejkar jag ett tecken 0x84 (på raden där det står "<== krock här"). Om jag då har tecknet 'ä' omvandlas det på andra raden till 0x84 och sedan på "krock"-raden till 0x99... dvs FEL! För att vara på säkra sidan mot krockar måste jag alltså ha "else if" på alla följande rader istället för bara nya if-satser. Men jag blev nyfiken på assemberkoden, eftersom varje rad bara använde fyra instruktioner. Dessa var:

Kod: Markera allt

202:      			if (tecken == 'Å') tecken = 0x8f; // Å
+00002A8E:   3C85        CPI       R24,0xC5       Compare with immediate
+00002A8F:   F411        BRNE      PC+0x03        Branch if not equal
+00002A90:   E81F        LDI       R17,0x8F       Load immediate
+00002A91:   C02D        RJMP      PC+0x002E      Relative jump
Det visade sig att den redan gör "else if" på alla rader eftersom det är tidseffektivare än att gå igenom alla andra if-satser nedanför om en ovanför var sann. Förutom i fallet då tecken = 'ä'. Då hoppar den direkt till tecken = 0x99; Den skiter alltså att lagra "mellanvärdet 0x84" och hoppar över alla andra if-satser! Kan ju inte bli smartare, även om den då skulle göra "fel" om jag nu tänkt mig få resultatet 0x84 för ett 'ä'.

Så jag ändrade alla satser till else if...

Kod: Markera allt

		if (tecken > 127) { // ÅÄÖ åäö é \ $¤£$~ñ
			if (tecken == 'å') tecken = 0x83; // å
			else if (tecken == 'ä') tecken = 0x84; // ä
			else if (tecken == 'ö') tecken = 0x94; // ö
			else if (tecken == 'Å') tecken = 0x8f; // Å
			else if (tecken == 'Ä') tecken = 0x8e; // Ä
			else if (tecken == 'Ö') tecken = 0x99; // Ö
			else if (tecken == 'ü') tecken = 0x81; 
			else if (tecken == 'Ü') tecken = 0x9a; 
			else if (tecken == 'é') tecken = 0x82; 
		else if (tecken == 0x84) tecken = 0x99; // ingen krock!
			else if (tecken == 'É') tecken = 0x90; 
			else if (tecken == '£') tecken = 0xe5; 
			else if (tecken == '~') tecken = 0xf8; // (divisionstecken ./.) 
			else if (tecken == '§') tecken = 0x12;
			else if (tecken == 'ñ') tecken = 0x9b; 
			else if (tecken == 'Ñ') tecken = 0x9c; 
		}
Då fungerar koden som den ska, dvs 'ä' blir 0x84 och 0x84 blir 0x99. Men om inga rader "krockar" så blir assemberkoden för if och för else if identisk!

En fundering: denna optimering var bäst för hastigheten. Om man istället skulle prioritera kortare kod hade man kunnat ta bort "RJMP" i slutet på varje if-sats. Det hade sparat 25% av koden:

Kod: Markera allt

203:      			else if (tecken == 'Ä') tecken = 0x8e; // Ä
+00002A8E:   3C85        CPI       R24,0xC5       Compare with immediate
+00002A8F:   F411        BRNE      PC+0x03        Branch if not equal
+00002A90:   E81F        LDI       R17,0x8F       Load immediate
204:      			else if (tecken == 'Ö') tecken = 0x99; // Ö
+00002A91:   3D86        CPI       R24,0xD6       Compare with immediate
+00002A92:   F411        BRNE      PC+0x03        Branch if not equal
+00002A93:   E919        LDI       R17,0x99       Load immediate
...
Helt o-optimerad var koden inte lika rolig:

Kod: Markera allt

201:      			else if (tecken == 'ö') tecken = 0x94; // ö
+00004B95:   8189        LDD       R24,Y+1        Load indirect with displacement
+00004B96:   3F86        CPI       R24,0xF6       Compare with immediate
+00004B97:   F419        BRNE      PC+0x04        Branch if not equal
+00004B98:   E984        LDI       R24,0x94       Load immediate
+00004B99:   8389        STD       Y+1,R24        Store indirect with displacement
+00004B9A:   C047        RJMP      PC+0x0048      Relative jump

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 06:06:19
av stekern
Rent stilmässigt skulle jag nog skriva den där if-else-if-satsen som en switch-case sats, men det är ju en smaksak.
Då får du ju ytterligare ett "case" att jämföra vad kompilatorn hittar på ;)

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 09:15:42
av Icecap
Om det är hastighet som gäller är det snabbast att ha en tabell som det slås upp i.

const char Tecken_Konvertering[] = {alla tecken från 0x80 till 0xFF där placering motsvara inkommande värde};

if(Tecken > 127) Tecken = Tecken_Konvertering[Tecken - 128];

Ett lika snabbt sätt som det nuvarande är att scanna en tabell, fördel är att det fyller mindre:

Kod: Markera allt

const Incoming[] = {'å'   , 'ä'    , 'ö'   , 'Å'   , 'Ä'   , 'Ö'    ,'ü'    , 'Ü'    , 'é'   , 'É'   , 0x84, '£'    , '~'  , '§'    , 'ñ'   , 'Ñ'};
const Outgoing[] = {0x83, 0x84, 0x94, 0x8F, 0x8E, 0x99, 0x81, 0x9A, 0x82, 0x90, 0x99, 0xE5, 0xF8, 0x12, 0x9B, 0x9C};

if(Tecken >= 0x80)
  {
  BYTE Counter;
  for(Counter = 0; Counter < sizeof(Incoming); Counter++) // Scan through table
    {
    if(Tecken == Incoming[Counter]) // Test if it's in table
      {
      Tecken = Outgoing[Counter]; // Excange value
      Counter = sizeof(Incoming); // Terminate loop
      }
    }
  }

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 17:38:26
av jesse
Dom där konstanterna tar väl upp ram-minne va?
Nu är det inte så hemskt tidskritiskt, så jag tycker koden blir mer läsbar som jag gjort - med if-satser.

stekern: nja, när det gäller one-liners som dessa föredrar jag else if faktiskt. Jag stör mig på att switch-case koden ser så klumpig ut - med enbreak i varje case dessutom :x

(Men i samma program har jag en enorm switch-case sats som sträcker sig över ett antal a4-sidor med masor av kod i varje case... då är det definitivt överskådligare än med else if...)

NU är det så att om jag verkligen vill spara tid (tusentals klockcykler) i denna LCD utskrift så kom jag just på något fiffigt :idea: (fast potentiellt farligt om jag gör ett misstag):

LCD-displayen är SPI-ansluten, men måste adresseras först med en byte. Men om jag inte släpper upp Chip-Select mellan varje byte , vilket jag gör nu i putChar(tecken); så kan jag köra ut databitarna allihop i en följd.

Det farliga är att jag måste ha stenkoll att jag verkligen stänger Chip-Select när jag är klar, och att jag inte anropar nån annan SPI enhet under tiden... (då går det åt skogen)... så jag har struntat i detta hittills... men det skulle dubbla hastigheten. Varje byte ut på SPI tar 52 uS - eller 1024 klockcykler!

Jag kan tänka mig att implementera det i vissa "säkra" funktioner där det gör mest nytta:
t.ex. i

print(text); // skickar ut en text från progmem till förvald enhet....
printDecimalPoint(number,digits,decimals); // skriver ut decimaltal utifrån ett heltal till vald enhet...
spaces(antal_mellanslag); // skriver ut mellanslag till vald enhet

Dessa är vanligt förekommande och ingen annan SPI-enhet kontaktas under tiden.

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 17:42:15
av sodjan
> Dom där konstanterna tar väl upp ram-minne va?

Nej, "const" arrayer bör/ska inte göra det.

> i samma program har jag en enorm switch-case sats som
> sträcker sig över ett antal a4-sidor med masor av kod i varje case...

Då bör varje case följas av en CALL till resp rutin. D.v.s så att själva
case-strukturen blir mer överskådlig/mindre.

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 17:59:56
av jesse
>Nej, "const" arrayer bör/ska inte göra det.

Är du säker på det, Sodjan? (jag ska kolla upp det)... men om det är som du säge, så är ju biblioteket <avr/pgmspace.h> helt överflödigt eller? Alla konstanta arrayer jag använder deklarerar jag som t.ex.

Kod: Markera allt

int16_t PROGMEM VVdefaultValues[VV_ANTAL] = {3200, 3050, 2900,1500, 2100, 2300,2800, 4};
uint8_t PROGMEM text_logend[]  = "*** LOGGNING AVSLUTAD ***\n";
Dessa går inte att använda direkt utan måste läsas med value = pgm_read_byte(...)

Det hade ju varit extremt bekvämt om kompilatorn sköter allt det åt mig om jag bara skrev:

Kod: Markera allt

const int16_t VVdefaultValues[VV_ANTAL] = {3200, 3050, 2900,1500, 2100, 2300,2800, 4};
const uint8_t text_logend[]  = "*** LOGGNING AVSLUTAD ***\n";

och läsa dessa som "vanliga" arrayer: [c]value = VVdefaultValues[nr];[/c]

>Då bör varje case följas av en CALL till resp rutin.
Nja... jag hittar faktiskt den relevanta koden bättre som det är nu. I de fall jag gjort en funktion så får jag leta mer då jag aldrig kan minnas funktionernas namn. Nu kan jag bara söka på case 'U' så är jag där.
Och så slipper jag göra massor av variabler globala (vilket jag vill undvika).

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 18:03:36
av Icecap
Som sodjan skriver: const betyder just att värden kan (/ska) läggas i ROM, det är just orsaken till att man skriver dom som const.

Om en AVR behöver ett speciellt kommando för att peta in dessa ROM-minnet är det väl ett problem du måste lösa. Alla de kompilatorer jag har jobbat med sparar dessa värden i prog. minnet.

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 18:09:52
av monstrum
Ska försöka reda ut det här med const och progmem.
Deklarerar du en variabel enbart som const så kommer den att sparas i flashet. Men den kommer även att kopieras till RAM:et under uppstart för att kunna hanteras utav alla standardbibliotek.
Deklarerar du variabeln som progmem så kommer den enbart att läggas i flashet. Problemet nu är ju att flash-minnet inte är mappat i någon vanlig addressrymd på AVR:erna, utan måste läsas genom speciella instruktioner/register. Så, vill du snåla med RAM-minne så får du ha funktioner som antingen använder progmem-variablerna direkt och läser ut dom ur flashet när dom behövs, eller så får du göra detta manuellt inför varje funktionsanrop.

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 18:12:46
av AndLi
monstrums förklaring är alltså inte generell utan gäller för AVR (och säkert andra µC med icke mappat flashminne)

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 18:15:05
av jesse
RAM minnet är ju ytterst begränsat på en AVR, så jag vill ju inte den ska hålla på och kopiera över till nån slags buffer i RAM för varje gång jag ska läsa en const. Det tar ju tid också. I värsta fall kopieras allihopa variablerna över till SRAM i start - då fyller jag RAM-minnet flera gånger om (jag har massor av texter).

Re: Jesses följetång om AVR programmering i C...

Postat: 25 oktober 2010, 18:23:29
av AndLi
Nu vet jag ju inte om jag tycker att 16kbyte är så värst begränsat,det beror ju helt på vilket du väljer.
Har man inte fysisk platsbrist är det väll nästan alltid bäst att börja stort och sen skala sig nedåt i div cost reduction runder (atmega1284 finns i allt från dil40 till en 5x5 mm (om inte tom en ännu mindre bga))

Varför måste du läsa upp hela texterna i ram? Är väll bara att läsa en byte med pgm funktionen och skicka ut den på spi direkt? Sannolikt kommer kompilatorn optimera ner den i något register nånstans.?