Hallo zusammen,
zuerst möchte ich mich bedanken, dass es so eine tolle Community gibt –
habe schon viel nützliches hier gelesen.
Projektbeschreibung:
In meinem Projekt muss ich ein Energieversorgungsmodul für ein autarkes
System entwickeln. Das System selbst kommuniziert über den I2C-Bus und
wird von einem Solarladeregler mit 12V Spannung versorgt. Damit man
immer weiß, was der Laderegler tut bzw. wie groß sein Strom und Spannung
sind, habe ich mir einen Laderegler Tracer 1215BN mit dem MODBUS
ausgesucht. Nun ist das Problem, dass eine Schnittstelle zwischen dem
MODBUS und dem I2C- organisiert werden muss. Dabei habe ich an den
MSP430G2553 mit dem Transceiver SP3485 von SparkFun (Breakout RS485)
gedacht, da sie, meiner Meinung nach, die Voraussetzungen dafür
erfüllen.
Voraussetzungen:
Auf der I2C-Seite muss der Mikrocontroller als ein Slave-Gerät mit
400KHz fungieren.
Auf der MODBUS-Seite ist der MSP430G2553 als Master-Gerät mit folgenden
Einstellungen (laut dem MODBUS-Protokoll vom Laderegler) zu betreiben:
Modus: RTU
16bit MODBUS addresses (per the modbus.org spec)
BPS: 115200 baud
Parity: none
Data bits: 8
Stop bits: 1
Flow control: none
Kommunikation:
Die Kommunikation zwischen dem System und dem Laderegler stelle ich mir
folgendermaßen vor:
Das I2C-Master-System sendet eine Anfrage-Nachricht an den
I2C-Slave-Mikrocontroller und löst eine Interrupt-Routine aus.
Der Mikrocontroller sendet eine Anfrage-Nachricht als Master per MODBUS
an den Laderegler weiter. Um z.B. den Wert der aktuellen Spannung vom
Laderegler zu bekommen, wird folgende Nachricht an den Laderegler
geschickt: 01 04 31 04 00 01 7E F7
01 Device ID
04 Function Code
31 04 Register Address
00 01 Register Count
7E F7 CRC Summe
Der Laderegler antwortet darauf und sendet die Werte von Spannung und
Strom, die im RAM vom Mikrocontroller gespeichert werden. Die
Antwort-Nachricht für die Spannung würde z.B. folgendermaßen aussehen:
01 04 02 04 CE 3A 64
Die hexadezimale Zahl 04 CE würde als dezimale Zahl dem Wert 1230
entsprechen bzw. 12,3V.
Nachdem die Werte im Speicher vom Mikrocontroller abgelegt sind, werden
diese per I2C-Bus an das I2C-Mastersystem weiter geschickt.
Frage:
Würde es so, wie ich es beschrieben habe, funktionieren oder ist das
Konzept nicht zu realisieren?
Bin für jede Hilfe und Kritik sehr Dankbar!
Das sollte soweit als Gateway funktionieren. Die Baudrate kann ggf. Nach
Gerät abweichen, also Blick ins Datenblatt. Dazu im Hinterkopf
behalten, dass je nach modbus Gerät die in ein Timeout laufen können,
wenn nicht zyklisch Modus Pakete ankommen, aber auch dazu müsste was im
Datenblatt stehen.
Danke für die rasche Antwort.
Mir war erstmal wichtig, ob es so überhaupt funktionieren würde.
Das mit der zyklischen Abfrage werde ich dann auch genau anschauen, aber
soweit ich mich erinner kann, stand im Datenblatt nichts davon.
Hab nun den MSP430G2553 soweit programmiert, dass ich eine Nachricht
verschicken kann (Bild vom Oszi im Anhang). Allerdings antwortet der
Laderegler auf die Anfrage nicht...
1. Warum kommt nichts zurück vom Laderegler?
Wenn ich die gleiche Anfrage [01 04 31 04 00 01 7E F7] per Simply Modbus
Master vom PC->USB-Adapter->Laderegler schicke, dann antwortet mir auch
der Laderegler zurück.
2. Beim Senden sollte die P1.0 LED angehen - dies passiert aber auch
nicht...
Hier ist ebenfalls der Code, den ich benutze:
Daß die "Sende"-LED nicht angesteuert wird, ist klar, denn Du verwendest
kein interruptgesteuertes Senden. Daher wird der Sendeinterrupt nie
ausgelöst und Deine USCI0TX_ISR nie aufgerufen.
Woher stammt Dein Code zur Ansteuerung der USCI?
Du musst beim 'G2553 sehr genau hinsehen sein, der verwendet zwar wie
diverse andere MSP430-Varianten auch die USCIA/B, aber mit einer
ziemlich anderen Interruptverteilung.
Hallo Rufus,
ich dachte, dass die Sende-ISR wird dann aufgerufen, wenn in TX-Buffer
was kopiert wird?
Den Code habe ich aus verschiedenen Quellen und Codes im Internet
zusammengebastelt und er funktioniert, leider nur zum Teil.
Mir fehlt noch, glaube ich zumindest, Pull-UP und Pull-DOWN Widerstand
am P2.0. Sehe ich das richtig? Mir gefällt das Signal am Oszi irgendwie
nicht - zu viele Spikes...
Vadim D. schrieb:> ich dachte, dass die Sende-ISR wird dann aufgerufen, wenn in TX-Buffer> was kopiert wird?
Nur, wenn der entsprechende Interrupt auch aktiviert ist. Ist er aber
nicht.
Rufus Τ. F. schrieb:> Vadim D. schrieb:>> ich dachte, dass die Sende-ISR wird dann aufgerufen, wenn in TX-Buffer>> was kopiert wird?>> Nur, wenn der entsprechende Interrupt auch aktiviert ist. Ist er aber> nicht.
Meinst du dann diese Zeile im Code?
IE2 |= UCA0TXIE; // Enable USCI_A0 TX interrupt?
Wenn ich sie wieder einklammere, dann geht die P1.0 LED an, selbst wenn
ich in den TX-Puffer nichts reinschreibe...
Habe ich da einen Denkfehler?
Vadim D. schrieb:> ich dachte, dass die Sende-ISR wird dann aufgerufen, wenn in TX-Buffer> was kopiert wird?
Nein. Die Sende-ISR wird aufgerufen, wenn
* der Interrupt aktiviert ist und
* der Sendepuffer leer ist
Der typische Code in der Sende-ISR lautet deswegen
1. Hole nächstes zu sendendes Zeichen
2. Schreibe dieses Zeichen in den Sendepuffer
> Mir gefällt das Signal am Oszi irgendwie nicht - zu viele Spikes...
Das Signal ist doch top.
Max
Max G. schrieb:> Vadim D. schrieb:>>> ich dachte, dass die Sende-ISR wird dann aufgerufen, wenn in TX-Buffer>> was kopiert wird?>> Nein. Die Sende-ISR wird aufgerufen, wenn> * der Interrupt aktiviert ist und> * der Sendepuffer leer ist>> Der typische Code in der Sende-ISR lautet deswegen> 1. Hole nächstes zu sendendes Zeichen> 2. Schreibe dieses Zeichen in den Sendepuffer>>> Mir gefällt das Signal am Oszi irgendwie nicht - zu viele Spikes...>> Das Signal ist doch top.>> Max
Das mit der Sende-ISR muss ich mich noch beschäftigen...
Aber wenn das Signal in Ordnung ist und auf die gleiche Anfrage vom PC
der Laderegler sich meldet, warum antwortet er dann in diesem Fall
nicht?
Ich habe zwischen zwei Bytes eine Pause von 4µS und laut dem MODBUS
Protokoll müssen es mindestens 3,5µS sein. Mir fällt da momentan nichts
mehr ein, was ich noch jetzt machen bzw. ändern kann.
das sind 100ns pro Abschnitt, das Schwingen ist vollkommen normal bzw
ein Messfehler, darüber brauchst du dir keine Gedanken zu machen, wenn
du die Schleife zwischen Messspitze und Messmasse kleiner machst wird
das Schwingen auch kleiner.
Also ich sehe schon einen gewaltigen Unterschied zwischen dem PC und µC
beim Versenden der gleichen Anfrage - 01 04 31 04 00 01 7E F7.
Die Anfrage vom PC ist ungefähr 700µS lang und hat am Anfang zwei
Zeichen gleicher Länge. Bei dieser Anfrage meldet sich der Laderegler
und sendet eine Antwort.
Beim µC ist die Anfrage ca. 600µS lang und hat am Anfang zwei Zeichen
mit gleicher Länge nicht.
Screenshots 09,10,11 im Anhang sind vom PC und 07 vom µC.
Wenn man beim PC nun noch etwas erkennen würde...
Was zeigen CH1 und CH2? Während der Übertragung sind beide zueinander
invers, außerhalb der Übertragung beide Null. Oder sehe ich das falsch?
Max
Max G. schrieb:> Wenn man beim PC nun noch etwas erkennen würde...>> Was zeigen CH1 und CH2? Während der Übertragung sind beide zueinander> invers, außerhalb der Übertragung beide Null. Oder sehe ich das falsch?>> Max
Bei dem Protokoll wird eine Nachricht verschickt, die über die
Potenzialdifferenz zwischen dem Kanal A und B übertragen wird. Je
nachdem, ob zwischen der Datenleitung A und B eine Differenzspannung von
mind. +/-200mV vorhanden ist, wird eintweder eine 1 oder 0 übertragen.
Bei mir sind beide Leitungen vor dem Übertragen auf GND, so sehe ich es
zumindest...
OK, danke.
Kannst du die Screenshots der µC-Variante noch mal mit einem Offset
machen, so dass man auch tatsächlich etwas sieht? Die beiden Linien
liegen dermaßen übereinander, dass das Nutzsignal nur schwer zu erkennen
ist. Beim PC-Signal (letztes Bild) ist es ok.
Du könntest alternativ dein Oszi auch per Math-Mode dazu bringen, die
Differenz der Kanäle auszugeben. Das wäre sogar noch besser.
Und: wo sehe ich eigentlich die zeitliche Auflösung?
Max
Max G. schrieb:> OK, danke.>> Kannst du die Screenshots der µC-Variante noch mal mit einem Offset> machen, so dass man auch tatsächlich etwas sieht? Die beiden Linien> liegen dermaßen übereinander, dass das Nutzsignal nur schwer zu erkennen> ist. Beim PC-Signal (letztes Bild) ist es ok.>> Du könntest alternativ dein Oszi auch per Math-Mode dazu bringen, die> Differenz der Kanäle auszugeben. Das wäre sogar noch besser.>> Und: wo sehe ich eigentlich die zeitliche Auflösung?>> Max
Im Anhang sind nun alle Screenshots vom µC-Signal.
Die Zeitliche Auflösung steht oben links bei TB.
Mich stört gerade, dass die beiden Signale unterschiedliche Länge
aufweisen und gar nicht ähnlich sogar am Anfang sind...
Die Math-Mode Funktion ist mir nicht bekannt. Würde dies was bringen?
Ich meine bloß ich bin der Meinung das Problem liegt eher am falschen
Signal, das mein µC liefert.
Weiss aber ehrlich gesagt noch nicht warum...
Laut dem MODBUS-Protokoll vom Laderegler handelt es sich um die folgende
Übertragungsart:
Modus: RTU
16bit MODBUS addresses (per the modbus.org spec)
BPS: 115200 baud (ca. 8,7µS pro Bit)
Parity: none
Data bits: 8
Stop bits: 1
Flow control: none
Es heißt es sind pro Frame
- 1 Startbit
- 8 Databit
- 1 Stopbit
zu versenden.
Dazu kommt eine Sendepause zwischen 2 Frames von mind. 3,5 µS.
Somit komme ich bei einer Nachricht 01 04 31 04 00 01 7E F7 auf:
8,7µS * 10 (Bit) * 8 (Frames) = 696µS - dies sehe ich auch beim
PC-Signal(siehe Bild - PC-Signal).
Das µC-Signal mit gleicher Nachricht ist aber kürzer und es sind ca.
580µS(siehe Bild - µC-Signal).
Wenn ich nun aber nun 1 Byte (0x01) vom µC sende, dann sieht alles top
aus (siehe Bild - 1 Byte 0x01).
Warum schickt dann der µC die Nachricht nicht komplett?
Jemand eine Idee?
Also ich habe es hingekriegt!)
Die Anfrage vom µC fiel mir, wie ich schon sagte, etwas kurz auf.
Dann habe ich eine Pause beim Versenden von 2 Bytes hinzugefügt und dann
am Ende fehlten mir noch 40µS mit dem HIGH Signal (siehe Bild: Signal
mit Pause). Danach ist es mir gelungen, die Antwort von meinem
Laderegler zu bekommen (siehe Bild: Anfrage mit Antwort).
Die Anfrage vom µC und die Antwort vom Laderegler sind im Bild
verschoben, da sie unterschiedliche Groundpotentiale haben: beim µC
Stromversorgung vom PC und beim Laderegler Stromversorgung von
Autobatterie. Der DC-DC Wandler für den µC ist momentan nicht
funktionsfähig, sobald ich einen neuen bekomme, werde ich die Potentiale
ausgleichen können und das Problem sollte gelöst sein.
Ich hätte aber noch zwei Fragen:
1. In meinem Code verwende ich einen Pointer, der auf eins von 6 Frames
(Zeilen 20 bis 25) zeigt. Kann ich zum Versenden dieser Frames nur einen
Pointer mit einer Schleife organisieren oder ist für jedes Frame ein
eigener Pointer notwendig?
2. Wie würdet ihr das Frame vom Laderegler z.B. [01 04 02 04 CE 3A 64]
im Speicherregister des µC für weitere Bearbeitung bzw. Weitersenden per
I2C speichern? Gibt es vllt. irgendwo ein gutes Beispiel?
Hier ist der aktuelle Code, mit dem es bei mir funktioniert:
Vadim D. schrieb:> 1. In meinem Code verwende ich einen Pointer, der auf eins von 6 Frames> (Zeilen 20 bis 25) zeigt. Kann ich zum Versenden dieser Frames nur einen> Pointer mit einer Schleife organisieren oder ist für jedes Frame ein> eigener Pointer notwendig?
Die Variable "Pointer" ist überflüssig.
Du kannst Deiner Funktion "Send" auch direkt Deine Arrays übergeben:
1
send(BATT_VOLTAGE);
Ungeschickt ist die Schreibweise dieser Namen; es ist eine verbreitete
(und sinnvolle) Konvention, daß nur Macros (d.h. #defines) in Versalien
geschrieben werden - eben um sie auf einen Blick von Variablen,
Funktionsnamen etc. unterscheiden zu können.
Du könntest Deine Funktion "send" auch universeller gestalten; derzeit
geht sie fest davon aus, daß sie 8 Bytes zu versenden hat.
Das ist unpraktisch, sobald Du ein Paket anderer Größe versenden
möchtest, denn dann müsstest Du eine andere "send"-Funktion bauen.
Du solltest der Funktion einen zweiten Parameter mitgeben, die Anzahl
der zu sendenden Bytes.
Und dann sieht der Aufruf von "send" so aus:
1
send(BATT_VOLTAGE,sizeof(BATT_VOLTAGE));
Nein, der sizeof-Operator muss beim Aufruf der Funktion angewandt
werden, in der Funktion selbst funktioniert das nicht, da liefert
sizeof nicht die Größe des (vermeintlich) übergebenen Arrays, sondern
die Größe der verwendeten Adresse - auf Deinem MSP430, der mit
16-Bit-Pointern arbeitet, also den für Dich nutzlosen Wert 2.
Rufus Τ. F. schrieb:> Du könntest Deine Funktion "send" auch universeller gestalten; derzeit> geht sie fest davon aus, daß sie 8 Bytes zu versenden hat.>> Das ist unpraktisch, sobald Du ein Paket anderer Größe versenden> möchtest, denn dann müsstest Du eine andere "send"-Funktion bauen.>> Du solltest der Funktion einen zweiten Parameter mitgeben, die Anzahl> der zu sendenden Bytes.>> Und dann sieht der Aufruf von "send" so aus:>>
1
>send(BATT_VOLTAGE,sizeof(BATT_VOLTAGE));
2
>
>> Nein, der sizeof-Operator muss beim Aufruf der Funktion angewandt> werden, in der Funktion selbst funktioniert das nicht, da liefert> sizeof nicht die Größe des (vermeintlich) übergebenen Arrays, sondern> die Größe der verwendeten Adresse - auf Deinem MSP430, der mit> 16-Bit-Pointern arbeitet, also den für Dich nutzlosen Wert 2.
Ich versende zwar immer ein 8-Byte Frame, werde aber aber deinem Tipp
folgen, da das Programm dadurch flexibler wird :)
Danke!
Weiss du vllt. noch, wie ich am besten die 6 Frames senden sollte?
Wenn ich send() 6 mal nacheinander aufrufe, dann sieht es nicht schön.
Ist es möglich alle 6 in ein struct zu packen und dann in einer Scheife
abzuarbeiten? Bin gerade am Probieren, ob es so geht...
Vadim D. schrieb:> Wenn ich send() 6 mal nacheinander aufrufe, dann sieht es nicht schön.
Ist das kritisch?
Wenn ja: Pack sie in ein Array. Das kannst Du mit 'ner Schleife
abarbeiten.
Das Macro MSG_ARRAY_COUNT zeigt übrigens, wie man aus einem Array die
Anzahl der enthaltenen Elemente bestimmen kann; wie Dir vielleicht
aufgefallen ist, habe ich bei der Deklaration von MsgArray leere eckige
Klammern verwendet und nicht explizit die Elementanzahl angegeben.
Das Senden klappt nun einwandfrei.
Das Empfangen geht meiner Meinung nach nicht, da in der Empfang-ISR
keine Werte in einem Array gespeichert werden. Sie wird nur ein mal
aufgerufen, wie ich dies der Variable RX_COUNTER nach beurteilen konnte.
Im Anhang habe ich ein Bild hinzugefügt, wie die Kommunikation
stattfindet. Es werden insgesamt 6 Frames vom µC an den Laderegler
geschickt, jeweils 8 Byte lang. Nach jedem Frame vom µC antwortet der
Laderegler mit seinem eigenen Frame, der 7 Byte lang ist.
Weiss vllt. jemand, warum bie mir im Code nicht nach jedem empfangenen
Byte die Empfang-ISR ausgelöst wird?
Hier ist der Code:
1
#pragma vector = USCIAB0RX_VECTOR //UART RX USCI Interrupt. This triggers when the USCI receives a char
2
__interruptvoidUSCI0RX_ISR(void)
3
{
4
LPM0_EXIT;
5
P1OUT|=BIT6;//Gruene LED ist an, wenn Empfangen aktiv ist
6
RX_BYTE=UCA0RXBUF;//Beim Auslesen wird das Flag UCA0RXIFG gelöscht, aber nicht der UCA0RXBUF geleert
7
RX_COUNTER++;//Nach jedem empfangenen Byte wird der Counter inkrementiert
8
switch(RX_COUNTER)
9
{
10
case4:REQUEST_FRAMES[0][0]=RX_BYTE;
11
case5:REQUEST_FRAMES[0][1]=RX_BYTE;
12
case11:REQUEST_FRAMES[1][0]=RX_BYTE;
13
case12:REQUEST_FRAMES[1][1]=RX_BYTE;
14
case18:REQUEST_FRAMES[2][0]=RX_BYTE;
15
case19:REQUEST_FRAMES[2][1]=RX_BYTE;
16
case25:REQUEST_FRAMES[3][0]=RX_BYTE;
17
case26:REQUEST_FRAMES[3][1]=RX_BYTE;
18
case32:REQUEST_FRAMES[4][0]=RX_BYTE;
19
case33:REQUEST_FRAMES[4][1]=RX_BYTE;
20
case39:REQUEST_FRAMES[5][0]=RX_BYTE;
21
case40:REQUEST_FRAMES[5][1]=RX_BYTE;
22
case42:ALL_RECEIVED_FLAG=1;
23
}
24
25
P1OUT&=~BIT6;//Gruene LED ist aus, wenn Empfangen inaktiv ist
Du solltest Dir noch mal genau ansehen, wie ein switch/case-Statement
funktioniert, und welche Rolle das von Dir nicht verwendete
Schlüsselwort break dabei spielt.
> LPM0_EXIT;
Was soll das in Deiner ISR bewirken?
Das mit dem break; habe ich übersehen, danke)
Es funktioniert aber trotzdem nur zum Teil - die ISR wird nur einmal
ausgelöst bzw. der RX_COUNTER = 1.
Ich habe das Gefühl, da stimmt was mit dem RXFlag nicht, aber er wird ja
zurückgesetzt?!
Im Code leichtet die LED P1.0 ein, aber die LEDP1.6 leider nicht...
1
#pragma vector = USCIAB0RX_VECTOR //UART RX USCI Interrupt. This triggers when the USCI receives a byte
2
__interruptvoidUSCI0RX_ISR(void)
3
{
4
P1OUT&=~BIT0;
5
P1OUT&=~BIT6;
6
RX_BYTE=UCA0RXBUF;//Beim Auslesen wird das Flag UCA0RXIFG gelöscht, aber nicht der UCA0RXBUF geleert
7
RX_COUNTER++;//Nach jedem empfangenen Byte wird der Counter inkrementiert
Dann solltest Du Deinen Code mal genau mit dem Beispielcode von TI
vergleichen, der den interruptgesteuerten Empfang (und das
interruptgesteuerte Senden) mit der USCI zeigt.
Achte darauf, daß Du den Code nimmst, der für den 'G2553 gedacht ist,
und nicht welchen, der beispielsweise für den 'F5438, denn der verwendet
zwar die gleiche USCI, aber die gesamte Interruptsteuerung sieht anders
aus.
http://www.ti.com/lit/zip/slac485
So, jetzt funktioniert alles einwandfrei!!! :))
Das Problem war das, dass ich zuesrt in einer Schleife alle Nachrichten
per MODBUS geschcikt habe und erst nach dieser Schleife ging der µC in
LPM und Globale Interrupts aktiviert wurden. Ich habe ohne Schleife und
ohne delay eine Nachricht geschickt und gleich danach war schon die
Interrupt aktiv, worauf ich 14 Bytes (1LSB und 1MSB pro Byte) im Puffer
speichern konnte.
Die send-Funktion habe ich ebenfalls überarbeitet - ein ganz wichtiger
Punkt bei mir war nählich while(UCA0STAT & UCBUSY) hinter dem letzten
gesendeten Byte zu schreiben!
Den funktionierenden Code poste ich ebenfalls hier.
Der nächste Schritt ist nun die per MODBUS empfangenen Daten per I2C
weiter zu schicken. Schauen wir mal, wie schnell es gehen wird :)
Danke Euch allen!
Vadim D. schrieb:> void send(const unsigned char *BYTE, int SIZE_OF_FRAME)
Das solltest Du Dir abgewöhnen.
Variablennamen sollte man nie in Versalien schreiben.
Die verbreitete Konvention ist, das nur mit Macros (#define) zu machen.
Rufus Τ. F. schrieb:> Vadim D. schrieb:>> void send(const unsigned char *BYTE, int SIZE_OF_FRAME)>> Das solltest Du Dir abgewöhnen.>> Variablennamen sollte man nie in Versalien schreiben.>> Die verbreitete Konvention ist, das nur mit Macros (#define) zu machen.
Kannst Du mir bitte irgendein Link posten, wo ich dies nachschauen
könnte?
Verstehe nicht ganz, wie es dann aussehen sollte...
Vadim D. schrieb:> Kannst Du mir bitte irgendein Link posten, wo ich dies nachschauen> könnte?
Hast Du schon mal von anderen Leuten geschriebenen C-Code gesehen?
Vielleicht hast Du auch nur nicht verstanden, was Versalien sind? So
nennt man es, wenn ein Wort KOMPLETT in Großbuchstaben geschrieben ist.
Statt
1
voidsend(constunsignedchar*BYTE,intSIZE_OF_FRAME)
wäre
1
voidsend(constunsignedchar*byte,intsize_of_frame)
besser.
Der Parameter "byte" (oder "BYTE") ist allerdings auch problematisch;
einerseits ist das bei manchen Compilern/SDKs ein Datentyp, andererseits
ist das nichtssagend und falsch, denn es soll schließlich ein Pointer
auf Dein "Frame" sein.
Also wäre "frame" oder "framepointer" ein passenderer Name: