Interrupciones, instrucciones e ISRs

Me preguntaba si alguien podría ayudarme con las siguientes dudas:
Como se programan más interrupciones aparte de las externas? Como puedo acceder a los vectores de programa asociados a las distintas interrupciones a las que el atmega168/328 me permite acceder? Donde puedo encontrar una lista de instrucciones que el IDE de arduino interpreta más completa que la facilitada en la página de arduino (Arduino - Home).

Os doy un poco de contexto. Hasta ahora tenía un arduino UNO con el que cacharreaba de vez en cuando pero en general programaba micros de la familia PIC (marca Microchip), en ensamblador a veces pero sobre todo en C.
El caso es que estoy leyéndome la documentación del atmega328 para poder utilizar arduino en mis proyectos, y a pesar de que en esencia es muy parecido (salvando las distancias) a PIC, el entorno de progamación está muy simplificado. Encuentro unos vacíos enormes a la hora de ahondar un poco en conceptos mínimamente complicados. Vamos, que si quieres hacer parpadear un led tienes mil tutoriales, instrucciones, alternativas, ayuda, comunidad... pero si quieres atender a una interrupción por captura :o :o o por comparación para actualizar el valor de tu señal PWM en cada periodo o cualquier cosa mínimamente complicada, hay muchísma menos información y casi toda la que hay consiste en tirar de librerias. Librerías que me parecería magnífico usar, si pudiera entender como están hechas, pero me falta guía.
Encontré varias librerías para programar y atender interrupciones de los distintos timers (interrupción al uso, que salta al desbordar el timerx y punto), y entonces intenté entender como están programadas y encontré un sinfín de instrucciones que no entiendo. Os pongo un cachito de la librería TimerOne:

  void attachInterrupt(void (*isr)()) __attribute__((always_inline)) {
  isrCallback = isr;
  TIMSK1 = _BV(TOIE1);
    }
    void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) {
  if(microseconds > 0) setPeriod(microseconds);
  attachInterrupt(isr);
    }
    void detachInterrupt() __attribute__((always_inline)) {
  TIMSK1 = 0;
    }
    static void (*isrCallback)();

  private:
    // properties
    static unsigned short pwmPeriod;
    static unsigned char clockSelectBits;






#elif defined(__arm__) && defined(CORE_TEENSY)

  public:
    //****************************
    //  Configuration
    //****************************
    void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) {
  setPeriod(microseconds);
    }
    void setPeriod(unsigned long microseconds) __attribute__((always_inline)) {
  const unsigned long cycles = (F_BUS / 2000000) * microseconds;

No está todo pero hay suficiente para ilustraros en mi problema:
Qué significa public y private, por donde van los tiros con " attribute((always_inline))", que significa _BV, e isrCallback... vamos que estoy perdido, y lo que necesito es la documentación para programar en el IDE de arduino, en C, el microcontrolador del arduino que sea.
Cualquier ayuda, enlace a tutorial o a documentación es bienvenida, y muchas gracias de antemano.

Pd: Si el consejo es "pásate al IDE de atmega" os tomo la palabra porque ya me lo estoy planteando, aunque no quería renunciar a arduino, por toda la comunidad que tiene, y el valor que puedes aportar compartiendo

altofalantes:
Pd: Si el consejo es "pásate al IDE de atmega" os tomo la palabra porque ya me lo estoy planteando, aunque no quería renunciar a arduino, por toda la comunidad que tiene, y el valor que puedes aportar compartiendo

No tienes que hacerlo. Aunque la IDE parezca que lo simplifica todo, eso no quiere decir que no se puedan realizar cosas más avanzadas.

Yo tampoco me molestaría en instalar Atmel Studio, todavía prefiero algunas de las facilidades que ofrece Arduino. Yo he experimentado cambiando registros y creando interrupciones con la IDE de Arduino (escribiendo código en ensamblador no porque no tengo experiencia en ese lenguaje, sin embargo también es posible).

Gracias a esa experimentación es que sé cómo crear ISR aparte de la externas, rutinas de transmisión/recepción más eficientes para el puerto serial, formas más rápidas de cambiar o leer el estado de un pin, modificar parámetros de cierto timer según la necesidad de PWM o ejecución periódica de rutinas.

Si fueras más al grano y dijeras realmente qué es lo que quieres hacer, podríamos dar en el clavo en una menor cantidad de respuestas.
Hacer ajustes y optimizaciones a nivel más bajo, no siempre son soluciones generales; usualmente son para realizar tareas más específicas pero de la forma más rápida posible.

Si quieres preguntame a mi lo que quieras respecto al tema, que hasta tengo estudiada la mayor parte de la hoja de datos del Atmega328P (y sus similares).

Si fueras más al grano y dijeras realmente qué es lo que quieres hacer, podríamos dar en el clavo en una menor cantidad de respuestas.

Pues me gustaría poder progamar las ISR de las distintas interrupciones. No la parte en la que modificas unos registros para habilitarlas y configurarlas sino poder escribir en la IDE el "void blablabla" que corresponda al vector que establece el micro para cada interrupción o la manera de indicar que las instrucciones que estás programando deben estar localizadas en en la dirección de programa que necesitas :


(captura sacada de la documentación del ATmega328

y bueno muchas gracias por la respuesta, si tengo dudas más concretas te tomo la palabra y te pregunto

Bueno, si quieres hacerlo como lo hace TimerOne, tienes que declarar una variable global (o static) de la siguiente manera:

void (*isrAUsar)();

Un poco difícil de explicar, pero básicamente es una variable que almacena un puntero de una subrutina (función) que no retorna nada ni necesita parámetros.
Un puntero de estos debe apuntar (valga la redundancia) al primer byte que representa la primera instrucción (máquina) de dicha función. El programa siempre está en memoria flash; por lo tanto, el valor de esa variable se interpretará como una dirección de dicha memoria.
¿Cuál valor exactamente debería tener? De eso no te preocupes, que el compilador de eso se encargará.

Para asignarle una subrutina, sería de la siguiente manera:

void attachSomeInterrupt(void (*funcion)()) {
  isrAUsar = funcion;
  // Otras configuraciones
}

Sin embargo aún así no te libras de la forma en que el compilador realmente define una rutina de interrupción:

ISR(NOMBRE_DEL_VECTOR_vect) {
  isrAUsar(); // Así de simple se usa la función que le asignes
}

PD: cuidado al crear funciones tipo detachInterrupt, usualmente estos lo único que hacen es deshabilitar la interrupción, pero nunca resignan un valor a la variable (como ponerle cero). Redefinir el valor del puntero mientras la interrupción siga habilitada puede llevar a alguna de las siguientes situaciones:

  • Ejecutar la rutina main (implícita en los códigos de Arduino, pero obligatoria en lenguajes C), lo que básicamente llevaría a un "soft-reset" pero sin pasar por el bootloader y sin limpiar la pila de ejecución. Si esto se repitiera indefinidamente, eventualmente el programa se cuelga por desbordamiento de pila ("stack overflow" en inglés); en efecto eso sería como recursividad infinita.
  • Ejecutar de nuevo la misma ISR, como un ciclo infinito pero con el eventual desbordamiento de pila (recursividad infinita).
  • Ejecutar erróneamente y total o parcialmente, funciones ya existentes (sin contar el antes mencionado main). El resultado puede variar desde la modificación inesperada de variables globales, recursividad infinita, o si la función difiere de tipo de retorno y número de parámetros, corrupción de la pila de ejecución. En otras palabras, un desastre total.
  • La más probable: ejecutar "basura", datos grabados con PROGMEM o incluso código de programas antiguos (la memoria flash nunca se sobrescribe en su totalidad). En el peor (o mejor) de los casos ocurre un cuelgue inmediato por intentar ejecutar una "instrucción ilegal"; y en el mejor (o peor) de los casos, la rutina sale normalmente, pero con la memoria RAM lo suficientemente corrompida para que, sin mucha demora, ocurra otro cuelgue.

Por cosas como estas es que los videojuegos de los 80s y 90s tienen de esos fallos de programación que resultan en desastres o ventajas (trampas).

Vale ya casi lo tengo:

void (*isrAUsar)(); //Tiene que ser exactamente *isrAUsar?? o puede ser *isrTMR2
void setup() {
  //Aquí: Configuramos la atención a interrupciones global
void attachSomeInterrupt(void (*funcion)()) {
  isrAUsar = funcion;
  //Aquí: Modificamos los registros para activar y configurar la interrupción y el timer
}
ISR(0x0012_vect) { //podemos poner el nombre del vector o hay que poner el valor?
  isrAUsar(); //Definimos el vector de la interrupción
}
}
void loop() {
//Código del loop principal
}
void funcion() {
//Código de la interrupción 
}

Debería tener esta pinta ? Este código no me compila, podrías ponerme un ejemplo completo del timer0 por ejemplo? que haga algo sencillo, cada 50ms aumentar en uno una variable por ejemplo

Hay un '}' de más, y si es definir una interrupción de timer0, se declara con el nombre del vector:

ISR(TIMER0_COMPA_vect) // Cuando el contador coincide con OCR0A
ISR(TIMER0_COMPB_vect) // Cuando el contador coincide con OCR0B
ISR(TIMER0_OVF_vect) // Cuando el contador se desborde. No recuerdo sí podía ocurrir aún en CTC

Declarar esta última quizá sí te dé error de compilación; ya está definida (en wiring.c) para uso de millis().

Aquellos valores hexadecimales son solo direcciones fijas donde el microprocesador puede consultar la ubicación de cierta ISR que debe ejecutar (dato útil para redefinir estas rutas en lenguaje ensamblador).

Lo olvidaba:

void (*isrAUsar)(); //Tiene que ser exactamente *isrAUsar?? o puede ser *isrTMR2

La parte "isrAUsar" viene siendo el nombre de la variable; así que puede ser el que quieras siempre y cuando no coincida con los utilizados para las interrupciones externas.

Muchísimas gracias por la ayuda Lucario!!