ich arbeite an einer Steuerung für einen Wechselrichter. Daten per modbus einlesen, Regelung berechne, regeln mit atmega 328 nano.
Ich würde gerne einen Teil der c-code-Entwicklung auf den pc auslagern - am liebsten mit c#, weil das debugging und testen des codes etwas umständlich ist. Messen und Regeln per modbus geht genau so gut auf dem ps wie auf dem nano.
Problem ist eigentlich nur die unterschiedliche Variablen-Definition. Wenn es in c# #defines gäbe wäre das kein Problem. also etwa
#defines u8 byte
gibt es aber wohl nicht.
Ich gehe mal davon aus, dass es für meine Anforderungen fertigem Lösungen gibt. also z.B.
Wie wäre es mit C oder C++ statt C#? Dann ist die Syntax identisch. Achja, das geht dann sogar mit dem GCC. Der normale x86 GCC eben statt des AVR-GCC. Das kann man auch gut debuggen.
Allerdings ist es insbesondere unter Windows i.A. einfacher, Visual Studio ("Community" Version ist gratis) statt GCC zu nehmen, weil da alles praktisch integriert ist, auch ein guter Debugger. Der C-Support ist dort etwas vernachlässigt/veraltet, aber C++ geht gut (wobei GCC das noch besser kann). Dass du Typen wie "byte" nutzt klingt als würdest du Arduino nutzen - das ist ja auch sowieso C++, nicht C.
Statt "u8" oder "byte" kannst du einfach "uint8_t" nutzen, das gibt es auf beiden Plattformen ohne "#define" Gebastel (typedef wäre sowieso besser).
Ich würde gerne einen Teil der c-code-Entwicklung auf den pc auslagern
Kann es sein, daß du noch nie von der Existenz von Cross-Compilern gehört hast? Und wo entwickelst du denn dann für den AVR, wenn nicht am PC?
am liebsten mit c#
AFAIK gibt es in der gcc (GNU Compiler Collection) keinen C# Compiler für das AVR backend. Insofern geht C# auf AVR erstmal nicht.
Wenn es in c# #defines gäbe wäre das kein Problem. also etwa
#defines u8 byte
u8 und byte sind keine Standardtypen. uint_8t hingegen schon. Tu dir selber einen Gefallen und verwende diesen. Definiert sind diese Typen in stdint.h. Übrigens: ein Byte ist im Kontext von C etwas anderes als das was du glaubst.
Sehe ich auch so. Die gängige Verfahrensweise ist, die Applikationslogik von der Hardwareansteuerung zu trennen und plattformunabhängig zu implementieren. Dann nimmt man sich ein beliebiges Testframework her - für den Anfang gerne etwas kleines übersichtliches wie https://github.com/mity/acutest - und schreibt Testfälle mit denen man die Applikationslogik als PC-Programm ohne Hardware testen und debuggen kann. Die benötigten Hardwarefunktionen müssen natürlich trotzdem als Fake implementiert werden, aber halt nur mit so viel Funktionalität wie für den jeweiligen Testfall benötigt (Stubs).
Alternativ oder zusätzlich lässt man das mit dem PC-Programm bleiben und baut ein Hardware-In-the-Loop Testsystem mit dem man die Hardware direkt als Blackbox testen kann. Dafür bildet man die Umgebung deiner Steuerung so nach dass man ihn so testen kann als wäre er richtig eingebaut. Das ist in meinen Augen am Ende wertvoller weil näher an der Realität aber mit hohem initialen Zeitaufwand verbunden. On-Chip debugging ist nicht viel weniger komfortabel als debuggen auf dem PC. Und man braucht es eh.
Wie auch immer, am Ende ist das wichtigste dass man seinen Kram irgendwie (automatisiert) getestet bekommt. Software ohne Testabdeckung ist wie klettern ohne Seil.
Ob der Atmega für deinen Anwendungsfall noch zeitgemäß ist? Ich verwende für alle Steuerungsaufgaben zu Hause mittlerweile den esp32. Die direkte Anbindung ans wlan gepaart mit der Möglichkeit zum Firmwareupdate over-the-air ist Gold wert und spart unglaublich viel Zeit.
Und wenn deine Anwendung nicht all zu zeitkritisch ist, gäbe es auch micropython für den esp32. Das macht die SW Entwicklung noch mal eknfacher.
Übrigens: ein Byte ist im Kontext von C etwas anderes als das was du
glaubst.
Nee, ist es nicht. Zwar erlaubt der Standard da beliebige Größen, in der Praxis gibt's aber bis auf ein paar ganz seltene abstruse Exotenhardwares nur 8 Bit.
Ansonsten ist die Idee, auf dem PC in C# und auf dem Zielsystem C zu benutzen, nicht sonderlich sinnvoll.
stimmt - da stand wohl mal noch was zum senden, was nicht richtig funktioniert hat. Wurde dann durch eine alte Funktion
wr_write(0x001000,0x5000010100019650);
ersetzt. So etwas würde ich gerne am PC austesten bis es funktioniert.
Wie wäre es mit C oder C++ statt C#? Dann ist die Syntax identisch.
Achja, das geht dann sogar mit dem GCC. Der normale x86 GCC eben statt
des AVR-GCC. Das kann man auch gut debuggen.
Hat allerdings den Nachteil, dass die Datentypen unterschiedlich groß sind, insbesondere int (wäre natürlich bei C# auch nicht besser - da wären zusätzlich auch noch die Konvertierungsregeln anders). Dadurch können sich gewisse Fehler einschleichen (Stichwort integer-promotion).
Übrigens: ein Byte ist im Kontext von C etwas anderes als das was du
glaubst.
Nee, ist es nicht. Zwar erlaubt der Standard da beliebige Größen, in der
Praxis gibt's aber bis auf ein paar ganz seltene abstruse
Exotenhardwares nur 8 Bit.
So abstrus sind die nun auch wieder nicht. Zum Beispiel irgendwelche DSPs, bei denen es nix kleineres als 24 Bit gibt. Das dann in C auf die üblichen 8, 16, 32, 64 Bit abzubilden würde C auf sowas komplett unbrauchbar machen.
Ansonsten ist die Idee, auf dem PC in C# und auf dem Zielsystem C zu
benutzen, nicht sonderlich sinnvoll.
Das müßte ja auch ein uint64_t sein.
Arbeiten die kleinen AVRs überhaupt mit solchen Zahlen? Und die
wr_write() Funktion?
Dem AVR ist das egal, der kann eh nix größeres als 8 Bit. Da kümmert sich der Compiler drum. Allerdings ist der Umgang mit so großen Datentypen mitunter nicht sehr effizient. Man sollte das daher nur tun, wenn man auch wirklich so große Zahlen braucht.
Wie wäre es mit C oder C++ statt C#? Dann ist die Syntax identisch.
Achja, das geht dann sogar mit dem GCC. Der normale x86 GCC eben statt
des AVR-GCC. Das kann man auch gut debuggen.
Hat allerdings den Nachteil, dass die Datentypen unterschiedlich groß
sind
Wenn man anstelle des GCC einen 16-Bit-Compiler (bspw. Watcom) nimmt,
ist dieser Nachteil beseitigt.
Ich finde typedef trotzdem besser. Einzig der Name ist unglücklich
gewählt, weil man damit keinen Typ definiert.
Auch die Syntax ist ein wenig quer.
Mir schmeckt zB. nicht, dass das zu definierende manchmal mitten im
Ausdruck erscheint, erscheinen muss.
Die Syntax entspricht der von Variablendeklarationen, nur dass der
Variablenname durch den Namen des Typ-Alias ersetzt wird. Da C++ nach
wie vor die Variablendeklarationssyntax von C benutzt, wirkt es recht
komisch, wenn ausgerechnet für Typ-Aliase (die ja viel seltener als
Variablendeklarationen benötigt werden) eine andere Syntax verwendet
wird.
Die Syntax entspricht der von Variablendeklarationen, nur dass der
Variablenname durch den Namen des Typ-Alias ersetzt wird.
Da C++ nach wie vor die Variablendeklarationssyntax von C benutzt, wirkt
es recht komisch, wenn ausgerechnet für Typ-Aliase (die ja viel seltener
als Variablendeklarationen benötigt werden) eine andere Syntax verwendet
wird.
Genau deshalb mag ich typedef lieber. Es weicht nicht von der Syntax von Variablendeklarationen ab. Das finde ich konsistenter.
Wenn ich dafür eigene Typ-Aliase will, schreib ich einfach typedef davor, und den Rest kann ich genau gleich lassen. Bei using sieht es deutlich anders aus, also uneinheitliche Syntax.
Vielleicht gehts nur mir so, aber ich muss da schon manchmal einen
Gedanken mehr drauf verschwenden: "Was wird da überhaupt definiert?"
So ähnlich ging es mir mal nur bei der Funktion signal(), die einen Funktionszeiger als Parameter bekommt und einen zurückgibt:
1
void(*signal(intsig,void(*func)(int)))(int);
signal selbst ist hier übrigens kein Funktionszeiger.
Aber das ist eben eine Funktionsdeklaration. Deren Syntax bleibt also so.
(klar kann - und sollte - man sich für den Signalhandler ein eigenes Typ-Alias anlegen)
using MeinArray = ParameterType[42];
using CallBackType = AusgabeType (*)(ParameterType);
Die using Variante erscheint mir da ein wenig
eingängiger/offensichtlicher zu sein.
Ob jetzt (*)() für den Namen einen Funktionszeiger-Typs besonders eingängig ist, ist Ansichtssache.
Rolf M. schrieb:
void (*signal(int sig, void (*func)(int)))(int);
C Programmierer sind schon ein wenig masochistisch weil sie sich so
etwas antun. Ich glaube alleine wegen obigen Konstrukt wurde typedef
erfunden ;)
Möglicherweise. Mit typedef sieht's auch schon bedeutend übersichtlicher aus:
Wenn man anstelle des GCC einen 16-Bit-Compiler (bspw. Watcom) nimmt,
ist dieser Nachteil beseitigt.
Aber kann man das auf heutigen PCs und deren Betriebssystemen noch nativ ausführen und debuggen? Darum ging es ja schließlich. Man könnte natürlich einen Emulator nehmen wie DosBox, aber dann könnte man auch gleich den AVR emulieren.
C# wäre mir auch zu weit weg für so eine Emulation. Und gerade der kritische Teil mit dem Modbus wird kaum kompatibel mit der µC Software sein. Außer vielleicht Modbus über TCP, aber das passt nicht zum Nano.
Wenn man anstelle des GCC einen 16-Bit-Compiler (bspw. Watcom) nimmt,
ist dieser Nachteil beseitigt.
Aber kann man das auf heutigen PCs und deren Betriebssystemen noch nativ
ausführen und debuggen?
Je nachdem, welches Betriebssystem man nativ laufen hat ;-)
Darum ging es ja schließlich. Man könnte natürlich einen Emulator
nehmen wie DosBox
Ja, das ist eine einfach aufzusetzende Möglichkeit, den 16-Bit-Code zu
testen.
Ich selber benutze das Kompilieren auf dem PC vor allem, um etwas
kompliziertere Berechnungen oder Algorithmen vorab ausgiebig mit
verschiedenen reproduzierbaren Eingabedatensätzen zu testen, weil das
auf dem Target-Mikrocontroller meist sehr mühsam ist. Erst wenn diese
Tests erfolgreich sind, wird das Programm auf das Target geflasht. Dort
kann ich mich dann voll auf das Testen der I/O-Funktionen und des
Echtzeitverhaltens konzentrieren (was wiederum auf dem PC kaum möglich
ist), weil ich weiß, dass der ganze Rest bereits fehlerfrei ist.
Das Testen mit einem 16-Bit-Compiler (OpenWatcom) habe ich erst vor
Kurzem ausprobiert, da ich mich gefragt habe, wie man auch Fehler auf
Grund von Integer-Überläufen bereits beim Testen auf dem PC erkennen
kann.
aber dann könnte man auch gleich den AVR emulieren.
Ja, das geht natürlich auch, nur ist es damit nicht so einfach, den
Eingabedatenstrom von einer Datei zu lesen und die Ergebnisse wieder in
eine Datei zu schreiben, um sie genauer analysieren zu können.
Das Testen mit einem 16-Bit-Compiler (OpenWatcom) habe ich erst vor
Kurzem ausprobiert, da ich mich gefragt habe, wie man auch Fehler auf
Grund von Integer-Überläufen bereits beim Testen auf dem PC erkennen
kann.
aber dann könnte man auch gleich den AVR emulieren.
Ja, das geht natürlich auch, nur ist es damit nicht so einfach, den
Eingabedatenstrom von einer Datei zu lesen und die Ergebnisse wieder in
eine Datei zu schreiben, um sie genauer analysieren zu können.
Für rein algorithmische Tests (also keine SFRs oder IRQs involviert), verwende ich AVRtest als Simulator.
Als ich zum Beispiel eine 64-Bit double Implementation gemacht have, war das sehr hilfreich -- das auf einem komplett anderen System mit bereits funktionierender double-Arithmetik zu testen wäre ziemlich sinnlos gewesen...
Daten aus AVRtest raus bekommen ist einfach: Im einfachsten Falle per printf (was auf dem stdout des Hosts landet), oder per spezieller Syscalls, die den Overhead von printf vermeiden.
Daten von Platte zu lesen geht prinzipiell auch, ist aber wegen des Designs der AVR-LibC etwas umständlich. AVRtest stellt dafür ein Target-Modul zur Verfügung, das man nur noch in die AVR-Applikation reinzulinken brauch; und dann funktioniert alles wie per stdio.h.
AVRtest ist auch einigermaßen fix. Beispielsweise folgender Test, der ca. 2*16000000 24-Bit Multiplikation testet
1
#include<stdint.h>
2
3
voidtest_mul16(void)
4
{
5
uint16_ta=0;
6
do
7
{
8
uint8_tb=0;
9
do
10
{
11
__uint24ab1=(__uint24)a*b;
12
__asm("":"+r"(a),"+r"(b));
13
__uint24ab2=(__uint24)b*a;
14
15
if(ab1!=ab2)
16
__builtin_abort();
17
}while(++b);
18
}while(++a);
19
}
20
21
intmain(void)
22
{
23
test_mul16();
24
return0;
25
}
Simuliert werden 436470188 Cycles in ca. 3.5 Sekunden, das entspricht einem AVR auf 125 MHz.
Array Bezeichner zerfallen automatisch zu einem Zeiger auf das erste
Element.
Da brauchts kein define für.
kannst du das erklären - mit u8* bekomme ich in c# eine Fehlermeldung - mit u8[] kommt die Fehlermeldung bei gcc?
von J. S. (jojos)▼
C# wäre mir auch zu weit weg für so eine Emulation. Und gerade der
kritische Teil mit dem Modbus wird kaum kompatibel mit der µC Software
sein. Außer vielleicht Modbus über TCP, aber das passt nicht zum Nano.
Ich hänge ja nicht an c# - eher an gccc. Womit geht es denn leichter? Wichtig wäre eine grafische Oberfläche um ein lcd zu simulieren und serial tx/rx.
Wichtig wäre eine grafische Oberfläche um ein lcd zu simulieren und
serial tx/rx.
Das können so gut wie alle Programmiersprachen/Systeme. Wie gesagt, Visual Studio würde sich anbieten; identischer C/C++ Code, GUI Kram dran flanschen.
Das Testen mit einem 16-Bit-Compiler (OpenWatcom) habe ich erst vor
Kurzem ausprobiert, da ich mich gefragt habe, wie man auch Fehler auf
Grund von Integer-Überläufen bereits beim Testen auf dem PC erkennen
kann.
Da wäre ein gcc für aktuelle PC-Systeme praktisch, bei dem aber die Typen entsprechend denen vom AVR definiert sind. Aber das wäre wohl etwas komplizierter, weil das dann nicht mehr mit den bestehenden Bibliotheken (inklusive libc) funktionieren würde.
wie bekomme ich den Austausch mit einem #define hin? u8[] zu u8* hin?
Gar nicht. Ich weiß, du willst das nicht hören, aber hier nochmal in aller Deutlichkeit: C und C# sind zwei gänzlich verschiedene Sprachen. Es macht keinen Sinn, zum Testen C-Code als C# übersetzen zu wollen. Zum einen müsstest du es dann irgendwie hinbekommen, haufenweise derartige Fälle zu mappen, zum anderen hast du am Ende trotzdem kein aussagekräftiges Ergebnis.
Ich hänge ja nicht an c# - eher an gccc. Womit geht es denn leichter?
Wichtig wäre eine grafische Oberfläche um ein lcd zu simulieren und
serial tx/rx.
Wie geschrieben, schaue dir Wokwi an.
Serial.write ist aber nur die halbe Miete bei Modbus, da gibt es Timeouts die so nicht beachtet werden. Modbus schreibt in Register, das wird hier mit einer fertigen Sequenz versteckt.
Es ist keine Schande für so eine Anwendung Arduino und eine fertige Modbus Lib zu nehmen, genauso wie für das Display. Und eben lieber einen ESP32, der kostet ja auch nix im Vergleich zum gesamten Aufwand für so ein Projekt. Nebenbei kann der einen Webserver per WLAN bereitstellen um z.B. den Status anzuzeigen.
Das Testen mit einem 16-Bit-Compiler (OpenWatcom) habe ich erst vor
Kurzem ausprobiert, da ich mich gefragt habe, wie man auch Fehler auf
Grund von Integer-Überläufen bereits beim Testen auf dem PC erkennen
kann.
Macht es Sinn sich bei sowas auf so eine exotische Software zu verlassen...?
Man könnte auch in C++ den uint16_t durch eine eigene Klasse ersetzen welche das exakte Promotion&Overflow Verhalten des AVRs emuliert sodass man es auf dem PC verifizieren kann. Ist zwar langsam aber spielt da ja keine Rolle. Wäre aber etwas fummelig das Verhalten wirklich korrekt zu implementieren.
Das Testen mit einem 16-Bit-Compiler (OpenWatcom) habe ich erst vor
Kurzem ausprobiert, da ich mich gefragt habe, wie man auch Fehler auf
Grund von Integer-Überläufen bereits beim Testen auf dem PC erkennen
kann.
Macht es Sinn sich bei sowas auf so eine exotische Software zu
verlassen...?
So arg exotisch ist OpenWatcom ja auch wieder nicht. Das ist immerhin
ein aktiv gepflegtes Produkt (letzte Version vom Dezemeber 2024), das
für mehrere Betriebssysteme (DOS, Windows, OS/2 und Linux) verfügbar
ist.
Oder man bleibt beim (AVR-)GCC und verfolgt den Vorschlag von Johannes:
Für rein algorithmische Tests (also keine SFRs oder IRQs involviert),
verwende ich AVRtest als Simulator.
Man könnte auch in C++ den uint16_t durch eine eigene Klasse ersetzen
welche das exakte Promotion&Overflow Verhalten des AVRs emuliert sodass
man es auf dem PC verifizieren kann.
Nur uint16_t anzupassen, reicht nicht. Man müsste entsprechendes auch
mit int tun, um bspw. zu bewirken, dass 1000*1000 nicht 1000000, sondern
16960 ergibt. Das geht aber nicht, da int in C++ ein feststehender
Datentyp ist.
Das geht aber nicht, da int in C++ ein feststehender
Datentyp ist.
Man muss eben alle Literale direkt in die eigene Klasse stecken sodass kein "normaler" Int draus wird. Man braucht mehrere Klassen für die diversen Typen und muss die Rechenregeln entsprechend implementieren. Wie gesagt, recht aufwendig aber möglich
aber dann könnte man auch gleich den AVR emulieren.
Ja, das geht natürlich auch, nur ist es damit nicht so einfach, den
Eingabedatenstrom von einer Datei zu lesen und die Ergebnisse wieder in
eine Datei zu schreiben, um sie genauer analysieren zu können.
Mal ein Minimalbeispiel mit AVRtest über stdin / stdout:
exit-atmega128.o ist ein Target-Modul, das von AVRtest beim Build erstellt wird und stdin, stdout und stderr auf den PC umleitet. $AVRTEST ist der Pfad wo auch das avrtest Executable liegt.
-q ist quiet Mode, d.h. AVRtest zeigt am Ende keine Zusammenfassung.
< in.c leitet die Textdatei in.c zum stdin des PCs, und AVRtest leitet es weiter zum stdin des simulierten Programms.
Die Ausgabe erscheint auf stdout des PCs, von wo aus sie weiterverarbeitet oder gespeichert werden kann, etwa mit > in.txt
Um einen String direkt einzugeben kann man z.B:
1
$ avrtest -q in.elf <<< "in.c"
2
READ:in.c
Nachteil eines Simulators ist, dass man auf die Resourcen eines AVRs beschränkt ist in Flash- und RAM-Größe. Die Flash-Größe dürfte kein großes Hindernis sein, man simuliert einfach einen AVR, der genug Flash hat.
Und mit der RAM-Größe ist man nicht an die Hardware gebunden, d.h. per Linker-Flags kann man ein größeres RAM darstellen. Man kann etwa einen ATmega4808 nehmen, und ihm knapp 16K RAM verpassen bis 0x4000. (Ab 4000 ist der Flash im RAM-Adressraum sichtbar, man braucht also auch kein PROGMEM und so Gehampel um RAM zu sparen, da .rodata im Flash liegt).
Mal ein Minimalbeispiel mit AVRtest über stdin / stdout:
Sehr gut, danke, hab's gerade erfolgreich ausprobiert :)
Weitere Möglichkeit, Daten ins Programm zu bekommen, ist per #include oder #embed. Braucht zwar Flash, ist dafür aber einfacher und schneller als erst alles durch scanf etc. zu wursten.
Oder (Pseudo)-Zufall um Test-Daten (reproduzierbar) zu erzeugen:
1
#include<stdint.h>
2
#include"avrtest.h"
3
4
uint64_tu64_rand(void)
5
{
6
uint32_tlo=avrtest_prand();
7
uint32_thi=avrtest_prand();
8
return(uint64_t)hi<<32|lo;
9
}
10
11
intmain(void)
12
{
13
for(inti=0;i<6;++i)
14
LOG_FMT_U64("Wert = 0x%016llx\n",u64_rand());
15
16
return0;
17
}
1
Wert = 0x161391eacafebabe
2
Wert = 0x3d6bc2c35c3d5af4
3
Wert = 0x7ec540f4f07af5bd
4
Wert = 0x9adcc96a584c6c52
5
Wert = 0xed8934b23b5e0b7f
6
Wert = 0x71e4bc36667f8b1c
Als Option für avr-gcc brauch's dann noch ein -I$AVRTEST, damit der avrtest.h Header gefunden wird, der avrtest_prand() etc. implementiert.
avrtest_prand und LOG_FMT_U64 sind als AVRtest Syscalls implementiert, d.h. die eigentliche Arbeit übernimmt der Simulator. Das macht die Tests merklich effizienter, da man auf printf und rand verzichten kann.