Forum: Mikrocontroller und Digitale Elektronik Servomotor steuern - C Routine von dieser Seite


von Jan H. (janiiix3)


Lesenswert?

Guten Morgen,

Ich habe hier eine Routine gefunden, um einen Servomotor zu steuern. Das 
klappt bis jetzt auch ziemlich gut.
Nur löst der leider nicht fein genug auf für meine Zwecke.
Ich benutze einen : BMS-630MG und steuere diesen mit einem MEGA32U4.

Diese Routine verwende ich. Der Quarz ist auf 16MHz festgelegt.
1
//https://www.mikrocontroller.net/articles/Modellbauservo_Ansteuerung#Signalerzeugung_f.C3.BCr_mehrere_Servos_mittels_Timer_.28C.29
2
3
//
4
// Programm fuer einen ATmega32U4
5
//
6
#define F_CPU 16000000UL
7
8
#include <avr/io.h>
9
#include <avr/interrupt.h>
10
#include <util/delay.h>
11
12
//
13
// Der Prescaler muss so gewählt werden, dass der Ausdruck
14
// für MILLISEC_BASE einen Wert kleiner als 128 ergibt
15
// MILLISEC_BASE ist der Timerwert, der 1 Millisekunde Zeitdauer ergeben
16
// soll.
17
//
18
#define PRESCALER      1024
19
#define PRESCALER_BITS (1<<CS02 | 1<<CS00)
20
21
#define MILLISEC_BASE  ( F_CPU / PRESCALER / 1000 )
22
#define CENTER         ( MILLISEC_BASE / 2 )
23
24
25
#define NR_SERVOS      8
26
#define SERVO_DDR      DDRD
27
#define SERVO_PORT     PORTD
28
29
uint8_t ServoPuls[NR_SERVOS] = { 
30
                 1<<PD0,
31
                 1<<PD1, 
32
                 1<<PD2, 
33
                 1<<PD3,
34
                 1<<PD4, 
35
                 1<<PD5, 
36
                 1<<PD6, 
37
                 1<<PD7 
38
                };
39
40
volatile uint8_t ServoValue[NR_SERVOS];
41
42
ISR (TIMER0_COMPA_vect)
43
{
44
  static uint8_t ServoId = 0;
45
  
46
  SERVO_PORT &= ~ServoPuls[ServoId];
47
  
48
  if( ++ServoId >= NR_SERVOS )
49
  {
50
    ServoId = 0;    
51
  }
52
53
  SERVO_PORT |= ServoPuls[ServoId];
54
  
55
  OCR0A = MILLISEC_BASE + ServoValue[ServoId];
56
}
57
58
void InitServo()
59
{
60
  uint8_t i;
61
  
62
  SERVO_DDR = (ServoPuls[0] | ServoPuls[1] | ServoPuls[2] | ServoPuls[3] | ServoPuls[4] | ServoPuls[5] | ServoPuls[6] | ServoPuls[7]);
63
  
64
  OCR0A   = MILLISEC_BASE + ServoValue[0];
65
  TIMSK0 |= (1<<OCIE0A);          // OutputCompareMatchEnable
66
  TCCR0A  = (1<<WGM01);          // CTC mode
67
  TCCR0B  = (PRESCALER_BITS);        // Prescaler
68
}
69
70
int main(void)
71
{
72
  InitServo();
73
  
74
  sei();
75
  
76
  while( 1 )
77
  {  
78
                            // ZUM TEST
79
    for (uint8_t x = 0 ; x < CENTER * 2 ; x++)
80
    {
81
      ServoValue[1] = x;
82
83
    }
84
  }
85
}

von Karl H. (kbuchegg)


Lesenswert?

Jan H. schrieb:

> Nur löst der leider nicht fein genug auf für meine Zwecke.

> //
> // Der Prescaler muss so gewählt werden, dass der Ausdruck
> // für MILLISEC_BASE einen Wert kleiner als 128 ergibt
> // MILLISEC_BASE ist der Timerwert, der 1 Millisekunde Zeitdauer ergeben
> // soll.
> //
> #define PRESCALER      1024
> #define PRESCALER_BITS (1<<CS02 | 1<<CS00)
>
> #define MILLISEC_BASE  ( F_CPU  PRESCALER  1000 )

Das wundert mich nicht, dass du da kaum Auflösung hast. Wenn ich das mal 
mit 16 Mhz durchrechne, dann erhalte ich
1
16000000 / 1024 -> 15625
2
15625 / 1000 -> 15

D.h. dein Servo steht bei einem Wert von ca 7 in der Mittelstellung und 
du kannst es gerade mal +- 7 Positionen links bzw. rechts aus dieser 
Mittelstellung rausfahren.

Aber der Timer kann ja auch noch andere Prescaler.
Zum Beispiel einen Prescaler von 256.

Dann würde sich für MILLISEC_BASE immerhin schon ein Wert von
1
16000000 / 256 -> 62500
2
62500 / 1000  -> 62
ergeben. Sie Servomittelstellung wäre dann schon bei 31 und du hättest 
links und rechts jeweils 31 anfahrbare Servopositionen.

Der nächst kleinere Prescaler wäre 64. Würde der auch noch gehen?
1
16000000 / 64   -> 250000
2
250000 / 1000 -> 250
250 ist schon zu gross. Die Bedingung im Servocode lautet: kleiner als 
128 und das ist nicht erfüllt.

Ohne also den Timer auf einen CTC Modus umzustellen, ist ein Prescaler 
von 256 das Maximum.
1
#define PRESCALER      256
2
#define PRESCALER_BITS (1<<CS02)

wenn du noch feiner auflösen willst, müsste man entweder einen der 16 
Bit Timer opfern oder für den 8 Bit Timer mit einem CTC Modus arbeiten 
um sich in der Berechnung so nahe wie möglich an die 128er Grenze 
heranzutasten.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Karl H. schrieb:

> Der nächst kleinere Prescaler wäre 64. Würde der auch noch gehen?
>
1
> 16000000 / 64   -> 250000
2
> 250000 / 1000 -> 250
3
>
> 250 ist schon zu gross. Die Bedingung im Servocode lautet: kleiner als
> 128 und das ist nicht erfüllt.

Wobei:
Wenn man die ISR überarbeitet und den Puls auf 2 Interrupt Aufrufe 
verteilt, wäre auch das noch machbar, wenn es sonst keine anderen 
Interrupts im System gibt.

von Jan H. (janiiix3)


Lesenswert?

Moin,

vielen dank für deine Antwort.
Der 16 Bit Timer ist bis jetzt ohne Nutzung und könnte für diesen Fall 
ruhig herangezogen werden.

Die Berechnung sollte ja fast die gleiche sein.

Da ich zum ersten mal mit Servos arbeite, habe ich noch eine Frage.
Den Servo kann ich manuel mit der Hand nen stückchen weiter drehen als 
ich es mit der Software hinbekomme. Ist das normal ?
Habe es auch hart mal mit delay versucht...

von Thomas W. (Gast)


Lesenswert?

Jan H. schrieb:
> Den Servo kann ich manuel mit der Hand nen stückchen weiter drehen als
> ich es mit der Software hinbekomme. Ist das normal ?

Ja, sonst könnte es passieren, dass er bei entsprechenden Steuerpulsen 
in den mechanischen Anschlag fährt und den Motor nicht abschaltet, weil 
er seine Sollposition noch nicht erreicht hat.

von Karl H. (kbuchegg)


Lesenswert?

Jan H. schrieb:
> Moin,
>
> vielen dank für deine Antwort.
> Der 16 Bit Timer ist bis jetzt ohne Nutzung und könnte für diesen Fall
> ruhig herangezogen werden.
>
> Die Berechnung sollte ja fast die gleiche sein.

Ja. Ist genau die gleiche.
Nur hast du nicht mehr das Limit aufgrund des Zählumfangs des Timers, 
dass du für 1 Millisekunde einen errechneten Wert kleiner 128 haben 
musst, sondern du kannst den Prescaler soweit verkleinern, bis du einen 
maximalen Millisekundenwert von 32767 hast. Dann hast du massenhaft 
Servopositionen, die das Servo mechanisch wahrscheinlich gar nicht mehr 
exakt anfahren kann :-)

von Jan H. (janiiix3)


Lesenswert?

Damit stelle ich jetzt aber nur den Puls (High Zeit) ein oder ? Wie 
generiere ich denn meine ~ 20 ms. Pause ?

Nachtrag:

Habe mir gerade noch einmal den Artikel durchgelesen, es ist also nicht 
"wirklich" wichtig, dass eine Pause von ca. 20 ms. eingehalten wird.

Also ist es wirklich nur wichtig, wenn ich eine möglichst feine 
Positions Auflösung haben will, die Zeit so fein auf zu lösen wie es 
geht (was der Servo halt so mit macht), richtig ?

: Bearbeitet durch User
von Jan H. (jan_m_h)


Lesenswert?

Jan H. schrieb:
> Habe mir gerade noch einmal den Artikel durchgelesen, es ist also nicht
> "wirklich" wichtig, dass eine Pause von ca. 20 ms. eingehalten wird.

Analoge Servos lassen ihren Regler nur einmal pro Puls durchlaufen, zu 
langsam ist also schlecht für Präzision und reaktionszeit des Servos. Zu 
schnell kommt irgenwann der Servo nicht mehr mit. Also man sollte schon 
grob in der Nähe bleiben.

Edit: nicht verwirren lassen, bin ein anderer Jan H.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Jan H. schrieb:

> Also ist es wirklich nur wichtig, wenn ich eine möglichst feine
> Positions Auflösung haben will, die Zeit so fein auf zu lösen wie es
> geht (was der Servo halt so mit macht), richtig ?

Richtig.

Die Pause für jedes einzelne Servo ergibt sich dadurch, dass prinzipiell 
8 Servos angesteuert werden. Wenn 1 Servo gerade seinen Puls bekommt, 
haben die jeweils anderen 7 Pause. Die einzelnen Servos erhalten 
nacheinander ihren Puls!. Und wie du schon gelesen hast, ist die Länge 
der Pause nicht wirklich wichtig. Ob das 12 oder 16 oder 20 oder 22 ms 
sind, spielt keine grosse Rolle.

1
            <----- hier entsteht ganz ---->
2
                   von alleine eine
3
                   Pause, weil in dieser
4
                   Zeit die anderen Servos
5
                   ihre Pulse erhalten
6
       
7
        +--+                               +--+
8
        |  |                               |  |
9
  1   --+  +-------------------------------+  +------
10
11
12
           +--+                               +--+
13
           |  |                               |  |
14
  2   -----+  +-------------------------------+  +----
15
16
              +--+
17
              |  |
18
  3   --------+  +---------------------- .....
19
20
                 +--+
21
                 |  |
22
  4   -----------+  +------------------- .....
23
24
.
25
.
26
.
27
                 <-->
28
           Der Timer muss mit seinem Zählumfang nur diesen Teil
29
           abdecken können: die maximale Pulslänge.

: Bearbeitet durch User
von Jan H. (janiiix3)


Lesenswert?

Verstanden.

Ich will die Positionen über RS232 entgegen nehmen.


Am Anfang will ich via. PC eine default Position in das EEPROM schreiben 
(je nach dem wie viele Servos ich im Einsatz habe).

Also brauche ich schon mal ein Commando für diese Aktion.
Dann will ich die Position der verschiedenen Servos übergeben.

Habe mir gedacht ich werde das so machen :
Ist das eine gute Idee oder sollte man das anderst machen?
1
                /* commands for default position */
2
        /************************************************************************    
3
        *  set default pos for servo:                      *
4
        *                                    *
5
        *  first byte = default parameter command , second byte = servo_nr    *
6
        *                                    *  
7
        *  servo_pos_0 = 0x11 & 0x00                      *
8
        *  servo_pos_1 = 0x11 & 0x01                      *
9
        *  servo_pos_2 = 0x11 & 0x02                      *
10
        *  servo_pos_3 = 0x11 & 0x03                      *
11
        *  servo_pos_4 = 0x11 & 0x04                      *
12
        *  servo_pos_5 = 0x11 & 0x05                      *
13
        *  servo_pos_6 = 0x11 & 0x06                      *
14
        *  servo_pos_7 = 0x11 & 0x07                      *
15
        *                                    *
16
        ************************************************************************/
17
        
18
                /* commands for new position */
19
        /****************************************************************************************    
20
        *  set new pos for servo:                                *
21
        *                                            *
22
        *  first byte = new position parameter command , second byte = servo_nr , new position *
23
        *                                            *  
24
        *  servo_pos_0 = 0x12 & 0x00 & byte_value                        *
25
        *  servo_pos_1 = 0x12 & 0x01 & byte_value                        *      
26
        *  servo_pos_2 = 0x12 & 0x02 & byte_value                        *      
27
        *  servo_pos_3 = 0x12 & 0x03 & byte_value                        *      
28
        *  servo_pos_4 = 0x12 & 0x04 & byte_value                         *
29
        *  servo_pos_5 = 0x12 & 0x05 & byte_value                         *
30
        *  servo_pos_6 = 0x12 & 0x06 & byte_value                         *
31
        *  servo_pos_7 = 0x12 & 0x07 & byte_value                         *
32
        *                                             *
33
        ****************************************************************************************/



Nur wie sortiere ich die ganzen Befehle? Wartet man darauf bis es ein 
RX_Int gibt und schreibt das erste Byte (Command?) in eine Variable und 
wartet auf das zweite Byte (Value?) und springt dann je nach Commando in 
die jeweilige Funktion?

von Karl H. (kbuchegg)


Lesenswert?

Würde ich  nicht machen.

Es geht zwar aus deinem Beispiel nicht explizit hervor, aber ich würde 
da eine Textschnittstelle machen. Vorteil: kann mit jedem 
Terminalprogramm getestet und in Betrieb genommen werden, ohne dass man 
dazu auf dem PC erst  mal ein Programm (mit seinen eigenen 
Fehlermöglichkeiten) schreiben muss.

Jedes Kommando ist eine Zeile Text, abgeschlossen mit einem Return.
Kommandos sind 1 Buchstabe, nowtwendige weitere Angaben folgen dahinter, 
jeweils mit einem Komma getrennt

Als Kommandos würde ich erst mal vorsehen
1
L          Load positions from EEPROM
2
S          Save positions to EEPROM
3
Pn,m       Position Servo 'n' to position 'm'

Wenn du mal genau überlegst, dann ist zb dein Laden der 
Defaultpositionen nix anderes als erst mal alle 8 Servos auf ihre 
Positionen zu verfahren und dann das Kommando 'S' zu geben, welches die 
Positionen aller 8 Servos speichert. Wenn nur eines eine neue 
Defaultposition kriegen soll, dann geb ich zuerst das Kommando 'L', 
daraufhin fahren alle 8 Servos diese Position an, ich richte sie 
entsprechend mit P Befehlen ein und mit einem S werden alle wieder im 
EEPROM gespeichert.

> Nur wie sortiere ich die ganzen Befehle? Wartet man darauf bis
> es ein RX_Int gibt und schreibt das erste Byte (Command?) in eine
> Variable und wartet auf das zweite Byte (Value?) und springt dann
> je nach Commando in die jeweilige Funktion?

Zum Beispiel. Möglichkeiten gibt es viele. Wenn ich eine 
Textschnittstelle mache, dann lasse ich erst mal eine komplette Zeile 
zusammenkommen und analysiere dann den String was zu tun ist und wie die 
Argumente aussehen. Ganz normale Stringverarbeitung, die hier umso 
einfacher ist, da alle Kommandos nur aus 1 Buchstaben bestehen :-) und 
für die Zahlenwandlung dann eben beispielsweise atoi.

Den Empfang würde ich Interrupt getrieben machen, in der ISR wird das 
jeweils nächste Zeichen in einen Buffer geschrieben, sofern es kein '\n' 
ist. Erkennt die ISR einen '\n' dann setzt sie ein Jobflag und ein 
Teilzweig in der Hauptschleife aktiviert sich und sieht sich den Buffer 
an.

: Bearbeitet durch User
von Jan H. (janiiix3)


Lesenswert?

Das mit den ASCII Zeichen ist eine gute Idee. So in der Art hatte ich es 
auch erst angedacht.

Meine default Werte möchte ich aber auch gerne schreiben (vom 
Terminalprogramm z.B)

Also würde ich das am besten so machen (default Werte schreiben):
1
"S" "1" "80" // S = save default position , 1 = for servo 1, 80 = position

hoffe habe das richtig verstanden.

von Karl H. (kbuchegg)


Lesenswert?

Jan H. schrieb:
> Das mit den ASCII Zeichen ist eine gute Idee. So in der Art hatte ich es
> auch erst angedacht.
>
> Meine default Werte möchte ich aber auch gerne schreiben (vom
> Terminalprogramm z.B)
>
> Also würde ich das am besten so machen (default Werte schreiben):
>
>
1
> 
2
> "S" "1" "80" // S = save default position , 1 = for servo 1, 80 = 
3
> position
4
> 
5
>
>
> hoffe habe das richtig verstanden.

Das ist doch Quatsch. Du wirst doch  nicht am Terminnal die ganzen 
Gänsefüsschen eingeben.

Am Terminal gibst du ein
1
S1,80<Return>
(das Return ist ein Druck auf die Enter Taste) und der µC klamüsert sich 
das auseinander. Du wirst dir doch nicht selbst die Eingabe mit lauter 
Sonderzeichen anfüllen, für die du jedesmal auf der Shift-Taste 
rumklopfen musst.

Ist doch nicht schwer
1
volatile uint8_t haveLine;
2
volatile uint8_t lineLength;
3
volatile char line[20];
4
5
ISR( UART Empfangs Interrupt )
6
{
7
  c = UDR;
8
9
  if( haveLine )
10
    return;       // Hauptschleife hat das vorhergehende noch nicht verdaut
11
12
  else if( c == '\r' )
13
    return;       // ignorieren
14
15
  else if( c == '\n' ) {  // Ende der Zeile (Return wurde gedrückt)
16
    line[lineLength] = '\0';
17
    haveLine = 1;
18
    lineLength = 0;
19
  }
20
21
  else if( lineLength < sizeof(line) ) {
22
    line[lineLength++] = c;
23
    uart_putc( c );   // eventuell Echo schicken, damit der Benutzer auch was sieht
24
  }
25
}
26
27
28
int main()
29
{
30
  ....
31
32
  while( 1 ) {
33
34
    if( haveLine ) {      // Benutzer will was
35
36
      if( line[0] == 'S' ) {     // Servo soll neue Position kriegen
37
        actServo = line[1] - '0';
38
        if( line[2] == ',' )
39
          servoPos[actServo] = atoi( &line[3] );
40
      }
41
42
      else if( line[0] == '+' )
43
        servoPos[actServo]++;
44
45
      else if( line[0] == '-' )     
46
        servoPos[actServo]--;
47
48
      else if( line[0] == '? ) {
49
        itoa( tmp, servoPos[actServo], 10 );
50
        uart_puts( tmp );
51
      }
52
53
      haveLine = 0;     // alles bearbeitet, ISR darf wieder empfangen
54
    }
55
56
    .....
57
}

(Fehlerbehandlung könnte besser sein :-)

: Bearbeitet durch User
von Jan H. (janiiix3)


Lesenswert?

Ich hätte es jetzt so versucht :
1
  while(1)
2
  {    
3
    if (uart_getc() > 1)
4
    {
5
      static uint8_t in_cnt;
6
      
7
      in_cnt++;
8
      
9
      switch(in_cnt)
10
      {
11
        case 1 : {received_data[0] = uart_getc();}break;
12
        case 2 : {received_data[1] = uart_getc();}break;
13
        case 3 : {received_data[2] = uart_getc();}break;
14
        case 4 : {received_data[3] = uart_getc();}break;
15
        default:{in_cnt = 0;}break;
16
      }
17
    }
18
    
19
    if (received_data[3] == '\n')
20
    {
21
      switch(received_data[0])
22
      {
23
          case 'S' : {write_default_position_eeprom();}break;
24
          case 'P' : {set_servo_position(received_data[4], received_data[3]);}break;    
25
      }
26
27
    }
28
  }

von Karl H. (kbuchegg)


Lesenswert?

Jan H. schrieb:

>     if (received_data[3] == '\n')

Du willst das nicht an fixe Zeilenlängen koppeln, wenn es nicht sein 
muss.

>           case 'P' : {set_servo_position(received_data[4],
> received_data[3]);}break;

genausowenig, wie du hier davon ausgehen willst, dass die 
Positionsangabe immer 2 stellig ist. Mach einen String draus und setz 
atoi darauf an. Das kann damit umgehen.
Wie man das ganze ohne Annahme einer Nachrichtenlänge macht, hab ich dir 
ja schon gezeigt. Character von der UART rein und solange es kein \n 
ist, kommt er in ein Array hinten drann.

Ob man diese Character_Sammel_Sache in einer ISR macht oder direkt in 
der Hauptschleife, spielt erst mal weniger die grosse Rolle. In einer 
ISR hat es halt den Vorteil, dass es auch dann funktioniert, wenn der µC 
gerade anderweitig beschäftigt ist.

Edit:
ach ja. Den Backspace Character sollte man auch noch auswerten, damit 
ein Benutzer an einem Terminal auch eine Chance hat eine ungültige 
Eingabe zu korrigieren.
1
ISR( UART Empfangs Interrupt )
2
{
3
  c = UDR;
4
5
  if( haveLine )
6
    return;       // Hauptschleife hat das vorhergehende noch nicht verdaut
7
8
  else if( c == '\r' )
9
    return;       // ignorieren
10
11
  else if( c == '\b' ) {  // Backspace
12
    if( lineLength > 0 )
13
      lineLength--;
14
    uart_puts( "\b \b" );  // Echo, damit auch am Terminal ausradiert wird
15
  }
16
17
  else if( c == '\n' ) {  // Ende der Zeile (Return wurde gedrückt)
18
    line[lineLength] = '\0';
19
    haveLine = 1;
20
    lineLength = 0;
21
  }
22
23
  else if( lineLength < sizeof(line) ) {
24
    line[lineLength++] = c;
25
    uart_putc( c );   // eventuell Echo schicken, damit der Benutzer auch was sieht
26
  }
27
}

: Bearbeitet durch User
von Jan H. (janiiix3)


Lesenswert?

Damit ich nicht immer 'P' senden muss, sende ich ein "+" oder "-" an 
erster stelle um zu incrementieren oder dekrementieren, richtig?

von Karl H. (kbuchegg)


Lesenswert?

Jan H. schrieb:
> Damit ich nicht immer 'P' senden muss, sende ich ein "+" oder "-" an
> erster stelle um zu incrementieren oder dekrementieren, richtig?

Ja, zum Beispiel.
Ich hab mir einfach überlegt, wie ich das wohl machen würde, wenn ich 
ein Servo an eine bestimmte Position einrichten möchte.
Da werde ich ganz sicher nicht dauernd
1
P5,80
2
P5,81
3
P5,82
4
P5,83
tippen wollen (ich bin ja faul), sondern ich hab mir überlegt das sich 
das Positionierkommando das zuletzt benutzte Servo merken könnte. Auch 
will ich das Positionierkommando auch ohne Angabe einer Position 
benutzen können, um es sozusagen einfach nur als das zuletzt benutzte 
Servo bekannt zu machen um es dann in weiterer Folge ansprechen zu 
können. Dann kann ich einfach tippen
1
P5,80
2
+
3
+
4
+
5
-

oder wenn ich die aktuelle Position des Servos gar nicht kenne
1
P5
2
+
3
+
4
+
5
-
6
-
7
+
und das Servo verfährt.
+ und - hab ich genommen, weil sie am Zehnerblock so schön daliegen.

Denk dir was aus! Auf deinem µC bist du Gott! Was immer dir gefällt 
kannst du implementieren.

: Bearbeitet durch User
von Jan H. (janiiix3)


Lesenswert?

Was genau machst du hier?
Eine art Convertierung?
1
actServo = line[1] - '0';

von Karl H. (kbuchegg)


Lesenswert?

Jan H. schrieb:
> Was genau machst du hier?
> Eine art Convertierung?
>
>
1
> actServo = line[1] - '0';
2
>

Aus dem Buchstaben '3' die Zahl 3

Wie gesagt: man sollte da die Fehlerbehandlung noch etwas verbessern.

von Jan H. (janiiix3)


Lesenswert?

1
if( line[2] == ',' )

Und das soll wohl ein Zeichen dafür sein, dass danach ein neuer 
Parameter kommt oder ?

von Karl H. (kbuchegg)


Lesenswert?

genau. Nicht mehr Aufwand als notwendig.

Der Unterschied zwischen
1
P5,80
und
1
P5
besteht augenscheinlich darin, dass hier
1
P5
2
  ^
3
  |

kein ',' eingegeben wurde

Das ist nicht 100% nach den Regeln der Kunst, für eine einfache 
Testumgebung, bei der das Programm nicht gleich bei jeder Falscheingabe 
auf die Nase fällt, sollte es aber reichen.

: Bearbeitet durch User
von Jan H. (janiiix3)


Lesenswert?

Also bräuchte ich diese Abfrage nicht unbedingt?

von Karl H. (kbuchegg)


Lesenswert?

Jan H. schrieb:
> Also bräuchte ich diese Abfrage nicht unbedingt?

mach wie ud es für richtig hältst. Es ist dein Programm.

von Jan H. (janiiix3)


Lesenswert?

Vielen dank. Du hast mir extem geholfen!

von Jan H. (janiiix3)


Lesenswert?

Kann es sein das ich für die Funktion
1
atoi

noch eine Bibliothek einbinden muss?

Mein Compiler meckert :
1
    if( haveLine ) // Benutzer will was
2
    {      
3
4
      if( received_data[0] == 'S' ) // Servo soll neue Position kriegen
5
      {    
6
        servo_nr_tmp  = atoi(&received_data[1]);
7
        servo_pos_value = atoi(&received_data[2]);
8
        
9
        set_servo_position(servo_nr_tmp, servo_pos_value);
10
      }
11
      else if(received_data[0] == '+' )
12
      {
13
        set_servo_position(servo_nr_tmp, servo_pos_value++);
14
      }
15
      else if( received_data[0] == '-' )
16
      {
17
        set_servo_position(servo_nr_tmp, servo_pos_value--);
18
      }
19
      else if( received_data[0] == '?' ) 
20
      {
21
22
      }
23
    }
24
25
      haveLine = 0;     // alles bearbeitet, ISR darf wieder empfangen
26
27
  }

von Jan H. (janiiix3)


Lesenswert?

Sorry.

Das Array sollte man auch als "Char" deklarieren :-P.

von Jan H. (janiiix3)


Lesenswert?

Mit deiner Abfrage klappt es nicht.
Ohne klappt es 1 x und dann nicht wieder.
1
   if( haveLine )
2
   { //
3
        }

von Jan H. (janiiix3)


Lesenswert?

Es scheint mir auch so als wenn er nur das erste Zeichen betrachten 
würde und das wars dann. Die anderen Bytes werden nicht ausgewertet.
1
    if( received_data[0] == 'P' )
2
    {
3
      servo_nr_tmp    = received_data[1];
4
      servo_pos_value_tmp = received_data[2];
5
      
6
      servo_nr_tmp    = atoi(&servo_nr_tmp);
7
      servo_pos_value_tmp = atoi(&servo_pos_value_tmp);
8
      set_servo_position(servo_nr_tmp, servo_pos_value_tmp);
9
    }
10
    else if(received_data[0] == '+' )
11
    {
12
      set_servo_position(servo_nr_tmp, 88);
13
    }
14
    else if( received_data[0] == '-' )
15
    {
16
      set_servo_position(servo_nr_tmp, servo_pos_value_tmp--);
17
    }
18
    else if(received_data[0] == 'S')
19
    {
20
      servo_nr_tmp    = received_data[1];
21
      servo_pos_value_tmp = received_data[2];
22
      
23
      servo_nr_tmp    = atoi(&servo_nr_tmp);
24
      servo_pos_value_tmp = atoi(&servo_pos_value_tmp);
25
          
26
      write_default_position_eeprom(servo_nr_tmp,servo_pos_value_tmp);
27
    }

von Jan H. (janiiix3)


Lesenswert?

Also wenn ich via. Terminal 1 x P sende klappt das. Sende ich jetzt aber 
1 x S, so reagiert er einfach nicht drauf und überschreibt P nicht...
An was kann das liegen?
1
unsigned int uart_getc (void)
2
{    
3
    unsigned char tmptail;
4
    unsigned char data;
5
6
7
    if ( UART_RxHead == UART_RxTail ) {
8
        return UART_NO_DATA;   /* no data available */
9
    }
10
    
11
    /* calculate /store buffer index */
12
    tmptail = (UART_RxTail + 1) & UART_RX_BUFFER_MASK;
13
    UART_RxTail = tmptail; 
14
    
15
    /* get data from receive buffer */
16
    data = UART_RxBuf[tmptail];
17
    
18
    data = (UART_LastRxError << 8) + data;
19
    UART_LastRxError = 0;
20
  
21
22
  if(haveLine)
23
  {
24
    return(0);
25
  }
26
  else if( data == '\r' )
27
  {
28
    return(0);
29
  }
30
  else if( data == '\n' )
31
  {
32
    received_data[lineLength] = '\0';
33
    haveLine = 1;
34
    lineLength = 0;
35
  }
36
  else if( lineLength < sizeof(received_data) )
37
  {
38
    received_data[lineLength++] = data;
39
  }
40
  
41
42
    return data;
43
44
}/* uart_getc */

von Jan H. (janiiix3)


Lesenswert?

Wenn ich ASCII "50" sende, dann brauche ich doch 2 Bytes um den Wert auf 
dem MEGA zu speichern oder nicht?

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

Jan H. schrieb:
> Wenn ich jetzt im Terminal folgendes als ASCII Sende :
1
P 1 50
>
> Übergebe ich meiner
> Funktionset_servo_position(servo_nr_tmp,servo_pos_value_tmp);
>
> folgende Werte
1
P 49 ???
>
> Ich kann doch in ASCII keine 50 angeben ?!

Du vermischt in der zweiten Ausgabe dezimal und asccii, das ist 
verwirrend. Wenn das Erste wirklich genauso eingegeben wurde, ist 
received_data[1] der Abstand, und du verwendest atoi falsch, dass nimmt 
ein  nullterminiertes array von chars, deins ist nicht nullterminiert. 
Wenn du 2 zahlen in ascii hast, sind diese in 2 chars, dann rechnet man 
erste Zahl mal 10 plus zweite zahl, so macht das auch atoi und 
wiederholt es nis zum ersten nullbyte.

von Jan H. (janiiix3)


Lesenswert?

Okay...

Also für meine Werte erwarte ich :
1
    1        2       3
2
S oder P , 0-8 , 0 - 255

das heißt (Würde ich es in ASCII eingeben)
1
received_data[0] = S | P; (erster Parameter)
2
3
received_data[1] = 0-8; (zweiter Parameter)
4
5
received_data[2] = 0-9; (dritter Parameter)
6
received_data[3] = 0-9;

sehe ich das richtig?

von Jan H. (janiiix3)


Lesenswert?

Danke für die Anregung!
Habe es jetzt so gelöst.
1
      received_data[1] -= 48;
2
      received_data[2] -= 48;
3
      received_data[3] -= 48;
4
5
      received_data[2] *= 10;
6
      received_data[2] += received_data[3];

Nur wie mache ich das wenn ich einen Wert > 100 habe?

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

Jan H. schrieb:
> Nur wie mache ich das wenn ich einen Wert > 100 habe?

Du musst dir ein geschicktes Dateiformat überlegen, bei dem erkennbar 
ist wo etas Anfängt und wo etwas Aufhört. Dann kannst du so lange die 
Nächste Zahl zum Zwischenergebnis addieren, bis die Endbedungung 
Auftritt, z.B. das Zeichen keine zahl mehr ist oder es das letzte 
Zeichen war, etc.

von Jan H. (janiiix3)


Lesenswert?

Wenn die Eingabe zuende ist bekomme ich ein '\n'.

von Daniel A. (daniel-a)


Lesenswert?

Ich würde einfach eine Funktion schreiben die Zahlen parst, und einen 
Iterator verwenden:
1
// ungetestet
2
int parseInt( char** data, unsigned max_len, bool allow_sign ){
3
  int res = 0;
4
  bool sign = false;
5
  if( max_len && allow_sign && **data=='-' ){
6
    sign = true;
7
    (*data)++;
8
    if( ~max_len )
9
      max_len--;
10
  }
11
  for(; max_len && **data >= '0' && **data <= '9'; (*data)++,  ~max_len && max_len-- ){
12
    res *= 10;
13
    if(sign)
14
      res -= **data;
15
    else
16
      res += **data;
17
  }
18
  return res;
19
}

Das könnte man dann so anwenden:
1
  char* it = received_data;
2
  switch(*it++){
3
    case 'P': {
4
      servo_nr_tmp = parseInt(&it,1,false);
5
      servo_pos_value_tmp = parseInt(&it,~0,false); 
6
      set_servo_position(servo_nr_tmp, servo_pos_value_tmp); 
7
    } break;
8
    case '+': {
9
...

von Jan H. (janiiix3)


Lesenswert?

Hallo ;)

Ich sende jetzt einfach als letztes Byte die Potenz des Wertes mit. 
Klappt prima ;)
1
    if( received_data[0] == 'P' ) // set new position
2
    {
3
      received_data[1] -= 48;
4
      received_data[2] -= 48;
5
      received_data[3] -= 48;
6
      received_data[4] -= 48;
7
      received_data[5] -= 48;
8
      
9
      if (received_data[5] == 1) // one
10
      {
11
        set_servo_position(received_data[1], received_data[4]);
12
      }else if(received_data[5] == 2) // ten
13
      {
14
        received_data[3] *= 10;
15
        received_data[3] += received_data[4];
16
        
17
        set_servo_position(received_data[1], received_data[3]);
18
      }else if(received_data[5] == 3) // hounder
19
      {
20
        received_data[2] *= 100;
21
        received_data[3] *= 10;
22
        received_data[2] += (received_data[3]) + received_data[4];
23
      
24
        set_servo_position(received_data[1], received_data[2]);
25
      }  
26
    }

: Bearbeitet durch User
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.