Guion Practica 1B
Guion Practica 1B
Guion Practica 1B
Práctica 1.B
Introducción
Este segundo bloque de la primera práctica pretende introducir los elementos principales de los
núcleos de tiempo real, a veces llamados también sistemas operativos, para microcontroladores.
Estos sistemas permiten desarrollar una programación mucho más flexible y modular basada en
tareas (hebras) y aportan, como mínimo, un interesante conjunto de primitivas de sincronización,
comunicación y priorización de tareas que facilitan el desarrollo de aplicaciones complejas con
restricciones de tiempo real.
Objetivos
• Introducir los componentes básicos de un sistema de tiempo real para microcontroladores.
• Conocer las características principales de ChibiOS y algunos de los recursos disponibles
para sincronizar, comunicar o priorizar tareas.
• Explorar el uso de ChibiOS para implementar firmware multienhebrado basado en un
planificador de tipo Round-Robin con prioridades.
1
Las funciones malloc() y free(), que gestionan la asignación dinámica de memoria, no son “thread-
safe”, como tampoco lo serán las librerías que las utilicen, como es el caso de las librerías SD y
String de Arduino. La gestión de la memoria es un aspecto esencial en cualquier RTOS, y todos
disponen de primitivas para asignar a cada hebra el uso exclusivo de una parte de la RAM para ser
usado como pila de llamadas o stack de esa hebra. Adicionalmente, cada RTOS puede aportar
diferentes soluciones para permitir la creación un memoria heap (montículo libre, memoria global
reservada, ...) donde sea posible gestionar cierta cantidad de memoria de forma dinámica.
Un problema central a cualquier RTOS es la forma en la que se gestiona el reparto del tiempo de
procesador entre las diferentes hebras. La solución básica, presente en cualquier RTOS, consiste en
ordenar la precedencia o importancia de cada tarea en base a un nivel de prioridad. Cuando una
tarea de prioridad superior está lista para ejecutarse, la tarea de prioridad inferior que se esté
ejecutando será desplazada. Cuando la competencia por el procesador se presente entre hebras de
idéntica prioridad, hay dos soluciones o esquemas básicos:
• Round-robin cooperativo: la tarea en ejecución debe contemplar esta posibilidad en su
diseño y estar preparada para ejecutarse de manera ágil durante periodos de tiempo de unos
pocos milisegundos y ceder el control del procesador entre estos periodos.
• Round-robin con desplazamiento: Se establece en la configuración del RTOS el cuanto o
rodaja de tiempo máximo que una tarea puede conservar el control del procesador. La hebra
puede ceder el control del procesador de forma voluntaria si ha terminado, pero si agota su
cuota de tiempo, será desplazada por el planificador de tareas para permitir la ejecución de
cualquier otra tarea en su mismo nivel de prioridad que esté lista para ejecutarse.
ChibiOS
ChibiOS es un proyecto liderado por Giovanni Di Sirio y se encuentra fuertemente vinculado a la
seri de procesadores STM32 de la empresa ST Microelectronics, aunque incluye un soporte básico
2
para otras arquitecturas. Se caracteriza por la optimización del código del RTOS, tanto en tamaño
como en sobrecarga en tiempo de ejecución.
Se organiza en dos capas o niveles principales (ver figura 1). La capa de abstracción del hardware o
HAL oculta los detalles de acceso y programación de periféricos de cada arquitectura mediante la
introducción de modelos genéricos de manejadores de dispositivos o drivers. La otra capa es la que
corresponde al núcleo de tiempo real (RT) e implementa todos los elementos de despacho de tareas
y mecanismos de sincronización y/o comunicación de hebras del RTOS. Las dos capas son
independientes y es posible usar el núcleo de tiempo real excluyendo la capa HAL, justo la
aproximación usada en la adaptación de Bill Greiman de ChibiOS como veremos a continuación.
Como en el caso de RTOS y Zephyr, es posible usar ChibiOS de forma integral, pero esto implicaría
un largo proceso de aprendizaje del entorno de programación, de las diferentes opciones de
configuración vinculadas al microcontrolador, etc. Con el objetivo de poder introducir y ejercitar
estos conceptos, pero esquivando estas dificultades, en esta práctica usaremos la adaptación de
ChibiOS 20.3.3 de Bill Greiman, basado en el núcleo de tiempo real ChibiOS/RT 6.1.3, disponible
para procesadores ARM y AVR. Esta implementación excluye la capa de abstracción hardware
(HAL) y contiene solo la parte vinculada a la gestión de hebras, incluyendo el despachador de
tareas, y los mecanismos de comunicación y sincronización de las mismas. Es una implementación
muy ligera que, a través de una librería, permite una integración muy sencilla con el IDE de
Arduino.
3
FreeRTOS
FreeRTOS es un RTOS de larga tradición con una licencia tipo MIT de código abierto que soporta
una amplia variedad de arquitecturas de mcrocontroladores. Dispone de una buena documentación y
que se sigue desarrollando activamente en la actualidad. Recientemente se ha postulado su
integración como capa de tiempo real en AZURE, la plataforma de computación en la nube de
Microsoft.
Se organiza en base a un núcleo (kernel) y un conjunto de librerías específicas. El núcleo de tiempo
real no es tan compacto como en el caso de otros RTOS y su uso implica sumar entre 6 y 12 Kbytes
al firmware.
Incluye un planificador de tareas basado en prioridades. Como en muchos otros RTOS, los dos
modos básicos de control en la ejecución de las tareas son por desplazamiento (preemptive) de la
tarea en ejecución al agotar su rodaja de tiempo o de forma cooperativa entre hebras de idéntica
prioridad. En este último modo el diseñador debe estructurar la ejecución de las diferentes tareas en
etapas ágiles, de corta duración, tras las cuales la tarea cede el control del microcontrolador a otras
tareas.
En principio, Bill Greiman adaptó en 2015 la versión 8.2.3 de FreeRTOS para su uso como librería
desde el IDE de Arduino, pero esta adaptación no se mantiene de forma activa y puede considerarse
obsoleta. Posteriormente, en 2020 la empresa china Seeed-Studio ha adaptado la versión 10.4.3 de
FreeRTOS para permitir el uso de FreeRTOS desde el IDE de Arduino, pero solo sobre
procesadores SAMD21 y SAMD51, sin documentación y con un conjunto de ejemplos muy
reducido.
Zephyr
Zephyr es una iniciativa relativamente reciente que ha conseguido un gran respaldo por parte de
empresas y usuarios. Se trata de un RTOS que comparte muchos de los elementos básicos ya
descritos para ChibiOS y FreeRTOS. Adicionalmente, añade un conjunto más amplio de esquemas
de despacho de tareas y pone un cierto énfasis en facilitar el uso de librerías con cumplan con el
estándar POSIX o librerías optimizadas CMSIS (Cortex Microcontroller Software Interface
Standart) para microcontroladores de la familia ARM CORTEX-M. Se trata de un software de
código abierto con una licencia de tipo Apache 2.0, salvo para ciertas partes que se han tomado de
otros desarrollos y que tienen sus propias licencias.
4
• Una hebra que se limita a hacer parpadear el BULTIN_LED del Arduino MKR 1310 a 200
ms (50 ms encendido, 150 ms apagado). En el modo round-robin no cooperativo esta hebra
es la de mayor prioridad (NORMALPRIO + 1).
• Una segunda hebra que únicamente incrementa una variable contadora. Cuando se usa un
esquema no cooperativo es la hebra de menor prioridad (NORMALPRIO – 1) que se
ejecuta, por ese nivel prioridad, “siempre que puede”, es decir, cuando ninguna de las hebras
de mayor prioridad está preparada para ejecutarse.
• La tercera hebra imprime el valor del contador en intervalos de 1 segundo. Esta hebra
reinicia a cero el valor de la variable contadora cada vez que se ejecuta y, en el modo no
cooperativo, se ejecuta con un nivel de prioridad intermedio (NORMALPRIO). Nótese que
en este ejemplo, las tres hebras reciben el mismo nivel de prioridad cuando se activa el
modo cooperativo.
Aspectos a notar:
• La función chSetup() verifica que la configuración de ChibiOS es compatible con la
elección del esquema de despacho de tareas. Para ello verifica el valor de algunas macros
definidas en el fichero chconfig correspondiente a la arquitectura del microcontrolador. Más
concretamente, el uso del esquema cooperativo exige fijar la macro CH_TIME_QUANTUM
a cero. En este ejemplo, el valor de la macro COOPERATIVE_SCHEDULER permite automatizar
estas verificaciones y detener la ejecución, si la configuración es incorrecta.
• Es necesario definir un tamaño de stack para cada una de las hebras. El tamaño “correcto”
debe ser aquel que, sin malgastar la siempre escasa RAM, permita la invocación de las
funciones y la declaración de las variables auxiliares requeridas. Es recomendable comenzar
con unos tamaños generosos e irlos ajustando a medida que la implementación del firmware
se vaya estabilizando. ChibiOS dispone de la función chUnusedThreadStack() para
consultar el tamaño de la parte no usada del stack de la hebra.
• Las variables que se actualizan y/o se consultan desde diferentes hebras, en este ejemplo la
variable contadora count, deben declararse volatile.
• El uso de la pareja de llamadas noInterrupts() e interrupts() para proteger un cierto
bloque de sentencias frente a interrupciones, incluyendo cambios de contexto.
/* ----------------------------------------------------------------------
* Ejemplo ChibiOS-1
* Este ejemplo ilustra cómo es posible crear varia hebras de forma
* estática. Permite experimentar con el uso de un esquema
* cooperativo de despacho de hebras de igual prioridad u otro
* no cooperativo (round-robin con desplazamiento)
*
* Requiere el uso de la librería ChRt de Bill Greiman
* https://github.com/greiman/ChRt
*
* Asignatura (GII-IoT)
* ----------------------------------------------------------------------
*/
5
#include "ChRt.h"
#if COOPERATIVE_SCHEDULER
#else
#endif
// ----------------------------------------------------------------------
// BlinkingThread - Hace parpadear el LED
// Prioridad: BLINKING_THD_PRIO
// STACK: 64 bytes
// ----------------------------------------------------------------------
static THD_WORKING_AREA(wa_BlinkingThread, 64);
// LED_BUILTIN = 13 (predefinido)
pinMode(LED_BUILTIN, OUTPUT);
6
// Sleep for 150 milliseconds.
chThdSleepMilliseconds(150);
}
}
// ----------------------------------------------------------------------
// PrinterThread - Imprime el valor del contador
// Prioridad: PRINTING_THD_PRIO
// STACK: 256 bytes
// ----------------------------------------------------------------------
static THD_WORKING_AREA(wa_PrinterThread, 256);
while (true) {
// Dormimos durante un segundo
chThdSleepMilliseconds(1000);
SerialUSB.print("Count: ");
SerialUSB.println(count);
#else
SerialUSB.print("Count: ");
SerialUSB.print(count);
SerialUSB.print(" Max delay(usec): ");
SerialUSB.println(TIME_I2US(maxDelay_i));
// ----------------------------------------------------------------------
// CountingThread - Hebra contadora
// Prioridad: COUNTING_THD_PRIO
// STACK: 64 bytes
// ----------------------------------------------------------------------
7
static THD_WORKING_AREA(wa_CountingThread, 64);
while (true) {
noInterrupts();
count++;
interrupts();
#if COOPERATIVE_SCHEDULER
systime_t t_i = chVTGetSystemTimeX();
chThdYield();
t_i = chVTTimeElapsedSinceX(t_i);
if (t_i > maxDelay_i) maxDelay_i = t_i;
#endif
}
}
// ----------------------------------------------------------------------
// Creamos tres hebras
// ----------------------------------------------------------------------
void chSetup()
{
// Aquí asumimos que el valor de CH_CFG_ST_TIMEDELTA es siempre cero
// para cualquier tarjeta basada en SAMD que están soportadas solo en
// "tick mode"
8
#elif defined(__AVR__)
SerialUSB.print("src/avr/chconfig_avr.h");
#endif
SerialUSB.println(" to enable round-robin scheduling.");
while (true) {}
}
}
// ----------------------------------------------------------------------
// Set up puede usarse para activar los dispositivos que vayan a
// emplearse
// ----------------------------------------------------------------------
void setup()
{
SerialUSB.begin(115200);
// ----------------------------------------------------------------------
// El entorno Arduino exige la declaración de esta función que se trata
// como una hebra de prioridad normal (NORMALPRIO)
//
// NUNCA usar delay() para imponer un retardo. Si lo hacemos, impediremos
// la ejecución de las hebras de menor prioridad. Esto es fácil de comprobar
// en este ejemplo sustituyendo la llamada a chThdSleepMilliseconds() por
// delay(). Usar siempre chThdSleepMilliseconds() o alguna función
9
// equivalente
// ----------------------------------------------------------------------
void loop()
{
SerialUSB.println("Loop");
chThdSleepMilliseconds(5000);
//delay(5000);
}
//------------------------------------------------------------------------------
// Número de slots disponibles en el Mailbox/memory pool.
const size_t MB_PRINT_SLOTS = 10;
10
// Tipo para el los slots del memory pool.
typedef struct
{
char* name;
int msg;
systime_t time_i;
} printerMsg_t;
//------------------------------------------------------------------------------
// Slots de mensajes del mailbox. Aloja punteros a los objetos del banco
// de memoria.
msg_t letter[MB_PRINT_SLOTS];
//------------------------------------------------------------------------------
// Prioridades de las hebras
#define WORK_THD_PRIO (NORMALPRIO + 1)
#define PRINTER_THD_PRIO (NORMALPRIO)
//------------------------------------------------------------------------------
// Declaramos el tamaño de los stacks de las hebras
// Estas macros deben declararse aquí o en la pestaña principal
static THD_WORKING_AREA(wa_PrinterThread, 512);
static THD_WORKING_AREA(wa_WorkThread_1, 512);
static THD_WORKING_AREA(wa_WorkThread_2, 512);
static THD_WORKING_AREA(wa_WorkThread_3, 512);
//------------------------------------------------------------------------------
// Puerto de depuración
#define debugPort SerialUSB
El módulo worker.ino incluye la declaración de las hebras de trabajo o “worker”. Nótese que la
creación de un mensaje implica obtener primero un objeto tipo printerMsg_t del pool de memoria
printerMemPool, vinculado al mailbox de la hebra printer. El envío del mensaje se realiza mediante
la función chMBPostTimeout() que se invoca de forma no bloqueante (TIME_IMMEDIATE). El
éxito de esta operación se verifica inspeccionando el valor devuelto. Típicamente, esta operación
solo puede fallar si el mailbox está lleno, es decir, si se han consumido todos los slots y no dispone
de slots libres. Si el tercer parámetro de chMBPostTimeout()se cambia a TIME_INFINITE la
11
llamada se vuelve bloqueante, lo que en general implica esperar a que se libere algún slot del
mailbox.
// ----------------------------------------------------------------------
// WorkThread - Elabora mensaje que envía a la hebra Printer usando un
// mailbox.
// Prioridad: WORK_THD_PRIO
// STACK: 512 bytes
// ----------------------------------------------------------------------
static THD_FUNCTION(WorkThread, name)
{
int msg = 0;
while (true) {
// Send message.
msg_t s = chMBPostTimeout(&printer_mailbox, (msg_t)p, TIME_IMMEDIATE);
if (s != MSG_OK) {
debugPort.print((char*)name);
debugPort.println(": chMBPost failed");
}
}
chThdSleepMilliseconds(1000);
}
}
El módulo printer.ino contiene la definición de la hebra printer. Nótese que esta hebra ejecuta una
llamada bloqueante a chMBFetchTimeout() para obtener un nuevo mensaje de tipo printerMsg_t.
Si tiene éxito, organiza su impresión por pantalla para finalmente liberar el objeto al pool de
memoria. No realizar este último paso impedirá que se puedan alojar nuevos mensajes una vez se
consuma la capacidad inicial. Nótese también el uso de la macro TIME_I2MS(ticks)para convertir
ticks (unidades internas de conteo de tiempo) a milisegundos (ver otras macros similares en
chtime.h).
12
// ----------------------------------------------------------------------
// PrinterThread - Imprime los mensajes recibidos en el mailbox
// Prioridad: PRINTING_THD_PRIO
// STACK:256 bytes
// ----------------------------------------------------------------------
static THD_FUNCTION(PrinterThread , arg)
{
(void)arg;
while (true) {
printerMsg_t *p = 0;
// Get mail.
msg_t res = chMBFetchTimeout(&printer_mailbox, (msg_t*)&p, TIME_INFINITE);
if (res == MSG_OK) {
debugPort.print(p->name);
debugPort.write(' ');
debugPort.print(TIME_I2MS(p->time_i));
debugPort.print(" ms ");
debugPort.println(p->msg);
/* ----------------------------------------------------------------------
* Ejemplo ChibiOS-2
* Este ejemplo muestra cómo usar los mecanismos de comunicación
* entre hebras (mailboxes) disponibles en ChibiOS.
*
* Requiere el uso de la librería ChRt de Bill Greiman
* https://github.com/greiman/ChRt
* Asignatura (GII-IoT)
* ----------------------------------------------------------------------
*/
#include <ChRt.h>
#include "config.h"
13
// ----------------------------------------------------------------------
// Creamos tres hebras
// ----------------------------------------------------------------------
void chSetup()
{
// Aquí asumimos que el valor de CH_CFG_ST_TIMEDELTA es siempre cero
// para cualquier tarjeta basada en SAMD que están soportadas solo en
// "tick mode"
chThdCreateStatic(wa_WorkThread_1, sizeof(wa_WorkThread_1),
WORK_THD_PRIO, WorkThread, name[0]);
chThdCreateStatic(wa_WorkThread_2, sizeof(wa_WorkThread_2),
WORK_THD_PRIO, WorkThread, name[1]);
chThdCreateStatic(wa_WorkThread_3, sizeof(wa_WorkThread_3),
WORK_THD_PRIO, WorkThread, name[2]);
}
14
// ----------------------------------------------------------------------
// La función setup puede usarse para activar los dispositivos que vayan
// a emplearse
// ----------------------------------------------------------------------
void setup()
{
debugPort.begin(115200);
// ----------------------------------------------------------------------
// El entorno Arduino exige la declaración de esta función que se trata
// como una hebra de prioridad normal (NORMALPRIO)
//
// NUNCA usar delay() para imponer un retardo. Si lo hacemos, impediremos
// la ejecución de las hebras de menor prioridad. Esto es fácil de comprobar
// en este ejemplo sustituyendo la llamada a chThdSleepMilliseconds() por
// delay(). Usar siempre chThdSleepMilliseconds() o alguna función
// equivalente
// ----------------------------------------------------------------------
void loop()
{
debugPort.println("Loop");
chThdSleepMilliseconds(5000);
//delay(5000);
}
15
El segundo aspecto configurable es el periodo (en milisegundos) con el que se ejecutan estas hebras,
ajustable a través de los valores declarados, posiciones 1, 2 y 3, en el vector threadPeriod_ms[]. En
su configuración inicial la hebra 1 y 3 se ejecutan a 5Hz, cada 200 ms, mientras que la segunda
hebra tiene una tasa de repetición de 10 veces por segundo (10 Hz o un período de 100 ms).
Algunos detalles a notar:
• Es fundamental cambiar los valores de dos parámetros de configuración de ChibiOS
editando el fichero ChRt/src/rt/templates/chconf.h para fijar TRUE los parámetros:
CH_DBG_THREADS_PROFILING y CH_CFG_NO_IDLE_THREAD. De otra forma, el
código proporcionado no compilará.
• La primera opción activa las opciones que permiten medir la carga computacional de cada
hebra mediante la función chThdGetTicksX(), que devuelve el número de ticks, como
medida interna de tiempo, consumidos durante la ejecución de cada hebra.
• La segunda opción, CH_CFG_NO_IDLE_THREAD, impide que se lance la hebra “Idle”,
una hebra de la mínima pripridad que, en general, no realiza ninguna tarea. Por esta hebra,
por ser la de mínima prioridad, sólo se ejecuta si ninguna otra hebra está demandando
tiempo de CPU. Es consecuencia, el número de ticks que acumule será una medida directa
del tiempo que la CPU está ociosa o sin carga. En nuestro caso, al impedir que se lance esta
hebra, pasamos a usar la función loop() como hebra idle.
• Usar reales de precisión simple o doble tiene un impacto importante en este micro, como
puede verificarse ajustando la macro USE_DOUBLE. Este es detalle a tener en cuenta si el
microcontrolador dispone de una FPU, Floating Processing Unit, que soporte tipos de doble
precisión real. La librería matemática incorpora variantes de tipo float, de simple precisión,
para todas las funciones matemáticas incluidas en la librería. Son fácilmente identificables
por el sufijo f en el nombre de la función.
• Nótese el uso de un semáforo binario como temporizador, chBSemWaitTimeout(), en la
hebra top. Perfectamente podría haber empleado una llamada a chThdSleepMilliseconds() o
similar. Sin embargo, usar chBSemWaitTimeout() permite abortar desde otra hebra la espera
en el semáforo y así “despertar” esta hebra.
/* ======================================================================= *\
* Ejemplo ChibiOS-3
* Este ejemplo muestra cómo estimar la carga computacional de cada hebra
*
* IMPORTANTE: en ChRt/src/rt/templates/chconf.h
* - CH_DBG_THREADS_PROFILING debe activarse (TRUE)
* - CH_CFG_NO_IDLE_THREAD debe activarse (TRUE)
*
* Requiere el uso de la librería ChRt de Bill Greiman
* https://github.com/greiman/ChRt
* Asignatura (GII-IoT)
\* ======================================================================= */
16
#include <ChRt.h>
#include <math.h>
//------------------------------------------------------------------------------
// Parametrization
//------------------------------------------------------------------------------
#define USE_DOUBLE FALSE // Change to TRUE to use double precision (heavier)
// Struct to measure the cpu load using the ticks consumed by each thread
typedef struct {
thread_t * thd;
systime_t lastSampleTime_i;
sysinterval_t lastPeriod_i;
sysinterval_t ticksTotal;
sysinterval_t ticksPerCycle;
float loadPerCycle_per;
} threadLoad_t;
typedef struct {
threadLoad_t threadLoad[NUM_THREADS];
uint32_t idling_per;
} systemLoad_t;
systemLoad_t sysLoad;
17
//------------------------------------------------------------------------------
// Load estimator (top)
// High priority thread that executes periodically
//------------------------------------------------------------------------------
BSEMAPHORE_DECL(top_sem, true);
static THD_WORKING_AREA(waTop, 256);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, ledState);
systime_t lastTime_i = 0;
systime_t period_i = TIME_MS2I(CYCLE_MS);
while (!chThdShouldTerminateX()) {
// This assumes that no other thread will accumulate ticks during this sampling
// so we can use this timestamp for all threads
lastTime_i = chVTGetSystemTimeX();
18
// tid starts at 1 because we do not include this thread (top)
for (int tid = 1; tid < NUM_THREADS; tid++) {
threadLoad_t * thdLoad = &(sysLoad.threadLoad[tid]);
thdLoad->lastSampleTime_i = lastTime_i;
systime_t ticks = chThdGetTicksX(thdLoad->thd);
SerialUSB.print(thread_name[tid]);
SerialUSB.print(" ticks(last cycle): ");
SerialUSB.print(thdLoad->ticksPerCycle);
SerialUSB.print(" CPU(%): ");
SerialUSB.print(thdLoad->loadPerCycle_per);
SerialUSB.print(" Cycle duration(ms): ");
SerialUSB.print(threadCycle_ms[tid]);
SerialUSB.print(" period(ms): ");
SerialUSB.println(threadEffectivePeriod_ms[tid]);
}
SerialUSB.println();
//------------------------------------------------------------------------------
// Worker thread executes periodically
//------------------------------------------------------------------------------
static THD_WORKING_AREA(waWorker1, 256);
static THD_WORKING_AREA(waWorker2, 256);
static THD_WORKING_AREA(waWorker3, 256);
19
static THD_FUNCTION(worker, arg)
{
int worker_ID = (int)arg;
sysinterval_t period_i = TIME_MS2I(threadPeriod_ms[worker_ID]);
systime_t deadline_i = chVTGetSystemTimeX();
systime_t lastBeginTime_i = 0;
while (!chThdShouldTerminateX()) {
systime_t beginTime_i = chVTGetSystemTimeX();
threadEffectivePeriod_ms[worker_ID] = TIME_I2MS(beginTime_i - lastBeginTime_i);
deadline_i += period_i;
lastBeginTime_i = beginTime_i;
threadCycle_ms[worker_ID] = TIME_I2MS(chVTGetSystemTimeX() - beginTime_i);
if (deadline_i > chVTGetSystemTimeX()) {
chThdSleepUntil(deadline_i);
}
}
}
//------------------------------------------------------------------------------
// Continue setup() after chBegin() and create the two threads
//------------------------------------------------------------------------------
void chSetup()
{
// Here we assume that CH_CFG_ST_TIMEDELTA is set to zero
20
// All SAMD-based boards are only supported in “tick mode”
// Check first if ChibiOS configuration is compatible
// with a non-cooperative scheme checking the value of CH_CFG_TIME_QUANTUM
if (CH_CFG_TIME_QUANTUM == 0) {
SerialUSB.println("You must set CH_CFG_TIME_QUANTUM to a non-zero value in");
#if defined(__arm__)
SerialUSB.print("src/<board type>/chconfig<board>.h");
#elif defined(__AVR__)
SerialUSB.print("src/avr/chconfig_avr.h");
#endif
SerialUSB.println(" to enable round-robin scheduling.");
while (true) {}
}
SerialUSB.print("CH_CFG_TIME_QUANTUM: ");
SerialUSB.println(CH_CFG_TIME_QUANTUM);
// This thread ID
sysLoad.threadLoad[4].thd = chThdGetSelfX();
}
21
//------------------------------------------------------------------------------
// setup() function
//------------------------------------------------------------------------------
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
SerialUSB.begin(115200);
while(!SerialUSB) { ; }
//------------------------------------------------------------------------------
// loop() function. It is considered here as the idle thread
//------------------------------------------------------------------------------
void loop() { }
Ejercicio propuesto
Usando el código presentado en el último ejemplo como punto de partida, se pide desarrollar un
esquema de monitorización que permita balancear la carga computacional en el microprocesador y
llevarla a una condición “óptima”, definida como aquella en la que la utilización del procesador no
supera el 85% (15% del tiempo en la tarea idle/loop) y las tareas se ejecutan respetando el periodo
establecido, pero con el máximo posible de intensidad computacional.
Veamos algunos detalles e ideas:
• A diferencia del ejemplo anterior, en el que usábamos una intensidad fija para cada hebra o
tarea, definida mediante el vector threadLoad[NUM_THREADS], ahora cada tarea
dispondrá de un rango ajustable de intensidad o carga computacional y será necesario
declarar un valor mínimo y otro máximo.
• Se recomienda comenzar verificando que se respetan las frecuencias de operación de cada
hebra de trabajo con las hebras configuradas al valor mínimo de intensidad o carga (mínimo
consumo de tiempo de procesador).
• Una vez “estabilizado” el periodo/frecuencia de repetición de las hebras, la hebra top puede
ajustar de forma cíclica la intensidad de carga de cada hebra y evaluar la nueva
configuración verificando que:
22
a) Se respetan las frecuencias de repetición de las hebras
b) No se supera el umbral de carga del sistema
En caso contrario, deberá reducirse la intensidad computacional de las hebras y revaluar la
situación.
Extra:
• Contemplar la posibilidad de que el tiempo de procesador de cada hebra pueda incluir un
cierto porcentaje de carga variable (aleatoria). En este supuesto, el margen que se ha
reservado de tiempo de CPU debería servir para absorber estos “picos” momentáneos de
carga. Es evidente que este supuesto exige cierta tolerancia o histéresis para distinguir
sobrecargas puntuales de otras situaciones de sobrecarga permanente, entendiendo como
sobrecarga aquella situación en la que el uso de procesador sobrepasa el umbral establecido
(p.e. del 85%).
23