Tutorial für Fortgeschrittene - Teil 3

In diesem Teil werden wir den generierten Source Code in das Atmel Studio Projekt integrieren und die noch fehlenden Aufrufe zu dem ATtiny API manuell ergänzen. Am Ende werden wir das Projekt compilieren und am Curiosity Board ausführen.

Ziel von Teil 3

Wir wollen mit dem ATtiny 1607 Curiosity Nano Evaluation Kit eine Temperatur-Überwachung mit optischer Übertemperatur-Anzeige entwickeln.

Im Teil 1 dieses Tutorials hatten wir ein Atmel Studio Projekt aufgesetzt und für das ATtiny Curiosity Board konfiguriert. Im vorangegangenen zweiten Teil hatten wir mit Meta Genius zwei Zustandsmaschinen und einige Units modelliert und daraus mit EmStaMaG C Soucre Code generiert, der schon die wesentlichen Teile der Funktions-Logik implementiert.

In diesem Teil werden wir nun den generierten Source Code in das Atmel Studio Projekt integrieren und die noch fehlenden Aufrufe zu dem ATtiny API manuell ergänzen.

Schritt #3.1: Generierten C Source Code in Atmel Studio Projekt integrieren

Im Teil 1 dieses Tutorials hatten wir mit der Konfiguration des Atmel Studio Projektes die grundlegende Verzeichnisstruktur festgelegt. Im Teil 2 wurden dann in den GenerationOptions die Generierungsverzeichnisse festgelegt.

Durch die Generierung sollten nun folgende Verzeichnisse und Files entstanden sein:

Wir müssen nun die generierten *.h und *.c Files in das Atmel Studio Projekt integrieren. Im Teil 1 hatten wir hierfür schon ein eigenes Verzeichnis Code vorbereitet.

  • Fügen Sie im Atmel Studio Projekt dem Code Verzeichnis alle generierten *.h und *.c Files aus dem GeneratedCode Verzeichnis hinzu.
    Verwenden Sie hierfür am besten die Funktion „Add > Existing Item …“.

Durch das Hinzufügen über diese Art, werden die ausgewählten Dateien aus dem GeneratedCode Verzeichnis sowohl in den Build-Prozess integriert als auch in das Zielverzeichnis kopiert.

Speziell das Kopieren ist für uns wichtig, da wir diese Files nun manuell editieren werden. Sollte später noch einmal eine Generierung erforderlich sein, kann so verhindert werden, dass die manuellen Änderungen dabei verloren gehen.

Nach dem Hinzufügen der Files sollte das Atmel Studio Projekt folgendermaßen aussehen:

Schritt #3.2: A/D-Konvertierung implementieren

Für die A/D-Konvertierung konnten wir alle Schritte des funktionalen Ablaufes bereits vollständig in einer Zustandsmaschine erfassen. Daher sind hier nur mehr wenige Zeilen Code erforderlich um die ATtiny API Funktionen aufzurufen.

(1) Result Ready Interrupt mit Callback Funktion verknüpfen

Wir hatten das ADC-Modul des ATtiny so konfiguriert, dass ein Interrupt ausgelöst wird, sobald eine A/D-Konvertierung abgeschlossen wurde. Für diesen Interrupt registrieren wir nun eine Callback-Funktion aus der wir später dann einen ADC_COMPLETED Event auslösen werden.

hal.c

/*###########################################################################################################################################################*/
//      Includes
/*###########################################################################################################################################################*/
#include "hal.h"
#include "adc_basic.h"



/*###########################################################################################################################################################*/
//      External function implementations
/*###########################################################################################################################################################*/


/*===========================================================================================================================================================*/
extern void Hal_init(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
	ADC_0_register_callback( Hal_convCompletedCb );
}


//...


/*===========================================================================================================================================================*/
extern void Hal_convCompletedCb(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
    // ... will be implemented later.
}

(2) A/D-Konvertierung starten

Diese Action Funktion dient nur als einfacher Wrapper für die ATtiny API Funktion zum Starten der A/D-Konvertierung, bei der zusätzlich noch den Konvertierungs-Kanal als Argument mit übergeben werden muss. In unserem Fall wählen wir den Kanal, mit dem der Temperatur-Sensor verbunden ist.

hal.c

/*===========================================================================================================================================================*/
extern void Hal_startConversion(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
	ADC_0_start_conversion( ADC_MUXPOS_TEMPSENSE_gc );
}

(3) ADC_COMPLETED Event auslösen

Immer dann, wenn eine A/D-Konvertierung abgeschlossen wurde, müssen wir in unserer AdcSm Zustandsmaschine einen ADC_COMPLETED Event auslösen. Hierfür implementieren wir nun die Interrupt Callback-Funktion von vorhin.

hal.c

/*###########################################################################################################################################################*/
//      Includes
/*###########################################################################################################################################################*/
#include "hal.h"
#include "adc_basic.h"
#include "adc_sm.h"



/*###########################################################################################################################################################*/
//      External function implementations
/*###########################################################################################################################################################*/


//...


/*===========================================================================================================================================================*/
extern void Hal_convCompletedCb(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
    AdcSm_executeWithAdcSmEvent( AdcSm_Event_ADC_COMPLETED );
}

(4) ADC Ergebnis verarbeiten und in Temperatur umrechnen

Sobald eine A/D-Konvertierung abgeschlossen ist, ruft die AdcSm Zustandsmaschine die Action processResult() auf, in der das Ergebnis ausgelesen und weiterverarbeitet wird. Im ATtiny 1607 Datasheet ist im Kapitel „Temperature Measurement“ genau beschrieben, wie der ADC-Wert umgerechnet werden muss, um daraus eine korrigierte Temperatur zu erhalten. Diese ergänzen wir nur um die Umrechnung nach Celsius.

hal.c

/*===========================================================================================================================================================*/
extern void Hal_processResult(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
	// from ATtiny 1607 data-sheet, chapter temperature measurement
	int8_t   sigrow_offset = SIGROW.TEMPSENSE1;             // Read signed value from signature row
	uint8_t  sigrow_gain   = SIGROW.TEMPSENSE0;             // Read unsigned value from signature row
	uint16_t adc_reading   = ADC_0_get_conversion_result(); // ADC conversion result with 1.1 V internal reference
	uint32_t tempInK       = adc_reading - sigrow_offset;
	
	tempInK *=  sigrow_gain;                                // Result might overflow 16 bit variable (10bit+8bit)
	tempInK +=  0x80;                                       // Add 1/2 to get correct rounding on division below
	tempInK >>= 8;                                          // Divide result to get Kelvin
	
	// added conversion to celsius
	int16_t tempInC = ((int16_t) tempInK ) - ((int16_t) 273 );

    // ... to be continued later ...
}

Schritt #3.3: Delay Timer implementieren

Für unsere Temperatur-Überwachung gibt es eigentlich keinen zwingenden Grund, einen Timer zu verwenden. Da Timer aber in vielen realen Anwendungsfällen sinnvoll oder sogar zwingend erforderlich sind, soll hier anhand eines sehr kleinen Beispiels gezeigt werden, wie einfach das Handling von Timern unter Verwendung von Zustandsmaschinen ist. Daher hatten wir in unsere AdcSm Zustandsmaschine eine timerbasierte Pause eingefügt.

Im Teil 1 hatten wir das Timer-Modul des ATtiny schon so konfiguriert, dass jede Millisekunde ein Interrupt ausgelöst wird. Dieser landet im Code in der Datei driver_isr.c in der Funktion ISR(TCA0_OVF_vect).

Von hier aus rufen wir eine neu angelegte Funktion tick() aus unserer Timer Unit auf.

timer.h

/*###########################################################################################################################################################*/
//      Function declarations
/*###########################################################################################################################################################*/


/*===========================================================================================================================================================*/
extern void Timer_tick(void);


/*===========================================================================================================================================================*/
extern void Timer_startIdleWaiting(void);

driver_isr.c

#include <driver_init.h>
#include <compiler.h>
#include "timer.h"


ISR(TCA0_OVF_vect)
{
	/* Insert your TCA overflow interrupt handling code */
	Timer_tick();

	/* The interrupt flag has to be cleared manually */
	TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm;
}

(5) Timer starten und (6) IDLE_WAITING_EXPIRED Event auslösen

In unserer Timer Unit wir nun vom Timer Interrupt jede Millisekunde die tick() Funktion aufgerufen. Damit ist es nun sehr einfach, einen Idle-Waiting-Timer mit einer Dauer von 1 Sekunde als Down-Counter zu realisieren, der am Ende einen IDLE_WAITING_EXPIRED Event auslöst.

Auf vergleichbare Weise könnte man hier natürlich auch ein ganzes Array von Timern und Timer-Events verwalten.

timer.c

/*###########################################################################################################################################################*/
//      Includes
/*###########################################################################################################################################################*/
#include "timer.h"
#include "adc_sm.h"
#include <stdint.h>



/*###########################################################################################################################################################*/
//      Internal variables
/*###########################################################################################################################################################*/
static uint16_t       Timer_idleWaitingTickCounter = 0u;
static const uint16_t Timer_idleWaitingTicks       = 1000u;



/*###########################################################################################################################################################*/
//      External function implementations
/*###########################################################################################################################################################*/


/*===========================================================================================================================================================*/
extern void Timer_tick(void)
/*===========================================================================================================================================================*/
{
	if ( Timer_idleWaitingTickCounter > 0u )
	{
		Timer_idleWaitingTickCounter--;
		
		if ( Timer_idleWaitingTickCounter == 0u )
		{
			AdcSm_executeWithAdcSmEvent( AdcSm_Event_IDLE_WAITING_EXPIRED );
		}
	}
}



/*===========================================================================================================================================================*/
extern void Timer_startIdleWaiting(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
	Timer_idleWaitingTickCounter = Timer_idleWaitingTicks;
}

Schritt #3.4: Temperatur Überwachung und Übertemperatur Anzeige implementieren

Bisher hatten wir vom ADC nur den konvertierten Wert ausgelesen und in eine Temperatur umgerechnet, aber noch nicht weiter verwendet. Das wird sich nun ändern.

In der Zustandsmaschine benötigen wir einen Event TEMP_CHANGED als Benachrichtigung, dass sich die Temperatur geändert hat.

(7) TEMP_CHANGED Event auslösen

Um das TEMP_CHANGED Event auszulösen erweitern wir die Implementierung der Hal_processResult() Funktion. Dabei bereiten wir auch noch eine Getter-Funktion für die Temperatur vor, die wir dann später gleich benötigen werden.

hal.h

/*###########################################################################################################################################################*/
//      Includes
/*###########################################################################################################################################################*/
#include <stdint.h>



/*###########################################################################################################################################################*/
//      Function declarations
/*###########################################################################################################################################################*/

//...

/*===========================================================================================================================================================*/
extern int16_t Hal_getTempInC(void);

//...

hal.c

/*###########################################################################################################################################################*/
//      Includes
/*###########################################################################################################################################################*/
#include "hal.h"
#include "adc_basic.h"
#include "adc_sm.h"
#include "temp_sm.h"



/*###########################################################################################################################################################*/
//      Internal variables
/*###########################################################################################################################################################*/
static int16_t Hal_tempInC = 0;



/*###########################################################################################################################################################*/
//      External function implementations
/*###########################################################################################################################################################*/

//...

/*===========================================================================================================================================================*/
extern void Hal_processResult(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
	// from ATtiny 1607 data-sheet, chapter temperature measurement
	int8_t   sigrow_offset = SIGROW.TEMPSENSE1;             // Read signed value from signature row
	uint8_t  sigrow_gain   = SIGROW.TEMPSENSE0;             // Read unsigned value from signature row
	uint16_t adc_reading   = ADC_0_get_conversion_result(); // ADC conversion result with 1.1 V internal reference
	uint32_t tempInK       = adc_reading - sigrow_offset;
	
	tempInK *=  sigrow_gain;                                // Result might overflow 16 bit variable (10bit+8bit)
	tempInK +=  0x80;                                       // Add 1/2 to get correct rounding on division below
	tempInK >>= 8;                                          // Divide result to get Kelvin
	
	// added conversion to celsius
	int16_t oldTempInC = Hal_tempInC;
	int16_t newTempInC = ((int16_t) tempInK ) - ((int16_t) 273 );
	
	Hal_tempInC = newTempInC;
	
	if ( newTempInC != oldTempInC )
	{
		TempSm_executeWithTempSmEvent( TempSm_Event_TEMP_CHANGED );
	}
}

//...

/*===========================================================================================================================================================*/
extern int16_t Hal_getTempInC(void)
/*===========================================================================================================================================================*/
{
	return Hal_tempInC;
}

//...

(8) Condition isHot() und (9) Condition isCold() implementieren

Diese Aufgabe erklärt sich nach allen anderen Vorbereitungen von selbst.

Die Temperatur-Schwellwerte wurden hier so gewählt, dass normale Raumtemperatur noch als Cold gilt, man den Prozessor aber durch Anfassen so weit erwärmen können sollte, um ihn in den Zustand Hot zu versetzen.

temp_sm.c

/*===========================================================================================================================================================*/
extern bool TempSm_isHot(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
	return ( Hal_getTempInC() > 30 );
}



/*===========================================================================================================================================================*/
extern bool TempSm_isCold(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
	return ( Hal_getTempInC() < 27 );
}

(10) switchLedOff() und (11) switchLedOn() implementieren

Für die Implementierung der LED Ansteuerung sollte man wissen, dass diese in Hardware am Curiosity Board als Open-Collector Beschaltung ausgeführt ist. Um die LED einzuschalten, müsste man den GPIO Ausgang also auf den logischen Pegel false setzen. Solche Invertierungen führen früher oder später immer zu Verwirrungen. Erfreulicherweise bietet das ATtiny API hierfür einfache Abhilfe mit der Funktion LED0_set_inverted( true ). Nach einmaligem Aufruf, wird der logische Pegel dann intern automatisch invertiert.

/*###########################################################################################################################################################*/
//      Includes
/*###########################################################################################################################################################*/
#include "hal.h"
#include "adc_basic.h"
#include "adc_sm.h"
#include "temp_sm.h"
#include "atmel_start_pins.h"


//...


/*===========================================================================================================================================================*/
extern void Hal_init(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
    ADC_0_register_callback( Hal_convCompletedCb );
    LED0_set_inverted( true );
}


//...


/*===========================================================================================================================================================*/
extern void Hal_switchLedOn(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
    LED0_set_level( true );
}



/*===========================================================================================================================================================*/
extern void Hal_switchLedOff(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
    LED0_set_level( false );
}

Schritt #3.5: FatalErrorHandler implementieren

Der FatalErrorHandler wird vom generierten Zustandsmaschinen Code bei schwerwiegenden Problemen aufgerufen, bei denen eine weitere Ausführung nicht mehr sinnvoll möglich ist.

Die häufigste Ursache hierfür ist die vergessene Initialisierung einer Zustandsmaschine. Oder Probleme in der Initialisierungsreihenfolge mehrerer Zustandsmaschinen.

Um derartige Probleme von Anfang bemerkbar zu machen, empfiehlt sich eine Implementierung in der der Fehlerzustand sofort und möglichst unmissverständlich sichtbar wird. Wir aktivierten hierzu unsere einzige LED mit 50% Helligkeit.

common.c

/*###########################################################################################################################################################*/
//      Includes
/*###########################################################################################################################################################*/
#include "common.h"
#include "atmel_start_pins.h"



/*###########################################################################################################################################################*/
//      External function implementations
/*###########################################################################################################################################################*/


/*===========================================================================================================================================================*/
extern void Common_handleFatalError(void)
/*===========================================================================================================================================================*/
{
    // add your action implementation code here
    for ( ; ; )
    {
        LED0_set_level( true );
        LED0_set_level( false );
    }
}

Schritt #3.6: Zustandsmaschinen in main() integrieren

Das gesamte Verhalten unserer Applikation ist durch die zwei Zustandsmaschinen beschrieben. Für die main() Funkion bleibt daher nur der Aufruf der jeweiligen init() Funktionen, wobei hier darauf zu achten ist, dass die TempSm vor der AdcSm initialisiert werden sollte.

main.c

#include <atmel_start.h>
#include "adc_sm.h"
#include "temp_sm.h"


int main(void)
{
	/* Initializes MCU, drivers and middleware */
	atmel_start_init();

	/* Replace with your application code */
	TempSm_init();
	AdcSm_init();
	
	
	while (1) {
	}
}

Schritt #3.7: Projekt bauen und ausführen

Nun ist es endlich so weit. Wir können unser Projekt bauen und am ATtiny 1607 Curiosity Nano Evaluation Kit ausführen. Hierzu muss man nur das Board über ein USB-Kabel am Computer ansteckt, dann sollte sich das Board im Atmel Studio schon automatisch als Target konfigurieren. Für unser Projekt müssen wir das Board dann nur als Tool auswählen.

  • Verbinden Sie das ATtiny 1607 Curiosity Nano Evaluation Kit über USB mit dem Computer.
  • Selektieren Sie das Board als Tool für das Projekt.
  • Starten Sie eine Debug-Session mit der Taste F5.
    Dabei wird das Projekt automatisch compiliert und per integriertem OnChip Debugger auf das Target programmiert und dort ausgeführt.

Nach kurzem Erwärmen des ATtiny sollte die LED als Signalisierung von Übertemperatur eingeschaltet werden.

Herzlichen Glückwunsch! Sie haben damit dieses Tutorial erfolgreich abgeschlossen.

Navigation

Adc Tutorial Gesamtübersicht

Adc Tutorial Teil 1 – Atmel Studio Projekt aufsetzen

Adc Tutorial Teil 2 – Zustandsmaschinen in Meta Genius modellieren und mit EmStaMaG generieren

AdcTutorial Teil 3 – Source Code integrieren und manuell vervollständigen

Fragen oder Anregungen?

Haben Sie Fragen? Oder Probleme?

Dann wenden Sie sich doch bitte an: support@papa-lima.de

Anregungen und Verbesserungsvorschläge werden natürlich auch gerne angenommen!