Forum: Mikrocontroller und Digitale Elektronik port brav nach c Standard setzen


von Phillip H. (philharmony)


Lesenswert?

Nachdem mir vom einen oder anderen etwas der Kopf gewaschen wurde (meist 
natürlich berechtigterweise) möchte ich mun brav angewöhnen, wie mans 
richtig und Standardgerecht macht.
In der Mainschleife möchte ich der Übersicht halber möglichst nur 
schöne, Namentlich eindeutige Funktionen stehen haben, kein 
Bit-geschiebe etc.
Ich hänge jetzt an dem Punkt, wo ich an einem Port einige Ausgänge 
setzen will. Ich habe ein byte in dem die Soll-Zustände gespeichert 
werden (kommen an anderer Stelle über den uart) und in einem Setup File 
werden zusätzlich Masken definiert um auch wirklich nur die Pins zu 
lesen/schreiben die auch für diesen Gebrauch vorgesehen sind.
Nur wie geht es jetzt richtig?
1
void write_io(unsigned char port, unsigned char mask, unsigned char value)
2
{ unsigned char rem;
3
  rem = (value & mask);  
4
  rem |= (port | ~(mask));//die restlichen bits übernehmen
5
  port = rem;  //müßte zumindest funktionieren weil PORTA etc pointer sind oder?
6
}
7
8
Aufruf:
9
write_io(PORTA, 0b00110100, 0b00100100);
oder
1
unsigned char write_io(unsigned char port, unsigned char mask, unsigned char value)
2
{ unsigned char result;
3
  result = (value & mask);  
4
  result |= (port | ~(mask));//die nicht maskierten bits einfach annehmen
5
  return result;  
6
}
7
8
Aufruf:
9
PORTA = write_io(PORTA, 0b00110100, 0b00100100);

1. Entspräche ja zb bei Delphi einer Procedure, 2. eine Function.
1. Hätte den charme daß es in der main "schöner" aussieht
2. Bei sagt mir mein Gefühl daß die C-Ianer das lieber sehen

von Peter D. (peda)


Lesenswert?

Phillip Hommel schrieb:
>
1
> Aufruf:
2
> write_io(PORTA, 0b00110100, 0b00100100);
3
>

Also ich kann damit garnichts anfangen.
Und bezüglich Effektivität ist ein extra Funktionsaufruf mit 3 
Argumenten auch nicht der Brüller.
Ganz abgesehen, daß es Atomicity-Probleme geben wird, wenn auch 
Interrupts Portpins setzen.


Ich gebe immer der Lesbarkeit den absoluten Vorrang.
D.h. ich kann mit der Pinnummer überhaupt nichts anfangen, ich will die 
Funktion des Pins wissen.
Daher nehme ich dafür Bitvariablen, die lassen sich bequem mit einem 
Bitmacro erzeugen.
Und daß das besonders effektiv in atomare Bitbefehle compiliert wird, 
ist doch auch ein hübscher Nebeneffekt.

Hier mal ein Beispiel für Bitvariablen, die nach ihrer Funktion heißen:

http://www.mikrocontroller.net/attachment/30300/lcd_drv.zip

Welcher Pin das dann ist, ist völlig wurscht. Man kann bequem erst das 
Layout machen, wie es am besten paßt und dann die Pins zuweisen.



Peter

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Wenn Du "brav nach c Standard" arbeiten willst, dann kannst Du keine 
Konstanten mit dem Präfix 0b verwenden - so etwas gibt es in C nicht.

von Sven P. (Gast)


Lesenswert?

Also da finde ich aber das klassische Bitgeschiebe oder mit _BV 
übersichtlicher. Da kann man dann auch hübsche Bitnamen benutzen.

Ich habs bei mir mal so gelöst:
1
/* IO ports */
2
#define IO_STRAIGHT    0
3
#define IO_INVERT    1
4
5
#define OP_BUZZER    B, B1, IO_STRAIGHT
6
#define OP_DCF      B, B3, IO_INVERT
7
#define OP_RELAIS    B, B5, IO_STRAIGHT
8
9
#define OP_PORT(port, bit, invert)    \
10
      PORT##port
11
12
#define __IO_SET(port, bit, invert, state)  \
13
  if ( (invert) ^ ((state) ? 1 : 0)   \
14
    PORT##port |= _BV(PORT##bit);  \
15
  else           \
16
    PORT##port &= ~_BV(PORT##bit);  \
17
  }
18
19
#define IO_SET(port, state)      \
20
  __IO_SET(port, state)

Später ging das dann so:
1
IO_SET(OP_BUZZER, 1);
2
IO_SET(OP_BUZZER, 0);

von Phillip H. (philharmony)


Lesenswert?

Danke für die Hilfen, aber nichts davon beantwortet meine Frage.
Ich wollte eignetlich nur wissen ob ich die Ports innerhalb einer 
Funktion setzen kann(ob "man" das so macht) oder ob ich deren Wert 
zurück gebe und dann zuweise.
Das 0b-präfix war einfach nur dazu da, hier in meinem Codebeispiel 
irgendeine Maske und Irgendeinen Wert zu setzen ohne daß sich diese 
wiedersprechen, vollkommen egal für die Fragestellung...

von Stefan E. (sternst)


Lesenswert?

> Nur wie geht es jetzt richtig?

Keines deiner beiden Beispiele funktioniert. Die Übergabe des Ports 
funktioniert so ganz grundsätzlich nicht, und auch das Maskieren hat 
nicht die gewünschte Wirkung.
1
void write_io(volatile unsigned char *port, unsigned char mask, unsigned char value) {
2
  *port &= ~mask;
3
  *port |= value & mask;
4
}
Aufruf:
1
write_io(&PORTA,0x0f,0xaa);

von Phillip H. (philharmony)


Lesenswert?

Wenn ich die FUnktion mit PORTA etc aufrufe, dann müßte es doch gehen 
oder? (also zumindest TUT es das hier) da PORTA doch sicher eh als 
Zeiger auf den port definiert ist oder?
Das Maskieren tut auch das was es soll.
Ich möchte damit im ersten Teil sicherstellen, daß nur Werte aus dem 
"value" Byte berücksichtigt werden, die ich auch setzen möchte. (könnte 
ja sein daß da aus irgendwelchen Gründen an der falschen Stelle eine 1 
auftaucht.)
Im zweiten Teil möchte ich daß die Pins, die außerhalb meines zu 
schreibenden bereichs stehen, erhalten bleiben.
Ich möchte gesetzte Pins damit ja auch wieder löschen können ohne dabei 
einen Taktzyklus lang alle gelöscht zu haben und dann erst wieder die zu 
setzenden (und die die an bleiben sollen) zu setzen.
Wenn da weitere Elektronik dran hängt würde diese u.U. eine Flanke 
erkennen.

von sebastian (Gast)


Lesenswert?

Also bei dir funktioniert mit Sicherheit nur diese Variante:
1
unsigned char write_io(unsigned char port, unsigned char mask, unsigned char value)
2
{ unsigned char result;
3
  result = (value & mask);  
4
  result |= (port | ~(mask));//die nicht maskierten bits einfach annehmen
5
  return result;  
6
}
7
8
Aufruf:
9
PORTA = write_io(PORTA, 0b00110100, 0b00100100);

Und dies auch nur weil du den Rückgabewert dem Port zuweist.
Dass du hier den Port übergibst ist 1. überflüssig und 2. hat es keinen 
Effekt.

Überleg dir mal was diese write Funktion jedes mal macht:
3 Variablen auf den Stack pushen, IP ablegen und dann einen JUMP Befehl 
zu deiner Funktion. Wirklich effektiv ist das nicht.
1
#define LED_PIN   3 // Pin 3 an Port x
2
3
#define SET_BIT( BIT_ , PORT_) \
4
PORT_ = (PORT_ | (1 << BIT_))
5
6
void main (void)
7
{
8
    SET_BIT(LED_PIN,PORTB);
9
}

Und jetzt die Quizfrage: Wieviel Code wird wohl hier erzeugt? ;-)

von sebastian (Gast)


Lesenswert?

die VARIABLE PORTA hat mit Sicherheit die Adresse im RAM, genauer gesagt 
vom Special Function Register, dass dem Datenregister von PORTA 
entspricht..

Wenn PORTA jetzt eine Variable wäre, so wie du vermutest, dann würde ja 
bei jedem schreibenden Zugriff die Adresse des SFR überschrieben werden. 
Nicht so toll.

Also wird es wohl eine Variable vom Typ unsigned char sein, deren 
Adresse &(PORTA) wohl dem SFR entspricht.

Deshalb musst du wohl die Adresse von PORTA übergeben und diesen Zeiger 
dann dereferenzieren.

von Phillip H. (philharmony)


Lesenswert?

Wieso kann ich denn in der Main-Schleife einfach den Port per PORTA = 
...
ansprechen aber in einer Unterfunktion nicht. Dann müßte doch auch beim 
Aufruf in main die Adresse überschrieben werden.
Nicht als besserwissen missverstehn, ich kapiers einfach noch nicht...

von Stefan E. (sternst)


Lesenswert?

Was macht "var = PORTA;"?
Es ließt den Inhalt des Port-Registers aus und speichert diesen in var 
ab.
Was macht dann also "func(PORTA);"?
Es übergibt den Inhalt des Port-Registers an die Funktion, nicht den 
Port selber (also seine Adresse).

von Sven P. (Gast)


Lesenswert?

Phillip Hommel schrieb:
> Wieso kann ich denn in der Main-Schleife einfach den Port per PORTA =
> ...
> ansprechen aber in einer Unterfunktion nicht. Dann müßte doch auch beim
> Aufruf in main die Adresse überschrieben werden.

Guck mal:
1
void eine_funktion(char p) {
2
  p = 10;
3
}
4
5
void andere_funktion(char *p) {
6
  *p = 10;
7
}
8
9
int main() {
10
  char eine_variable = 5;
11
12
  eine_funktion(eine_variable);
13
  /* eine_variable ist immer noch 5 */
14
15
  eine_funktion(PORTA);
16
  /* PORTA bleibt trotzdem unverändert */
17
18
19
  andere_funktion(&eine_variable);
20
  /* eine_variable ist jetzt 10 */
21
22
  eine_funktion(&PORTA);
23
  /* PORTA ist jetzt 10 */
24
}

PORTA ist für dich nichts anderes als eine globale Variable. Wo und wie 
und warum du die zuweist, ist vollkommen egal. Wenn du die aber z.B. an 
'eine_funktion' übergibst, wird -- wie bei jeder anderen Variable und 
vereinfacht gesagt -- der Wert kopiert und mit diesem weitergerechnet.

von sebastian (Gast)


Lesenswert?

die VARIABLE PORTA hat mit Sicherheit die Adresse im RAM, genauer gesagt
vom Special Function Register, dass dem Datenregister von PORTA
entspricht..

Wenn PORTA jetzt eine Variable (FALSCHE) -> ZEIGER wäre, so wie du 
vermutest, dann würde ja
bei jedem schreibenden Zugriff die Adresse des SFR überschrieben werden.
Nicht so toll.

Also wird es wohl eine Variable vom Typ unsigned char sein, deren
Adresse &(PORTA) wohl dem SFR entspricht.

Deshalb musst du wohl die Adresse von PORTA übergeben und diesen Zeiger
dann dereferenzieren.

Im 2. Absatz, sollte es Zeiger und nicht Variable heißen, vielleicht 
wird es jetzt klar?

Klar in Main und sonst überall wo PORTA bekannt ist kannst du PORTA 
einen Wert zuweißen.
Aber deine Funktion soll ja universell, für jeden Port sein? Deshalb 
musst du deiner Funktion ja irgendwie sagen, welcher Port gemeint ist. 
So wie ich deine Schnittstelle verstehe willst du dann direkt darauf 
zugreifen.

Aber wenn du nur PORTA übergibst, dann ist dann in der Funktion eine 
lokale Variable, die ihren Speicherplatz auf dem Stack hat und rein gar 
nix mit dem SFR von Port A zu tun hat. Wenn du das erreichen willst 
musst du die Schnittstelle so anpassen, dass du die ADRESSE zu deinem 
Port übergibst. Z.b. so
1
&(PORTA) // Zeiger auf PORTA

von sebastian (Gast)


Lesenswert?

und von wegen C konform...

Wenn du es den C Compilern recht machen willst, dann solltest du casten 
um Typunsicherheiten und Warnungen zu vermeiden.

z.B. was denkst was für ein datentyp kommt hier heraus?
1
unsigned char register_8bit = 0x10;
2
unsigned short register_16bit = 0x00;
3
4
register_16bit = ~(register_8bit);

???

Bei Cosmic C Compiler ist es z.B. so, dass der 8 Bit wert auf einen 16 
bit Wert gecastet wird und dann negiert!

Also: 0x10 -- 16bit --> 0x0010 -- ~ --> 0xFFEF

Deshalb haut einem ein C Compiler zig Warnungen um die Ohren, wenn man 
unsauber mit Bitoperatoren arbeitet.

Nur so by the way...

von Phillip H. (philharmony)


Lesenswert?

Aaaah, ok jetzt hab ichs verstanden, autsch das hätte mir sogar mit 
meinem bisherigen Wissen klar sein müssen *AUF_DEM_SCHLAUCH!!!*
Noch eine weitere Frage zur Sinnhaftigkeit:
Ich bekomme alle möglichen Werte auf meinem Board, eben Pin-Zustände, 
evtl mehrere ADC-Messungen etc.
Diese Werte sollen jeweils mit den bereits gesendeten Werten verglichen 
werden und bei Ungleichheit per uart als string verschickt werden 
(string besteht dann aus dem Namen des Wertes und dem Wert selbst).
Macht es Eurer Meinung nach mehr Sinn, nach jeder einzelnen Messung je 
einen Wert zu vergleichen und evtl zu Senden oder am Ende, wenn alle 
Werte einmal eingelesen sind einmal alles zu vergleichen und dort zu 
senden?

von sebastian (Gast)


Lesenswert?

Mhm, du meinst einen Soll-Ist Wert Vergleich?

Hängen die einzelnen Werte logisch miteinander zusammen?

Willst du die Fehler einzeln ausgeben, also wenn nur ein Bit falsch ist, 
oder wenn innerhalb eines Moduls z.B. "Analogausgang" ein Fehler 
vorliegt`?

Aber da sich das ganze doch recht universell anhört, würde ich es so 
machen

<1: WERT ERFASSEN X_NEU>

<2: X_NEU UNGLEICH X_ALT?> --> Nein --> Gehe zu 4
|
Ja
|
<3: SENDE INFO PER UART: X_NEU UNGLEICH X_ALT>

<4: Erfasse naechsten Wert>

Ich verstehe zwar nicht den logischen Zusammenhang und warum du die 
Soll/Ist Werte vergleichst, aber es spricht eigentlich nix gegen ein 
lineares Vorgehen..

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

> write_io(...);
Solche Funktionen habe ich echt gefressen....  :-/
Welche IO denn? Ein Timer? Eine SIO? PWM? SPI? I²C? ... oder gar ein 
Port?
Bekomme ich nach dem Aufruf die Pins zurück oder das Port-Register?

von sebastian (Gast)


Lesenswert?

write_io...

glas klar!

"Schreibe auf Eingang/AUsgang"... hoppla, Schreibe auf Eingang... nee, 
stimmt ja gar nicht ;-) sorry, kleiner Scherz

joa, hab aber auch schon sinnvollere Namen gesehen ;)

von Phillip H. (philharmony)


Lesenswert?

Man jetzt zerhackt mir doch nicht jeden einzelnen Namen, ich wollte doch 
nur was prinzipielles Fragen.
Die ganze Anwendung ist dafür gedacht, in einem Flugsimulator als 
Interface zwischen Hardware und Software zu fungieren.
Der Simulator ist in Module unterteilt die jeweils unterschiedliche 
Anzahlen von Schaltern, Drehgebern, Potis, 7seg-Displays und Lampen 
(LEDs) haben.
Da ich nicht für jedes einzelne Modul eine andere Platine anfertigen 
lassen möchte soll das ganze eben so universell wie möglich ausgelegt 
sein.
Es werden vom Rechner Befehle in der Art "Lampe_irgendwas=AN", 
"7Seg_wasanderes=5690" "PWM_nocheinanderer=245" (nicht genau in diesem 
Wortlaut sondern vom Prinzip her) über den uart gesendet und dann die 
entsprechenden Ausgaben an den Beinchen geschalten. Auf der anderen 
Seite sollen die Schalter, Potis etc abgetastet werden und bei änderung 
des Zustandes oder Wertes der neue Wert im gleichen Stil 
"Schalter_bla=Aus", "Poti_blubb=167" an den Rechner zurückschicken.
Wird ein neues Modul in Betrieb genommen wird in einer GUI ausgewählt 
was dieses Modul alles an Ein- und Ausgaben (also Schalter, LEDs...) 
besitzt und jeweils einer Systemvariablen (bzw ihrer internen Nummer <- 
Speicherplatz) zugeordnet. (Dh in Wirklichkeit wird nicht 
"Schalter_bla=An" sondern "0x9ad3=1" gesendet und dann auf dem Rechner 
interprätiert).
Diese GUI erzeugt dann ein Setup-File das in den Code eingebunden wird 
und in dem ua die Zuweisung steht, also im Prinzip an welchem Pin was 
dranhängt.
Dazu wird eine Pinbelegung für den Benutzer ausgegeben anhand derer 
dieser die entsprechenden Kabel an die vorgesehenen Stecker/Klemmen 
einschließen muß.

von Karl H. (kbuchegg)


Lesenswert?

Du wirst schnell merken, dass du dir mit dieser Universalität eine Menge 
Ärger und umständlichen Code einhandelst.

von Phillip H. (philharmony)


Lesenswert?

Das merke ich jetzt schon, aber so ist nunmal die Vorgabe.
Und genau dafür versuche ich ja auch hier einige Fragen zu stellen.
Wie ich grundsätzlich einen Port lese oder schreibe, oder einzelne Pins, 
das weiß ich auch, so weit am Anfang stehe ich mit der Materie jetzt 
auch nicht auch wenn ich natürlich in C noch jede Menge zu lernen habe.
Im Simulator stecken schätzungsweise 50-60 module, da werde ich nicht 
für jede nen eigenen Code schreiben, zumal ja auch Leute, die von 
Programmieren gar keine Ahnung haben ein neues Modul in Betrieb nehmen 
sollen.

von Karl H. (kbuchegg)


Lesenswert?

Phillip Hommel schrieb:
> Das merke ich jetzt schon, aber so ist nunmal die Vorgabe.
> Und genau dafür versuche ich ja auch hier einige Fragen zu stellen.
> Wie ich grundsätzlich einen Port lese oder schreibe, oder einzelne Pins,
> das weiß ich auch, so weit am Anfang stehe ich mit der Materie jetzt
> auch nicht auch wenn ich natürlich in C noch jede Menge zu lernen habe.
> Im Simulator stecken schätzungsweise 50-60 module, da werde ich nicht
> für jede nen eigenen Code schreiben, zumal ja auch Leute, die von
> Programmieren gar keine Ahnung haben ein neues Modul in Betrieb nehmen
> sollen.

Da du sowieso vor hast, die Konfiguration im C Code zu machen, würde ich 
das so angehen, dass ich einzelne funktionale Baugruppen zur Verfügung 
stelle, die von einem Wissenden zusammengestellt werden, ehe das 
Programm durch den Compiler läuft.

Nur so als Idee:
1
// Output Types
2
#define LAMP_TYPE   0
3
#define SEG_7_TYPE  1
4
5
// Input Types
6
#define POTI_TYPE   1
7
8
struct LampData
9
{
10
  volatile uint8_t* port;
11
  uint8_t           pinMask;
12
};
13
14
struct PotiData
15
{
16
  uint8_t           channel;
17
  int16_t           kNom;          // Value = kNom * ADC / kDenom + d
18
  int16_t           kDenom;
19
  int16_t           d;
20
  int16_t           PrevValue;
21
};
22
23
struct Seg7Data
24
{
25
  uint8_t           port;
26
};
27
28
struct Device
29
{
30
  char     name[20];
31
  uint8_t  type;    // LAMP_TYPE, POTI_TYPE, etc...
32
  void*    data;
33
};
34
35
//
36
// ab hier kommt die Konfiguration
37
// welche Geräte gist es, welche Daten haben sie
38
//
39
struct LampData ErrorLamp = { &PORTB, 1 << PA5 };   // es gibt eine Led an PA5
40
struct LampData ReadyLemp = { &PORTB, 1 << PB1 };
41
struct PotiData Throttle  = { 0, 1, 2, 0, 0 };   // Poti am ADC 0
42
                                                 // der zurückgemeldete Wert ist die Hälfte
43
                                                 // des gemessenen ADC Wertes
44
45
//
46
// und jetzt die Zusammenfassung aller Geräte
47
//
48
struct OutDevices[] =
49
{
50
  { "Error", LAMP_TYPE, ErrorLamp },
51
  { "Ready", LAMP_TYPE, ReadyLamp }
52
};
53
54
#define NR_OUT_DEVICES  ( sizeof( OutDevices ) / sizeof( *OutDevices ) )
55
56
struct InDevices[] =
57
{
58
  { "Throttle", POTI_TYPE, Throttle }
59
};
60
61
#define NR_IN_DEVICES  ( sizeof( OutDevices ) / sizeof( *OutDevices ) )
62
63
//
64
// als Beispiel: Das Gerät mit dem Namen deviceName einschalten
65
// es soll dabei keine Rolle spielen, was genau einschalten für das
66
// Gerät eigentlich bedeutet. Die jeweilige Logik dafür wird in jeweils
67
// einem eigenen else if Zweig behandelt.
68
// Man könnte auch die Logik dafür in Funktionen auslagern und zb mit
69
// Funktionszeigern arbeiten.
70
//
71
uint8_t TurnOn( const char* deviceName )
72
{
73
  uint8_t i;
74
  struct LampData* pLamp;
75
76
  for( i = 0; i < NR_OUT_DEVICES; ++i ) {
77
    if( strcmp( deviceName, OutDevices[i].name ) == 0 ) {
78
      if( OutDevices[i].type == LAMP_TYPE ) {
79
        pLamp = (LampData*)OutDevices[i].data;
80
        *(pLamp->port) |= pLamp->pinMask;
81
      }
82
      else if( OutDevices[i].type == ....
83
      ....
84
85
      return TRUE;
86
    }
87
  }
88
89
  return FALSE;
90
}
91
92
....

von Phillip H. (philharmony)


Lesenswert?

Das klingt schonmal ziemlich gut, ich verstehe nur ein paar Stellen noch 
nicht.

- Was hat es mit dem Knom und Kdenom und warum gibst Du die hälfte des 
ADC Wertes zurück?

- Die Art wie Du die Structs ansprichst ist mir noch nicht ganz klar, 
sehe ich es richtig daß mit
1
struct LampData ErrorLamp = { &PORTB, 1 << PA5 };
ein neues Struct mit dem Namen ErrorLamp gebildet wird, daß vom Struct 
LampData abgeleitet wird, und dessen beide Eigenschaften *port und 
pinmask mit dem Zeiger auf PORTB und "00100000" gefüllt werden?

- Wozu dient das struct "device"? Das wird doch im Code nirgends mehr 
verwendet oder?

- Was macht dieser Ausdruck?
1
LampData* pLamp;
2
pLamp = (LampData*)OutDevices[i].data;
3
*(pLamp->port) |= pLamp->pinMask;
Da verstehe ich absolut nur Bahnhof, allein den Operator -> habe ich 
nicht gefunden, was bedeutet das?
Vielen Dank für deine Mühe mich hier auf den richtigen Weg zu bringen...

von Stefan E. (sternst)


Lesenswert?

Phillip Hommel schrieb:

> - Wozu dient das struct "device"? Das wird doch im Code nirgends mehr
> verwendet oder?

Das ist der Typ von InDevices und OutDevices. Karl Heinz war da wohl 
etwas tippfaul. ;-)
Korrekt muss es heißen:
1
struct Device InDevices[] =
2
...
3
struct Device OutDevices[] =

>  - Was macht dieser Ausdruck?
1
LampData* pLamp;
2
pLamp = (LampData*)OutDevices[i].data;
3
*(pLamp->port) |= pLamp->pinMask;
> Da verstehe ich absolut nur Bahnhof, allein den Operator -> habe ich
> nicht gefunden, was bedeutet das?

Das ist die Dereferenzierung eines Struct-Members über einen Pointer.
1
SomeStruct a;
2
a.var = 0;
3
4
SomeStruct *b;
5
b->var = 0;

Was genau verstehst du sonst noch nicht an dem Code-Schnipsel? Den Cast?

von Karl H. (kbuchegg)


Lesenswert?

Phillip Hommel schrieb:

> - Was hat es mit dem Knom und Kdenom und warum gibst Du die hälfte des
> ADC Wertes zurück?

Das ist doch nur ein Beispiel.
Wenn dein Benutzer damit glücklich ist, dass der ADC immer Werte 
zwischen 0 und 1024 liefert, dann brauchst du den Klimbim nicht.

Es ist allerdings manchmal nett, wenn man den ADC gleich so 
'konfigurieren' kann, dass er zb Werte von 0 bis 100  (0% .. Maschine 
aus, 100% Maschine läuft Vollgas) liefert und sich dein Benutzer nicht 
mehr darum kämmern muss, wie man die Umrechnung von 0..1024 auf 0..100 
hinbekommt (*)
Daher hätte ich in mein Poti-Device gleich eine Möglichkeit eingebaut, 
dass das Device selber diese Umrechnung macht. Ist ja nur eine lineare 
Gleichung

    Wert = k * x  + d

  x       ... Messwert vom ADC
  k und d ... Koeffizienten der linearen Gleichung

Nun ist k aber gerne mal eine Kommazahl und Fliesskomma möchte ich vom 
AVR fernhalten, wenns geht. Also ersetze ich k durch einen Bruch

             kNom
    Wert = -------- * x + d
            kDenom

( Nom wie engl. Nominator. Der Zähler eines Bruchs
  Denom wie engl. Denominator. Der Nenner eines Bruchs)

Will ich also haben, dass mein Poti in Prozent arbeitet, dann brauch ich 
die Umrechnung

           100
   Wert = ------ * ADC_Wert + 0
           1024


Will ich haben, dass mein Poti am linken Anschlag den Wert -100 und am 
rechten Anschlag den Wert +100 liefert, dann brauch ich die Umrechnung

            200
   Wert = ------ * ADC_Wert - 100
           1024


> - Die Art wie Du die Structs ansprichst ist mir noch nicht ganz klar,
> sehe ich es richtig daß mit
>
1
> struct LampData ErrorLamp = { &PORTB, 1 << PA5 };
2
>
> ein neues Struct mit dem Namen ErrorLamp gebildet wird, daß vom Struct
> LampData abgeleitet wird

Da wird nichts abgeleitet. struct LampData ist ein Datentp wie jeder 
andere auch, nur dass es eine struct ist.
Du schreibst ja auch

   int xy = 5;

> , und dessen beide Eigenschaften *port und
> pinmask mit dem Zeiger auf PORTB und "00100000" gefüllt werden?

Ja.
Jedes noch so grindige C-Buch weiß noch vieles mehr über Strukturen.

> - Wozu dient das struct "device"? Das wird doch im Code nirgends mehr
> verwendet oder?

Schreibfehler. War schon spät in der Nacht.
1
//
2
// und jetzt die Zusammenfassung aller Geräte
3
//
4
struct Device OutDevices[] =
5
{
6
  { "Error", LAMP_TYPE, ErrorLamp },
7
  { "Ready", LAMP_TYPE, ReadyLamp }
8
};
9
10
#define NR_OUT_DEVICES  ( sizeof( OutDevices ) / sizeof( *OutDevices ) )
11
12
struct Device InDevices[] =
13
{
14
  { "Throttle", POTI_TYPE, Throttle }
15
};
16
17
#define NR_IN_DEVICES  ( sizeof( OutDevices ) / sizeof( *OutDevices ) )


> - Was macht dieser Ausdruck?
>
1
> struct LampData* pLamp;
2
> pLamp = (LampData*)OutDevices[i].data;
3
> *(pLamp->port) |= pLamp->pinMask;
4
>
> Da verstehe ich absolut nur Bahnhof, allein den Operator -> habe ich
> nicht gefunden, was bedeutet das?

Das du schleunigst ein C-Buch brauchst.
Das sind Grundlagen.

  struct LampData* pLamp;
definiere eine Pointer Variable vom Typ "Zeiger auf LampData"

  pLamp = (LampData*)OutDevices[i].data;
nimm den data Pointer aus Outdevices[i] her (welcher ja ein void Pointer 
ist) und tu so als ob er ein Pointer auf eine LampData ist. Dieser 
Pointer Wert wird in pLamp (welches ja ebenfalls ein Pointer auf eine 
LampData ist) abgespeichert.

  *(pLamp->port) |= pLamp->pinMask;

  pLamp ist ein Zeiger auf eine struct LampData. MIt -> kommt man an 
dieses LampData Objekt heran und holt sich von dort die Member port bzw. 
pinMask.



(*)
Edit:
Das hat dann den wunderschönen Nebeneffekt, dass dein Benutzer gar nicht 
wissen muss, welche Auflösung der ADC eigentlich hat. Du kannst den 
10-Bit ADC gegen einen 8-Bit austauschen, in deinem Platinen-Programm 
die Umrechnung entsprechend anpassen und dein Benutzer bekommt wieder 
die gewohnten Werte, so wie eh und je.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

>> so weit am Anfang stehe ich mit der Materie jetzt auch nicht ...
:
> allein den Operator -> habe ich nicht gefunden
Holla :-o
Stichwort: Pointer

Ich hoffe nur, dass ich so ein universalhochgezüchtetes Programm nicht 
(nochmal) in die Finger bekomme, und das (zehn Jahre später) verstehen 
und anpassen muß.

von bartsch (Gast)


Lesenswert?

>> Da verstehe ich absolut nur Bahnhof, allein den Operator -> habe ich
>> nicht gefunden, was bedeutet das?
> Das du schleunigst ein C-Buch brauchst.
> Das sind Grundlagen.
Dem muss ich ausdrücklich zustimmen!
hs-bremen == http://www.hs-bremen.de/ ? Gibt es da kein C im ersten 
Semester?

Vielleicht hilft folgendes Beispiel:
1
struct struktur {
2
  char text[64];
3
} direkt = {"Hier kommt die Maus."};
4
5
struct struktur *indirekt = &direkt; // (1)
6
7
printf("direkt  : %s\n", direkt.text); // (2)
8
printf("indirekt: %s\n", indirekt->text); // (3)

(1) Es wird ein Zeiger auf ein struct vom Typ struktur mit dem Namen 
direkt gesetzt.
(2) und (3) Greifen auf den gleichen String in text zu. Einmal direkt 
und einmal indirekt via Zeiger.

Um den ersten Zeilen noch mehr Gewicht zu geben:
    C ohne Zeiger ist wie Bier ohne Alkohol.

von Phillip H. (philharmony)


Lesenswert?

Das mit dem C-Buch sollte ich wirklich mal angehn. Ich habe bisher 
einfach "irgendwie" (eben so wie ichs zb von Delphi und VB gewohnt war) 
drauflos geschreiben (ja schlagt ruhig die Hände überm Kopf zusammen) 
und es hat eben trotzdem immer funktioniert, auch wenn ich globale 
Variablen verwendet habe und noch nie einen Pointer. Mir ist auch die 
Funktionsweise der Microcontroller klar und denke daß ich da ganz gut 
durchsteige. Nur C ist so eine Sache. Ich mag einfach Quelltexte in 
denen schöne Namen vergeben werden und alles in übersichtliche 
Funktionen verpackt ist (eben -> Delphi, VB).
C sieht für mich immernoch sehr Kryptisch aus, daß Ihr das gewohnt seit 
ist mir klar, ich habe daran noch ganz schön zu knapsen.
Ob Lothar das ganze jetzt zu universell ist oder nicht kann ich nicht 
ändern, ich habe nunmal die Vorgabe was das ganze können soll, wenn Du 
einen guten Vorschlag hast wie man das umgehen kann dann gerne her 
damit. Das ganze wird natürlich umfassend kommentiert und dokumentiert, 
daher sollte auch in 10 Jahren das ganze durchschaubar sein (zumal ich 
doch die aussagekräftigen Namen so mag).
 Ich versuche seit über einer Woche so etwa einen Programmablauf 
zusammen zu kriegen der mir hier jedes mal wieder zerrissen wird. Habt 
Ihr vielleicht einen Vorschlag wie ich das ganze angehn soll? Langsam 
bin ich nämlich gründlich frustiert...

von Karl H. (kbuchegg)


Lesenswert?

Phillip Hommel schrieb:
> Das mit dem C-Buch sollte ich wirklich mal angehn. Ich habe bisher
> einfach "irgendwie" (eben so wie ichs zb von Delphi und VB gewohnt war)
> drauflos geschreiben (ja schlagt ruhig die Hände überm Kopf zusammen)
> und es hat eben trotzdem immer funktioniert,

Darum gehts nicht.

Irgendwie kriegt man immer alles zum Laufen.
Die Frage ist, ob du mit den Händen ein Loch im Boden gräbst, weil du 
nicht weißt, dass im Schuppen ein Spaten steht.

>  Ich versuche seit über einer Woche so etwa einen Programmablauf
> zusammen zu kriegen der mir hier jedes mal wieder zerrissen wird. Habt
> Ihr vielleicht einen Vorschlag wie ich das ganze angehn soll? Langsam
> bin ich nämlich gründlich frustiert...

Das ist auch kein Wunder, wenn du mehr als die Hälfte deines 
Handwerkszeugs nicht benutzt, weil du ganz einfach nicht weißt, dass es 
da ist und wie man es benutzt.

Deine Aufgabenstellung ist alles andere als trivial. Möglichst 
universell konfigurierbare Progamme zu schreiben ist sogar verdammt 
schwer. Vor allen Dingen dann, wenn das alles auch noch performant sein 
soll. Das Mindeste ist aber sein Handwerkszeug zu kennen. Und zwar nicht 
nur so lala. Wenn du nicht weißt, wie dir die Sprache unter die Arme 
greifen kann und wie sie dir helfen kann, kannst du diese Dinge auch 
nicht einsetzen. Das ist so, wei wenn du nicht weißt, dass es in C eine 
Multiplikation gibt und du daher alles auf Additionen zurückführst. Klar 
funktioniert das auch. Aber du machst dir nur selbst unnötig Arbeit.

von Phillip H. (philharmony)


Lesenswert?

>Das ist auch kein Wunder, wenn du mehr als die Hälfte deines
>Handwerkszeugs nicht benutzt, weil du ganz einfach nicht weißt, dass es
>da ist und wie man es benutzt.
Da muss ich dir absolut recht geben, mir war einfach nicht klar, wie 
sehr sich C von den bisher verwendeten Sprachen unerscheidet. Lernen 
Lernen Lernen ;)

>Gibt es da kein C im ersten Semester?
Bestimmt bei E-Technikern und Informatikern. Ich bin keines von beiden 
sondern hab mir das ganze selbst in Form eines Projektes aufgehalst über 
das ich nun meine Thesis schreibe. Da es mit Programmieransätzen 
eigentlich gut geklappt hat und ich mir um die besondernheiten von C 
noch keinerlei Kopf gemacht habe war ich da recht zuversichtlich. Ich 
bins auch jetzt eigentlich noch, ich werd mich halt erstmal etwas mehr 
in C einfuchsen müssen. Ich verstehe zwar was Pointer sind, und 
(abgesehen von manchen "aufm schlauch steh momenten" s.o.) ist mir auch 
der Unterschied klar ob ich einer Funktion nun eine Varibale oder deren 
Zeiger übergebe. Nur wie man das ganze Anwendet, da hängts eben noch 
sehr.
Daher möchte ich nochmal eine etwas allgemeinere Frage zu meinem Projekt 
stellen, damit ich mal einen Einstieg kriege:

Ohne Frage muß es dabei irgendwo einen Speicher geben, in dem jede 
verwendete Variable auftaucht, ihr Typ (also Poti, Schalter...),deren 
Wert, evtl ein vorangegangener Wert. So wie Karl Heinz das oben ja 
gezeigt hat.
Jetzt sehe ich im Grunde drei Möglichkeiten wie das Programm arbeitet:

1. Variable für Variable aufrufen und jeweils entsprechend ihres Typs 
die passende Abfrage starten.

2. Gerät für Gerät (Port, ADC, Timer[PWM]) ansprechen und in einem weg 
alle jeweils zugewiesenen Variablen bearbeiten.

3. Interrupt-Gesteuert den ADC arbeiten lassen, und seine Werte im 
entsprechenden Speicher ablegen. Ebenso stetig per Timer 
interrupt-gesteuert einen PWM ausgeben (für Servos, also nicht der 
eingebaute PWM).
UART Empfang ist sowieso interrupt-gesteuert. Auch in dieser Routine 
einfach den empfangenen Wert entsprechend ablegen.
Jetzt soll es ja nicht ganz unkritisch sein, wenn mehrere Interrupts 
paralel betrieben werden, das ist mir bewusst und würde ich aber gerne 
an anderer Stelle nochmal explizit klären.
Das Programm würde in diesem 3. Fall einfach reihum die Werte im 
Speicher mit Werte_alt vergleichen und bei ungleichheit einmal senden.

Welche dieser drei Varianten klingt für Euch am sinnvollsten, evtl 
warum?

von P. S. (Gast)


Lesenswert?

Phillip Hommel schrieb:

> C sieht für mich immernoch sehr Kryptisch aus, daß Ihr das gewohnt seit
> ist mir klar, ich habe daran noch ganz schön zu knapsen.

C ist ganz einfach. Kryptisch wird es nur, wenn man damit kryptischen 
Code schreibt. Was leider weit verbreitet ist. Scheint irgendwie cool zu 
sein.

> (zumal ich doch die aussagekräftigen Namen so mag).

Dann verwende aussagekraeftige Namen. Kryptisch und nicht 
aussagekraeftig ist z.b., wenn eine Funktion write_io() heisst und dann 
kein io schreibt. Das gilt eigentlich fuer deine beiden Varianten (von 
den Fehlern mal abgesehen) - denn was du beschreiben willst ist ein 
Port. Also sollte das Ding schonmal WritePort() heissen (wie du siehst 
verwende ich auch die Gross-/Kleinschreibung). Und dann sollte es das 
auch tun und nicht einen neuen Portwert ausrechnen - denn wenn es das 
tut, sollte es z.B. CalculatePortValue() heissen.

P.S: Auch wenn das leider auch in der Industrie weit verbreitet ist, 
halte ich Trial & Error fuer die falsche Herangehensweise fuer einen 
Hochschulabgaenger.

von Phillip H. (philharmony)


Lesenswert?

Wie gesagt, das war nur ein Beispiel womit ich etwas grundsätzliches 
fragen wollte. Ich hatte mit io Input/Output (also das was man mit dem 
Port machen kann) verstanden. Sorry wenn io eigentlich was anderes 
bedeutet.
Zum Thema trial and error: Das möchte ich ja vermeiden, darum stell ich 
hier ja so viele dusselige Fragen. Mir war vorher einfach nicht bewusst, 
wieviel ich über C noch nicht weiß...Und jetzt wo mir langsam klar wird 
wieviel da noch fehlt werde ich mich natürlich noch mal tiefergehend 
darin einlesen und mir mal ein paar tutorials zu gemüte führen etc.
Darum hatte ich ja am Ende nochmal ganz allgemein gefragt auf welche 
Ansatz ich denn am besten anfangen soll...

von Peter D. (peda)


Lesenswert?

Ich habe den Eindruck, Du willst die eierlegende Wollmilchsau 
erschaffen.
Bisher ist aber jeder gescheitert, der das versucht hat.

Versuche die Aufgabe Schritt für Schritt zu lösen.
Mache also erstmal alle Funktionen mit fester Pinzuweisung per Define.
Und wenn das wirklich läuft, kann man als nächsten Schritt die 
Universalität hinzufügen.

Es ist wesentlich einfacher, kleine Schritte zu machen, anstatt die 
gesamte Aufgabe als großen monolithischen Block zu sehen und dann davon 
erschlagen zu werden.

Es ist vielleicht auch garnicht nötig allen möglichen Schrunz variabel 
zu halten.
Mit Defines kann man bequem und übersichtlich die Zuordnung zur 
Compilezeit machen.
Und anstelle haufenweise Variablen zu verbraten, lädt man einfach die 
compilierten Programme per Bootloader in die einzelnen Module.
So ist man genauso flexibel.


Peter

von P. S. (Gast)


Lesenswert?

Phillip Hommel schrieb:
> Wie gesagt, das war nur ein Beispiel womit ich etwas grundsätzliches
> fragen wollte.

Ich habe dich so verstanden, dass deine grundsaetzliche Frage ist, ob 
die Funktion den Port setzen sollte, oder ob sie ihn zurueckgeben sollte 
und er dann von aussen gesetzt wird. Die Antwort ist ein eindeutiges: 
"Kommt darauf an."

> Ich hatte mit io Input/Output (also das was man mit dem
> Port machen kann) verstanden. Sorry wenn io eigentlich was anderes
> bedeutet.

Es heisst input/output - aber ein Port ist nur eine Moeglichkeit der 
Ein-/Ausgabe. RS232, TWI, SPI, etc. ist alles auch I/O.

> Zum Thema trial and error: Das möchte ich ja vermeiden, darum stell ich
> hier ja so viele dusselige Fragen. Mir war vorher einfach nicht bewusst,
> wieviel ich über C noch nicht weiß...Und jetzt wo mir langsam klar wird
> wieviel da noch fehlt werde ich mich natürlich noch mal tiefergehend
> darin einlesen und mir mal ein paar tutorials zu gemüte führen etc.

Vor dem Web gab es Buecher. Ich halte das heute noch fuer eine gute 
Methode. Oft besser als Tutorials, die wieder sehr zur fluechtigen 
Bearbeitung verleiten:

http://www.amazon.de/Programmieren-C-ANSI-2-C-Reference/dp/3446154973/ref=sr_1_2?ie=UTF8&s=books&qid=1248261705&sr=8-2

> Darum hatte ich ja am Ende nochmal ganz allgemein gefragt auf welche
> Ansatz ich denn am besten anfangen soll...

Sorry, aber diese Frage (die 3 Ansaetze) ist so allgemein, dass man sie 
nicht beantworten kann.

von Karl H. (kbuchegg)


Lesenswert?

Peter Dannegger schrieb:

> Es ist wesentlich einfacher, kleine Schritte zu machen, anstatt die
> gesamte Aufgabe als großen monolithischen Block zu sehen und dann davon
> erschlagen zu werden.

Ich denke, dass ist einer der wichtigsten Punkte, die man in der 
Progrmmierung lernen muss: Das Arbeiten in kleinen Schritten. Auch 
einmal einen kleinen Unweg in Kauf zu nehmen (besonders Anfängern 
schrecken gerne davor zurück) um dann später auf geradem Weg ins Ziel zu 
kommen.

Was auch immer dazu kommt: Das man während der eigentlichen Arbeit noch 
immer einen Lernprozess hat. Dadurch dass man in kleinen Schritten 
arbeitet, kann man darauf reagieren. Dadurch dass man einzelne Details 
ev. in einem eigenen Testprogramm abklärt, erhält man zusätzliches 
Wissen welches in die Gesamtkonzeption einfliessen kann.

Viele Menschen stellen sich programmieren so vor:
Da sitzt sich einer hin, denkt 2 Minuten über das Problem nach und dann 
drischt er stundenlang auf seine Tastatur ein um irgendwann nach 
Mitternacht ein Programm zu haben, welches auf Anhieb perfekt 
funktioniert. Die Wahrheit könnte nicht weiter davon entfernt sein. 
Zumindest ist sie mindestens ebensoweit davon entfernt, wie der Glaube, 
dass es in der Chemie darum geht bunte Flüssigkeiten zusammenzuschütten 
und zu hoffen dass es nur qualmt und nicht explodiert.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:
> Viele Menschen stellen sich programmieren so vor:
> Da sitzt sich einer hin, denkt 2 Minuten über das Problem nach und dann
[...]
> funktioniert. Die Wahrheit könnte nicht weiter davon entfernt sein.
Stimmt, meistens wird bis Mitternacht programmiert und dann versucht 
über das Problem nachzudenken. ;)

von Phillip H. (philharmony)


Lesenswert?

Hehe, kommt mir bekannt vor.
Ich möchte mal kurz Feedback geben:
Als erstes möchte ich mich nochmal bei allen bedanken für Eure Geduld 
und die vielen Ratschläge, Ihr habt mir heute echt sehr weiter geholfen!
Ich habe mir heute mal zwei Tutorials zu Gemüte geführt (die Bib is so 
weit weg und es regnet und und und weitere Ausreden ;) )
Ich habe jetzt zur Motivation mal ein kleines Programmchen geschrieben 
um einige Sachen zu testen und es tut auch (freude!)
Allerdings mit einer Einschränkung:
Hier mal der Code:
1
#include <avr/io.h>
2
#include <AVR/interrupt.h>
3
#include <AVR/wdt.h>
4
#include <util/delay.h>
5
#include <rs232.h>
6
#include <string.h>
7
#include <bit_ops.h>
8
9
#define BAUD 38400
10
11
//Hier wäre später eine ellenlange liste (headerfile) mit allen -zigtausend
12
//systemvariablen die es so in der Software des Simulators gibt
13
14
#define TEST_LED_1   1
15
#define TEST_LED_2   2
16
#define TEST_SWITCH_0  3
17
#define TEST_SWITCH_1  4
18
#define TEST_SWITCH_2  5 
19
#define TEST_SWITCH_3  6
20
#define TEST_SWITCH_4  7
21
#define TEST_SWITCH_5  8
22
#define TEST_SWITCH_6   9
23
#define TEST_SWITCH_7  10
24
25
26
struct input_mem
27
{
28
  unsigned char* port;
29
  unsigned char pin;
30
  unsigned char value;
31
  unsigned char var_number;
32
};
33
34
struct input_mem switch_0 = {
35
  &PINA,
36
  PA0,
37
  0,
38
  TEST_SWITCH_0
39
  };
40
41
void read_port(struct input_mem *mem)
42
{  
43
  unsigned char iport = *(mem->port); 
44
//fand ich für  mich erstmal leichter nachzuvollziehen 
45
//wenn ich die werte "umspeichere", nur hier zur Übung
46
  unsigned char ipin = (mem->pin);
47
  unsigned char ivalue =(mem->value);
48
  unsigned char ivar_number = (mem->var_number);
49
  unsigned char buffer = 0;
50
  char str[20];   //19 Stellen + '\0'
51
  str[0] = '\0';  //"echten" string daraus machen
52
  buffer = get_bit(iport, ipin);
53
  if (!(buffer == ivalue))
54
  {  itoa(str, ivar_number, 10);  //so richtig?
55
    if (buffer == 0)
56
      strcat(str, "on");
57
    else
58
      strcat(str, "off");
59
    strcat(str, "\n");
60
    uart_puts(str);  
61
    mem->value = buffer;  
62
  }
63
}
64
65
66
int main()
67
{  DDRB = 0xff;
68
  PORTB |= (1<<PB0); //zum testen ob das prog läuft
69
  USART_Init(MYUBRR);
70
  uart_puts("Initialized\n");
71
  for(;;)
72
  
73
  {
74
  read_port(&switch_0);
75
  }
76
}

An PINA0 hängt ein DIP-Codierer mit externem Pull Up.
Das Program läuft jetzt, die LED an PINB0 leuchtet. "inizialized" wird 
gesendet, wenn schalter aus dann auch "off".
Jetzt kommts wo ich hänge:
Er sendet anstatt "3on"/"3off" nur "on"/"off". Dabei kopiere ich doch 
vorher die var_number (mit 3 definiert) mit Itoa schon in den String und 
setze mit strcat den Zustand dahinter.

von Karl H. (kbuchegg)


Lesenswert?

Phillip Hommel schrieb:

> Er sendet anstatt "3on"/"3off" nur "on"/"off". Dabei kopiere ich doch
> vorher die var_number (mit 3 definiert) mit Itoa schon in den String und
> setze mit strcat den Zustand dahinter.

Wenn du schon kein Buch hast, dann solltest du zumindest die Funktionen 
die du benutzt nachschlagen. Das geht heutzutage mit google einfacher 
als je zuvor.

char *  itoa ( int value, char * str, int base );

Hatte denn dein Compiler gar nichts dazu zu sagen?

von Phillip H. (philharmony)


Lesenswert?

Danke, soviel zum Thema Mitternacht und Fehler suchen...

von Phillip H. (philharmony)


Lesenswert?

Geht denn, abgesehen von dem peinlichen Parameter-Dreher, das ganze in 
die richtige Richtung?

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.