PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Das Gleiche ist nicht das Selbe - Laufzeitunterschiede



oberallgeier
28.10.2009, 08:28
Hallo alle,

bei zwei sehr kurzen, prinzipiell gleichen Codestücken habe ich Laufzeitunterschiede festgestellt.

Im Thread von copius über die empfohlene Vorgehensweise für Programmaufbau (https://www.roboternetz.de/phpBB2/viewforum.php?f=34&sid=6e9de270920addadb006aab3f003de32) wurde ein hübsches Beispiel zur Erleichterung der Les- und Schreibbarkeit von C durch askazo (https://www.roboternetz.de/phpBB2/zeigebeitrag.php?p=457999&sid=6e9de270920addadb006aab3f003de32#457999) vorgestellt. Es betrifft einfache Bitoperationen zum Setzen und Löschen von Port-/Registerpins. Hier mal 1:1 kopiert:

Zitatanfang:
#define SetBit(ADDRESS,BIT) ((ADDRESS) |= (1<<(BIT))) //!< Setzt ein bestimmtes Bit eines Registers
#define ClrBit(ADDRESS,BIT) ((ADDRESS) &= ~(1<<(BIT))) //!< Löscht ein bestimmtes Bit eines Registers
#define ToogleBit(ADDRESS,BIT) ((ADDRESS) ^= (1<<(BIT))) //!< Toogelt ein bestimmtes Bit eines Registers
#define IsBitSet(ADDRESS,BIT) (((ADDRESS) & (1<<BIT))?1:0) //!< Fragt ein bestimmtes Bit eines Registers ab
So wird zum Beispiel aus der recht kryptischen Abfrage eines Eingangs auf PB4
if (PINB &(1<<PB4)) {}
ein recht einfaches
if (IsBitSet(PINB,4)) {}
und das ohne dass der Compiler einen anderen Code draus macht. (Ende des Zitates)

Klasse - sagte ich, und verwendete die beiden ersten #defines auch in meinem Programm.

In meinen Programmen habe ich anfangs, vor allem ausführbaren Code - insbesondere vor irgendwelchen Interrupts, eine for-Schleife, die mit rund 10 Hz eine StatusLED 50 x blinken lässt. Das hat sich bei mir in der Entwicklungsphase bewährt, um ungewollte Resets zu erkennen; diese Blinksequenz tritt nur und ausschließlich hier auf.

Leider stellt sich beim Verwenden der hübschen #defines von askazo heraus, das nun die Blinkfrequenz nicht mehr stimmt. Also Suchen und Vergleichen - nicht sooo pfiffig in zwei *.lls mit 144 KB resp. fast 3400 Zeilen. Code gekürzt auf ein Minimum - und verglichen. Vielleicht habe ich ein wichtiges Detail übersehen, aber ich sehe keinen Unterschied. Auffällig ist, dass die beiden Schleifen hintereinander geschrieben mit unterschiedlicher Frequenz blinken.

Frage: Hat bitte jemand eine Erklärung dafür?

Mein Code - vollständig. WinXP-SP3, AVRStudio 4.16, Build 638, mega328 mit 20 MHz, Platine in meinem MiniD0.

/* >>
Sicherung 28Okt09 0850 ..\C2\tst328_10\tst328_10.c war D01_21x00.c
================================================== =================================
Target MCU : ATmega368P
Target Hardware : miniDO = R3D01
Target cpu-frequ. : 20 MHz, externer Quarzoszillator
================================================== =================================
Enthaltene Routinen :

void waitms(uint16_t ms) // Delay
int main(void)
================================================== =================================
*** Versionsgeschichte:
====================
x10 28Okt09 0850 Test Startblink mit und ohne defines
x00 27Okt09 2330 Test des Startloops
================================================== =================================
================================================== ============================== */

#include <stdlib.h>
#include <avr/io.h>
// #include <avr/interrupt.h>

#define MCU = AVR_ATmega368p
#define F_CPU 20000000 // Quarz 20 Mhz-CPU

// === #defines der PortPins beim 328p ===========================================
// ================================================== ===============================
#define PC5 5 // Pindefinition _ _ _ PortC/PDIP328p nur bis PC5

#define SetBit(ADDR,BIT) ((ADDR) |= (1<<(BIT))) // Setzt Bit
#define ClrBit(ADDR,BIT) ((ADDR) &= ~(1<<(BIT))) // Löscht Bit

// ================================================== ===============================
// ===== Subroutinen ================================================== ===========
// ================================================== ===============================
/*### Programm pausieren lassen !! Der Pausenwert ist nur experimentell !*/

void waitms(uint16_t ms)
{
for(; ms>0; ms--)
{
uint16_t __c = 4000;
__asm__ volatile (
"1: sbiw %0,1" "\n\t"
"brne 1b"
: "=w" (__c)
: "0" (__c)
);
}
}
/* ================================================== ============================ */
/* ===== ENDE Subroutinen ================================================== */
/* ================================================== ============================ */



// ================================================== ===============================
// === HAUPTProgramm ================================================== ============

int main(void)
{
uint8_t i;

// Pins/Ports als Ein- (0) oder Ausgänge (1) konfigurieren, Pull Ups (1) aktivieren
// A = Ausgang, E = Eingang ohne , EU = Eingang MIT PullUp
//
DDRC = 0b01110000; // PC3 ist ADC3, PC0 .. 6 , kein PC7-Pin bei m168
PORTC = 0b00000111; // Beachte für ADC: PC3 ist ADC-Eingang ##>> OHNE Pullup !!
//
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

for(i=0; i<50; i++) // gLED auf PC5 i-fach blinken lassen OHNE Interrupts
{
PORTC |= (1<<PC5); // LED auf PC5 schalten EIN, HELL
waitms(3); // ... damit man kurze resets besser erkennt
PORTC &= ~(1<<PC5); // LED auf PC5 schalten AUS, Dunkel
waitms(97);
}


for(i=0; i<50; i++) // gLED auf PC5 i-fach blinken lassen OHNE Interrupts
{
SetBit(PINC, 5); // LED auf PC5 schalten EIN, HELL
waitms(3); // ... damit man kurze resets besser erkennt
ClrBit(PINC, 5); // LED auf PC5 schalten AUS, Dunkel
waitms(97);
}

return 0;
}
// ===== Ende =====
// ================================================== ==============================

sternst
28.10.2009, 08:40
PORTC |= (1<<PC5); // LED auf PC5 schalten EIN, HELL
...
SetBit(PINC, 5); // LED auf PC5 schalten EIN, HELL

oberallgeier
28.10.2009, 08:51
Danke Stefan. Jetzt weiß ich, warum bei mir immer alles so lange dauert - und warum ich mich immer noch als C-Anfänger sehe. Kann mich nicht mal auf gestern Mitternacht berufen, weil ich es heute morgens noch mal geprüft hatte :(.

Nun laufen beide Versionen gleich schnell. Danke.

GeoBot
28.10.2009, 10:00
Ist das jetzt ein C Problem oder eines der Controller-Architektur?

oberallgeier
28.10.2009, 10:17
Ist das jetzt ein C Problem oder eines der Controller-Architektur?Das ist doch jetzt eine Scherzfrage, oder?

Es ist das Problem des Software-Autors mit der meist bekannten Konvention, dass Pinnausgänge, also Datenrichtung, d.h. Pindefinition als Ein- oder Ausgang, mit der Kurzbezeichnung PORTx identifiziert werden, während Pinzustände, d.h. Abfrage, ob ein Pin auf GND oder Vcc oder irgendwo dazwischen ist, üblicherweise mit PINx identifiziert werden. PINx war also mein Fehler, da ich ein- und ausschalten wollte. Durch PORTx läuft alles bestens. Im Prinzip ist mir das klar, aber ich hatte ein Brett vorm Kopf. Merke: selbst noch so dünne Bretter wollen gebohrt werden, auf lateinsich: mea culpa.

GeoBot
28.10.2009, 11:56
Ja und Nein :-)

Es ist also nicht wirklich ein C-Problem. Denn es
würde auch in jeder anderen Sprache wohl den
gleichen (selben) Effekt bewirken.

Allerdings hatte ich nur mit einer Phasenver-
schiebung gerechnet.

Aus dem Datenblatt (Rev. 8161D–AVR–10/09)

" Three I/O memory address locations are allocated
for each port, one each for the Data Register – PORTx,
Data Direction Register – DDRx, and the Port Input
Pins – PINx. The Port Input Pins I/O location is read
only, while the Data Register and the Data Direction
Register are read/write.

However, writing a logic one to a bit in the PINx
Register, will result in a toggle in the corresponding
bit in the Data Register. "

:-k #-o Ok: Im zweiten Fall blinkt die LED mit einer
Periode von 200 ms und einer Duty-Cycle von 50%. :-b

"logic zerro" tastet das Data Register nicht an. Ich denke
immer noch zu sehr "PIC".

Besserwessi
28.10.2009, 16:25
In der Makrodefinition von "IsBitSet(...)" sollte noch ein Klammer mehr hin: 1<<Bit kann sonst unerwartete ergebnisse geben wenn man z.B. schreibt: IsBitSet(PINB,1+3). Kommt wahrscheinlich selten vor, kann aber sehr schwer zu findene Fehler geben.


Edit:
Ups, da ist noch ein Fehler: Der Controller ist wohl eher Mega328 nicht Mega368. Das könnte einiges an Fehler verurschen. Eigentlich sollte es dann aber auch Warnungen geben, oder der in der IDE eingestelle µC wird benutzt.

askazo
28.10.2009, 16:40
In der Makrodefinition von "IsBitSet(...)" sollte noch ein Klammer mehr hin: 1<<Bit kann sonst unerwartete ergebnisse geben wenn man z.B. schreibt: IsBitSet(PINB,1+3). Kommt wahrscheinlich selten vor, kann aber sehr schwer zu findene Fehler geben.
Uups, stimmt, danke für den Hinweis.
Bei den anderen drei Makros hab ich's auch gemacht, bei dem letzten wohl vergessen...

oberallgeier
28.10.2009, 17:08
Erstmal allen vielen Dank für die ausführlichen Informationen. Schon nach dem Posting von stefan lief ja das Ganze korrekt - aber nun weiß ich ein bisschen mehr.


... noch ein Fehler: Der Controller ist wohl eher Mega328 nicht Mega368 ...Ohhhh - stimmt, der Mega368 wird ja noch garnicht gebaut/ausgeliefert. Zum Glück wurde der 328p in der IDE eingestellt - das AVRStudio bringt ja sein eigenes Device-Flyout mit. Das rettet mich vor Auswirkungen durch diesen dämlichen Fehler.

Danke allen.

oberallgeier
29.10.2009, 08:38
Hallo alle,

zur falschen Definition der MCU im Code als nichtexistener 368P gibt es eine Vermutung: Die Vorgänger MCU war ein mega168, da ist es wahrscheinlich, dass die mittlere Ziffer nicht aktualisiert wurde.

Der Grund, warum der falsche Code vom Compiler nicht angemeckert wird, ist mir noch nicht klar. Der ist aber auch weniger wichtig, als die Antwort auf die Frage, warum der ausführbare Code a) läuft und b) in der inkorrekten Variante zu einer längeren Einschaltdauer des betreffenden Pinns führt. Da muss ich bei Gelegenheit die *.lls nochmal durchforsten. Ein Durchgehen der iom328p.h (zu WinAVR-20090313) hatte keinen Hinweis gebracht.

Es ist nicht möglich, dass die fehlerhafte MCU-Bezeichnung auf eine bayerische Zifferntastatur zurückzuführen ist, da die bekanntlich keine 6 enthält. Ihr kennt die bayerische Zifferntastatur nicht? http://oberallgeier.ob.funpic.de/bayzifftast.gif

sternst
29.10.2009, 09:10
Der Grund, warum der falsche Code vom Compiler nicht angemeckert wird, ist mir noch nicht klar. Der ist aber auch weniger wichtig, als die Antwort auf die Frage, warum der ausführbare Code a) läuft und b) in der inkorrekten Variante zu einer längeren Einschaltdauer des betreffenden Pinns führt.Das hat doch GeoBot eigentlich schon erklärt.
Das Schreiben einer 1 nach PINx toggelt den Ausgang (nicht bei allen AVRs, nur bei "neueren") , eine 0 hat keine Auswirkung.
Einmal Toggeln pro Schleifendurchlauf => halbe Frequenz mit 50% Duty-Cycle

Und warum bitte sollte der falsche Code vom Compiler angemeckert werden?
Falls sich das auf das MCU-define bezieht, das ist eh völlig ohne Funktion. Da hättest du auch schreiben können:

#define MCU WirdGarNichtVerwendet

oberallgeier
29.10.2009, 09:31
Danke, ja, das wurde schon erklärt - aber ich habe das noch nicht durchgearbeitet. Gefunden und gelesen hatte ich zwar die Passage schon in meinem Doc 8161C–AVR–05/09, die ist übrigens gleich und auf derselben Seite wie im 8161D. Das muss ich mal in Ruhe durchgehen, um die 50% Duty Cycle und die 200 ms zu begreifen.

Danke jedenfalls für die geduldigen Erläuterungen - und den dontdefinetumuch-Hinweis. Letzterer hängt ein bisschen damit zusammen, dass vom AVRStudio eine so gute Unterstützung geliefert wird. Durch die hatte ich mich anfangs über mache Probleme mancher Kollegen mit dem makefile gewundert . . . das ich garnicht kannte *ggg*. Meine MCU-Definition im Code bleibt aber weiter aus Dokumentationsgründen (auch wenns nicht immer stimmt).

Ich hätte jedenfalls ein Meckern des Compilers erwartet - der ja auch mein PC5 beim mega168 problemlos verstanden hatte; für den mega328 brauchte ich dafür gesonderte defines in meiner ~com~.h.

sternst
29.10.2009, 09:57
Das muss ich mal in Ruhe durchgehen, um die 50% Duty Cycle und die 200 ms zu begreifen.Ach komm, so kompliziert ist das nicht ;-)

|-----Schleife-----||-----Schleife-----||-----Schleife-----||-----Schleife-----|
1---0------------- 1---0------------- 1---0------------- 1---0-------------
PORT an--aus----------- an--aus----------- an--aus----------- an--aus-----------
PIN tog-nix----------- tog-nix----------- tog-nix----------- tog-nix-----------
=> an---------------- aus--------------- an---------------- aus---------------


Ich hätte jedenfalls ein Meckern des Compilers erwartet - der ja auch mein PC5 beim mega168 problemlos verstanden hatte; für den mega328 brauchte ich dafür gesonderte defines in meiner ~com~.h.Hä? Du meinst, bei deinem WinAVR fehlt in den Controller-Defines für den Mega328P das PC5?

oberallgeier
29.10.2009, 10:25
... Ach komm, so kompliziert ist das nicht ...Ahhh - ja, danke, wenn Du das so schön erklärst . . . Und vermutlich haben dann die beiden Befehle SetBit(PINC, 5); und ClrBit(PINC, 5); immer (nur) getoggelt. DAS liesse sich ja praktisch kontrollieren - weil dann in einem 50xBlinke-Loop nur 25 Blinkies erscheinen dürften (habe ich jetzt richtig gedacht?).


... bei deinem WinAVR fehlt in den Controller-Defines für den Mega328P das PC5?Im Prinzip ja. Das heißt in der iom328p.h vom WinAVR-20090313 nämlich so:

/* Copyright (c) 2007 Atmel Corporation....*/
....
/* $Id: iom328p.h,v 1.3.2.14 2009/02/11 18:05:28 arcanum Exp $ */
....
/* avr/iom328p.h - definitions for ATmega328P. */
....
/* This file should only be included from <avr/io.h>, never directly. */
....
/* Registers and associated bit numbers */

#define PINB _SFR_IO8(0x03)
#define PINB0 0
#define PINB1 1
#define PINB2 2
#define PINB3 3
#define PINB4 4
#define PINB5 5
#define PINB6 6
#define PINB7 7

#define DDRB _SFR_IO8(0x04)
#define DDB0 0
#define DDB1 1
#define DDB2 2
#define DDB3 3
#define DDB4 4
#define DDB5 5
#define DDB6 6
#define DDB7 7

#define PORTB _SFR_IO8(0x05)
#define PORTB0 0
#define PORTB1 1
#define PORTB2 2
#define PORTB3 3
#define PORTB4 4
#define PORTB5 5
#define PORTB6 6
#define PORTB7 7

#define PINC _SFR_IO8(0x06)
#define PINC0 0
#define PINC1 1
#define PINC2 2
#define PINC3 3
#define PINC4 4
#define PINC5 5
#define PINC6 6

#define DDRC _SFR_IO8(0x07)
#define DDC0 0
#define DDC1 1
#define DDC2 2
#define DDC3 3
#define DDC4 4
#define DDC5 5
#define DDC6 6

#define PORTC _SFR_IO8(0x08)
#define PORTC0 0
#define PORTC1 1
#define PORTC2 2
#define PORTC3 3
#define PORTC4 4
#define PORTC5 5
#define PORTC6 6
und wurde von mir in meiner ~com~.h so ersetzt (weil ich es eben anders gewöhnt bin):

// === #defines der PortPins beim 328p ===========================================
// === Portpins sind beim 328p definiert als PINB1 bzw. PORTB1 ===================
// ================================================== ===============================
#define PB0 0 // Pindefinition
#define PB1 1 // Pindefinition
#define PB2 2 // Pindefinition
#define PB3 3 // Pindefinition
#define PB4 4 // Pindefinition
#define PB5 5 // Pindefinition
#define PB6 6 // Pindefinition
#define PB7 7 // _ // Pindefinition _ _ _ Port B hier zu Ende
#define PC0 0 // Pindefinition
#define PC1 1 // Pindefinition
#define PC2 2 // Pindefinition
#define PC3 3 // Pindefinition
#define PC4 4 // Pindefinition
#define PC5 5 // Pindefinition _ _ _ PortC/PDIP328p nur bis PC5Schien mir sicherer (weniger fehlerträchtig) als die neuen Pinbezeichnungen - vor allem, wenn ich mal wieder vom m328p auf den m168 zurückgehen will.

So - und nun schalte ich erstmal aus (und gehe noch ne Runde fliegen).

Danke für Deine Unterstützung-

sternst
29.10.2009, 10:36
Und vermutlich haben dann die beiden Befehle SetBit(PINC, 5); und ClrBit(PINC, 5); immer (nur) getoggelt.SetBit(PINC, 5) toggelt, und ClrBit(PINC, 5); macht gar nichts.


Im Prinzip ja. Das heißt in der iom328p.h vom WinAVR-20090313 nämlich so:Ah tatsächlich. Anscheinend hat Atmel die Namensgebung etwas geändert. Die Defines werden nämlich automatisch aus Atmels Device-Description-Files generiert.


Meine MCU-Definition im Code bleibt aber weiter aus DokumentationsgründenWürde ich nicht machen. Du hast doch schon im Kommentar am Anfang der Datei den Controller stehen. So ein funktionsloses Define stiftet höchstens Verwirrung, wie man ja hier gesehen hat. ;-)

oberallgeier
29.10.2009, 10:48
... Würde ich nicht machen ... ein funktionsloses Define stiftet höchstens Verwirrung ...Ok.

Übrigens: Bis auf dieses Detail mit den Portpins ist der m328p nach meinen bisherigen, eher bescheidenen Erfahrungen aber code-kompatibel. (Beispiel: Code total ca. 2800 Zeilen, davon ca. 50+ % Kommentar, Hex 20 KB bzw: Program: 7236 bytes (22.1% Full) Data: 418 bytes (20.4% Full)).

Jaecko
29.10.2009, 13:46
Es ist nicht möglich, dass die fehlerhafte MCU-Bezeichnung auf eine bayerische Zifferntastatur zurückzuführen ist, da die bekanntlich keine 6 enthält. Ihr kennt die bayerische Zifferntastatur nicht? http://oberallgeier.ob.funpic.de/bayzifftast.gif

Wer's noch nicht kennt:
http://www.affengeile-geschenke.de/artikelbilder/tastatur_ausschnitt.jpg

oberallgeier
29.10.2009, 19:15
... Ach komm, so kompliziert ist das nicht ...Stimmt, es ist tatsächlich garnicht kompliziert. Der Text der Dokumentation vom Mai 09 ist derselbe wie der vom Oktober 09 und für mich wirklich gut verständlich. Was die Entwickler dabei gedacht hatten - oder ist anzunehmen, dass diese Funktion sich einfach ergeben hat? Egal. Bleibt für mich allenfalls nur noch übrig zu klären, ob das Ding im roten Kreis ein FET ist?

................http://oberallgeier.ob.funpic.de/IO-Pin_schematic_k.gif

Allen herzlichen Dank für Mühe und Geduld.

PICture
29.10.2009, 19:25
Hallo!

@ oberallgeier

Laut Symbol ist es ein MOSFET (metall oxid semiconductor FET). Der Unterschied im Bau zum JFET (juncion FET) ist nur, dass Gate (G) von Kanal (S-D Strecke) isoliert ist.

MfG

sternst
29.10.2009, 19:38
Was die Entwickler dabei gedacht hatten - oder ist anzunehmen, dass diese Funktion sich einfach ergeben hat?Ne, das war schon Absicht. ;-)
Das gibt einem die Möglichkeit, Teile eines Ports atomar umzuschalten.

oberallgeier
29.10.2009, 22:04
... ein MOSFET (metall oxid semiconductor FET) ... dass Gate (G) von Kanal (S-D Strecke) isoliert ist ...Danke. Ich hatte Beschreibungen zu beiden Typen gelesen, aber den Doppelstrich im Symbol nicht beachtet - obwohl die Isolierung von Gate, Drain und Source schon im dritten Satz dieser Beschreibung (http://www.elektronik-kompendium.de/sites/bau/0510161.htm) steht. Muss doch wieder mal ein paar Stunden Lesen üben.

Was für ein schöner Tag. Sonne, Flugwetter und viel Neues!
... die Möglichkeit, Teile eines Ports atomar umzuschalten.An eine zufällige Eigenschaft hatte ich nicht wirklich geglaubt. Wozu das gut sein soll, weiß ich zwar noch nicht. Ich verstehe das so, dass damit ohne Kenntnis des aktuellen Zustandes der "andere" Zustand bei einer (echten oder unechten) Teilmenge der Portpins mit einem einzigen Maschinenbefehl herbeigeführt werden kann. Klingt tricky.

uwegw
29.10.2009, 22:27
Wenn du mit
PORTx^=(1<<Pxn);
einen Pin togglest, macht der Compiler (sofern er den Trick mit dem PIN-Register nicht kennt) folgendes:
PORTx einlesen
gelesenen Wert mit (1<<Pxn) verXODERn
Ergebnis auf PORTx ausgeben

Das sind drei Takte, und wenn zwischendurch ein Interrupt auftritt, der ebenfalls PORTx beschreibt, wird der vom Interrupt veränderte Wert sofort wieder überschrieben.


EDIT:

PINB=(1<<PB3);
PORTB^=(1<<PB3);
wird zu


62: 98 e0 ldi r25, 0x08 ; 8
64: 96 bb out 0x16, r25 ; 22

66: 88 b3 in r24, 0x18 ; 24
68: 89 27 eor r24, r25
6a: 88 bb out 0x18, r24 ; 24

WinAVR 20071221 kennt diese Möglichkeit zum toggeln eines Pins also noch nicht. Aber etwas hat er schon optimiert: das Ergebnis von (1<<PB3) wurde in r25 abgelegt und beim zweiten Mal wiederverwendet. Normalerweise würde die zweite Variante also sogar vier Takte brauchen.