Forum: Projekte & Code Code: Publisher-Subscriber eventloop


von Felix P. (zirias)


Lesenswert?

Ich bin gerade an meinem ersten Projekt mit einem Mikrocontroller, eine 
Rolladensteuerung. Schnell habe ich festgestellt, dass ich einen 
Interrupt in an sich nicht miteinander in Verbindung stehenden 
Programmteilen brauche. Da ich eigentlich eher aus dem Bereich 
Software-Engineering komme wollte ich eine Lösung, die die logische 
Kapselung erhält.

Ich stelle hier mal meinen Code vor aus zwei Gründen: Erstens natürlich, 
um Kommentare zu erhalten von Leuten mit Erfahrung. Der Code läuft in 
simavr, aber vielleicht ist ja trotzdem etwas ungünstig oder 
verbesserungswürdig. Und zweitens, vielleicht ist es ja sogar nützlich 
für jemanden.

Ideen dahinter:

- Eine zentrale Queue mit Events.
- Struktur der Events simpel genug, dass sie problemlos in einer ISR 
erstellt werden können (in meinem Fall gerade mal 2 Bytes)
- Eine Funktion um Events auch in Software zu generieren (meine 
Software-Timer nutzen das auf Basis des TICK events).
- Event-Handler, die registriert werden können
- Timer ticks nur bei Bedarf (wenn ein Handler für das TICK event 
registriert ist)

Verwendung:

event_init();
[...]
ein paar events registrieren mit event_register(...)
[...]
event_loop(); <- kehrt nie zurück.


event.h:
1
#ifndef EVENT_H
2
#define EVENT_H
3
4
#include "common.h"
5
6
/* Maximum queued up events, if more events stay unhandled, the buffer is
7
 * cleared and events are lost */
8
#define EVQUEUESIZE 16
9
10
/* Number of event types. Must be increased when using EV_CUSTOM or defining
11
 * own EV_* constants in order to reserve space for registered handlers */
12
#define NUMEVTYPES 3
13
14
/* Maximum number of handlers registered per event type */
15
#define MAXEVHANDLERS 4
16
17
typedef struct event event;
18
typedef enum ev_type
19
{
20
    EV_TICK      = 0,
21
    EV_PINCHANGE    = 1,
22
    EV_TIMER      = 2,
23
    EV_CUSTOM      = 3
24
} ev_type;
25
26
struct event
27
{
28
    ev_type type;
29
    uint8_t data;
30
};
31
32
typedef void (*ev_handler)(const event *ev, void *data);
33
typedef BOOL (*ev_filter)(const event *ev, ev_handler handler, void *data);
34
35
void event_init(void);
36
37
void event_raise(ev_type type, uint8_t ev_data);
38
39
BOOL event_register(ev_type type, ev_handler handler,
40
  ev_filter filter, void *data);
41
BOOL event_unregister(ev_type type, ev_handler handler, void *data);
42
43
void event_loop(void) __attribute__((__noreturn__));
44
45
#endif

event.c:
1
#include <avr/sleep.h>
2
#include <avr/interrupt.h>
3
#include <avr/io.h>
4
5
static event equeue[EVQUEUESIZE];
6
volatile static uint8_t eqhead = 0;
7
static uint8_t eqtail = 0;
8
9
static uint8_t ticksEnabled = 0;
10
11
typedef struct evhdl_record
12
{
13
    ev_handler handler;
14
    ev_filter filter;
15
    void *data;
16
} evhdl_record;
17
18
typedef struct evhdl_vector
19
{
20
    uint8_t nhandlers;
21
    evhdl_record handlers[MAXEVHANDLERS];
22
} evhdl_vector;
23
24
static evhdl_vector handlervector[NUMEVTYPES];
25
26
void event_init(void)
27
{
28
    /* configure interrupts: */
29
    GIMSK |= _BV(PCIE1);
30
    PCMSK1 |= _BV(PCINT8) | _BV(PCINT9) | _BV(PCINT10);
31
    TIMSK1 |= _BV(OCIE1A);
32
33
    /* power management, disable unused stuff: */
34
    PRR |= _BV(PRTIM1) | _BV(PRTIM0) | _BV(PRUSI) | _BV(PRADC);
35
    MCUCR &= ~(_BV(BODS) | _BV(BODSE));
36
    MCUCR |= _BV(BODS);
37
    MCUCR &= ~_BV(BODSE);
38
    set_sleep_mode(SLEEP_MODE_PWR_SAVE);
39
    sleep_enable();
40
}
41
42
void event_raise(ev_type type, uint8_t ev_data)
43
{
44
    cli();
45
    equeue[eqhead].type = type;
46
    equeue[eqhead].data = ev_data;
47
    if (++eqhead == EVQUEUESIZE) eqhead = 0;
48
    sei();
49
}
50
51
static void enableTicks(void)
52
{
53
    if (ticksEnabled++) return;
54
    PRR &= ~_BV(PRTIM1);
55
    GTCCR |= _BV(TSM) | _BV(PSR10);
56
//  TCCR1B = _BV(WGM12) | _BV(CS10) | _BV(CS11); /* CTC, 8MHz / 64 */
57
    TCCR1B = _BV(WGM12) | _BV(CS11); /* CTC, 1MHz / 8 */
58
    OCR1A = 1250; /* 10 ms */
59
    TCNT1 = 0;
60
    GTCCR &= ~_BV(TSM);
61
}
62
63
static void disableTicks(void)
64
{
65
    if (ticksEnabled && --ticksEnabled) return;
66
    GTCCR |= _BV(TSM) | _BV(PSR10);
67
    TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
68
    GTCCR &= ~_BV(TSM);
69
    PRR |= _BV(PRTIM1);
70
}
71
72
BOOL event_register(ev_type type, ev_handler handler,
73
        ev_filter filter, void *data)
74
{
75
    for (uint8_t i = 0; i < MAXEVHANDLERS; ++i)
76
    {
77
        if (handlervector[type].handlers[i].handler)
78
        {
79
            if (handlervector[type].handlers[i].handler == handler
80
                    && handlervector[type].handlers[i].data == data)
81
            {
82
                handlervector[type].handlers[i].filter = filter;
83
                return TRUE;
84
            }
85
            continue;
86
        }
87
        handlervector[type].handlers[i].handler = handler;
88
        handlervector[type].handlers[i].filter = filter;
89
        handlervector[type].handlers[i].data = data;
90
        ++handlervector[type].nhandlers;
91
        if (type == EV_TICK) enableTicks();
92
        return TRUE;
93
    }
94
    return FALSE;
95
}
96
97
BOOL event_unregister(ev_type type, ev_handler handler, void *data)
98
{
99
    for (uint8_t i = 0; i < MAXEVHANDLERS; ++i)
100
    {
101
        if (handlervector[type].handlers[i].handler != handler
102
                || handlervector[type].handlers[i].data != data)
103
        {
104
            continue;
105
        }
106
        handlervector[type].handlers[i].handler = 0;
107
        --handlervector[type].nhandlers;
108
        if (type == EV_TICK) disableTicks();
109
        return TRUE;
110
    }
111
    return FALSE;
112
}
113
114
ISR(PCINT1_vect)
115
{
116
    equeue[eqhead].type = EV_PINCHANGE;
117
    equeue[eqhead].data = PINB;
118
    if (++eqhead == EVQUEUESIZE) eqhead = 0;
119
}
120
121
ISR(TIM1_COMPA_vect)
122
{
123
    equeue[eqhead].type = EV_TICK;
124
    if (++eqhead == EVQUEUESIZE) eqhead = 0;
125
}
126
127
void event_loop(void)
128
{
129
    sei();
130
    while (1)
131
    {
132
        /* run event queue */
133
        while (eqtail != eqhead)
134
        {
135
            event *ev = &(equeue[eqtail]);
136
            evhdl_vector *v = &(handlervector[ev->type]);
137
            if (v->nhandlers)
138
            {
139
                for (uint8_t i = 0; i < MAXEVHANDLERS; ++i)
140
                {
141
                    if (!(v->handlers[i].handler)) continue;
142
                    if (!(v->handlers[i].filter) ||
143
                            v->handlers[i].filter(ev,
144
                                v->handlers[i].handler, v->handlers[i].data))
145
                    {
146
                        v->handlers[i].handler(ev, v->handlers[i].data);
147
                    }
148
                }
149
            }
150
            if (++eqtail == EVQUEUESIZE) eqtail = 0;
151
        }
152
153
        /* sleep if no more events are pending and no timer tick enabled */
154
        cli();
155
        if (eqtail == eqhead && !ticksEnabled)
156
        {
157
            sei();
158
            __asm__ volatile("sleep");
159
        }
160
        else sei();
161
    }
162
    UNREACHABLE();
163
}

Der Code nutzt #defines aus common.h:
1
#ifndef COMMON_H
2
#define COMMON_H
3
4
#include <stdint.h>
5
6
#ifndef __GNUC__
7
#define __attribute__(x)
8
#define UNREACHABLE()
9
#else
10
#define UNREACHABLE() __builtin_unreachable()
11
#endif
12
13
typedef uint8_t BOOL;
14
#define TRUE 1
15
#define FALSE 0
16
17
#endif

Je nachdem, was man damit tun will, muss natürlich event_init() sowie 
enableTicks() und disableTicks() angepasst werden. Das hier ist für 
einen attinyx4 bei dem Eingänge auf PORTB überwacht werden. Der 
komplette Code liegt auf github: 
https://github.com/Zirias/shuttercontrol/tree/master/shutterctl_attiny84

Ich bin gespannt auf Kommentare und Verbesserungsvorschläge.

*edit*: Eine erste Verbesserung, die originale Version hatte eine kleine 
race condition wenn ein Interrupt genau vor dem "sleep" kam.

: Bearbeitet durch User
von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo Felix,
zwei Gedanken:

1) Oft sind die subscribten Empfänger ja relativ statisch. Sprich in der 
Initialisierung einer Applikation wird ein handler etabliert. Da könnte 
man auf die Idee kommen, dass es eine zweite Liste mit Handler geben 
könnte, die nicht im kostbaren RAM stehen muss. 1.1) Was soll ich 
machen, wenn event_register() false zurück gibt? Gerade im Fall, dass 
die subscriptions eher statischer Natur sind, wird der Fehler immer oder 
nie auftreten. Meiner Meinung nach eher ein Anwendungsfall für asserts.

2) Ein Beispiel sagt mehr als 1000 Worte. Hast Du ein Beispiel, an dem 
Du zeigen kannst, dass die Verwendung Deiner Lib einen erkennbaren 
Vorteil (z.B. Lesbarkeit) bringt?

mfg Torsten

P.S: Gibt es Unit-Tests?

: Bearbeitet durch User
von Felix P. (zirias)


Lesenswert?

Hallo Torsten,

vielen Dank, ich habe jetzt lange nicht reingeschaut und gar nicht 
gemerkt, dass doch jemand geantwortet hat! Deine Anregungen aus deinem 
Punkt 1 sind sehr nützlich für mich, und sobald ich wieder Zeit finde 
für mein Projekt werde ich sie berücksichtigen -- hier sieht man eben, 
dass "embedded" nicht meine Kernkompetenz ist sondern ich damit ganz neu 
anfange, ich muss mich erst an den Gedanken gewöhnen, daraufhin zu 
optimieren, dass letztenendes die komplette Software sehr statisch ist.

Was das Beispiel angeht -- da es zunächst nur für mich war ist das 
Beispiel der benutzende Code im Github repository ;) Aber vielleicht 
würde es sich wirklich lohnen, minimale Beispiele zu bauen, um weiteres 
Verbesserungspotential aufzudecken.

Unit Tests gibt es bisher auch keine, hauptsächlich weil ich mich noch 
nicht mit entsprechenden Frameworks für C beschäftigt habe. Kannst du 
mir da etwas empfehlen?

Danke und Grüße,
Felix

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hi Felix,

Felix P. schrieb:
> Unit Tests gibt es bisher auch keine, hauptsächlich weil ich mich noch
> nicht mit entsprechenden Frameworks für C beschäftigt habe. Kannst du
> mir da etwas empfehlen?

ne, ich mach eher nur C++. Aber im Fred nebenan tauchen ja vielleicht 
noch gute Tipps auf: 
http://www.mikrocontroller.net/topic/goto_post/4262902

mfg Torsten

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.