Sida 1 av 6

Effektivisera C kod

Postat: 18 april 2007, 10:42:58
av ankan
Denna kod ska få en PIC att agera slav vilket gör den väldigt tidskritisk. Därför publicerar jag den här och hoppas att ni kan tycka till om hur jag skulle kunna effektivisera den så att den blir snabbare.

Kompilatorn är MikroC som tyvärr inte går att ställa in för att få en så snabb kod som möjligt. Jag skulle kunna lägga upp den C-kommenterade kompilerade ASM koden om ni vill.

Kod: Markera allt

/*
    Test konfiguration:
     MCU:               PIC16F88
     Dev.Board:       EasyPIC4
     Kristall      :      20.0000 MHz
     Ext. Modules:    -
     SW:              mikroC v6.2
*/

#include "USART.h"
#include "ERx00TRS.h"
#include "1Wire_Common.h"
#include "1Wire_Master.h"
#include "Common.h"

union
  {
  T_RF_Data_Struct Data;
  byte Buffer[1];
  } Rx_Data;
  
byte ROM_id[8];
byte SCRATCHPAD[8];
byte *ANSWARE_DATA;
byte ow_active = 0;


enum states { WAITING_FOR_RESET=0, GET_CMD, SEARCH_ROM_BIT, SEARCH_ROM_CMP_BIT, SEARCH_ROM_READ_BIT, MATCH_ROM_READ, ANSWARE_REQUEST } onewire_state;

void main() {

  init();

  ow_get_id(&ROM_id,OW_PORT,OW_PIN);
  ow_get_ds18b20_data(&SCRATCHPAD,OW_PORT,OW_PIN);
  
  onewire_state=WAITING_FOR_RESET;
  
  while(1) {}

}

  
void interrupt() {

byte olow_cmd;
byte bit_index;
byte byte_index;
byte bit_temp;


  if (INTCON.INTF) {

    switch (onewire_state) {

      case GET_CMD         : TRISB.F0=1;
                             olow_cmd >>= 1;
                             delay_us(10);
                             if(PORTB.F0)
                               olow_cmd |= 0x80;
                             bit_index++;

                             if (bit_index > 7) {

                               byte_index=0;
                               bit_index=0;

                               if (ow_active) {

                                  switch (olow_cmd) {

                                    case READ_SCRATCHPAD   :
                                                             onewire_state=ANSWARE_REQUEST;
                                                             ANSWARE_DATA=SCRATCHPAD;
                                                             bit_temp=(ANSWARE_DATA[byte_index] >> bit_index) & 0b00000001;
                                                             break;

                                    default                :
                                                             onewire_state=WAITING_FOR_RESET;
                                                             break;
                                  }
                                }
                                else {

                                  switch (olow_cmd) {

                                    case SEARCH_ROM        :
                                                             onewire_state=SEARCH_ROM_BIT;
                                                             bit_temp=(ROM_id[byte_index] >> bit_index) & 0b00000001;
                                                             break;

                                    case SKIP_ROM          :
                                                             ow_active=1;
                                                             break;

                                    case MATCH_ROM         :
                                                             onewire_state=MATCH_ROM_READ;
                                                             bit_temp=(ROM_id[byte_index] >> bit_index) & 0b00000001;
                                                             break;


                                    case READ_ROM          :
                                                             onewire_state=ANSWARE_REQUEST;
                                                             ANSWARE_DATA=ROM_id;
                                                             bit_temp=(ANSWARE_DATA[byte_index] >> bit_index) & 0b00000001;
                                                             break;

                                    default                :
                                                             onewire_state=WAITING_FOR_RESET;
                                                             break;
                                  }

                               }
                             }
                             break;
      case SEARCH_ROM_BIT      :
                                if(bit_temp) {
                                  MACRO_OLOW_HI
                                }
                                else {
                                  MACRO_OLOW_LO
                                }
                                  
                                delay_us(20);
                                MACRO_OLOW_HI
                                onewire_state=SEARCH_ROM_CMP_BIT;
                                break;

      case SEARCH_ROM_CMP_BIT  :
                                if(!bit_temp) {
                                  MACRO_OLOW_HI
                                }
                                else {
                                  MACRO_OLOW_LO
                                }
                                  
                                delay_us(20);
                                MACRO_OLOW_HI
                                onewire_state=SEARCH_ROM_READ_BIT;
                                break;

      case SEARCH_ROM_READ_BIT : delay_us(10);

                                onewire_state=SEARCH_ROM_BIT;

                                if (PORTB.F0!=bit_temp) {
                                  onewire_state=WAITING_FOR_RESET;
                                }

                                bit_index++;
                                if (bit_index>7) {
                                  bit_index=0;
                                  byte_index++;
                                }
                                if (byte_index>7) {
                                  bit_index=0;
                                  ow_active=1;
                                  onewire_state=GET_CMD;
                                }
                                else
                                  bit_temp=(ROM_id[byte_index] >> bit_index) & 0b00000001;

                                break;

      case MATCH_ROM_READ     : delay_us(10);

                                if (PORTB.F0!=bit_temp) {
                                  onewire_state=WAITING_FOR_RESET;
                                }

                                bit_index++;
                                if (bit_index>7) {
                                  bit_index=0;
                                  byte_index++;
                                }
                                if (byte_index>7) {
                                  bit_index=0;
                                  ow_active=1;
                                  onewire_state=GET_CMD;
                                }
                                else
                                  bit_temp=(ROM_id[byte_index] >> bit_index) & 0b00000001;

                                break;

      case ANSWARE_REQUEST     :
                                if(bit_temp) {
                                  MACRO_OLOW_HI
                                }
                                else {
                                  MACRO_OLOW_LO
                                }

                                delay_us(20);
                                MACRO_OLOW_HI

                                bit_index++;
                                if (bit_index>7) {
                                  bit_index=0;
                                  byte_index++;
                                }
                                if (byte_index>7) {
                                  bit_index=0;
                                  ow_active=1;
                                  onewire_state=GET_CMD;
                                }
                                else
                                  bit_temp=(ANSWARE_DATA[byte_index] >> bit_index) & 0b00000001;


                                break;


      case WAITING_FOR_RESET   :
                                break;

      default              : break;
    }
    TMR0=0;

    INTCON.INTF=0;
    INTCON.INTE=1;
  }
  
  if (INTCON.T0IF) {

    if (!PORTB.F0) {
      while(!PORTB.F0){}
      delay_us(14);
      MACRO_OLOW_LO
      delay_us(103);
      MACRO_OLOW_HI
      bit_index=0;
      TRISB.F0=1;   // Ska vara OLOW_DIR=1;
      TRISA.F0=0;   //För debugging
      INTCON.INTF=0;
      ow_active=0;
      onewire_state=GET_CMD;
     }

    INTCON.T0IF=0;
  }

}
  
void init() {

  ANSEL  = 0x00;
  TRISA  = 0b11111011;
  TRISB  = 0b11111111;
  PORTB  = 0b00000000;
  PORTA  = 0b00000000;

  INTCON = 0b10010000;                   // Enable GIE, RBIE

  OPTION_REG = 0x02;
  
  INTCON.T0IE=1;

}

Postat: 18 april 2007, 10:53:38
av sodjan
> Kompilatorn är MikroC som tyvärr inte går att ställa in för att få en så snabb kod som möjligt.

Vadå "ställa in" ??
Den ger väl redan "så snabb kod som möjligt" (för att vara C)... 8) 8)

Postat: 18 april 2007, 11:21:48
av TomasL
Att ha en massa kod i interrupt rutinen är väl inte så lämpligt, bättre måste väl vara att enbart bestämma vad som genererat interruptet och hantera det externt via ett anrop från main.

Postat: 18 april 2007, 11:33:18
av karlstedt
Sodjan> en C-kompilator brukar kunna kompilera på storlek eller snabbhet. Vet dock inte hur bra det fungerar...

Postat: 18 april 2007, 11:56:11
av sodjan
Visst, sure, men i detta fall är det så "tajt" så även den standardkod
som kompilatorn lägger in (för att spara unden sin *egen* kontext)
blir för mycket. D.v.s utan att ha någon "användarkod" alls i ISR'en.

Så har jag tolkat det i alla fall...

Och då kan man naturligtsvis hamna i den situationen att man
*måste* göra det som ska göras i ISR'en, men hinner helt enkelt
inte med att göra retfie (med den extra kod som kompilatorn
igen lägger in för att återställa sin egen kontext) för att ta hand
om det i main(), även om det generellt sätt skulle ge en "snyggare"
arkitektur på det hela...

*Pesonligen* anser jag att denna applikation (1-Wire client) är
on-the-edge för att klara utan en ren ASM version, man måste
helt enkelt ha bättre kontroll över processor-cyklerna än vad C
kompilatorn medger.

Postat: 18 april 2007, 12:01:46
av oJsan
Om du vill ha så snabb kod som möjligt så bör du plocka bort alla delay_us()... Anledningen att du har delayer är väl för att slöa ner något som går för fort. Då känns det lite motsägelsefullt att vilja köra koden snabbare..

Postat: 18 april 2007, 12:08:36
av Icecap
Det finns 2 lösningar:
1: Snabbare processor + ASM
2: Flytta en del till hårdvara.

Jag hade vald lösning 2 och byggt en hårdvara som klarar att skicka/ta emot en byte som sedan skickas/används av processorn.

Att göra en hårdvara som kan skicka en byte på 1-wire bussen är inte så himla svårt (en timer och en gate typ), kombinerar man det med att använda USART'en som synkron-port med extern klock bör det gå att få en interrupt för varje hela byte och behandla denna snabbt.

En möjlighet är att behandla alla signal i main-loopen och göra klar dom till de fall som kan tänkas uppstå, alltså räkna ut på förhand vad svaret ska vara på ROM-match, Read Temp. osv. sedan är interruptsvaret bara att flytta rätt data till lämpligt register.

Postat: 18 april 2007, 12:11:21
av ankan
Tyvärr känns det inte som jag har tid med att sätta mig ner och börja koda om allt i ASM i stället vilket säkert skulle få PICen att jobba effektivare.

Men hur tycker ni annars med strukturen med switch-case satser och if satser? För det är ju de som tar längst tid.

Tyvärr så dummar sig kompilatorn lite och gör en hel del hopp som igentligen inte skulle behövas men jag antar att det inte är så mycket att göra åt.

Att jag kör delay_us är för att jag måste hålla nere eller uppe 1-wire bussen en viss tid eller för att avläsa ett värde på 1-wire bussen som dyker upp efter en viss tid.

Borde jag gå över till PIC18 för att kunna köra på en snabbare processor pga av mitt timing problem eller ska jag försöka koda om interruptet till ASM kanske?

Det kommer tillkomma mer kod sedan som kommer köras i main vilket inte är lika tidskritiskt på samma sätt.

För övrigt att jag måste lösa timingen så kommer jag behöva en hel del minne och helst i PICen för att snabba upp det.

Är det RAM-minnet som alla variabler ligger i?

Postat: 18 april 2007, 12:15:40
av ankan
Beräknar redan svaret i förhand genom att sätta nästa värde i slutet av interruptet eftersom varje read eller write slot på 1-wire bussen är 60uS lång har jag för mig.

snabbar processor + ASM blir nog för mycket jobb, tror jag. Men jag vet inte hur jag ska lösa med extern hårdvara heller.
Det finns Seriell till 1-wire kretsar men det är för 1-wire mastern och inte för slaven tyvärr och kan därför inte jobba på rätt sätt. Maxim will väll inte att man ska skapa egna 1-wire slavara helst antar jag.

Postat: 18 april 2007, 12:18:35
av ankan
Kom på att man kanske skulle kunna plocka in allt data i ett 1-wire EPROM och sedan får den som är master på bussen plocka datat från den.
Dock kan man ju inte ha två mastrar på samma buss så man måste skunna koppla bort 1-wire kretsen för att vara master mot den och sedan sätta tillbaka den. Problemet är om 1-wire mastern på bussen skulle vilja komma åt enheten när man skriver till den.

Postat: 18 april 2007, 12:23:00
av TomasL
Nu vet jag ju inte vad din kompilator klarar av, men en teknik som jag använder mig av ibland är att först göra det i C, sedan byta ut en del av C-koden mot assembler.
Man ser ju hur kompilatorn har tänkt, och kan då använda en dela av dess assembler kod, och förbättra den.

Detta förutsätter förståss att kompilatorn kan hantera assembler i C-koden, min gör det, alla gör tyvärr inte det.

Hos mig kan det se ut ungefär så här:

Kod: Markera allt

void function(datatype data)
{
     en del c-kod:
     ytterligare c-kod;
     #asm
       tidskritisk assemblekod
       ytterligare assembler
     #asmend
     en massa c-kod;
}
Detta brukar fungera mycket bra, fördelen för mig är att kompilatoren helt hanterar minnestilldelning osv, så jag slipper tänka på den biten.
Bara att använda de addresser som kompilatorn har bestämt att de olika variablerna ligger på.

Postat: 18 april 2007, 12:39:13
av ankan
Visst kan man göra så och det kanske jag kommer göra om jag hittar några delar som jag skulle kunna göra effektivare i ASM.

Problemet med min kompilator MikroC är att den har massa hyss för sig när det uppkommer ett avbrott. Den sparar inte bara undan work-register mm utan den sparar undan någon typ av mjukvarustack också.
Har för mig att den gör så för att de har implementerat att man ska kunna göra funktionsanrop i avbrottet.

Hur som helst skulle det innebära att jag måste skriva hela interruptet i ASM vilket i mitt fall är typ all kod som det är nu. Skulle vilja kunna stänga av mjukvarustacken så skulle jag få bort typ 20-30% kod skulle jag gissa.

Postat: 18 april 2007, 12:53:22
av TomasL
Mjukvarustacken "måste" du ha om du skriver i C, Annars kan inte kompilatorn hantera funktionsanrop mm.
Det är ofta så att kompilatorn använder en scratch-pad för mellanlagring av temporära data, dessa måste sparas undan vid ett funktionsanrop.
Sedan är det så att de flesta PIC16 bara har en HW stack som är 1 anrop djup, vilket inte är tillräckligt, kompilatorn använder i stället mjukvarustacken för att lagra PC, W, mfl register, även de mjukvaruregister som kompilatorn använder, typ ACC mfl.
Oavsett vilken PIC du använder, så sparas INTE mjukvaru registren på HW-stacken, ej heller W-Reg eller Status-reg, enbart PC hamnar på HW-stacken.

Därför "måste" du ha en mjukvarustack.

Postat: 18 april 2007, 13:04:56
av ankan
Hur gör man i ASM då? För inte har man en mjukvarustack där inte. Borde man inte kunna komma ifrån att använda mjukvarustack i C?
Bara att sätta lite restriktioner i kodandet och se till att kompilatorn blir bättre. Finns det något program kanske som kan kompilera om en ASM fil så den blir effektivare?

Postat: 18 april 2007, 13:12:21
av v-g
Man kommer normalt inte åt stacken på en PIC16 tex finns inga PUSH eller POP-kommandon ens i ASM utan detta sköts av processorn.

Varför spara undan temporära variabler? Antigen skriver man en ny variabel eller återanvänder en gammal. Jämför med i i c (for i = 1 to 10 osv) som man kan ha i många funktioner där den används bara i just den funktionen så gör man INTE i ASM. Har man variabler som man förändrar data i så ja då förändrar man det i den variabeln. Men i i-exemplet ovan kan du ju återanvända i flera gången om vi säger att du istället för funktioner kodar allt i main, på samma sätt gör jag iaf i ASM.

Lokala och globala variabler finns inte i asm på samma sätt som i c. Är dock inte 100% på detta men så har jag förstått det iaf.

Finns optimeringsprogram iagf för PC vet jag sen om de fungerar är en annan femma. Frågan är verkligen om det är rätt väg att gå :roll: En usel lösning från början kan aldrig bli bra. (därmed inte sagt att C är en usel lösning) C kanske inte är rätt sak för tidskritiska applikationer på PIC. Tror att AVR är bättre när det gäller detta men det är så gott som en ren gissning.