Heren,

Ik ben al een tijdje aan het worstelen om op een snelle manier een waarde uit een 2 dimensionale array te halen.

Ik gebruik op het moment een AtMega8 in combinatie met AVR Studio en AVR-GCC (WinAvr).

Ik heb een timer interrupt op 57kHz draaien die een waarde uit de tabel haalt en deze vervolgens op PORTA zet welke is verbonden met een R2R D/A converter.

Dit is de tabel:

[CODE=C]
uint8_t table[4][48] PROGMEM=
{

//"000" - 0
{124, 140, 156, 172, 188, 202, 214, 226, 236, 244, 250, 253,
254, 254, 252, 247, 240, 231, 220, 208, 195, 180, 164, 148,
132, 116, 100, 84, 68, 54, 42, 30, 20, 12, 6, 3,
2, 2, 4, 9, 16, 25, 36, 48, 61, 76, 92, 108},

//"001" - 1
{124, 140, 156, 172, 188, 202, 214, 226, 236, 244, 250, 253,
254, 254, 252, 247, 240, 231, 220, 208, 195, 180, 164, 148,
131, 114, 98, 82, 68, 55, 45, 36, 29, 24, 22, 22,
23, 26, 31, 37, 44, 49, 55, 62, 66, 69, 72, 75},

//"010" - 2
{71, 68, 63, 57, 50, 42, 34, 26, 18, 11, 6, 3,
2, 2, 4, 9, 16, 25, 36, 48, 61, 76, 92, 108,
125, 142, 158, 174, 188, 200, 211, 220, 227, 232, 234, 234,
233, 230, 225, 219, 212, 207, 201, 194, 189, 187, 184, 181},

//"011" - 3
{71, 68, 63, 57, 50, 42, 34, 26, 18, 11, 6, 3,
2, 2, 4, 9, 16, 25, 36, 48, 61, 76, 92, 108,
124, 140, 156, 172, 188, 202, 214, 226, 236, 244, 250, 253,
254, 254, 252, 247, 240, 231, 220, 208, 195, 180, 164, 148},
}
[/CODE]

Dit is de timer interrupt die een waarde uit de tabel haalt en op PORTA zet

[CODE=C]
ISR(TIMER1_OVF_vect)
{
PORTC = pgm_read_byte(&table[ubPos++][BITtimer++]);
}
[/CODE]

Dit werkt maar bestaat er een snellere manier om de data uit de tabel te halen?

Ik denk dat het *iets* sneller is als je die table in RAM zet (de vraag is natuurlijk of je dat kunt mbt. beschikbaar RAM).

Veel sneller gaat het niet worden, tenzij je het eendimensionaal kunt schrijven (zonder dan zelf weer om te gaan rekenen naar 2-dimensionaal, dan ben je terug-bij-af...)

Wat er nu gebeurt is dat er uit je 2 indices het baseaddress het adres van de byte die je hebben wilt wordt berekend. Dat is wat vermenigvuldigen en optellen. Daar wordt een lpm op losgelaten. Het resultaat daarvan wordt naar PORTC geschoven. Die offset berekenen gaat niet handiger worden als je 2D blijft werken. Door het spul in RAM te zetten kun je een paar instructies sparen mbt. de lpm. Maar het zal niet veel zijn.

Wellicht scheelt het ook als je de value naar PORTA zet :)

Kijk ook eens naar de assembly die GCC genereert. Meestal zit dat behoorlijk in elkaar, maar heel soms denk ik ook wel eens WTF! Zeker mensen die alles erg compact proberen te schrijven (zoals jij dat nu doet; de increment op die manier... blergh!) triggeren dat soms nog wel eens. Soms...

Heb je overigens een snelheidsprobleem?

Op 7 november 2010 14:36:59 schreef EricP:
Wellicht scheelt het ook als je de value naar PORTA zet :)

Atmega8 heeft uit m'n hoofd geen PORTA....

Je code vertrouw ik niet. Die gaat fout omdat de tellers in no time voorbij de 4 en de 48 gaan.

Ik zou een 1-dimensionale array gebruiken. Ik zou zo veel mogelijk dingen vooraf uitrekenen.


PORTC = pgm_read_byte (pgmptr);
pgmptr += 49;
if (pgmptr > (table + 4 * 48)) pgmptr -= 4*48;

En dan luisteren naar Eric: Effe met gcc -S compileren en naar de assembly kijken of gcc rare dingen heeft verzonnen.

Je zult dan mogelijk dingen zien dat ie met twee bytes gaat klooien om een increment te doen. Dan heb je int's gebruikt in plaats van unsigned char....

[Bericht gewijzigd door rew op (11%)]

ik denk dat het *iets* sneller is als je die table in RAM zet (de vraag is natuurlijk of je dat kunt mbt. beschikbaar RAM).

Ik heb hem al in RAM gezet maar dan wordt het juist trager. De manier die gebruikt wordt om uit flash te lezen is blijkbaar sneller. Ik heb al in de assembly file gekenen maar daar word ik niet wijzer van. GCC haalt soms leuke capriolen uit die ik niet snap.

Veel sneller gaat het niet worden, tenzij je het eendimensionaal kunt schrijven (zonder dan zelf weer om te gaan rekenen naar 2-dimensionaal, dan ben je terug-bij-af...)

1 dimensionaal heb ik ook al geprobeerd maar door de verminigvuldiging die ik dan moet toepassen ben ik idd weer terug bij af.

Wellicht scheelt het ook als je de value naar PORTA zet.

Wat bedoel je daar mee? De waarde moet juist naar de poort waar de D/A aan zit.

Je code vertrouw ik niet. Die gaat fout omdat de tellers in no time voorbij de 4 en de 48 gaan.

Klopt idd maar de code die hier staat is niet volledig, wat ik hier laat zien is alleen het princiepe hoe ik de data uit de tabel naar buiten schrijf.

Hoe netjes ik het ook in C probeer te schrijven, GCC doet dingen die ik niet wil. Ik zit er dan ook over na te denken om maar assembly te gaan leren en dan de kritische stukjes als inline assembly toe te gaan passen.

Het project is overigens een RDS encoder. Hij werkt al wel maar maar er komt teveel jitter uit die in stereo uitzendingen hoorbaar is in de vorm van knetterend geluid.Het is op de achtergrond hoorbaar. In het spectrum is er rond 57kHz ook rommel te zien. Als ik het R2R gedeelte en de tabellen weglaat en vervang door de interne PWM generator en keihard van fase wissel om de biphase signalen op te wekken dan is het knetterend geluid weg. Het nadeel is dan wel dat er een blokgolf uitkomt met veel harmonischen die moeilijk weg te filteren zijn. Dat was juist de reden om voorberekende biphase tabellen te gebruiken.

De enige manier waarop je jitter kunt krijgen is als je ISR trager wordt uitgevoerd dan dat hij opgeroepen wordt. Op wat voor frequentie roep je hem aan?

RAM is normaal sneller dan ROM, dus dat zo altijd sneller moeten zijn. Post de ASM (.lss file) die GCC genereert voor beide methodes eens, dan kunne we zien of er iets bizars gebeurt.

Je code kan misschien nog iets geoptimaliseerd worden. Aangezien ubPos en BITtimer gelijktijdig verhoogd worden kan je dit misschien vervangen door een enkele index in een 1D array? Je gebruikt op de huidige manier namelijk maar 1 op 4 van elke sub-array.

Op 7 november 2010 16:43:22 schreef rew:
[...]
Atmega8 heeft uit m'n hoofd geen PORTA....

Klopt. Alleen B, C en D. Echter, in de startpost...

Ik heb hem al in RAM gezet maar dan wordt het juist trager.

Dat is raar. Reden om te kijken wat de compiler er van bakt. Het zou met minder instructies moeten kunnen. Echter, als het een registertje meer kost en dat levert wat push-pop op, kan het averechts werken. Hand-optimized assembly?
Overigens is GCC ook nog wel te temmen. Was in dat opzicht altijd wel blij met SDCC voor 8051. Je laat de compiler de assembly genereren, die plak je vervolgens in je C-source als inline assembly en dan optimize je het op de hand... Is met GCC iets lastiger.

Hoe hard draait die AVR overigens? 57kHz IRQs zijn nou niet echt vreselijk... Als het ding op 16MHz loopt, kun je 280 (single cycle) instructies per interrupt doen. Daar zit dan wel alles in, inclusief je aanroep / exit van de ISR. Je moet in een had vol instructies toch wel een waarde uit die table kunnen halen??

Aangezien je increment binnen de ISR plaats vindt (en je zegt dat eea. elders weer 'recht' getrokken wordt), heb ik het idee dat de rest van je code roet in het eten gooit (er is duidelijk een 'rest' in de code :) ). Link eens naar de volledige ISR / code.

RAM is normaal sneller dan ROM

Dat maakt hier geen zak uit. Echter, IMHO moet RAM access met minder instructies kunnen (je kunt niet zoals een 8051 rechtstreeks uit FLASH (ROM) lezen, daar moet je wat voor trucen).

Op 7 november 2010 17:23:59 schreef Echoflex:

1 dimensionaal heb ik ook al geprobeerd maar door de verminigvuldiging die ik dan moet toepassen ben ik idd weer terug bij af.

Huh? Niet als je twee constanten vermenigvuldigd zoals in de code van rew, de compiler maakt daar gewoon 192 van. Als je die twee waarden in vars hebt zitten moet er wel iedere keer vermenigvuldigd worden ...

Code van rew:

[code=C]
PORTC = pgm_read_byte (pgmptr);
pgmptr += 49;
if (pgmptr > (table + 4 * 48)) pgmptr -= 4*48;
[/code]

pgmptr+=49 werkt niet, de compiler klaagt over:

error: assignment of read-only variable 'pgmptr'

Dit werkt wel:

[code=C]PORTC = pgm_read_byte (pgmptr+49);[/code]

Dat zou dan moeten worden:

[code=C]

uint8_t TablePointer = 0;

PORTC = pgm_read_byte (pgmptr+TablePointer);

TablePointer+= 49;
if (TablePointer > (table + 4 * 48)) TablePointer -= 4*48;

De complete code heb ik thuis, ik zit nu met m`n laptop elders en kan de code nu niet in zijn geheel testen.

In het eerste stukje code moet je pgmptr declareren als uint16_t. Zorg er zeker voor dat hij NIET in PROGMEM zit en niet const is. Je moet hem initialiseren op de waarde van "table".

In het tweede stukje code zitten nog een aantal fouten:
- pgmptr ipv table ?
- table uit de if weghalen.

Op 7 november 2010 20:42:30 schreef Gyrbo:
In het eerste stukje code moet je pgmptr declareren als uint16_t. Zorg er zeker voor dat hij NIET in PROGMEM zit en niet const is. Je moet hem initialiseren op de waarde van "table".

En zonder ergens naar gekeken te hebben: maak het ding volatile als je in een ISR er aan trekt!

Op 7 november 2010 17:23:59 schreef Echoflex:
1 dimensionaal heb ik ook al geprobeerd maar door de verminigvuldiging die ik dan moet toepassen ben ik idd weer terug bij af.

Het moet JUIST geen vermenigvuldiging zijn: De keer-vier is een shift. Oh wacht. Keer 48! Ah! dan moet je de array indices omdraaien.

Iemand maakte ook de opmerking dat je beide variabelen tegelijk ophoogt. En omdat 4 een deler is van 48, gebruik je altijd dezelfde 25% van je tabel. Mee eens? Of komt er een initializatie bij kijken waardoor het soms een andere 25% kan zijn? Dan kan je gewoon 4 verschillende, maar andere tabellen gebruiken.

In mijn voorbeeld code was het zo dat "pgmptr" hetzelfde type zou hebben als die tabel van jou. (maar dan als pointer ipv array). Dan moet het volgens mij door de compiler komen. De "eind" van die pointer, wanneer ie weer terug moet is dan ook een constante. Nadeel van mijn code is dat dit dan berekeningen zijn die in 16 bits moeten. Da's jammer. Het zou beter kunnen met een unsigned char, en dan toch maar een optelling "live" in de loop.

Als je optimaliseert is het soms lastig, omdat wat je makkelijk in C kan opschrijven niet altijd makkelijk gaat in avr assembly. Dus probeer het eens wat anders op te schrijven in C en kijk of het dan sneller of langzamer wordt. Kijk naar de assembly. Is die langer of korter?

Ik weet het voor AVR ook nog niet wat nu het snelste is. Nog te weinig tijd in gestoken.

Wel weet ik dat ik eerst iets had waarbij ik het uitgeschreven had in C, en daar later een loop voor 4x en een index in een array voor de vier verschillende constantes in die loop. FOUT! Daar werd het gewoon 2x langzamer van! gewoon los uitschrijven. Array indexeren is heel traag op AVR....

Op 8 november 2010 00:17:08 schreef rew:
[...]
Het moet JUIST geen vermenigvuldiging zijn: De keer-vier is een shift.

Kleine opmerking: Als je AVR een hardware multiply heeft wil je juist niet dat hij shift. AVRs hebben namelijk geen barrel shifter, waardoor je voor elke shift een cycle nodig hebt. Een hardware multiply neemt altijd 2 cycles en is dus efficiënter als je meer dan een positie naar links moet shiften. AVRs hebben ook een SWAP instructie, waarbij de nibbles omgewisseld worden. Op die manier kan je ook vrij efficiënt shiften met 4. In dit geval maakt het weinig uit, maar vooral als je met bitfields werkt kun je op deze manier wat code plaats uitsparen.

In ieder geval, schrijf gewoon *4 als je maal 4 wil doen, GCC is slim genoeg om de meest efficiënte code te gebruiken. Je staat soms verstelt hoe goed hij bepaalde dingen kan optimaliseren.

[Bericht gewijzigd door Gyrbo op (12%)]

Op 8 november 2010 00:17:08 schreef rew:
[...]
Array indexeren is heel traag op AVR....

Dat valt ernstig mee hoor. Assembly kan het prima (en best efficiënt). Echter niet elke compiler doet het altijd even handig.
Alhoewel ik inderdaad (net als Gyrbo) soms versteld sta van de foefjes van een compiler... (en heel soms ook van de stupiditeit van zo'n ding).

[offtopic]Toen ik net met 8051 begon ooit eens een middag verspeeld op een stuk (interrupt) code wat absoluut niet vooruit te branden was. De oorzaak was uiteindelijk een void pointer. Aangezien een 8051 een Harvard architectuur heeft en een void pointer dus zowel naar RAM als ROM kan pointen, wordt er voor elke access een stukje runtime lib aangeroepen wat eea. voor je regelt (pointer wordt een byte langer; in die byte staat wat er gereferenced wordr). Maak er een char * van en het kan wel 'native' (die hebben afhankelijk van het model een storage class; IRAM of XDATA). Scheelde een factor 5-10 in performance; had het zo snel niet achter die pointer gezocht...[/offtopic]

Ik heb hetvolgende gestest:

Voor de handigheid meet ik nu op PORTD met de scoop hoe lang het duurt. De timer van de dubugger in AvrStudio geeft hele andere waarden.

De AVR draait nu op 24MHz ;-)

[code=C]
PORTD |= (1<<PD5);
PORTA = pgm_read_byte(&ubBiphase[ubPos*48+BITtimer]);
PORTD &=~(1<<PD5);
[/code]
740nS

[code=C]
PORTD |= (1<<PD5);
PORTA = pgm_read_byte(&ubBiphase[ubPos][BITtimer]);
PORTD &=~(1<<PD5);
[/code]
740nS

[code=C]
PORTD |= (1<<PD5);
PORTA = ubBiphase[ubPos][BITtimer]);
PORTD &=~(1<<PD5);
[/code]
1.08uS

[code=C]
PORTD |= (1<<PD5);
PORTA = ubBiphase[ubPos*48+BITtimer]);
PORTD &=~(1<<PD5);
[/code]
Eveneens 1.08uS

[code=C]
uint16_t *ptr = &ubBiphase; //adres toekennen aan pointer
ptr = (ubPos*48+BITtimer); //pointer positie bepalen
PORTD |= (1<<PD5);
PORTA = *ptr;//inhoud van de pointer
PORTD &=~(1<<PD5);
[/code]

Deze duurt 610nS!! Maar! Nu moet ik de table ook alloceren als een uint16_t. Omdat de table in totaal 348 Bytes lang is kan ik met een uint8_t als pointer niet volledig door de table lopen (max 255).

Als ik de table alloceer als een uint8_t en de pointer als uint16_t en ik increase de pointer dan krijg ik als resultaat de waarde van 2 plekker verder in de array.

Het blijkt dus dat het lezen uit flash sneller gaat. Er zit geen verschil in het gebruik van een 2 of 1 dimensionale array.

Ik heb de code gecompileerd met -S

[Bericht gewijzigd door Echoflex op (14%)]

Zoals gezegd, post de relevante delen van de .lss file, dan kunnen we kijken waarom bepaalde zaken beter gaan.

Een 1D array is op zich inderdaad niet sneller als een 2D, beide stukken code doen exact hetzelfde. Je kan wel winnen door je algoritme op een hoger niveau te optimaliseren, waardoor je een enkele variabele kan gebruiken.

Gecompileerde met -S, bedoel je -Os? Als je voor snelheid wil gaan, kan je -O3 proberen. Dit zou normaal de snelste code moeten zijn, alhouwel dat hier waarschijnlijk weinig uitmaakt.

ik heb


#include <avr/io.h>
#include <avr/pgmspace.h>


#define PORTA PORTC

uint8_t ubBiphase[4][48]  PROGMEM = {{0,},{0,}, {0,},{0,}};
uint8_t ubBiphaser[4][48];

uint8_t ubPos, BITtimer;


void case1 (void)
{
 
PORTD |= (1<<PD5);
PORTA = pgm_read_byte(&ubBiphase[ubPos*48+BITtimer]);
PORTD &=~(1<<PD5);
}

void case2 (void)
{PORTD |= (1<<PD5);
PORTA = pgm_read_byte(&ubBiphase[ubPos][BITtimer]);
PORTD &=~(1<<PD5);
 }

void case3 (void) 
{
PORTD |= (1<<PD5);
PORTA = ubBiphaser[ubPos][BITtimer];
PORTD &=~(1<<PD5);
 }

void case4(void)
{
PORTD |= (1<<PD5);
PORTA = ((char *)ubBiphaser)[ubPos*48+BITtimer];
PORTD &=~(1<<PD5);
 }

void case5(void)
{
 
uint8_t *ptr = (uint8_t *)ubBiphaser; //adres toekennen aan pointer
ptr += (ubPos*48+BITtimer); //pointer positie bepalen
PORTD |= (1<<PD5);
PORTA = *ptr;//inhoud van de pointer
PORTD &=~(1<<PD5);
}

Door de avr-gcc compiler laten compileren met:


avr-gcc -Wall -mmcu=atmega168 -O2 testspeed.c -S

en vervolgens in testspeed.s kan je zien wat ie ongeveer doet om de boel te compleren.

Jou hardware-pulsje om te timen vertaalt naar:


        sbi 43-32,5

Dus alles wat er tussen staat is de code voor die ene regel code.

Ik tel trouwens rond de 23 clocks terwijl jij 17.76 gemeten hebt. Raar. Wat voor CPU had je ook al weer?

Okee,

Dit staat er in de listing file:

Vanuit flash 1 dimensionaal:
[code=C]
PORTA = pgm_read_byte(&ubBiphase1[ubPos*48+BITtimer]);
21a: e0 91 c7 01 lds r30, 0x01C7
21e: 80 91 a4 01 lds r24, 0x01A4
222: e9 02 muls r30, r25
224: f0 01 movw r30, r0
226: 11 24 eor r1, r1
228: e8 0f add r30, r24
22a: f1 1d adc r31, r1
22c: ec 5a subi r30, 0xAC ; 172
22e: ff 4f sbci r31, 0xFF ; 255
230: e4 91 lpm r30, Z+
232: eb bb out 0x1b, r30 ; 27
[/code]

Vanuit Flash 2 dimensionaal:
[code=C]
PORTA = pgm_read_byte(&ubBiphase1[ubPos][BITtimer]);
21a: e0 91 c7 01 lds r30, 0x01C7
21e: 80 91 a4 01 lds r24, 0x01A4
222: e9 02 muls r30, r25
224: f0 01 movw r30, r0
226: 11 24 eor r1, r1
228: e8 0f add r30, r24
22a: f1 1d adc r31, r1
22c: ec 5a subi r30, 0xAC ; 172
22e: ff 4f sbci r31, 0xFF ; 255
230: e4 91 lpm r30, Z+
232: eb bb out 0x1b, r30 ; 27
[/code]

Vanuit RAM 1 dimensionaal:

[code=C]
PORTA = ubBiphase1[ubPos*48+BITtimer];
9c: 80 91 47 03 lds r24, 0x0347
a0: 20 91 24 03 lds r18, 0x0324
a4: 99 27 eor r25, r25
a6: 87 fd sbrc r24, 7
a8: 90 95 com r25
aa: 84 9f mul r24, r20
ac: f0 01 movw r30, r0
ae: 85 9f mul r24, r21
b0: f0 0d add r31, r0
b2: 94 9f mul r25, r20
b4: f0 0d add r31, r0
b6: 11 24 eor r1, r1
b8: e2 0f add r30, r18
ba: f1 1d adc r31, r1
bc: e0 57 subi r30, 0x70 ; 112
be: ff 4f sbci r31, 0xFF ; 255
c0: 80 81 ld r24, Z
c2: 8b bb out 0x1b, r24 ; 27
[/code]

Vanuit RAM 2 dimensionaal:
[code=C]
PORTA = ubBiphase1[ubPos][BITtimer];
9c: 80 91 47 03 lds r24, 0x0347
a0: 20 91 24 03 lds r18, 0x0324
a4: 99 27 eor r25, r25
a6: 87 fd sbrc r24, 7
a8: 90 95 com r25
aa: 84 9f mul r24, r20
ac: f0 01 movw r30, r0
ae: 85 9f mul r24, r21
b0: f0 0d add r31, r0
b2: 94 9f mul r25, r20
b4: f0 0d add r31, r0
b6: 11 24 eor r1, r1
b8: e2 0f add r30, r18
ba: f1 1d adc r31, r1
bc: e0 57 subi r30, 0x70 ; 112
be: ff 4f sbci r31, 0xFF ; 255
c0: 80 81 ld r24, Z
c2: 8b bb out 0x1b, r24 ; 27
[/code]


	PORTA = ((char *)ubBiphase1)[ubPos*48+BITtimer];
      9c:	80 91 47 03 	lds	r24, 0x0347
      a0:	20 91 24 03 	lds	r18, 0x0324
      a4:	99 27       	eor	r25, r25
      a6:	87 fd       	sbrc	r24, 7
      a8:	90 95       	com	r25
      aa:	84 9f       	mul	r24, r20
      ac:	f0 01       	movw	r30, r0
      ae:	85 9f       	mul	r24, r21
      b0:	f0 0d       	add	r31, r0
      b2:	94 9f       	mul	r25, r20
      b4:	f0 0d       	add	r31, r0
      b6:	11 24       	eor	r1, r1
      b8:	e2 0f       	add	r30, r18
      ba:	f1 1d       	adc	r31, r1
      bc:	e0 57       	subi	r30, 0x70	; 112
      be:	ff 4f       	sbci	r31, 0xFF	; 255
      c0:	80 81       	ld	r24, Z
      c2:	8b bb       	out	0x1b, r24	; 27

Het blijkt dus dat hij voor de 1 en 2 dimensionale array dezelfde asm maakt.

EDIT:

Ik tel trouwens rond de 23 clocks terwijl jij 17.76 gemeten hebt. Raar. Wat voor CPU had je ook al weer?

Op mijn testboard zit nu een AtMega16 die op 24MHz draait.

[Bericht gewijzigd door Echoflex op (11%)]

Ik heb nu twee gevallen toegevoegd:


void case6(void)
{
uint8_t index;

PORTD |= (1<<PD5);
index = BITtimer*4 + ubPos;
PORTA = ((uint8_t*)ubBiphaser) [index];//inhoud van de pointer
PORTD &=~(1<<PD5);
}

void case7(void)
{
uint8_t index;

PORTD |= (1<<PD5);
index = BITtimer*4 + ubPos;
PORTA = pgm_read_byte((uint8_t *) (&ubBiphase) + index);
PORTD &=~(1<<PD5);
}

en deze twee zijn alletwee bijna 2x sneller dan alles wat je tot nu toe had... (rond de 11 cycles ipv 22)

Je moet wel je array op z'n kant zetten. De vermenigvuldiging met 48 vervalt en wordt vervangen door een vermenigvuldiging met vier. Verder zorg ik er voor dat deze dingen in 8bits gedaan worden en niet in 16 bits.

[edit] Ik heb in de laatste run nu gecompileerd voor atmega16, dus mijn #define PORTA vervalt dan. De resulterende assembly lijkt niet veranderd.

[edit2]Het kan NOG een 2x sneller als je de array ubBiphase op 256 byte boundary laat beginnen. De 16bit optelling om de index bij het startadres op te tellen kost veel tijd.

Thnx!

maar het probleem is dat mijn table in totaal 8*48 lang is, de index moet dus wel een uint16_t zijn anders kan ik niet voorbij index 255;

EDIT:

Als ik de index in een uint16_t verander dan groeit hij alweer:


    index = BITtimer*4 + ubPos;
      98:	e0 91 24 03 	lds	r30, 0x0324
      9c:	80 91 47 03 	lds	r24, 0x0347
    PORTA = pgm_read_byte((uint8_t *) (&ubBiphase1) + index);
      a0:	f0 e0       	ldi	r31, 0x00	; 0
      a2:	ee 0f       	add	r30, r30
      a4:	ff 1f       	adc	r31, r31
      a6:	ee 0f       	add	r30, r30
      a8:	ff 1f       	adc	r31, r31
      aa:	99 27       	eor	r25, r25
      ac:	87 fd       	sbrc	r24, 7
      ae:	90 95       	com	r25
      b0:	e8 0f       	add	r30, r24
      b2:	f9 1f       	adc	r31, r25
      b4:	e0 57       	subi	r30, 0x70	; 112
      b6:	ff 4f       	sbci	r31, 0xFF	; 255
      b8:	e4 91       	lpm	r30, Z+
      ba:	eb bb       	out	0x1b, r30	; 27

Ehh. je declareert hem als uint8_t[4][48], da's 192 bytes. Kan best dat het in progmem in 384 bytes gezet wordt (als je het declareert zoals hierboven), maar zeker als je het in ram zet moet het niet meer dan 192 worden. Dat zou dan een hint geven dat je het beter in ram kan zetten.

Eventueel moet je dus ook de oneven progmem locaties gaan gebruiken om de tabel onder 192 bytes te houden. Dus uint16_t gaan gebruiken in de tabel, en met de hand steeds de twee opeenvolgende waardes combineren in 1 short....

Verder is het efficienter om:


if (BITtimer & 1) 
  ... 
else 
  ... 

de code te dupliceren en dan op de puntjes twee verschillende arrays te gebruiken die alletwee kleiner zijn dan 256 bytes....

dat is nog altijd efficienter dan wat je nu hebt.

Bedankt voor de tip!

Ik ga nog even aan het stoeien en laat vanmiddag wel even zien wat de resultaten zijn.

Ik ben in iedergeval enorm geholpen met het commentaar, top!

Nog een variant op de code van rew, die wel 16-bit compatiebel is


void case8(void)
{
uint8_t *ptr;

PORTD |= (1<<PD5);

ptr = (uint8_t *) (BITtimer*4); // 8x8 -> 16 bit
ptr += ubBiphaser; // 16+16 bit add
ptr += (uint8_t *)ubPos; // 16+8 bit add
PORTA = *ptr;

PORTD &=~(1<<PD5);
}

void case9(void)
{
uint8_t *ptr;

PORTD |= (1<<PD5);

ptr = (uint8_t *) (BITtimer*4); // 8x8 -> 16 bit
ptr += ubBiphase; // 16+16 bit add
ptr += (uint8_t *)ubPos; // 16+8 bit add
PORTA = pgm_read_byte(ptr);

PORTD &=~(1<<PD5);
}

Hint: het uitschrijven van berekeningen en de tussenresultaten opslaan in een lokale variabele produceert vaak snellere code. Dus ipv. "BITtimer*4 + ubPos" kan je dus beter "temp = BITtimer; temp*=4; temp+=ubPos;" schrijven.

[Bericht gewijzigd door Gyrbo op (17%)]

Hmmm...

[code=C]
PORTD |= (1<<PD5);
PORTA = pgm_read_byte(&ubBiphase[ubPos*48+BITtimer]);
PORTD &=~(1<<PD5);

Listing:

PORTA = pgm_read_byte(&ubBiphase1[4*ubPos+BITtimer]);
3d8: e0 91 d3 01 lds r30, 0x01D3
3dc: 80 91 a4 01 lds r24, 0x01A4
3e0: ff 27 eor r31, r31
3e2: e7 fd sbrc r30, 7
3e4: f0 95 com r31
3e6: ee 0f add r30, r30
3e8: ff 1f adc r31, r31
3ea: ee 0f add r30, r30
3ec: ff 1f adc r31, r31
3ee: e8 0f add r30, r24
3f0: f1 1d adc r31, r1
3f2: ec 5a subi r30, 0xAC ; 172
3f4: ff 4f sbci r31, 0xFF ; 255
3f6: e4 91 lpm r30, Z+
3f8: eb bb out 0x1b, r30 ; 27

[/code]

Duurt 740uS

[code=C]

uint16_t pos;
pos = ubPos;
pos *= 4;
pos += BITtimer;

PORTD |= (1<<PD5);
PORTA = pgm_read_byte(&ubBiphase[pos]);
PORTD &=~(1<<PD5);

Listing:
pos = ubPos;
3d8: 80 91 d3 01 lds r24, 0x01D3
3dc: 99 27 eor r25, r25
3de: 87 fd sbrc r24, 7
3e0: 90 95 com r25
3e2: 90 93 b9 01 sts 0x01B9, r25
3e6: 80 93 b8 01 sts 0x01B8, r24
pos *= 4;
3ea: 80 91 b8 01 lds r24, 0x01B8
3ee: 90 91 b9 01 lds r25, 0x01B9
3f2: 88 0f add r24, r24
3f4: 99 1f adc r25, r25
3f6: 88 0f add r24, r24
3f8: 99 1f adc r25, r25
3fa: 90 93 b9 01 sts 0x01B9, r25
3fe: 80 93 b8 01 sts 0x01B8, r24
pos+= BITtimer;
402: 80 91 b8 01 lds r24, 0x01B8
406: 90 91 b9 01 lds r25, 0x01B9
40a: 20 91 a4 01 lds r18, 0x01A4
40e: 82 0f add r24, r18
410: 91 1d adc r25, r1
412: 90 93 b9 01 sts 0x01B9, r25
416: 80 93 b8 01 sts 0x01B8, r24
PORTA = pgm_read_byte(&ubBiphase1[pos]);
41a: e0 91 b8 01 lds r30, 0x01B8
41e: f0 91 b9 01 lds r31, 0x01B9
422: ec 5a subi r30, 0xAC ; 172
424: ff 4f sbci r31, 0xFF ; 255
426: e4 91 lpm r30, Z+
428: eb bb out 0x1b, r30 ; 27
[/code]

Duurt 1.86uS :-S

In dat laatste stukje, is die pos een lokale variabele (gedeclareerd in de functie zelf)? De enige reden waarom hij zulke code zou genereren is als het een globale variabele is.

Zijn ubPos en BITtimer trouwens uint8_t's? In dit geval lijkt het eerste stukje code zowat optimaal te zijn. Enige probleem dat ik zie is de sbrc + com combinatie, dit lijkt een sign extension te doen, wat overboding (en zelfs schadelijk) is voor unsigned variabelen.

Zo te zien kan je door het in RAM zetten van je array maximaal 1 clock winnen (LD = 2 vs. LPM = 3). Ik zou dus alles rustig in PROGMEM zetten en zo een hoop RAM besparen.

Korte conclusie: pgm_read_byte(&ubBiphase1[ubPos][BITtimer]); is de snelste manier om dit te doen zonder op hoger niveau je algoritme aan te passen.

[Bericht gewijzigd door Gyrbo op (11%)]

Op 8 november 2010 18:51:07 schreef Gyrbo:
In dat laatste stukje, is die pos een lokale variabele (gedeclareerd in de functie zelf)? De enige reden waarom hij zulke code zou genereren is als het een globale variabele is.

Dat is idd een globale variabele evenals BITtimer. Deze zijn globaal omdat de timer interrupt en de routine voor het bepalen van de positie deze variabelen gebruikt.

Op 8 november 2010 18:51:07 schreef Gyrbo:
Zijn ubPos en BITtimer trouwens uint8_t's? In dit geval lijkt het eerste stukje code zowat optimaal te zijn. Enige probleem dat ik zie is de sbrc + com combinatie, dit lijkt een sign extension te doen, wat overboding (en zelfs schadelijk) is voor unsigned variabelen.

Deze zijn inderdaad unsigned.

Op 8 november 2010 18:51:07 schreef Gyrbo:
Zo te zien kan je door het in RAM zetten van je array maximaal 1 clock winnen (LD = 2 vs. LPM = 3). Ik zou dus alles rustig in PROGMEM zetten en zo een hoop RAM besparen.

Heb ik inmiddels gedaan.

Op 8 november 2010 18:51:07 schreef Gyrbo:
Korte conclusie: pgm_read_byte(&ubBiphase1[ubPos][BITtimer]); is de snelste manier om dit te doen zonder op hoger niveau je algoritme aan te passen.

Dat is ook mijn conclusie nadat ik alle mogelijke manieren geprobeerd heb.

Het is dus hetvolgende geworden:

[code=C]

volatile uint8_t ubPos;
volatile uint8_t BITtimer;

ISR(TIMER1_OVF_vect)
{
PORTC = pgm_read_byte(&table[ubPos][BITtimer++]);
}

inline void PutBlock(uint16_t usData, uint16_t usOffSet)
{
......
if (BITtimer == 48)
{
BITtimer = 0;

ulBlock <<= 1;

ubPos = (ulBlock & 0x80000000 ? 1 : 0) ^ ubActual;

ubPos ^= ubActual;

ubPrev = ubLast;
ubLast = ubActual;
ubActual = ubPos;

ubPos = (ubPrev << 2) | (ubLast << 1) | ubActual;

BitsInShiftReg--;

....

}
}

[/code]

Iedereen bedankt voor het meedenken en de voorbeelden!

Mijn volgende uitdaging is het vereenvoudigen van een 10 bit CRC van een 16 bits block data.