Blobs vs spezifische Befehle

1. Mai 2020

Zu Beginn eines Projekt, insbesondere wenn es sich um einen Slave handelt, erstellt man eine Liste von Befehlen, die zur Konfiguration des Slaves benötigt werden. Diese Liste wächst rasant, vor allem weil es zu jedem SET-Befehl das GET-Pendent gibt. Das führt letztendlich zu einem unübersichtlichen switch-case-Block.

switch (com_rx())
{
	case SET_OPTION_A:
		set_option_A();
		break;
	case GET_OPTION_B:
		com_tx(get_option_A());
		break;
	case SET_OPTION_B:
		set_option_B();
		break;
//and so on
	default:
		break;
}

Wartungsfreundlicher wäre ein Hand voll Funktionen, mit denen alle Optionen gesetzt und abgefragt werden können. Damit dauern einzelne Änderungen etwas länger, da mehr Daten übertragen werden, allerdings spart man sich bei vielen Änderungen unzählige Handshakes.

Aus den vielen Funktionen dürften in etwa sechs werden.

switch (com_rx())
{
	case GET_PROTOCOL_VERSION:
		com_tx(get_protocol_version());
		break;
	case GET_BLOB_SIZE:
		com_tx(return(sizeof(calibration)));
		break;
	case GET_BLOB:
		com_tx(calibration);
		break;
	case SET_BLOB:
		calibration = com_rx();
		break;
	case GET_NVM_SIZE:
		com_tx(return(sizeof(constData)));
		break;
	case GET_NVM:
		com_tx(constData);
		break;
	default:
		break;
}

Um die Datenstrukturen zukünftig ändern zu können, wurde die Abfrage der Protokollversion vorgesehen. Über diese kann der Master relativ einfach Slaves verschiedener Versionen handhaben. Die Abfrage der Größe hilft dabei genügend Speicher für die Daten zu reservieren. Von diesen gibt es i.d.R. zwei Arten. Veränderliche und unveränderliche (Firmwareversion, Übersetzungsdatum, ..).

typedef struct {
	char hash[40];		//"e9b4c494926746e482377cdc75ff7b72d56dd79b853a0fcec2afed5fb09922b6"
	char version[10];	//"123.78-rc9"
	char date[20];		//"2020.05.01, 17:59:58"
	uint32_t endianess;	//0x12345678
} fw_t;

Die unveränderliche Struktur zurrt man i.d.R. recht schnell fest, anders sieht es mit der veränderlichen aus. Genügt ein Datenbereich von 256 Zuständen oder braucht man doch mehr?

typedef struct {
	struct {
		uint8_t red;	//0..100
		uint8_t green;	//0..100
		uint8_t blue;	//0..100
	} brightness;
	union {
		uint8_t u8[2];
		uint16_t u16;
	}
	uint16_t beams;		//0..300
	uint32_t timeout;	//[ms]
} calibrationl_t;

Das Augenmerk ist auf die Byte-Reihenfolge zu legen, sprich die Bytes müssen auf beiden Hardwareplattformen gleich behandelt werden. Sonst wird z.B. 0xff00 zu 0x00ff. Hilfestellung bietet, wie es in der Struktur fw_t gezeigt ist, das Element endianess. In dieses wird immer der gleiche bekannte Wert geschrieben und mit dessen Hilfe kann der Kommunikationspartner feststellen, ob die Daten gleich behandelt werden.

Diese Problematik besteht auch bei einzelnen Kommandos, wenn diese Daten behandeln, die größer als ein Byte sind. Denn die Übertragungsschnittstellen (SPI, UART, I2C, ..) sind meistens auf die Übertragung von einem Byte beschränkt. Mittels einer union lässt sich dieses Problem zwar relativ geschickt umgehen, aber die Ansicht im Debugger leidet bei Arrays solcher Datentypen. Jedes Arrayelement muss einzeln aufgeklappt werden, um an den Wert zu gelangen.

Anwendungsentwicklung mit QEMU #2

29. August 2019

Anwendungsentwicklung mit QEMU

19. Juni 2019

Buildroot Image mit QEMU booten

14. Juni 2019