BWP-WS19-02/Implementierungsdetails: Unterschied zwischen den Versionen

Aus Verteilte Systeme - Wiki
Zur Navigation springen Zur Suche springen
Zeile 494: Zeile 494:
 
== Kontrollstation ==
 
== Kontrollstation ==
   
Die Kontrollstation übernimmt das Empfangen von Nachrichten, die vom ITSE über BLE gesendet wurden. Dazu bedient sie sich eines Python Skripts, das sich mithilfe der MAC-Adresse mit dem Board verbinden und Nachrichten empfangen kann. Als Dienst für das BLE Interface wird Bluepy verwendet. Informationen hierzu sind [http://ianharvey.github.io/bluepy-doc/index.html in der Dokumentation] und [https://github.com/IanHarvey/bluepy im Github-Repository] von Bluepy zu finden.
+
Die Kontrollstation übernimmt das Empfangen von Nachrichten, die vom ITSE über BLE gesendet wurden. Dazu bedient sie sich eines Python Skripts, das sich mithilfe der MAC-Adresse mit dem Board verbinden und Nachrichten empfangen kann. Die MAC-Adresse des Boards muss hierfür ermittelt. Dafür steht auch ein Python Skript zur Verfügung, das Bluetooth-Geräte im Umfeld auflistet. Als Dienst für das BLE Interface wird Bluepy verwendet. Informationen hierzu sind [http://ianharvey.github.io/bluepy-doc/index.html in der Dokumentation] und [https://github.com/IanHarvey/bluepy im Github-Repository] von Bluepy zu finden.
   
 
<source lang="python">
 
<source lang="python">

Version vom 9. März 2020, 02:37 Uhr

Antrieb & Kommunikation

Implementierung der Hauptsteuerungs API (Team Antrieb und Mechanik)

Motorensteuerung

Für die Ansteuerung der GPIOs haben wir die vorhandenen Demo-Programme gesichtet und durch die Zephyr Dokumentation sowie der Datei ITS-E/zephyr/boards/arm/nrf52_adafruit_feather/board.h konnten wir die GPIO Pins verwenden, um eine LED blinken zu lassen.

Versuch den Motor über PWM anzusteuern

Um später mittels PWM die Pins ansteuern zu können, braucht man einen aktivierten PWM-Driver. Diesen haben wir aus den ⁨bwp_ws19⁩/zephyr⁩/boards⁩/⁨arm⁩/⁨nrf52_pca10040⁩/Kconfig.defconfig

if PWM
config PWM_0
	default y

endif # PWM

in die Kconfig.defconfig des adafruit featherboard kopiert und danach ebenfalls aus den ⁨nrf52_pca10040-bordkonfigurationen in der Datei nrf52_pca10040.dts die Passage

&pwm0 {
	status = "okay";
	ch0-pin = <17>;
	ch0-inverted;
};

in die .dts-Datei im Ordner des genutzen Adafruit Featherboards nrf52 (zephyr/boards/arm/nrf52_adafruit_feather) kopiert, um bei dem angegeben Pin das Pwm-Signal zu aktivieren. → Wenn man einen anderen Pin als Pin 17 mit Pwm versorgen möchte, muss man ihn an dieser Stelle „ch0-pin = <pinnummer>; angeben!

Danach haben wir das PWMSignal mit dem Beispielprogramm blink_LED ausprobieren, jedoch haben wir das Pwm nicht zum laufen bringen können, weil die defines DT_ALIAS_PWM_LED0_PWMS_CONTROLLER und DT_ALIAS_PWM_LED0_PWMS_CHANNEL nicht gefunden wurden.

Jedoch haben wir bei dem Programm servo-Motor ein Signal erhalten. Wichtig ist, dass man beim Aufruf von static int pwm_pin_set_usec(struct device *dev, u32_t pwm, u32_t period, u32_t pulse) Set the period and pulse width for a single PWM output. Parameters

   • dev: Pointer to the device structure for the driver instance. 
   • pwm: PWM pin. 
   • period: Period (in microseconds) set to the PWM. 
   • pulse: Pulse width (in microseconds) set to the PWM. 

Return Value

   • 0: If successful. 
   • Negative: errno code if failure. 
nochmals den PWM-pin angibt(typ u32_t) siehe Parameters.


Wir haben dieses Beispielprogramm in unseren ITS-E-Ordner kopiert. Hierfür muss man noch in der ITS-E/project.conf- Datei „CONFIG_PWM=y“ setzen.

Bei dem Servo-Beispiel setzt man MinPulsWidth und MaxPulsWidth in Mikrosekunden und gibt die Periodendauer


Text der Bildlegende


Des Weiteren haben wir versucht den Strom durch einen 3 Ohm Widerstand der 1206 Baureihe auf dem Breakout-Board auf 66,6 mA zu begrenzen. Die Formel zur Berechnung des Maximalstroms Imax=0,2V/R (0,066 A=0,2 V / 3 Ohm). Die Spannung ist laut Datenblatt auf 0,2 V festgelegt. Da der Motor als induktive Last bei fallenden Flaken des PWM Signals eine negative Spannung induziert, sind auf dem Breadkout-Board bereits Kick-Back-Dioden assembliert, um den Motortreiber vor negativen Spannungsimpulsen zu schützen. Trotzdessen muss die Spannung am Motor über einen Wechselspannungskondensator (ca. 10µF) geglättet werden und das Tastverhältnis (duty cycle) des PWM-Signals berücksichtigt werden, um die Spannung im gewünschten Spannungsbereich von 0,3 - 1V regeln zu können. Dem Datenblatt entsprechend haben wir die Pins VM(VCC) und SLP(SLEEP) mit einem 68 KOhm Pullup-Widerstand verbunden.

Vorerst haben wir uns dazu entschieden den Versuch die Motoren mittels PWM ansteuern zu können erstmal auf Eis zu legen, und die Motoren über die GPIOs anzusteuern.


Ermittlung der idealen Periodendauer des PWM Signals

Wann ist die Periodendauer ideal?

Da ein minimaler Energieverbrauch eine große Zielsetzung für unser Auto ist, haben wir das Wort 'ideal' daran fest gemacht, dass die Motoren mit maximal kurzen High Impulsen gerade noch laufen, die Periode jedoch nicht zu groß ist sodass die Motoren unruhig laufen.

Um den Energieverlust durch negative Spannungsimpulse des Motors beim Wechsel der Eingangsspannung von High zu Low zu minimieren besteht die Möglichkeit das Signal durch geeignete Kondensatoren zu glätten. Durch den Kondensator werden jedoch die Spannungsimpulse des PWM Signals auf die Periodendauer verteilt, und damit entsprechend kleiner. Dies hat den Nachteil das die Spannung bereits bei ca. 30% High Anteil des PWM Signals unter die Einschaltschwelle des Motors fällt und dieser folglich stehen bleibt. Eine zu große Kapazität ist damit aus Sicht eines minimalen Energieverbrauchs von Nachteil! Da nur während der High Impulse Energie verbraucht wird, kann es für unser Projekt von Vorteil sein, die Motoren mit minimalen High Impulsen an zu steuern.

Ein Versuch hierzu zeigt das der Motor prozentual gesehen mit einer maximal langen Periodendauer den geringsten Energieverbrauch aufweist um sich noch drehen zu können. Unsinnig wird dieses Experiment allerdings ab 100mS, da der Motor dann quasi im 100ms Takt angeschubst wird und durch seine Trägheit nicht ruhig rund läuft. Nicht umsonst scheint ein üblicher Wert für kleine Elktromotoren bei einer PWM-Periodendauer von 20mS zu liegen. Bei dieser Frequenz sind zwar noch mindestens 5% High Anteil des PWM Signals nötig, um den Motor noch zu bewegen, dafür läuft er allerdings von 5% - 100% sehr ruhig.

Einige Versuchswerte sind in dieser Tabelle ersichtlich:

opitmales PWM Signal

Die y-Achse gibt die Effizienz der Motoren bei minimalem PWM High Anteil in % an.

Für unsere Motoren hat sich ein 1µF Kondensator bei einer Periodendauer von 20mS als ideal erwiesen.

Implementierung der Motorfunktionen

Mit Hilfe der vorgegebenen GPIO-Funktionen, die die Zephyr Dokumentation zur Verfügung bereitstellt, haben wir die Motoren ansteuern können. Für eine bessere Wartbarkeit des Programms haben wir die Funktionalität für vorwärts, rückwärts, halten, rechts und links in einzelne Funktionen ausgelagert. Das Programm ist aktuell unter Link-zum-Programmcode zu finden.

Umrechnungen (Implementierung inkl.)

Da die Einheiten von der Distanz oder Drehwinkel, die das Auto fahren soll, sich unterscheidet von den Einheiten, mit denen diese Größen gemessen werden, mussten dafür Umrechnungsfunktionen erstellt werden. Das heißt, die zu fahrende Strecke muss in die benötigte Anzahl der Impulse von den Hallsensoren berechnet werden, damit das Auto nach dem Erreichen des Ziels stehen bleibt.

Eine Umdrehung des Reifens sind 39 Impulse der Hallsensoren am Motor. Der Radius des Reifens beträgt 3cm. Der Abstand zwischen beiden Reifen liegt bei 14,2cm. Auf der Basis diesen Daten konnten dann die nachfolgenden Funktionen berechnet werden. Hier werden die Zahlen jedoch als in Millimeterangaben verwendet, um den Datentyp float zu umgehen, da dieser bei der Ausgabe und beim Versenden mittels Mailboxen Probleme bereitete.

Berechnung des Durchschnitts

 int rpmAverage()
{
	return (countMotorRight + countMotorLeft) / 2;
}

Umrechnung Distanz in mm zu CounterTicks

u = 2πr

u = 2 * π * 3

u = 18,85cm = 188,5mm

1u = 188,5mm = 39ticks

1mm = 39/188,5 = 4,83333

/* Calculate the necessary ticks for the distance to drive*/
int distanceToCounterTicks(float distance)
{
	/*1 tick of the motorCounter = 4,83333 mm equals 188,5mm = 39ticks */
	return (int)(distance / 4.83333);
}

Ticks in Distanz

/* Return distance in mm */
float rpmToDistance(int rpm)
{
	/*1 tick of the motorCounter = 4,83333 mm equals 188,5mm = 39ticks */
	return rpm * (4.83333);
}

/*transfers mm/ms to mm/s*/ float distanceToVelocity(float distance, int time_ms) { return (distance / (time_ms)) / 1000; }

/**

* function calculates degree out of counted impulses
* @param rpms: impulses counted
  • /

int rpmToDegree(int rpms) { float dist = (0.843333) * rpms; // (1pi * r in cm)/180 = 1 grad // 0,01745 * r in cm = 1grad //dist = ? * (0,01745 * r) // ? * 1grad = ? grad float deg = dist / (0.01745 * RADIUS / 10); // RADIUS -> Abstand zwischen beiden Rädern in mm return (int)deg; }

/**

* calculates neccassary impulses of the counterregisters from given degree
* @param degree: number of degree, for example from new leftwards or rightwards job
* !! function does not differ between right or left
* */

int degreeToCounterTicks(int degree) { float dist = (degree * (0.01745 * RADIUS / 10)); int rpms = (int)(dist * (float)2.069); return rpms; }

</source>

Implementierung des ersten Meilensteins

Für die Implementierung des ersten Meilensteins wurde vom Systemarchiteckturteam die generelle Kommunikation zwischen den Teams festgelegt. Hierbei wird ein Job-System verwendet, womit jedes Subteam seine Jobs abfängt und abarbeitet. Während dieser Jobs ist es möglich mittels Mailboxen die Informationen der anderen Teams abzufragen. Die dazugehörige Implementierung findet sich aktuell unter diesem Link.

Implementierung und Erfüllung des zweiten Meilensteins

Bei der Implementierung des zweiten Meilensteins haben wir das Chassis des Prototyps für den ITS-E zusammen gebaut. Hierbei ist aufgefallen, dass das Auto, wenn es durch die Motoren angetrieben wurde leicht schräg fährt. Nach einigen Optimierungen am Chassis waren die Abweichungen nicht mehr ganz so groß - jedoch noch sichtbar. Das könnte durch nicht "echt-parallel" zueinander angebrachten Motoren und/oder der ungleichmäßigen Drehung der Motoren und/oder an der unterschiedlichen Spannungsversorgung durch den Motortreiber liegen. Die eigentliche Implementierung der Motorfunktionen war ja schon im ersten Meilenstein behandelt, für den zweiten Meilenstein haben wir hier nichts anpassen müssen. Das erbrachte Ergebnis ist unter diesem Link zu finden.


Implementierung und Erfüllung des dritten Meilensteins

Für die Modifizierung der Implementierung auf die Anforderungen des dritten Meilensteins, wurde die Funktion nach links zu drehen implementiert. Das Drehen der Räder wurde mit dem Stillstand des jeweils anderen Rads implementiert. Da die Motoren für die Räder noch nicht mit PWM angesteuert werden, fährt der Roboter noch leicht schief. Die dazugehörige Implementierung findet sich aktuell unter diesem Link.

Implementierung des Counters

Um externe Impulse der rotary-encoder und des solar-click Ladereglers ohne CPU zählen zu können sollen IC interne Counter verwendet werden. Beim verwendeten feather-board sind diese counter unter der Kategorie timer/counter zu finden. Es gibt keine expliziten Counter. Die Timer inkrementieren per Default über einen angeschlossenen Takt (Wahlweise 1MHz oder 16MHz). Über ein MODE Bit kann der Time als Counter fungieren:

Timer_counter.png

Kapitel 24 TIMER — Timer/counter - nRF52832_PS_v1.4.pdf


Linksammlung zum Thema Counter

Da sich derzeit sowohl das Antriebs- als auch Energie- Team mit dem Counter beschäftigt folgt hier eine Linksammlung mit vermutlich relevanten Dateien:


default MODE

bwp_ws19/modules/hal/nordic/nrfx_config_nrf52832.h:#define NRFX_TIMER_DEFAULT_CONFIG_MODE 0

driver

bwp_ws19/zephyr/drivers/counter/counter_nrfx_timer.c

#define COUNTER_NRFX_TIMER_DEVICE(idx)					       \
	BUILD_ASSERT_MSG(DT_NORDIC_NRF_TIMER_TIMER_##idx##_PRESCALER <=	       \
			TIMER_PRESCALER_PRESCALER_Msk,			       \
			"TIMER prescaler out of range");		       \
	DEVICE_DECLARE(timer_##idx);					       \
	static int counter_##idx##_init(struct device *dev)		       \
	{								       \
		IRQ_CONNECT(DT_NORDIC_NRF_TIMER_TIMER_##idx##_IRQ_0,	       \
			    DT_NORDIC_NRF_TIMER_TIMER_##idx##_IRQ_0_PRIORITY,  \
			    irq_handler, DEVICE_GET(timer_##idx), 0);	       \
		static const struct counter_timer_config config = {	       \
			.freq =	DT_NORDIC_NRF_TIMER_TIMER_##idx##_PRESCALER,   \
			.mode = NRF_TIMER_MODE_TIMER,			       \
			.bit_width = (TIMER##idx##_MAX_SIZE == 32) ?	       \
					NRF_TIMER_BIT_WIDTH_32 :	       \
					NRF_TIMER_BIT_WIDTH_16,		       \
		};							       \
		return init_timer(dev, &config);			       \
	}								       \
	static struct counter_nrfx_data counter_##idx##_data;		       \
	static struct counter_nrfx_ch_data				       \
		counter##idx##_ch_data[CC_TO_ID(TIMER##idx##_CC_NUM)];	       \
	LOG_INSTANCE_REGISTER(LOG_MODULE_NAME, idx, CONFIG_COUNTER_LOG_LEVEL); \
	static const struct counter_nrfx_config nrfx_counter_##idx##_config = {\
		.info = {						       \
			.max_top_value = (TIMER##idx##_MAX_SIZE == 32) ?       \
					0xffffffff : 0x0000ffff,	       \
			.freq = TIMER_CLOCK /				       \
			   (1 << DT_NORDIC_NRF_TIMER_TIMER_##idx##_PRESCALER), \
			.flags = COUNTER_CONFIG_INFO_COUNT_UP,		       \
			.channels = CC_TO_ID(TIMER##idx##_CC_NUM),	       \
		},							       \
		.ch_data = counter##idx##_ch_data,			       \
		.timer = NRF_TIMER##idx,				       \
		LOG_INSTANCE_PTR_INIT(log, LOG_MODULE_NAME, idx)	       \
	};								       \
	DEVICE_AND_API_INIT(timer_##idx,				       \
			    DT_NORDIC_NRF_TIMER_TIMER_##idx##_LABEL,	       \
			    counter_##idx##_init,			       \
			    &counter_##idx##_data,			       \
			    &nrfx_counter_##idx##_config.info,		       \
			    PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,  \
			    &counter_nrfx_driver_api)


... ##idx## wurde durch 0 ersetzt. Ob das Sinn macht weiß ich nicht, aber jetzt kann man besser lesen was dieses Makro macht:

/* counter init function */
static int counter_0_init(struct device *dev)
{
	IRQ_CONNECT(DT_NORDIC_NRF_TIMER_TIMER_0_IRQ_0,
		    DT_NORDIC_NRF_TIMER_TIMER_0_IRQ_0_PRIORITY,
		    irq_handler, DEVICE_GET(timer_0), 0);

	static const struct counter_timer_config config = {
		.freq =	DT_NORDIC_NRF_TIMER_TIMER_0_PRESCALER,
		.mode = NRF_TIMER_MODE_TIMER,		//Hier müssen wir dann den Counter Mode einstellen
		.bit_width = (TIMER0_MAX_SIZE == 32) ? NRF_TIMER_BIT_WIDTH_32 : NRF_TIMER_BIT_WIDTH_16,
	};		
	
	return init_timer(dev, &config);
}


static struct counter_nrfx_data counter_idx_data;
static struct counter_nrfx_ch_data counter_idx_ch_data[CC_TO_ID(TIMER_0_CC_NUM)];
LOG_INSTANCE_REGISTER(LOG_MODULE_NAME, idx, CONFIG_COUNTER_LOG_LEVEL);


/* counter options */
static const struct counter_nrfx_config nrfx_counter_0_config = {
		.info = {
						.max_top_value = (TIMER_0_MAX_SIZE == 32) ? 0xffffffff : 0x0000ffff,
						.freq = TIMER_CLOCK / (1 << DT_NORDIC_NRF_TIMER_TIMER_idx_PRESCALER),
						.flags = COUNTER_CONFIG_INFO_COUNT_UP,
						.channels = CC_TO_ID(TIMER_idx_CC_NUM),
		},
		.ch_data = counteridx_ch_data,
		.timer = NRF_TIMER_idx,
		LOG_INSTANCE_PTR_INIT(log, LOG_MODULE_NAME, idx)
};

/* Device and API init */
DEVICE_AND_API_INIT(timer_idx, DT_NORDIC_NRF_TIMER_TIMER_idx_LABEL, counter_idx_init,
		    						&counter_idx_data, &nrfx_counter_idx_config.info, PRE_KERNEL_1,
										CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &counter_nrfx_driver_api)


Jetzt können wir diese Structs zb im Programm

bwp_ws19/zephyr/samples/drivers/counter/alarm/src/main.c

einbinden, um einen counter zu initialisieren.


PPI Programmable Peripheral Interconnect

PPI.png

Kapitel 22 PPI — Programmable peripheral interconnect - nRF52832_PS_v1.4.pdf


Um das Peripheral "Event" (GPIO-Register) an die Peripheral "Task" Counter zu zu weisen suchen wir im Zephyr OS nach bereits bestehenden "event end point (EEP) and task end points (TEP)" um nicht die gleichen EEP und TEP zu verweden.

In der Datei nrfx_ppi.h sind die Funktionen deklariert die ein EEP und TEP definieren.

bwp_ws19/modules/hal/nordic/nrfx/drivers/include/nrfx_ppi.h oder bwp_ws19/modules/lib/openthread/third_party/NordicSemiconductor/nrfx/drivers/include/nrfx_ppi.h

/**
 * @brief Function for assigning task and event endpoints to the PPI channel.
 *
 * @param[in] channel PPI channel to be assigned endpoints.
 * @param[in] eep     Event endpoint address.
 * @param[in] tep     Task endpoint address.
 *
 * @retval NRFX_SUCCESS             The channel was successfully assigned.
 * @retval NRFX_ERROR_INVALID_STATE The channel is not allocated for the user.
 * @retval NRFX_ERROR_INVALID_PARAM The channel is not user-configurable.
 */
nrfx_err_t nrfx_ppi_channel_assign(nrf_ppi_channel_t channel, uint32_t eep, uint32_t tep);

/**
 * @brief Function for assigning fork endpoint to the PPI channel or clearing it.
 *
 * @param[in] channel  PPI channel to be assigned endpoints.
 * @param[in] fork_tep Fork task endpoint address or 0 to clear.
 *
 * @retval NRFX_SUCCESS             The channel was successfully assigned.
 * @retval NRFX_ERROR_INVALID_STATE The channel is not allocated for the user.
 * @retval NRFX_ERROR_NOT_SUPPORTED Function is not supported.
 */
nrfx_err_t nrfx_ppi_channel_fork_assign(nrf_ppi_channel_t channel, uint32_t fork_tep);

Die Implementierung der Funktionen findet sich in: bwp_ws19/modules/hal/nordic/nrfx/drivers/src/nrfx_ppi.c oder bwp_ws19/modules/lib/openthread/third_party/NordicSemiconductor/nrfx/drivers/src/nrfx_ppi.c


In der Datei bwp_ws19/zephyr/drivers/counter/counter_nrfx_rtc.c werden diese Funktionen verwendet.

	(void)nrfx_ppi_channel_assign(data->ppi_ch, evt_addr, task_addr);
	(void)nrfx_ppi_channel_enable(data->ppi_ch);


Auch die UART Ausgbe nutzt die diese Funktionen: bwp_ws19/zephyr/drivers/serial/uart_nrfx_uarte.c

static int uarte_nrfx_rx_counting_init(struct device *dev)
{
	struct uarte_nrfx_data *data = get_dev_data(dev);
	const struct uarte_nrfx_config *cfg = get_dev_config(dev);
	NRF_UARTE_Type *uarte = get_uarte_instance(dev);
	int ret;

	if (hw_rx_counting_enabled(data)) {
		nrfx_timer_config_t tmr_config = NRFX_TIMER_DEFAULT_CONFIG;

		tmr_config.mode = NRF_TIMER_MODE_COUNTER;
		tmr_config.bit_width = NRF_TIMER_BIT_WIDTH_32;
		ret = nrfx_timer_init(&cfg->timer,
				      &tmr_config,
				      timer_handler);
		if (ret != NRFX_SUCCESS) {
			LOG_ERR("Timer already initialized, "
				"switching to software byte counting.");
			data->async->hw_rx_counting = false;
		} else {
			nrfx_timer_enable(&cfg->timer);
			nrfx_timer_clear(&cfg->timer);
		}
	}

	if (hw_rx_counting_enabled(data)) {
		ret = gppi_channel_alloc(&data->async->rx_cnt.ppi);
		if (ret != NRFX_SUCCESS) {
			LOG_ERR("Failed to allocate PPI Channel, "
				"switching to software byte counting.");
			data->async->hw_rx_counting = false;
			nrfx_timer_uninit(&cfg->timer);
		}
	}

	if (hw_rx_counting_enabled(data)) {
#if CONFIG_HAS_HW_NRF_PPI
		ret = nrfx_ppi_channel_assign(
			data->async->rx_cnt.ppi,
			nrf_uarte_event_address_get(uarte,
						    NRF_UARTE_EVENT_RXDRDY),
			nrfx_timer_task_address_get(&cfg->timer,
						    NRF_TIMER_TASK_COUNT));

		if (ret != NRFX_SUCCESS) {
			return -EIO;
		}
#else
		nrf_uarte_publish_set(uarte,
				      NRF_UARTE_EVENT_RXDRDY,
				      data->async->rx_cnt.ppi);
		nrf_timer_subscribe_set(cfg->timer.p_reg,
					NRF_TIMER_TASK_COUNT,
					data->async->rx_cnt.ppi);

#endif
		ret = gppi_channel_enable(data->async->rx_cnt.ppi);
		if (ret != NRFX_SUCCESS) {
			return -EIO;
		}
	} else {
		nrf_uarte_int_enable(uarte, NRF_UARTE_INT_RXDRDY_MASK);
	}

	return 0;
}

...

Kommunikation

Anhand des Beispiels unter diesem Link und der Zephyr API zu Bluetooth wurde die Kommunikation des ITS-E realisiert. Da für den GATT-Service eine eindeutige / servicespezifische UUID verwendet wird, habe ich diesen Link verwendet, um eine eigene UUID zu erstellen. Die zu versendenen Daten des ITS-E wurde durch eine eigene Funktion realisiert, da die Versuche die Logger-API zu verwenden nicht funktioniert hatte. Aus dem Beispiel habe ich die Funktionen sendStringBLE, readString, bt_ready, disconnected und connected sowie die Structs übernommen. Die nachfolgenden Funktionen sind Eigenentwicklungen. Die Funktion ITSE_logInit muss einmal initial ausgeführt werden, um das Bluetooth zu aktivieren. Jedes Subteam kann durch das Aufrufen von ITSE_log einen String an einen zentralen Punkt senden. Der eigentliche String, der jedes Team senden möchte, wird durch einen Timestamp erweitert. Damit diese Log-Meldungen abgelegt werden können, wird der Start eines Strings durch ein # und das Ende durch ein $ angegeben und der Timestamp wird mit ** zu Beginn und Ende eingerahmt.

void ITSE_log(char* log_info){
 		s64_t timestamp;
		char time [20] ={'P'};
		timestamp = k_uptime_get();
		sprintf(time,"#**%ld**", timestamp);
		int mess_l = strlen(log_info);
		char* message;
		message	= k_malloc(mess_l+20);	 
		sprintf(message, "%s%s$",time,log_info);
		int counter =(mess_l /20)+1;
		int progress =0;
		while(mess_l > 0 && counter !=0){
			sendStringBLE(message+progress,20);
			mess_l = mess_l- 20;
			progress +=20;
			mess_l = strlen(log_info);
			counter--;
		}
		k_free(message);
}


void ITSE_logInit(void){
	printk("Starting BLE Communication \n");
	int err;
	err = bt_enable(bt_ready);
	bt_conn_cb_register(&conn_callbacks);
}


Außerdem musste ein Configfile angelegt werden, in dem das Bluetooth aktiviert und konfiguriert werden musste.


CONFIG_STDOUT_CONSOLE=y 
CONFIG_PRINTK=y 
CONFIG_LOG=y 
CONFIG_BT=y 
CONFIG_BT_PERIPHERAL=y 
CONFIG_BT_DEVICE_NAME="ITSE" 
CONFIG_BT_GATT_CLIENT=y 
CONFIG_LOG_BUFFER_SIZE=2048 
CONFIG_BT_L2CAP_TX_MTU=50 
CONFIG_HEAP_MEM_POOL_SIZE=2048

Kontrollstation

Die Kontrollstation übernimmt das Empfangen von Nachrichten, die vom ITSE über BLE gesendet wurden. Dazu bedient sie sich eines Python Skripts, das sich mithilfe der MAC-Adresse mit dem Board verbinden und Nachrichten empfangen kann. Die MAC-Adresse des Boards muss hierfür ermittelt. Dafür steht auch ein Python Skript zur Verfügung, das Bluetooth-Geräte im Umfeld auflistet. Als Dienst für das BLE Interface wird Bluepy verwendet. Informationen hierzu sind in der Dokumentation und im Github-Repository von Bluepy zu finden.

###########
# imports #
###########
import bluepy
from bluepy import btle
from bluepy.btle import Peripheral, DefaultDelegate

#####################################
# MAC address and UUID of the board #
#####################################
macAddress = 'cb:46:cc:9b:0f:73'
boardUUID = '5596cba78acc58b5a14a5263ba346319'

####################
# class MyDelegate #
####################

class MyDelegate(DefaultDelegate):
    def __init__(self):
        DefaultDelegate.__init__(self)

    def handleNotification(self, cHandle, data):
        # print received data in the terminal
        print(data)

########################
# connect to the board #
########################

print("DEBUG : connecting ...")

ITSE = Peripheral(macAddress, addrType=btle.ADDR_TYPE_RANDOM)
ITSE.setDelegate(MyDelegate())

print("DEBUG : connecting was successfull")

#############
# main loop #
#############

while True:

    # received a notification
    if ITSE.waitForNotifications(1.0):
        # handleNotification is called here
        continue

    # waiting for notifications
    print("Waiting...\n")

##############
# disconnect #
##############
ITSE.disconnect()

Sensoren & Kartierung

Zephyr Scientific Library (zscilib)

Für die Berechnungen, die für den Kalman Filter benötigt werden, wurde eine zusätzliche Library in das Projekt eingebunden. Es handelt sich dabei um die Zephyr Scientific Library (zscilib). Die Library wurde mit folgenden Schritten eingebunden:

Folgende Einträgen werden in der Datei zephyr/west.yml hinzugefügt:

1. In the manifest/remotes section add:

remotes:
  - name: zscilib
    url-base: https://github.com/zscilib

2. In the manifest/projects section add:

- name: zscilib
  remote: zscilib
  path: modules/lib/zscilib
  revision: master

3. Save the file, and run west update from the project root to retrieve the latest version of zscilib from Github, or whatever revision was specified above.

4. Folgende Parameter wurden in der .conf ergänzt:

CONFIG_FLOAT=y
CONFIG_FP_SHARING=y
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_ZSL=y
CONFIG_ZSL_PLATFORM_OPT=2
CONFIG_ZSL_SINGLE_PRECISION=y
CONFIG_ZSL_VECTOR_INLINE=n
CONFIG_ZSL_MATRIX_INLINE=n
CONFIG_ZSL_BOUNDS_CHECKS=y

Zephyr Scientific Library (zscilib)


Systemarchitektur & Energie

Das Systemarchitekturteam entwickelt eine Architektur, die den verschiedenen Modulen eine Arbeitsumgebung erschafft, die Kommunikation zwischen den Modulen ermöglicht und das Scheduling der Module festlegt. Als Betriebssystem wird Zephyr verwendet, das verschiedene APIs bereitstellt, deren Nutzung nun weiter erläutert wird.

Verteilung von Jobs

Jobs werden innerhalb des Systems über eine API verteilt, welche dafür die Zephyr Messageboxen nutzt. Es gibt die Möglichkeit Jobs zu senden und zu empfangen, mithilfe von zwei Messageboxen Queues die jeder Thread für sich selbst erstellen muss.


JobDiagrammITSE.png

Kommunikation zwischen den Threads

Zur Kommunikation zwischen den verschiedenen Modulen werden Mailboxen verwendet. Zur Orientierung, wie diese unter Zephyr verwendet werden, findet sich im Gitlab ein Beispiel für einen "Sender-Thread", sowie einen "Empfänger-Thread"

CommDiagrammITSE.png


Aufbau des Verhaltens von ITSE

Das Verhalten von ITSE wird über Listen bestimmt. Jede Liste besteht aus einzelnen Basisblöcken diese bestehen aus Anweisungen und Bedingungen, welche innerhalb eines Threads abgearbeitet werden.

struct basicBlock {
	u8_t hasStatus;
	u8_t condition;

	u8_t toStatus;
	struct k_msgq* jobQueue;
	u8_t jobToDo;
	u8_t jobResponse;
};

/*====== basic blocks for creating a behaviour logic ======*/
static struct basicBlock driveForward = { waiting,     distance,
					  driving,     &AntriebJobQ1,
					  jobForwards, FORWARD_ACK };
static struct basicBlock mapThis = { mapping, mapped, waiting, NULL, -1, 0 };

static struct basicBlock rotateLeft = { blocking, rotationLeft,
					rotating, &AntriebJobQ1,
					jobLeft,  ROTATE_LEFT_ACK };
static struct basicBlock rotateRight = { blocking, rotationRight,
					 rotating, &AntriebJobQ1,
					 jobRight, ROTATE_RIGHT_ACK };

/*====== end basic blocks ======*/

/* ====== behaviour lists ======*/
static struct basicBlock *explore[] = { &driveForward, &rotateLeft,
					&rotateRight, &mapThis };

Wie zuvor schon erwähnt kann jede Liste innerhalb eines Threads abgearbeitet werden, dabei ist es auch möglich die Ausführung zu pausieren und anschließen von außerhalb wieder aufzunehmen. Damit das ganze auch funktioniert müssen diverse Hilfsfunktionen geschrieben werden, welche die eigentliche Arbeit der Basisblöcke umsetzen.

void workList(void *ListWork, void *sizeOfListPointer, void *unused)
{
	u8_t count = 0;
	int sizeOfList = *(int *)sizeOfListPointer;
	struct basicBlock **List = (struct basicBlock **)ListWork;

	struct jobInfo myJob = { -1, -1 };
	while (1) {
		//k_sleep(100);
		/* if the stop execution job is received this thread ist halted */
		int ret = receiveJob(&exploreJob, &exploreJob2, &myJob);
		if (ret == 0) {
			switch (myJob.jobID) {
			case stopExecution:
				LOG_INF("child thread suspended");
				k_thread_suspend(k_current_get());
				break;

			default:
				break;
			}
			if (myJob.responseID != 0) {
				/* respond if the thread is halted */
				jobDone(myJob.responseID);
			}
		}

		/* iterate through a list of basic block */
		for (int i = 0; i < sizeOfList; ++i) {
			k_sleep(500);

			LOG_INF("Behaviour state: %s", status(curState));
			/* if the current state is matching with the conditional state */
			if (curState == List[i]->hasStatus) {
				/* check if the condition (which uses helper functions) is met */
				if (checkCondition(List[i]->condition)) {
					/* then send a job to the queue  */
					if (List[i]->jobToDo >= 0) {
						publishJob(
							List[i]->jobQueue,
							List[i]->jobToDo,
							List[i]->jobResponse);
						curState = List[i]->toStatus;
					};
				}
			}
		}
	}
}

BehaiourITSE.png