Los punteros de función provocan un comportamiento extremadamente extraño del ATMega328PB sin sistema operativo

Resuelto Jacksee asked hace 10 meses • 0 respuestas

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.

Jacksee avatar Feb 15 '24 22:02 Jacksee
Aceptado

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_bcdcontenga el valor 0xFF. La data_decvariable entonces tiene el valor 165. Y esto da como resultado que el valor 16se 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.

Peter Plesník avatar Feb 15 '2024 20:02 Peter Plesník