Effektivisera C kod
Du måste alltid använd nån form av "mjukvaru-stack" när du skall göra funktions-anrop, vad man sedan kallar det för är av akademiskt intresse.
Oavsett vilket språk du anväönder, ASM, C, BASIC osv, så måste du på nått sätt spara undan vissa register när du gör ett funktionsanrop.
Pic 16 har vill jag minnas bara en stack med ett djup av 1 (rätta mig om jag har fel, har aldrig jobbat med PIC16), de pic18 som jag jobbar med har ett djup om 31 tror jag, det innebär att nästade funktionsanrop med en PIC16 är omöjliga utan mjukvarustack för PC, och för PIC18 går naturligtvis gränsen vid 31.
För att kunna hanter "n" antal funktionsanrop måste man allstså använda nån form av logisk mekanism för att snabbt kunna återställa diverse register och PC, och den enklaste (samt snabbaste) metoden är att använda en mjukvarustack, om du tittar på en 18F8x22 och liknande ser du att det finns tre hårdvaru-register för just stackhantering, orsaken är att PIC18 är konstruerad för just detta med högnivåspråk och stackar.
En pic16 har max ett sådant register vilket krånglar till saker i onödan, och PIC16 är inte anpassad för högnivåspråk såsom C mfl.
Mitt råd är, byt ut din PIC16 mot en 18F452a eller nått liknande så får du en betydligt effektivare processor (mha stackhantering), och som dessutom kan snurra lite fortare.
Oavsett vilket språk du anväönder, ASM, C, BASIC osv, så måste du på nått sätt spara undan vissa register när du gör ett funktionsanrop.
Pic 16 har vill jag minnas bara en stack med ett djup av 1 (rätta mig om jag har fel, har aldrig jobbat med PIC16), de pic18 som jag jobbar med har ett djup om 31 tror jag, det innebär att nästade funktionsanrop med en PIC16 är omöjliga utan mjukvarustack för PC, och för PIC18 går naturligtvis gränsen vid 31.
För att kunna hanter "n" antal funktionsanrop måste man allstså använda nån form av logisk mekanism för att snabbt kunna återställa diverse register och PC, och den enklaste (samt snabbaste) metoden är att använda en mjukvarustack, om du tittar på en 18F8x22 och liknande ser du att det finns tre hårdvaru-register för just stackhantering, orsaken är att PIC18 är konstruerad för just detta med högnivåspråk och stackar.
En pic16 har max ett sådant register vilket krånglar till saker i onödan, och PIC16 är inte anpassad för högnivåspråk såsom C mfl.
Mitt råd är, byt ut din PIC16 mot en 18F452a eller nått liknande så får du en betydligt effektivare processor (mha stackhantering), och som dessutom kan snurra lite fortare.
Angående PIC16
Saxxat ur manualen:
Saxxat ur manualen:
4.3.2 STACK
The PIC16F627A/628A/648A family has an 8-level
deep x 13-bit wide hardware stack (Figure 4-1). The
stack space is not part of either program or data space
and the Stack Pointer is not readable or writable. The
PC is PUSHed onto the stack when a CALL instruction
is executed or an interrupt causes a branch. The stack
is POPed in the event of a RETURN, RETLW or a
RETFIE instruction execution. PCLATH is not affected
by a PUSH or POP operation.
För att fixa ett avbrott i PIC16-serien utan att fucka up STATUS, register osv (alltså helt transparent) gör jag följande:
Ja, det är "gammal" absolut kod men det duger åt mig. Det är ung. det samma som kompilern gör så jag ser inget problem i detta, vill du ha ett snabbare sätt är det dags att byta processor.
Kod: Markera allt
org 0x004 ; interrupt vector location
bcf PIR1,TMR1IF ; Clear interrupt flag first of all
movwf w_temp ; save off current W register contents
movf STATUS,W ; move status register into W register
movwf status_temp ; save off contents of STATUS register
movf PCLATH,W ; Read it
movwf pclath_temp ; Save it for later
clrf PCLATH ; Zerorize it
......; Do the woodoo that i do so well
ExitInt
movf status_temp,W ; retrieve copy of STATUS register
movwf STATUS ; restore pre-isr STATUS register contents
movf pclath_temp,W
movwf PCLATH
swapf w_temp,F
swapf w_temp,W ; restore pre-isr W register contents
retfie ; return from interrupt
Jo man kan med hjälp av sinrik kodning komma åt HW-stacken, men det ligger utanför denna diskussionen.v-g skrev: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å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.
Det är inte temporära variabler man vill spara undan utan snarare de lokala varibler man har i en funktion, eftersom de flesta kompilatorer använder ett gemensamt utrymme för just dessa lokala variabler, så måste dessa sparas undan så de inte blir överskrivna.
Låter som att PIC18 är rätt väg att gå om jag vill hålla mig till C-programmering och vill ha effektiv kod.
Ang stackdjupet hade jag för mig att det var 8 på PIC16.
Ska nog ta och kolla på PIC18 familjen. Vilket typ av minne är det som är snabbast på PICen som man kan mellanlagra data som man får in från UART?
EEPROMen är väll bara att glömma. Men är det RAM-minnet man ska kolla på när man väljer PIC?
Frågar min fråga om ni missade den. Finns det inget 1-wire minne som man skulle kunna lagra data i på annat sätt än via 1-wire och sedan låta mastern läsa av 1-wire minnet. Att använda samma ingång och utgång för att switcha mellan 1-wire mastrarna är väll bara att glömma.
Ang stackdjupet hade jag för mig att det var 8 på PIC16.
Ska nog ta och kolla på PIC18 familjen. Vilket typ av minne är det som är snabbast på PICen som man kan mellanlagra data som man får in från UART?
EEPROMen är väll bara att glömma. Men är det RAM-minnet man ska kolla på när man väljer PIC?
Frågar min fråga om ni missade den. Finns det inget 1-wire minne som man skulle kunna lagra data i på annat sätt än via 1-wire och sedan låta mastern läsa av 1-wire minnet. Att använda samma ingång och utgång för att switcha mellan 1-wire mastrarna är väll bara att glömma.
Nej, det är inte så mycket egentligen men då kompilern ALLTID är satt till att ha en totalt transparent ISR blir det lite overhead, jag vet inte hur det är med AVR men jag vet att processorer som är "lite större" har ofta möjlighet att byta registerbank, antingen automatisk vid interrupt eller med en enkelt instruktion ("välj register bank x") just för att få kort latency.
En sak jag har märkt när jag använde C på "enklare" processorer är att switch/case brukar bara en dyr konstruktion och if/else billigare...
Så kolla upp asmeblerkoden runt switch/case kontra if/else...
Använde IAR's kompilator till en processor och det var många rader asmebler extra i switch/case statsen som jag "optimerade" bort genom att byta ut den till en enkel if/else kedja.
När man ökade optimeringarna lite försvann en del av "slentrian" koden i kompilatorn... Dvs. den slutade att alltid allockera stack för funktionen trots att den inte hade anropsparametrar och inte använde lokala variabler...
Så kolla upp asmeblerkoden runt switch/case kontra if/else...
Använde IAR's kompilator till en processor och det var många rader asmebler extra i switch/case statsen som jag "optimerade" bort genom att byta ut den till en enkel if/else kedja.
När man ökade optimeringarna lite försvann en del av "slentrian" koden i kompilatorn... Dvs. den slutade att alltid allockera stack för funktionen trots att den inte hade anropsparametrar och inte använde lokala variabler...
Var rädd för att switch-case skulle vara en dyr grej. Ska prova att skriva om till if-else satser i stället. Några andra tips?
Här får ni ett exempel hur den håller på och hoppar omkring. Koden som är skriven i C är följande:
Den sätter uarten till 9600 baud och skickar tecknet X. Den kompilerade C-koden blir följande:
Inte särskillt effektiv alltså. Tycker den hoppar för mycket. Varför inte stoppa in all kod i rad utan massa hopp. Skulle gärna vilja att den duplicerade koden på en del ställen för att öka hastigheten. Har plats så det räcker i alla fall.
Här får ni ett exempel hur den håller på och hoppar omkring. Koden som är skriven i C är följande:
Kod: Markera allt
void main() {
Usart_Init(9600);
Usart_Write('X');
}
Kod: Markera allt
; ADDRESS OPCODE ASM
; ----------------------------------------------
$0000 $2824 GOTO _main
$0004 $ _Usart_Write:
$0004 $ L_Usart_Write_3:
$0004 $3000 MOVLW 0
$0005 $1303 BCF STATUS, RP1
$0006 $1683 BSF STATUS, RP0
$0007 $1898 BTFSC TXSTA, 1
$0008 $3001 MOVLW 1
$0009 $00F1 MOVWF STACK_1
$000A $0871 MOVF STACK_1, 0
$000B $3A00 XORLW 0
$000C $1D03 BTFSS STATUS, Z
$000D $2810 GOTO L_Usart_Write_4
$000E $0000 NOP
$000F $2804 GOTO L_Usart_Write_3
$0010 $ L_Usart_Write_4:
$0010 $1283 BCF STATUS, RP0
$0011 $0820 MOVF FARG_Usart_Write+0, 0
$0012 $0099 MOVWF TXREG
$0013 $0008 RETURN
$0014 $ _Usart_Init:
$0014 $1303 BCF STATUS, RP1
$0015 $1683 BSF STATUS, RP0
$0016 $1698 BSF TXSTA, 5
$0017 $3090 MOVLW 144
$0018 $1283 BCF STATUS, RP0
$0019 $0098 MOVWF RCSTA
$001A $1683 BSF STATUS, RP0
$001B $1787 BSF TRISC, 7
$001C $1307 BCF TRISC, 6
$001D $ L_Usart_Init_0:
$001D $1283 BCF STATUS, RP0
$001E $1E8C BTFSS PIR1, 5
$001F $2823 GOTO L_Usart_Init_1
$0020 $081A MOVF RCREG, 0
$0021 $00A4 MOVWF Usart_Init_tmp_L0
$0022 $281D GOTO L_Usart_Init_0
$0023 $ L_Usart_Init_1:
$0023 $0008 RETURN
$0024 $ _main:
;Led_Blinking.c,19 :: void main() {
;Led_Blinking.c,20 :: Usart_Init(9600);
$0024 $3033 MOVLW 51
$0025 $1303 BCF STATUS, RP1
$0026 $1683 BSF STATUS, RP0
$0027 $0099 MOVWF SPBRG
$0028 $1518 BSF TXSTA, BRGH
$0029 $2014 CALL _Usart_Init
;Led_Blinking.c,21 :: Usart_Write('X');
$002A $3058 MOVLW 88
$002B $00A0 MOVWF FARG_Usart_Write+0
$002C $2004 CALL _Usart_Write
;Led_Blinking.c,22 :: } //~!
$002D $282D GOTO $
> Skulle gärna vilja att den duplicerade koden på en del ställen för att öka hastigheten...
Hm, det är sådant som avancerande kompilatorer (eller assembler programmerare
) kan ägna sig åt.
Jag tror inte att MikroC kan räknas till de avancerade kompilatorerna, det
är lite leksak över det (i alla fall gratis versionerna) jämfört med
de stora verktygen. Deras katastrofer till manualer säger väl allt...
Sen måste jag kommentera den genererade koden som du visade...
Notera dessa rader :
BTFSC TXSTA, 1
BTFSS PIR1, 5
och :
BTFSS STATUS, Z
BSF TXSTA, BRGH
Varför i jösse namn blandar de *numeriska* bitvärden med *symboliska* ?
Det spelar ju ingen roll för resultatet, men för den som ska läsa
koden är det oerhört störande. Oproffsigt...
> Tycker den hoppar för mycket.
Var då ? Jag ser inget så där väldigt "hoppigt"...
Hm, det är sådant som avancerande kompilatorer (eller assembler programmerare

Jag tror inte att MikroC kan räknas till de avancerade kompilatorerna, det
är lite leksak över det (i alla fall gratis versionerna) jämfört med
de stora verktygen. Deras katastrofer till manualer säger väl allt...
Sen måste jag kommentera den genererade koden som du visade...
Notera dessa rader :
BTFSC TXSTA, 1
BTFSS PIR1, 5
och :
BTFSS STATUS, Z
BSF TXSTA, BRGH
Varför i jösse namn blandar de *numeriska* bitvärden med *symboliska* ?
Det spelar ju ingen roll för resultatet, men för den som ska läsa
koden är det oerhört störande. Oproffsigt...
> Tycker den hoppar för mycket.
Var då ? Jag ser inget så där väldigt "hoppigt"...
Är det inte möjligt att skriva hela avbrottsrutinen i asm (alltså inte inline) och resten i C?
Då kan man använda assemblerns fördelar med små effektiva avbrottsrutiner utan att behöva skriva hela applikationen i asm. Eftersom du inte behöver mjukvarustack och andra "C-features" i en asm-ISR så skulle man isåfall kunna byta ut hela avbrottsinitieringen och återställningen mot Icecaps asmversion.
Det är ju naturligtvis beroende på vad MikroC stödjer i fråga att länka ihop kod av olika typ i resp. delar av applikationen.
Då kan man använda assemblerns fördelar med små effektiva avbrottsrutiner utan att behöva skriva hela applikationen i asm. Eftersom du inte behöver mjukvarustack och andra "C-features" i en asm-ISR så skulle man isåfall kunna byta ut hela avbrottsinitieringen och återställningen mot Icecaps asmversion.
Det är ju naturligtvis beroende på vad MikroC stödjer i fråga att länka ihop kod av olika typ i resp. delar av applikationen.
Hittade nu att man kan göra följande för att få en viss funktion att hamna på en viss adress:
Tror ni det kommer lira utan att spara undan några mjukvarustackar? Tänker mest på att jag kommer ju ha en kod som ligger och snurrar i main som blir avbruten då och då när det blir avbrott för 1-wire kommunikation.
För övrigt skulle jag kunna göra om switch-case satserna till if-else satser och köra ASM-inline där det behövs. Detta borde väll få koden att bli rätt effektiv i alla fall.
Kod: Markera allt
void avbrott() org 0x04 {
// Nu hamnar jag här vid avbrott utan någon mjukvarustack eller spar av workregister mm.
}
För övrigt skulle jag kunna göra om switch-case satserna till if-else satser och köra ASM-inline där det behövs. Detta borde väll få koden att bli rätt effektiv i alla fall.