Clone
28
NodeOperatingSystem
Martin edited this page 2026-03-30 21:54:40 +02:00

Betriebssystem der Mikrocontroller

Der Anwender ist frei in der Wahl des Betriebssystem für seine Mikrocontroller-Platinen, solange die folgenden Grundregeln eingehalten werden:

  • muß als erstes eine im Netz eindeutige UID (64 Bit) generieren
  • muß das Internode-Nachrichten-Protokoll implementieren und sich eine Adresse zwischen 1-251 zuweisen
  • muß von Zeit zu Zeit ein Discovery-Paket senden, mit dem es sich bemerkbar macht
  • kann in beliebigen Abständen Pakete mit gemessenen Sensordaten versenden (dafür gibt es spezielle Nachrichten)
  • kann auf eingehende Pakete reagieren (z.B. reagiert bei mir ein LED-Strip-Controller-Node auf Nachrichten eines Bewegungsmelder-Nodes)

AqHomeControl liefert für meine eigenen Nodes ein einfaches Betriebssystem mit, das diese Aufgaben erfüllt und leicht erweiterbar ist.

Außerdem enthält das System noch einen Bootloader, der das Flashen einer neuen Firmware im Feld über das Inter-Node-Netzwerk erlaubt.

AqHomeControl-Betriebssystem

Übersicht

Das System weist folgenden Eigenschaften auf:

  • extrem geringe Komplexizität, verwendet z.B. keinerlei Verwaltungsstrukturen für Anwendungen oder Treiber, sondern wird nur durch einsortieren von Routinen-Aufrufen organisiert, benötigt dadurch selbst kaum RAM oder FLASH-Speicher und ist auch auf den kleinsten AtTiny einsetzbar
  • nur Single-Tasking (würde nur die Komplexizität und den RAM-Bedarf erhöhen)
  • Ereignis-gesteuert (Ereignisse sind z.B. Timer-Interrupt, eintreffende Nachrichten etc.)
  • vollständig in Assembler geschrieben

Historie

Am Anfang der Entwicklung meiner AVR-basierten Geräte hatte ich zunächst die Idee, ein einfaches Betriebssystem zu schreiben, das z.B. Treiber abstrahiert, und fortschrittliche Timer implementiert. Dabei hat sich schnell herausgestellt, dass hierzu selbst bei "leerem" System (also noch ohne Treiber und Anwendungen) schon erstaunlich viel RAM verbraucht wird, der aber z.B. für die Anwendungen benötigt wird. Zudem haben die AVRs nur sehr wenig RAM und teilweise auch nur wenig FLASH-Speicher. Daher musste eine Lösung her, die keine Verwaltungsstrukturen braucht (schon eine Sprungtabelle für Treiberfunktionen belegt 2 Byte pro Eintrag).

Das wird am ehesten erreicht, indem für jeden Treiber bzw. Anwendung ein paar wenige Einsprünge definiert werden und an entsprechenden Stellen des "Betriebssystems" aufgerufen werden.

Um das ganze modular zu halten und dennoch nicht für jedes Gerät den ganzen Code kopieren zu müssen, werden diese Aufrufe durch Precompiler-Direktiven geklammert, so dass in der Main-Datei eines Devices nur noch die Liste der zu verwendenden Treiber und Anwendungen definiert werden müssen. Um den Rest kümmert sich das System.

Beschreibung

Das System läuft auf den Mikroprozessor-Geräten (device, node), es unterscheidet zwischen Modulen (modules) und Anwendungen, (apps) wobei von beiden jeweils mehrere aktiv sein koennen.

Module sind lowlevel-Code und in erster Linie Treiber für Hardware-Geräte (z.B. den Umweltsensor SI7021) oder für Protokolle. Sie enthalten z.B. auch Interrupt-Handler.

Anwendungen hingegen liegen auf höherer Ebene und verwenden Module, um deren Funktionalität z.B. zu kombinieren oder aufzuwerten. Beispielsweise gibt es Anwendungen für den Netzwerk-Stack, für Bewegungsmelder-aktiviertes Licht, zum Berichten von Statistiken und von Sensordaten.

Zur Kommunikation mit Modulen und Anwendungen kennt das Betriebssystem verschiedene Funktionen:

Funktion Beschreibung Erforderlichkeit
INIT Initialisiert ein Modul oder eine Anwendung erforderlich
RUN Wird einmal pro MainLoop aufgerufen optional
MSGRECVD wird bei Empfang einer Nachricht über das Netzwerk aufgerufen optional
EVERY100MS wird einmal alle 100ms aufgerufen (system timer tick) optional
EVERY1S wird einmal pro Sekunde aufgerufen optional
EVERY1M wird einmal pro Minute aufgerufen optional
EVERY1H wird einmal pro Stunde aufgerufen optional
EVERY1D wird einmal pro Tag aufgerufen optional

Die Aufrufe dieser Funktionen liegen für Module in den Dateien avr/devices/all/modules_XXX.asm, z.B. liegen die Aufrufe der INIT-Funktion für Module in avr/devices/all/modules_init.asm. Falls also ein Treiber z.B. die INIT Funktion implementiert, muss der entsprechende Aufruf in diese Datei eingefügt werden.

Viele Treiber implementieren nicht alle Funktionen, daher findet sich nicht in jeder entsprechenden Datei ein solcher Aufruf.

Um die Verwendung durch die verschiedenen Platinen zu vereinfachen, werden die Aufrufe in den modules_xxx.asm und apps_xxx.asm Dateien immer durch Präprozessor-Direktiven eingeschlossen, z.B.:

#ifdef MODULES_SGP30
  bigcall SGP30_EverySecond
#endif

Dadurch muss in der main.asm eines Mikroprozessor-Nodes nur diese Direktive eingebaut werden, um diesen Treiber vollständig einzubinden:

#define MODULES_SGP30

Zentraler Loop

Das Betriebssystem selbst besteht eigentlich nur aus einem einfachen MainLoop.

Dieser Loop ruft als erstes alle RUN-Funktionen der Module und Anwendungen auf und legt den Prozessor dann schlafen (SLEEP). Aus diesem Schlaf wacht der Prozessor durch einen Interrupt auf. Das ist in der Regel der Timer-Interrupt, der alle 100 Millisekunden ausgelöst wird. Somit schläft das System die meiste Zeit, was den Stromverbrauch senkt.

Device-Hauptanwendung

Die Hauptanwendung eines Device/Node sollte die folgenden Labels enthalten, die vom Betriebssystem aufgerufen werden:

Funktion Beschreibung
onSystemStart Wird einmal nach Initialisierung des Betriebssystem aufgerufen
onEveryLoop Wird einmal pro MainLoop aufgerufen
onMessageReceived wird bei Empfang einer Nachricht über das Netzwerk aufgerufen
onEvery100ms wird einmal alle 100ms aufgerufen (system timer tick)
onEverySecond wird einmal pro Sekunde aufgerufen
onEveryMinute wird einmal pro Minute aufgerufen
onEveryHour wird einmal pro Stunde aufgerufen
onEveryDay wird einmal pro Tag aufgerufen

Bootloader

Das Betriebssystem von AqHomeControl enthält auch einen Bootloader. Dieser wird bei jedem Reset angesprungen und wartet dann für einige Sekunden, ob eine Anforderung zum Flashen einer neuen Firmware eintrifft. Ist dies der Fall, wird die neue Firmware in den FLASH des Mikroprozessors geschrieben und anschliessend aufgerufen.

Beispiel

Im folgenden die Quelldatei der Hauptanwendung des Devices N29.

Dabei handelt es sich um eine Mikroprozessorplatine mit einem ATtiny84, einem Bewegungssensor, einem Temperatur-/Luftfeuchtigkeitssensor und einer Photodiode/Phototransistor zur Messung der Hellichkeit im Raum.

Man sieht hier schön, daß verschiedene Treiber einfach über Präprozessor-Direktiven (z.B. #define MODULES_SGP30) eingebunden werden. Der Code der Hauptanwendung selbst ist ansonsten leer, der Rest wird vom Betriebssystem erledigt. Es müssen lediglich die oben beschriebenen Labels angelegt werden (wie z.B. onEverySecond), die hier aber lediglich ein ret (return) ausführen.

; ***************************************************************************
; copyright   : (C) 2026 by Martin Preuss
; email       : martin@libchipcard.de
;
; ***************************************************************************
; * This file is part of the project "AqHome".                              *
; * Please see toplevel file COPYING of that project for license details.   *
; ***************************************************************************




; ***************************************************************************
; Source file for temperature sensor node on AtTiny 84
;
; This is for the full system (i.e. not the boot loader).
;
; All definitions and changes should go into this file.
;
;
; ***************************************************************************


.equ clock=1000000 ; Define the clock frequency

.nolist
.include "include/tn84def.inc" ; Define device ATtiny84
.list

.include "../defs.asm"
.include "./data.asm"

.include "version.asm"
.include "devices/all/defs.asm"
.include "common/calls.asm"
.include "common/utils_io.asm"
.include "common/utils_wait.asm"



; ***************************************************************************
; defines

; ---------------------------------------------------------------------------
; generic

.equ NET_BUFFERS_NUM            =  6
.equ NET_MSGNUMINBUF_SIZE       =  8 ; max buffer nums in ringbuffer (global incoming)
.equ NET_IFACE_OUTMSGBUF_SIZE   =  8 ; max buffer nums in ringbuffer (per interface outbound)




; ---------------------------------------------------------------------------
; firmware settings including list of modules used

#define MODULES_CLOCK
#define MODULES_LED_SIMPLE
#define MODULES_NETWORK
#define MODULES_COM2W
#define MODULES_TWI_MASTER
#define MODULES_SI7021
#define MODULES_SGP30
#define MODULES_MOTION
#define MODULES_BRIGHTNESS

#define APPS_NETWORK
#define APPS_MOTION
#define APPS_REPORTSENSORS
#define APPS_STATS



; ---------------------------------------------------------------------------
; defines for values

.equ VALUE_ID_SI7021_TEMP      = 0x01
.equ VALUE_ID_SI7021_HUM       = 0x02
.equ VALUE_ID_MOTION           = 0x07
.equ VALUE_ID_SGP30_TVOC       = 0x09
.equ VALUE_ID_SGP30_CO2        = 0x0a
.equ VALUE_ID_BRIGHTNESS       = 0x0b
.equ VALUE_ID_LEDSIMPLE_TIMING = 0x88





; ***************************************************************************
; code segment

.cseg
.org 000000


; ---------------------------------------------------------------------------
; Reset and interrupt vectors

     rjmp BOOTLOADER_ADDR ; Reset vector  ; use this for flashed system
	reti ; EXT_INT0
        rjmp com2wPcintIsr         ; PCI0
	reti ; PCI1
	reti ; WATCHDOG
	reti ; ICP1
	reti ; OC1A
	reti ; OC1B
	reti ; OVF1
        rjmp baseTimerIrqOC0A ; OC0A
	reti ; OC0B
	reti ; OVF0
	reti ; ACI
        reti ; ADCC
	reti ; ERDY
	reti ; USI_STR
	reti ; USI_OVF


devInfoBlock:        ; 12 bytes
devInfoManufacturer: .db 'A', 'Q', 'U', 'A'
devInfoId:           .db DEVICEINFO_ID, 0
devInfoVersion:      .db DEVICEINFO_VERSION, DEVICEINFO_REVISION             ; version, revision
firmwareVersion:     .db FIRMWARE_VARIANT_TEMP_WINDOW, FIRMWARE_VERSION_MAJOR
                     .db FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCHLEVEL



; ---------------------------------------------------------------------------
; @routine firmwareStart @global

firmwareStart:
  rjmp main
; @end



; ---------------------------------------------------------------------------
; @routine onSystemStart

onSystemStart:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine onMessageReceived
;
; Called on every message received

onMessageReceived:
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine onEvery100ms
;
; Called every 100ms. Add your routine calls here. No arguments, no results.

onEvery100ms:
onEverySecond:
onEveryMinute:
onEveryHour:
onEveryDay:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine onEveryLoop
;
; Called on every loop (i.e. after awakening from sleep).
;
onEveryLoop:
  ret
; @end





; ***************************************************************************
; includes

.include "devices/all/hw_tn84.asm"
.include "devices/all/includes.asm"



; ---------------------------------------------------------------------------
; defines for network interface

.equ netInterfaceData = com2w_iface