Forum: Compiler & IDEs Bitte um Hilfe bei DS1307 Problem


von Dominik G. (moondryl)


Lesenswert?

Hallo,
nachdem ich I2C nun doch noch zum Laufen gebracht habe, sitze ich nun 
wieder vor einem Problem:
1
#define F_CPU 16000000UL
2
#include <avr/io.h>
3
#include <util/delay.h>
4
#include <uart.h>
5
#include <avr/interrupt.h>
6
#include <i2cmaster.h>
7
8
#define UART_BAUD_RATE 9600
9
10
#define DS1307  0xD0
11
12
unsigned char buffer;
13
char seconds;
14
15
16
char decToBcd(char val)
17
{
18
  return ((val / 10 * 16) + (val % 10));
19
}
20
21
// Convert binary coded decimal to normal decimal numbers
22
char bcdToDec(char val)
23
{
24
  return ((val / 16 * 10) + (val % 16));
25
}
26
27
int main(void)
28
{
29
  sei();
30
  DDRB = (1 << PB0);
31
  
32
  
33
  uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) ); //uart initialisieren
34
  i2c_init(); //i2c initialisieren
35
  
36
  //schreibe CH-Bit
37
  i2c_start_wait(DS1307+I2C_WRITE);
38
  i2c_write(0x81);
39
  i2c_stop();
40
  //*schreibe CH Bit
41
  
42
  //CH Bit gesetzt?
43
  i2c_start_wait(DS1307+I2C_WRITE);
44
  i2c_write(0x00);
45
  i2c_rep_start(DS1307+I2C_READ);
46
  buffer = i2c_readNak();
47
  i2c_stop();
48
  
49
  if ((buffer & 0x80)) //Falls CH Bit gesetzt ==> löschen
50
  {
51
    i2c_start_wait(DS1307+I2C_WRITE);
52
    i2c_write(0x00);
53
    i2c_write((buffer & 0x7F));
54
    i2c_stop();
55
    uart_puts("CH Bit gelöscht\n");
56
  }
57
      
58
  
59
    while(1)
60
    {
61
        PORTB ^= ( 1 << PB0 );  // Toggle PB0 
62
    
63
        i2c_start_wait(DS1307+I2C_WRITE);
64
        i2c_write(0x00); //Seconds Register
65
        i2c_rep_start(DS1307+I2C_READ);
66
        buffer = i2c_readNak(); // read seconds
67
        i2c_stop();
68
  
69
        seconds = bcdToDec((buffer & 0x7F));
70
      
71
        _delay_ms(500); 
72
    }
73
}

(1) Obwohl ich Anfang im Seconds Register zwangsweise das CH-Bit setze, 
scheint das Programm nicht in die If-Abfrage reinzugehen, um das Bit 
wieder zu löschen.

(2) In seconds sollen ja jetzt die Sekunden stehen. So wie ich Peter 
Fleury's UART-Library verstanden habe, kann ich seconds ja nicht einfach 
uart_puts(); übergeben. Kann ich das nun in einen String umwandeln?

Hoffe, ihr könnt mir einen Tipp geben.

Grüße

Dominik

von Karl H. (kbuchegg)


Lesenswert?

Dominik Gebhardt schrieb:

> (2) In seconds sollen ja jetzt die Sekunden stehen. So wie ich Peter
> Fleury's UART-Library verstanden habe, kann ich seconds ja nicht einfach
> uart_puts(); übergeben. Kann ich das nun in einen String umwandeln?

FAQ gleich der erste Punkt

von Karl H. (kbuchegg)


Lesenswert?

Die Überlegung, die dich zu diesem Code gebracht hat
1
  //schreibe CH-Bit
2
  i2c_start_wait(DS1307+I2C_WRITE);
3
  i2c_write(0x81);
4
  i2c_stop();
5
  //*schreibe CH Bit

ist mit unklar.

Nachdem du den Slave adressiert hast, ist das erste Byte, welches da auf 
den Weg bringst, die Register Adresse. Das wäre bei dir 0x81. Das ist 
aber nicht das, was du willst. Du willst ja das Register 0 adressieren 
und dort die 0x81 (also BCD-01 plus gesetztes CH-Bit) einschreiben.
Also müsste das lauten
1
//schreibe CH-Bit
2
  i2c_start_wait(DS1307+I2C_WRITE);
3
  i2c_write(0x00);
4
  i2c_write(0x81);
5
  i2c_stop();
6
  //*schreibe CH Bit

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Dominik Gebhardt schrieb:
>
>> (2) In seconds sollen ja jetzt die Sekunden stehen. So wie ich Peter
>> Fleury's UART-Library verstanden habe, kann ich seconds ja nicht einfach
>> uart_puts(); übergeben. Kann ich das nun in einen String umwandeln?
>
> FAQ gleich der erste Punkt


Obwohl.
Bei dir gehts viel einfacher. Genau das ist ja der springende Punkt, 
warum die Uhr überhaupt in BCD zählt. Eben weil man dann ganz trivial 
eine Textrepräsentierung der Zahl erzeugen kann, ohne viel rechnen zu 
müssen.
1
        ....
2
3
        buffer = i2c_readNak(); // read seconds
4
        i2c_stop();
5
  
6
        char num[3];
7
        num[0] = (( buffer & 0x7F ) >> 4 ) + '0';
8
        num[1] = (( buffer & 0x0F ) + '0';
9
        num[2] = '\0';
10
11
        uart_puts( num );
12
        uart_puts( "\n" );
13
14
        ....

von Dominik G. (moondryl)


Lesenswert?

Asche auf mein Haupt!
Weiter unten immer darauf geachtet, dass ich erst die Adresse schreibe 
und dann das Byte und dort vergessen; peinlich.

Danke dir, Karl Heinz. Funktioniert nun so, wie es soll.

Vielen Dank euch beiden für die sehr schnelle Hilfe! :)

Viele Grüße

Dominik

Edit: Gerade nochmal den die verlinkten FAQ angesehen. Mit itoa(); hatte 
ich es probiert. Allerdings stand in meiner Variablen Müll drin, 
weswegen ich es wieder verworfen hatte. Dann war ich ja doch auf der 
richtigen Fährte und meine Umrechnung stimmt doch nicht.

von Karl H. (kbuchegg)


Lesenswert?

Dominik Gebhardt schrieb:

> weswegen ich es wieder verworfen hatte. Dann war ich ja doch auf der
> richtigen Fährte und meine Umrechnung stimmt doch nicht.

Was mir als allererstes aufgefallen ist.

Wenn du in deinem Code mit 'Bytes' operierst, dann ist der Datentyp 
dafür ein 'unsigned char' oder noch besser ein 'uint8_t'. Aber 'char' 
ist nicht der Datentyp, den du für Arbeiten mit Bytes benutzen willst. 
Bei 'char' ist nicht festgelegt, ob der ein Vorzeichen hat oder nicht. 
Du willst kein Vorzeichen und zwar willst du das ganz sicher nicht. 
Daher willst du dich auch nicht deinem Compilerbauer ausliefern, wie er 
das für char festgelegt hat. An dieser Stelle bist du lieber explizit 
und verlangst einen uint8_t

D.h. das hier
1
char decToBcd(char val)
2
{
3
  return ((val / 10 * 16) + (val % 10));
4
}

ist besser als
1
uint8_t decToBcd(uint8_t val)
2
{
3
  return ((val / 10 * 16) + (val % 10));
4
}
(und die Umkehrung genauso.)

Und genauso willst du einen
1
uint8_t seconds;
und keinen char. Denn Sekunden sind nun mal nicht negativ.

von Karl H. (kbuchegg)


Lesenswert?

Dominik Gebhardt schrieb:


> Edit: Gerade nochmal den die verlinkten FAQ angesehen. Mit itoa(); hatte
> ich es probiert.

itoa      für einen int oder int8_t. Also etwas mit Vorzeichen
utoa      für einen unsigned int oder uint8_t. Also etwas ohne 
Vorzeichen


Es obligt deiner Verantwortung, die richtige Funktion zu benutzen! itoa 
bzw. utoa ist das wurscht! Die Funktion schnappt sich die übergebenen 
Bytes und interpretiert sich diese Bytes als mit/ohne Vorzeichen, je 
nach Funktion.

von Karl H. (kbuchegg)


Lesenswert?

Dominik Gebhardt schrieb:
> Asche auf mein Haupt!
> Weiter unten immer darauf geachtet, dass ich erst die Adresse schreibe
> und dann das Byte und dort vergessen; peinlich.

Jetzt weißt du, warum man sich für alles mögliche Funktionen schreibt 
:-)

von Dominik G. (moondryl)


Lesenswert?

Oh man, immer wenn ich denke, ich kann jetzt einen Schritt vorwärts 
gehen, habe ich das Gefühl, dass ich doch wieder zwei zurück gehe. :D

> itoa      für einen int oder int8_t. Also etwas mit Vorzeichen
> utoa      für einen unsigned int oder uint8_t. Also etwas ohne
> Vorzeichen

Genau, hatte halt den Fehler gemacht char statt unsigned char zu 
verwenden. Deswegen hatte ich noch itoa(); genutzt.


> Jetzt weißt du, warum man sich für alles mögliche Funktionen schreibt
> :-)

Damit man nicht einmal den Fehler, obwohl man es sonst immer richtig 
gemacht hat. Gleich mal eine Funktion geschrieben, in der man explizit 
Adresse, Adressregister und Inhalt übergeben muss. :)

Eine Frage zu deiner Berechnung habe ich aber gerade noch:
1
char num[3];
2
        num[0] = (( buffer & 0x7F ) >> 4 ) + '0'; 
3
        num[1] = (( buffer & 0x0F ) + '0';
4
        num[2] = '\0';

Die Bitmasken sind klar und die Bitverschiebung bei num[0] ist auch 
klar. Aber wozu das +'0'? Hat das eine ähnliche Funktion wie '\0'? ('\0' 
bezeichnet ja das Stringende).

Grüße und nochmals Danke für die ganzen Informationen!

von Uwe (de0508)


Lesenswert?

Hallo,

num[0] und num[1] enthält dann die Dezimalzahl, derer vorher in BCD 
repräsentierten Zahl.

Somit enthält num[] einen C-String mit der BCD-Zahl als String.

von Karl H. (kbuchegg)


Lesenswert?

Dominik Gebhardt schrieb:


> Die Bitmasken sind klar und die Bitverschiebung bei num[0] ist auch
> klar. Aber wozu das +'0'?

Weil am anderen Ende deiner UART ein Terminal sitzt. Und das will ASCII 
Codes sehen, damit es was auf den Monitor hinpinselt.

Nach dem Ausmaskieren und Verschieben hast du bereits numerische Ziffern 
von 0 bis 9. Die sind gut, kannst sie aber noch nicht gebrauchen. Damit 
das Terminal das Zeichen '0' hinpinselt, musst du ihm den ASCII Code für 
'0' schicken und nicht die Zahl 0. Damit es eine '1' hinpinselt, musst 
du ihm den ASCII Code für '1' schicken und nicht die Zahl 1. Damit ....

Glücklicherweise sind im ASCII Code die Codes für die Ziffernzeichen 
genau so angeordnet, dass sie hintereinander kommen. D.h. wenn du zum 
ASCII Code von '0' noch 5 dazuzählst, kriegst du den ASCII Code des 
Zeichens '5'.

Und da uns das zu fad ist, uns selber aus einer ASCII Tabelle 
rauszusuchen, welches der ASCII Code für '0' ist (der wäre 0x30), lassen 
wir das den Compiler machen. Der kann das genausogut, indem ich ihm 
einfach das Zeichen hinschreibe und ich muss mich nicht mit 
irgendwelchen Hex-Codes als ASCII Codes rumplagen. Wenn ich in c

   char c;

den ASCII Code des Zeichens 'p' haben will, dann schreibe ich auch

    c = 'p';

und nicht

    c = 0x70;

oder gar

    c = 112;

obwohl technisch gesehen alle 3 Varianten genau dasselbe machen. Für die 
Maschine mag das dasselbe sein (nämlich ein immer gleiches Bitmuster in 
den Character c zu laden), aber für mich als Mensch macht das einen 
gewaltigen Unterschied. Bei  c = 'p' sehe ich auf einen Blick was Sache 
ist. Bei c = 112 seh ich erst mal gar nichts, sondern muss eine ASCII 
Tabelle zu Hilfe nehmen, um zu wissen welchen ASCII Code ich da gerade 
in c gespeichert habe.


In der EDV sind alles Zahlen! Auch Zeichen sind Zahlen. Wenn dein 
Terminal ein 'A' auf den Bildschirm malt, dann deswegen, weil es ein 
Byte mit dem Wert 0x41 bekommen hat und das laut ASCII Tabelle der Code 
für 'A' ist.

von Dominik G. (moondryl)


Lesenswert?

Bis zu dem Punkt, dass in num[x] dann die einzelnen Zahlen für Zehner 
und Einser stehen, hatte ich es verstanden. Aber dass das '0' also 
ASCII-Code addiert wird und mann dann auf den ASCII-Code für z.B. 5 
kommt, war mir nicht klar.

Vielen Dank für die asuführliche Erklärung und für eure Geduld mit mir!
Nun ist aber alles klar!
Konnte jetzt auch meine Berechnungsfunktion korrekt über den UART 
ausgeben. Nun weiß ich auch, warum da vorher Murks drin stand. Deine 
Berechnung für BCD ist aber durchaus eleganter und hier auch 
einleuchtender als meins und werde es deshalb auch beibehalten. Wollte 
nur wissen, ob ich jetzt alles richtig verstanden habe und es auch mit 
dem alten Code zum Laufen bekomme. Dank eurer Hilfe ist dem jetzt so! :)

Vielen Dank und viele Grüße

Dominik

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.