Hallo Spezialisten,
ich bastle schon sein einiger Zeit an einem "Cockpit" für ein E-GoKart
und habe nun eigentlich nur noch vor mir, verschiedene Werte von einem
BMS abzufragen.
Das BMS selber hat einen UART und es versteht neben diversen
proprietären Kommandos auch Anfragen im Modbus Format. Letzteres würde
ich gerne verwenden, um folgende Daten abzufragen:
- Gesamtspannung
- Gesamtstrom
- SoC
- Temperatur
Für die Kommandos habe ich mir eine Senderoutine geschrieben, die dem
Befehl das CRC anfügt und das Telegram dann sendet:
1 | uint8_t _sendBMSRequest(uint8_t *req, uint8_t length) {
|
2 | uint8_t success = 0;
|
3 |
|
4 | // temporäres Array für die vollständige Befehlssequenz:
|
5 | // txdata = req + CRC (2 Byte)
|
6 | uint8_t *txdata;
|
7 | txdata = malloc(length+2/sizeof(uint8_t));
|
8 | memcpy(txdata, req, length);
|
9 |
|
10 | // CRC berechnen und an txdata anhängen.
|
11 | uint16_t crc = _CRC16(req, length);
|
12 | length +=2;
|
13 | txdata[length-2] = crc & 0xFF;
|
14 | txdata[length-1] = crc >> 8;
|
15 |
|
16 | // txdata an das BMS übertragen:
|
17 | for(uint8_t i=0; i<length; i++) {
|
18 |
|
19 | // Warten bis Senden möglich ist:
|
20 | while (!(UCSR1A & (1<<UDRE1))) { }
|
21 |
|
22 | UDR1 = txdata[i];
|
23 | }
|
24 |
|
25 | success = 1;
|
26 |
|
27 | return(success);
|
28 | }
|
Mit dieser Funktion kann ich bereits erfolgreich KOmmandos absetzen,
z.B.
1 | void bmsReset() {
|
2 | uint8_t req[] = { 0xAA, 0x02, 0x05};
|
3 | _sendBMSRequest(req, sizeof(req) / sizeof(uint8_t) );
|
4 | }
|
5 |
|
6 | void main() {
|
7 | ...
|
8 | bmsReset();
|
9 | }
|
Nach etwas Recherche habe ich schon aufgenommen, dass die Verwendung von
dynamischen Arrays eher nicht so schlau ist. Die Gründe sind
nachvollziehbar und ich werde das noch auf einen statischen Sendepuffer
umbauen. Aber meine Frage ist momentan erstmal eine andere...
Es geht mir nämlich um das Lesen der Antwort vom BMS.
Es gibt im Grunde zwei Möglichkeiten, wie das BMS sich auf das Kommando
zurückmeldet. Entweder mit einer Fehlermeldung oder bspw. mit den
gelesenen Registern
Fehlermeldung (Payload ist immer 0x00):
1 | Startzeichen: <0xAA>
|
2 | Kommando: <0x03>
|
3 | Payload-Length: <0x00>
|
4 | Error-Code: <0x01>
|
5 | CRC: 2 Byte
|
Messwert
1 | Startzeichen: <0xAA>
|
2 | Kommando: <0x03>
|
3 | Layload-Length: <0x04>
|
4 | Payload: hier z.B. 4 Byte
|
5 | CRC: 2 Byte
|
Wenn ich mir nun eine Funktion schreibe, um die Pack-Spannung
abzufragen, sähe die ungefähr so aus:
1 | float bmsGetPackVoltage() {
|
2 | float voltage = 0.0f;
|
3 | uint8_t req[] = { 0xAA, 0x03, 0x00, 0x37, 0x00, 0x02 };
|
4 | _sendBMSRequest(req, sizeof(req) / sizeof(uint8_t));
|
5 |
|
6 | // Response lesen
|
7 | // Auf Startzeichen 0xAA warten
|
8 | // Empfangens Byte No. 3 = 0x00 (dann Error, sonst Payload-Length)
|
9 | // CRC prüfen
|
10 | // Payload extrahieren
|
11 |
|
12 | // 4 Byte in float umwandlen:
|
13 | // uint8_t payloadData[4] = { r[3], r[4], r[5], r[6] };
|
14 | // memcpy(&voltage, &data, sizeof(float));
|
15 |
|
16 | return(voltage);
|
17 | }
|
Aber wie liest man jetzt wirklich sinnvollerweise die Antwort vom UART1
ein?
Es könnte ja theoretisch auch sein, dass das BMS gar nicht antwortet...
oder dass das Startzeichen nicht gefunden wird... oder das die
Übertragung vor Erreichen innerhalb des Telegrams abbricht.
Es müsste also irgendwie noch sowas wie ein Timeout her.
Kann mir vielleicht jemand ein Beispiel (gern auch Pseudocode) geben,
wie man sowas implementiert? Ich würde gern ohne den Interrupt arbeiten
und wirklich nach jedem Request an das BMS durch Polling die Antwort
abwarten.
Freue mich riesig über Denkanstöße oder Beispiele.
LG, Matze