Los punteros de función provocan un comportamiento extremadamente extraño del ATMega328PB sin sistema operativo
Estoy haciendo un proyecto de reloj digital en ATMega328PB sin sistema operativo. Una de las tareas de uC es controlar una pantalla de 7 segmentos utilizando pines de los bancos B y D como salidas digitales. Estoy subiendo código usando Arduino IDE con MiniCore y Arduino UNO como programador ISP. Mantengo todos los fusibles en los valores predeterminados de fábrica (oscilador interno, etc.) y no uso ningún gestor de arranque. La carga de código funciona bien, pero en mi código hay funciones para colocar los pines correctos en alto para mostrar el dígito correcto. Necesito asignar la ejecución de cada función al entero correspondiente, así que utilicé una matriz de punteros de función.
En el momento en que cargué el código donde se usaba la función "display_write", mi uC dejó de accionar los pines de la pantalla correctamente y actuó como si se estuviera reiniciando constantemente incluso cuando hay una resistencia pullup de 10k en el pin de reinicio. Estoy bastante seguro de que es un problema de software. Mantengo mi proyecto soldado en una PCB casera y he comprobado varias veces que la energía comienza a entregarse (a través del osciloscopio) y ahora hay eventos inesperados en algunos pines. ATMega en este estado crea impulsos cortos en los ánodos de algunos segmentos (conectados a pines PD), pero todos los cátodos (esta es una pantalla de cátodo común de 5 dígitos) no están bajos (a través de transistores NPN).
En este estado no hay comunicación con la UC. Sin embargo, cuando desconecto algunos pines de conducción del ánodo de la pantalla, uC comienza a funcionar normalmente hasta que los conecto nuevamente y siempre quedan los mismos 2 pines que, después de desconectarlos, lo devuelven temporalmente al estado normal. He medido todo en términos de hardware (los segmentos consumen menos de 1 mA de pines) y no hay absolutamente nada especial en esta configuración o lo que sea. Esto SÓLO sucede cuando ejecuto una función que usa punteros de función en mi programa, cuando no uso punteros de función todo funciona bien.
Sinceramente, no tengo idea de a qué se debe este comportamiento. Modifiqué el código para no usar la interrupción del temporizador, pero tampoco cambió nada hasta que utilicé la función "display_write".
Este es el código que aparece después de la carga.
/*
Wiring:
PD0 - a
PD1 - b
PD2 - c
PD3 - d
PD4 - e
PD5 - f
PD6 - g
PD7 - dp
PB0 - CA1 - position 0
PB1 - CA2 - position 1
PB2 - CA3 - position 2
PB6 - CA4 - position 3
PB7 - CA5 - position 4
*/
#include <Wire.h> //Library needed for I2C communication with RTC module
#include <Arduino.h>
#define digits_count 5
//DS1337 RTC register addresses
#define rtc_address 0x68
#define second 0x00
#define minute 0x01
#define hour 0x02
#define day 0x03
#define date 0x04
#define month 0x05
#define year 0x06
#define alarm1_minute 0x08
#define alarm1_hour 0x09
#define alarm1_state 0x0A
#define alarm2_minute 0x0B
#define alarm2_hour 0x0C
#define alarm2_state 0x0D
//Curent position for display
volatile int current_position = 0;
//Current digits array with sign number and dot point status
volatile int current_display[digits_count][2];
//Position lookup table for PORT manipulation
volatile int position_LUT[digits_count] = {0b00000001, 0b00000010, 0b00000100, 0b01000000, 0b10000000};
//Pointers assigning intigers to corresponding functions that display digits/signs they represent
volatile void (*fun_ptr[11])(int, bool);
//Interrupt service routine for Timer 1 to regularly update display
ISR(TIMER1_COMPA_vect) {
display_write(current_position);
}
void setup() {
//Set Timer 1 to interrupt and refresh screen periodacly
TCCR1A = 0b00000000;
TCCR1B = 0b00001001; //No prescaler, CTC for OCR1A
TCNT1 = 0; //Initial value
TIMSK1 |= 0b00000010; //Set interrupt on compare A
OCR1A = 5000; //Display new digit every 5ms
//Set D and B pins as outputs for driving the display
DDRD = 0b11111111;
DDRB = 0b11111111;
//Default output state
PORTD = 0b00000000;
PORTB = 0b00000000;
//Assigning function pointers to functions
fun_ptr[0] = d0;
fun_ptr[1] = d1;
fun_ptr[2] = d2;
fun_ptr[3] = d3;
fun_ptr[4] = d4;
fun_ptr[5] = d5;
fun_ptr[6] = d6;
fun_ptr[7] = d7;
fun_ptr[8] = d8;
fun_ptr[9] = d9;
fun_ptr[10] = null;
//Initial diplay values
current_display[0][0] = 4;
current_display[1][0] = 7;
current_display[2][0] = 2;
current_display[3][0] = 9;
current_display[4][0] = 8;
current_display[0][1] = 1;
current_display[1][1] = 1;
current_display[2][1] = 0;
current_display[3][1] = 0;
current_display[4][1] = 1;
//Initialize I2C communication with RTC
Wire.begin();
}
void loop() {
current_display[0][1] = 0;
current_display[1][1] = 1;
current_display[2][1] = 0;
current_display[3][1] = 1;
current_display[4][1] = 0;
rtc_read(hour, 0, 1, 1);
rtc_read(minute, 2, 3, 1);
rtc_read(second, 4, 4, 0);;
delay(1);
}
//Read data from RTC and insert digits to positions in display array
void rtc_read(int address, int position1, int position2, bool position2_valid) {
int data_bcd;
int data_dec;
Wire.beginTransmission(rtc_address);
Wire.write(address);
Wire.endTransmission();
Wire.requestFrom(rtc_address,1);
if (Wire.available()) {
data_bcd = Wire.read();
}
data_dec = (data_bcd / 16 * 10) + (data_bcd % 16); //Convert RTS's BCD format to decimal
current_display[position1][0] = data_dec / 10; //Write most siginificant digit to position 1
if (position2_valid != 0) {
current_display[position2][0] = data_dec % 10; //Write least siginificant digit to position 2
}
}
//Display array function
void display_write(int position) {
(*fun_ptr[current_display[position][0]])(position, current_display[position][1]);
current_position++;
if (current_position == digits_count) {
current_position = 0;
}
}
//Digit/sign display functions
void d0(int position, bool dp_status) {
PORTD = 0b00111111; // Set segment configuration
PORTB = position_LUT[position]; //Disable all digits and add bit corresponding to position argument from LUT
PORTD |= (dp_status << PD7); //Add dot point bit
}
void d1(int position, bool dp_status) {
PORTD = 0b00000110;
PORTB = position_LUT[position];
PORTD |= (dp_status << PD7);
}
void d2(int position, bool dp_status) {
PORTD = 0b01011011;
PORTB = position_LUT[position];
PORTD |= (dp_status << PD7);
}
void d3(int position, bool dp_status) {
PORTD = 0b01001111;
PORTB = position_LUT[position];
PORTD |= (dp_status << PD7);
}
void d4(int position, bool dp_status) {
PORTD = 0b01100110;
PORTB = position_LUT[position];
PORTD |= (dp_status << PD7);
}
void d5(int position, bool dp_status) {
PORTD = 0b01101101;
PORTB = position_LUT[position];
PORTD |= (dp_status << PD7);
}
void d6(int position, bool dp_status) {
PORTD = 0b01111101;
PORTB = position_LUT[position];
PORTD |= (dp_status << PD7);
}
void d7(int position, bool dp_status) {
PORTD = 0b00000111;
PORTB = position_LUT[position];
PORTD |= (dp_status << PD7);
}
void d8(int position, bool dp_status) {
PORTD = 0b01111111;
PORTB = position_LUT[position];
PORTD |= (dp_status << PD7);
}
void d9(int position, bool dp_status) {
PORTD = 0b01101111;
PORTB = position_LUT[position];
PORTD |= (dp_status << PD7);
}
void null(int position, bool dp_status) {
PORTD = 0b00000000;
PORTB = position_LUT[position];
PORTD |= (dp_status << PD7);
}
Probé todas las combinaciones posibles del código y el comportamiento extraño (reinicio constante y sin comunicación) siempre regresa cuando se ejecuta la función "display_write". Revisé todas las conexiones y problemas de hardware y medí todas las señales de uC y alimentación (USB 5V).
Esperaba que el código verificara periódicamente el número entero para cada posición en la matriz "current_display" y ejecutara una función d0/d1...null correspondiente a este número entero a través de la matriz de puntero de función.
Hay que tener mucho cuidado al trabajar con punteros. Al llamar funciones mediante punteros, debe estar 100% seguro de que se llamarán los punteros correctos. En su caso esto no está garantizado.
El siguiente código permite ingresar un valor mayor que 10 en la matriz.
data_dec = (data_bcd / 16 * 10) + (data_bcd % 16); //Convert RTS's BCD format to decimal
current_display[position1][0] = data_dec / 10; //Write most siginificant digit to position 1
Basta con que, por ejemplo, la variable data_bcd
contenga el valor 0xFF
. La data_dec
variable entonces tiene el valor 165
. Y esto da como resultado que el valor 16
se almacene en la matriz. Lo que da como resultado llamar a un puntero desde un índice de matriz que no está definido. Lo que automáticamente hace que el código falle.
Para convertir un número en un valor de una representación de siete segmentos, en ningún caso se necesita una construcción de programa tan complicada.
El resto del programa contiene muchísimas construcciones de programación incorrectas y es posible que haya algo más que también pueda causar el fallo.