Problema con Timer1 y ADC | Atmega328p

Hola a todos, estoy realizando un sistema de medición de un canal análogo del Atmega328p, estoy usando un sensor LM35, tambien estoy usando un LCD 16x2 para mostrar la lectura del sensor. La idea es que se vaya sensando dicho canal, muestre los datos y a la vez un LED haga blink cada 1 segundo como indicador que el sistema esta funcionando y para esto estoy usando el Timer1 y su interrupcion por desbordamiento, adicionalmente usaré la UART para la conexion con un modulo GSM para que realice notificaciones cada vez que tenga una temperatura top. Pero tengo el problema que cada vez que sucede la interrupcion el dato que se muestra en la LCD del ADC se duplica, es decir, antes que ocurra la interrupcion el LCD muestra por ejemplo : 26. 30 ºC -- Ocurre la interrupcion(prende el led) y el valor pasa a mostrarse 52.60 ºC. En simulacion se ve funciona bien pero lo manté y me di cuenta de este detalle. Quizás alguien pueda darme alguna sugerencia, todo critica es bien recibida para mejorar.

Gracias de antemano;

*El código lo hice en Atmel Studio 7
*Estoy usando oscilador interno de 1 MHz
*Adjunto código y las librerias de ADC y LCD.

Código:
#define F_CPU 1000000UL
#include <avr/io.h>
#include <stdio.h>
#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "lcd.h"
#include "ADC.h"


void Config_TMR1(void);
void Config_Interrupt_TMR1(void);

float Temp = 0.0;
char Str_Temp[6];


int main(void)
{
   Lcd_Init();
   Lcd_Clear();

   Config_TMR1();
   Config_Interrupt_TMR1();


   DDRB  |=  (1<<DDB2);  //B2 COMO SALIDA
   PORTB &= ~(1<<PORTB2); //B2 INICIA EN OFF

    while (1)
    {
          //Inicia lectura de ADC0, conversion y envio por LCD
        Temp = (analogRead(ADC0)*(VREF/RESOLUCION))/0.01;
      
        sprintf(Str_Temp, "%.2f", Temp);
      
        Lcd_Set_Cursor(1,1);
        Lcd_Write_String("Sist. de alarma");
        Lcd_Set_Cursor(2,1);
        Lcd_Write_String("Temp: ");
        Lcd_Write_String(Str_Temp);
      
  

    }
}

ISR(TIMER1_OVF_vect){
    TCNT1 = 49911; //Cargar nuevamente al TMR1 para generar 1 segundo
  
    //Aplicacion
    PORTB ^= (1<<PORTB2);
  
  
}



void Config_TMR1(void){
    TCCR1B |= (0<<CS12)|(1<<CS11)|(1<<CS10); //Elijo un preescaler de x64
    TCNT1 = 49911; //Carga para generar 1 segundo
}



void Config_Interrupt_TMR1(void){
    TIMSK1 |= (1<<TOIE1); //Habilito la interrup. por desbordamiento de TMR1
    sei();
}



Código:
/*********************************************************************
*Libreria para LCD 16x2

*********************************************************************/

#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>

//****************************************************************************
//    CONFIGURACIÓN DE LOS PINES DE INTERFACE
//****************************************************************************

/* Define el puerto a donde se conectará el BUS de datos del LCD
* Se utilizará el nible alto del puerto escogido (ejem. PB4-DB4,...,PB7-DB7) */
#define  LCD_DATA_OUT   PORTD      // Registro PORT del puerto
#define  LCD_DATA_DDR   DDRD       // Registro DDR del puerto

/* Define el puerto a donde se conectarán las líneas de control del LCD
* EN y RS (Puede ser el mismo puerto del BUS de datos) */
#define  LCD_CTRL_OUT   PORTB      // Registro PORT del puerto
#define  LCD_CTRL_DDR   DDRB       // Registro DDR del puerto

//Define los numeros de bits a donde se conectará el modulo LCD
#define RS    0     // Pin Register Select
#define EN    1    // Pin Enable
#define D4    4    //define el pin del MCU conectado LCD D4
#define D5    5    //define el pin del MCU conectado LCD D5
#define D6    6    //define el pin del MCU conectado LCD D6
#define D7    7    //define el pin del MCU conectado LCD D7

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
void Lcd_Port(unsigned char a)
{
    if(a & 1)
        //D4 = 1;
        LCD_DATA_OUT |= (1<<D4);
    else
        //D4 = 0;
        LCD_DATA_OUT &= ~(1<<D4);
    if(a & 2)
        //D5 = 1;
        LCD_DATA_OUT |= (1<<D5);
    else
        //D5 = 0;
        LCD_DATA_OUT &= ~(1<<D5);
    if(a & 4)
        //D6 = 1;
        LCD_DATA_OUT |= (1<<D6);
    else
        //D6 = 0;
        LCD_DATA_OUT &= ~(1<<D6);
    if(a & 8)
        //D7 = 1;
        LCD_DATA_OUT |= (1<<D7);
    else
        //D7 = 0;
        LCD_DATA_OUT &= ~(1<<D7);
}
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
void Lcd_Cmd(unsigned char a)
{
    LCD_CTRL_OUT &= ~(1<<RS);// => RS = 0         
    Lcd_Port(a);
    LCD_CTRL_OUT |= (1<<EN);// => EN = 1         
    _delay_ms(4);
    LCD_CTRL_OUT &= ~(1<<EN);// => EN = 0       
}

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
void Lcd_Init( void ) // Esta funcion permite inicializar el modulo LCD
{
    /* Configurar las direcciones de los pines de interface del LCD */
    LCD_DATA_DDR |= 0xF0; //Configuro los pines del BUS de datos del LCD como salida
    LCD_CTRL_DDR |= (1<<EN)|(1<<RS);//Configuro los pines de control del LCD como salida
  
    Lcd_Port(0x00);
    _delay_ms(20);
    Lcd_Cmd(0x03);
    _delay_ms(5);
    Lcd_Cmd(0x03);
    _delay_ms(11);
    Lcd_Cmd(0x03);
    /////////////////////////////////////////////////////
    Lcd_Cmd(0x02);
    Lcd_Cmd(0x02);
    Lcd_Cmd(0x08);
    Lcd_Cmd(0x00);
    Lcd_Cmd(0x0C);
    Lcd_Cmd(0x00);
    Lcd_Cmd(0x06);
}
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
void Lcd_Clear( void ) //Funcion que permite limpiar la pantalla LCD
{
    Lcd_Cmd(0);
    Lcd_Cmd(1);
}
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
//Esta funcion posiciona el cursor del modulo LCD
void Lcd_Set_Cursor(unsigned char a, unsigned char b)
{
    char temp,z,y;
    if(a == 1)
    {
      temp = 0x80 + b - 1;
        z = temp>>4;
        y = temp & 0x0F;
        Lcd_Cmd(z);
        Lcd_Cmd(y);
    }
    else if(a == 2)
    {
        temp = 0xC0 + b - 1;
        z = temp>>4;
        y = temp & 0x0F;
        Lcd_Cmd(z);
        Lcd_Cmd(y);
    }
}
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
//Esta funcion imprime un caracter en el modulo LCD
void Lcd_Write_Char(char a)
{
   char temp,y;
   temp = a&0x0F;
   y = a&0xF0;
   LCD_CTRL_OUT |= (1<<RS);// => RS = 1             
   Lcd_Port(y>>4);          //Transferencia de datos (formto 4 bits)
   LCD_CTRL_OUT |= (1<<EN);// => EN = 1
   _delay_us(40);
   LCD_CTRL_OUT &= ~(1<<EN);// => EN = 0
   Lcd_Port(temp);          //Transferencia de datos (formto 4 bits)
   LCD_CTRL_OUT |= (1<<EN);// => EN = 1
   _delay_us(40);
   LCD_CTRL_OUT &= ~(1<<EN);// => EN = 0
}
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
//Esta funcion imprime un String (cadena) en el modulo LCD
void Lcd_Write_String(char *a)
{
    int i;
    for(i=0;a[i]!='\0';i++)
       Lcd_Write_Char(a[i]);
}
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
void Lcd_Shift_Right( void )
{
    Lcd_Cmd(0x01);
    Lcd_Cmd(0x0C);
}
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
void Lcd_Shift_Left( void )
{
    Lcd_Cmd(0x01);
    Lcd_Cmd(0x08);
}


Código:
//**Libreria para ADC Atmega328p **
//** Marcelo Higa**

#define  F_CPU 1000000UL
#include <avr/io.h>


#define ADC0 0
#define ADC1 1
#define ADC2 2
#define ADC3 3
#define ADC4 4
#define ADC5 5
#define ADC6 6
#define ADC7 7

#define VREF 5.0
#define RESOLUCION 1024


void Config_ADC(void);  //Funcion vacia
void Leer_Canal(uint8_t canal); //Funcion con entrada de parametro
uint16_t analogRead(uint16_t canal); //Funcion con entrada de parametro y retorno de valor



void Config_ADC(void){
  
    ADMUX = 0X40; // VOLTAJE DE REFERENCIA DEL uC, JUSTIFICACION DEL RESULTADO A LA DERECHA
    ADCSRA |= (0<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); //ELEGIMOS EL CLOCK DEL RELOJ A 125 KH
    DIDR0 = 0XFF;  //DESHABILITO EL CANAL DIGITAL DE TODOS LOS CANALES
}



void Leer_Canal(uint8_t canal){
    ADMUX = 0X40; //ACTUALIZO EL ADMUX
    ADMUX |= canal; //SELECCIONO EL CANAL
  
}



uint16_t analogRead(uint16_t canal){
    ADMUX = 0X40; //ACTUALIZO EL ADMUX
    ADMUX |= canal; //SELECCIONO EL CANAL
  
    ADCSRA |= (1<<ADEN) | (1<<ADSC); //HABILITO EL MODULO ADC E INICIO LA CONVERSION
    while((ADCSRA & (1<<ADSC)) != 0); //ESPERO A QUE LA CONVERSION TERMINE
    ADCSRA &= ~(1<<ADEN); //DESHABILITO EL MODULO ADC
  
    return(ADC); //Retorna el valor al registro ADC
}
 
En particular no estoy viendo nada raro.

Ahora, me parece que sería conveniente en vez de estar mostrando en pantalla todo el tiempo, hacerlo una vez por segundo y las mediciones (en este caso de temperatura, variación lenta), podrían hacerse 8 o 16 veces por segundo y ya de por si serían muchas mediciones para hacer un promedio (si quisieras, sino solamente tomá una medición por segundo).

Lo que haría yo:

1- Si quisiera tomar 16 mediciones, configuraría el timer 1/16 seg.
2- Por cada interrupción, tomo una medición con el adc. Además usaría una variable global como contador en la interrupción del timer, de 0 a 15.
3- Cuando desborda el contador, reseteo, cambio el estado del led y mediante un flag, muestro en pantalla la medición haciendo un promedio móvil o de las últimas 16 muestras (a elección tuya) desde main.
 
Hola Doc !, Hice una modificación como indicas, ahora ya no se muestra permanentemente las lecturas en el LCD, las lecturas del ADC ahora se muestran cada 1 segundo, lo lleve al vector de interrupcion cada vez que se desborda el TMR1. Como comenté en simulación anda de maravilla pero armé el circuito y cada vez que el led hace un blink o parpadeo, la lectura del ADC que se muestra en el LCD cambia de valor.

Adjuntaré el cambio del código que realicé y un par de fotos cuando sucede la interrupción. Por qué sucederá eso ? :unsure:

Código:
Solo inserté lo del while en el vector de Interrupción

ISR(TIMER1_OVF_vect){
    TCNT1 = 49911; //Cargar nuevamente al TMR1 para generar 1 segundo

    //Aplicacion
   
        PORTB ^= (1<<PORTB2);
        Temp = (analogRead(ADC0)*(VREF/RESOLUCION))/0.01;
   
        sprintf(Str_Temp, "%.2f", Temp);
   
        Lcd_Set_Cursor(1,1);
        Lcd_Write_String("Sist. de alarma");
        Lcd_Set_Cursor(2,1);
        Lcd_Write_String("Temp: ");
        Lcd_Write_String(Str_Temp);
       
       
   
}



Las lecturas del ADC en el LCD cambian cada vez que sucede la interrupción.
Y solo pasa esto cuando esta presente la línea de blink del led : PORTB ^= (1<<PORTB2);
Si lo desaparezco del código, la lectura se ven de maravilla en el LCD . :unsure:


LCD_Lectura_Datos.jpegLCD_Lectura-ADC.jpeg
 
A ver, probá esto:

C:
#define F_CPU 1000000UL
#include <avr/io.h>
#include <stdio.h>
#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "lcd.h"
#include "ADC.h"


void Config_TMR1(void);
void Config_Interrupt_TMR1(void);

unsigned char flag_muestra=0;

int main(void)
{
   float Temp = 0.0;
   char Str_Temp[6];
  
   Lcd_Init();
   Lcd_Clear();

   Config_TMR1();
   Config_Interrupt_TMR1();

   DDRB  |=  (1<<DDB2);  //B2 COMO SALIDA
   PORTB &= ~(1<<PORTB2); //B2 INICIA EN OFF

   while (1)
   {
        if(flag_muestra)
        {
            flag_muestra=0;
            
            //Inicia lectura de ADC0, conversion y envio por LCD
            Temp = (analogRead(ADC0)*(VREF/RESOLUCION))/0.01;

            sprintf(Str_Temp, "%.2f", Temp);

            Lcd_Set_Cursor(1,1);
            Lcd_Write_String("Sist. de alarma");
            Lcd_Set_Cursor(2,1);
            Lcd_Write_String("Temp: ");
            Lcd_Write_String(Str_Temp);
        }       
    }
}

ISR(TIMER1_OVF_vect){
    TCNT1 = 49911; //Cargar nuevamente al TMR1 para generar 1 segundo       
    
    //Aplicacion
    PORTB ^= (1<<PORTB2); 
    flag_muestra=1;
}

Por cierto, me acordé de otra cosa, cuando convertís con el ADC, tenés que descartar la primera medición (o por lo menos cuando cambiabas de canal).

Probá esto también:

C:
#define F_CPU 1000000UL
#include <avr/io.h>
#include <stdio.h>
#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "lcd.h"
#include "ADC.h"


void Config_TMR1(void);
void Config_Interrupt_TMR1(void);

unsigned char flag_muestra=0;

int main(void)
{
   float Temp = 0.0;
   char Str_Temp[6];
  
   Lcd_Init();
   Lcd_Clear();

   Config_TMR1();
   Config_Interrupt_TMR1();

   DDRB  |=  (1<<DDB2);  //B2 COMO SALIDA
   PORTB &= ~(1<<PORTB2); //B2 INICIA EN OFF

   while (1)
   {
        if(flag_muestra)
        {
            flag_muestra=0;            
            analogRead(ADC0); //Conversión basura --> se descarta.

            //Inicia lectura de ADC0, conversion y envio por LCD
            Temp = (analogRead(ADC0)*(VREF/RESOLUCION))/0.01;

            sprintf(Str_Temp, "%.2f", Temp);
            ....
        }       
    }
}
 
esa modificación que hiciste de imprimir desde dentro de la interruoción NO ES SANO....
en la interrupción se deja lo minimo de código, y nada de cosas lentas como es mandar a imprimir texto a un lcd....
 
Cosme, ya lo probé (en físico y en simulación) y no anda 😔 , se ve bien tu algoritmo y debería funcionar tal cual pero no sucede, lo único que pasa que el led solo hace blink. La idea es minimizar los delay y hasta ahora anda bien pero no sé si esta instrucción :
PORTB ^= (1<<PORTB2); hace que la CPU solo se "concentre" o de mayor prioridad en prender o apagar. :unsure:
esa modificación que hiciste de imprimir desde dentro de la interruoción NO ES SANO....
en la interrupción se deja lo minimo de código, y nada de cosas lentas como es mandar a imprimir texto a un lcd....
Hola Ale, si ya lo modifiqué.

Por cierto, me acordé de otra cosa, cuando convertís con el ADC, tenés que descartar la primera medición (o por lo menos cuando cambiabas de canal).
Cosme, me olvidé preguntar a que te refieres con esto, por qué haces referencia que la primera lectura es basura ? :unsure:
 
Cuando se cambia de canal, la primera lectura es basura. Si bien, vos estás trabajando sobre el mismo canal, haces un switcheo en cada conversión y creo que eso también ocasiona que la primera lectura sea errónea.

Descartemos cosas:

1- ¿El timer te funciona más o menos bien c/1seg? ¿El led parpadea? (el timer, parece bien configurado)
2- ¿Los fuses está correctamente configurados para tener 1MHz interno?
3- Hacé esa conversión falsa que te mencioné y descarta el resultado.
4- Si con 3 sigue el problema, probá que sucede si al sprintf le asignás un valor fijo:

C:
...
Temp=25.04;
sprintf(Str_Temp, "%.2f", Temp);
...
 
Estaba pensando en minimizar el temporizador a 100 mS y añadir una variable "contador" y que cuando llegue a 10 haga una lectura y muestre los datos en el LCD cada segundo dentro del while para evitar que lo haga siempre y aprovechar el timer.
1- ¿El timer te funciona más o menos bien c/1seg? ¿El led parpadea? (el timer, parece bien configurado)
Claro Cosme, cada 1 segundo se desborda y puedo modificar el tiempo haciendo el cálculo correspondiente de Timer de acuerdo al tiempo que se necesita.



2- ¿Los fuses está correctamente configurados para tener 1MHz interno

Si, lo configuré para 1 MHz interno, esta en los bits de LOW FUSE = 0x62
4- Si con 3 sigue el problema, probá que sucede si al sprintf le asignás un valor fijo:
Haré unas pruebas y a ver que onda...
 
Una última cosa.

En esta función:

C:
uint16_t analogRead(uint16_t canal){
    ADMUX = 0X40; //ACTUALIZO EL ADMUX
    ADMUX |= canal; //SELECCIONO EL CANAL
 
    ADCSRA |= (1<<ADEN) | (1<<ADSC); //HABILITO EL MODULO ADC E INICIO LA CONVERSION
    while((ADCSRA & (1<<ADSC)) != 0); //ESPERO A QUE LA CONVERSION TERMINE
    ADCSRA &= ~(1<<ADEN); //DESHABILITO EL MODULO ADC
 
    return(ADC); //Retorna el valor al registro ADC
}

Probá que pasa si no deshabilitá y habilitás el ADC todo el tiempo:

C:
uint16_t analogRead(uint16_t canal){
    ADMUX = 0X40; //ACTUALIZO EL ADMUX
    ADMUX |= canal; //SELECCIONO EL CANAL
 
    ADCSRA |= /*(1<<ADEN) |*/ (1<<ADSC); //HABILITO EL MODULO ADC E INICIO LA CONVERSION
    while((ADCSRA & (1<<ADSC)) != 0); //ESPERO A QUE LA CONVERSION TERMINE
    //ADCSRA &= ~(1<<ADEN); //DESHABILITO EL MODULO ADC
 
    return(ADC); //Retorna el valor al registro ADC
}

Finalmente también vas a tener que modificar esta función:

C:
void Config_ADC(void){
  
    ADMUX = 0X40; // VOLTAJE DE REFERENCIA DEL uC, JUSTIFICACION DEL RESULTADO A LA DERECHA
    ADCSRA |= (1<<ADEN) |(0<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); //ELEGIMOS EL CLOCK DEL RELOJ A 125 KH
    DIDR0 = 0XFF;  //DESHABILITO EL CANAL DIGITAL DE TODOS LOS CANALES
}
 
Cosme ! que tal , realice unas pruebas :

1.
4- Si con 3 sigue el problema, probá que sucede si al sprintf le asignás un valor fijo:
Le asigné un valor fijo y cada vez que sucede la interrupción, el led hace blink, el dato fijo en la LCD se mantiene pero la LCD baja un poco su luminosidad (solo cuando el led hace blink).
Probá que pasa si no deshabilitá y habilitás el ADC todo el tiempo:
En esta función me di cuenta que si no vuelvo a "actualizar" el registro ADMUX no realiza ningun cambio en sus bits y mucho menos ocurre la conversión, asi que por eso es que siempre tengo que actualizarlo al inicio.

Si deshabilito el bit ADEN pues simplementela conversion no sucede y mucho menos se muestra datos en la LCD( solo se aprecia Temp= 0.0 )

ADCSRA &= ~(1<<ADEN);
Si solo deshabilito este bit no sucede nada relevante, la conversion se sigue dando, lo añadí solo para que cada vez que use la funcion la active y al final la desactive, sino siempre estará activada ( no hay problema si borramos esta línea del código).

Lo del flag_muestra tampoco entra al while cuando flag_muestra pasa a 1, quizásel temporizador del timer1 se esta generando demasiado rápido como para no entrar al if, pero no me explico como es que altera el valor mostrado en la LCD.

Si no me se ocurre alguna manera de como insertar el indicador del blink lo sacaré del código 😔 .
 
Lo del flag_muestra tampoco entra al while cuando flag_muestra pasa a 1, quizásel temporizador del timer1 se esta generando demasiado rápido como para no entrar al if, pero no me explico como es que altera el valor mostrado en la LCD.

Eso está raro, ¿cómo sabes que no entra? Tal vez se te planta en el while del conversor porque no tiene un timeout.

C:
while((ADCSRA & (1<<ADSC)) != 0); //ESPERO A QUE LA CONVERSION TERMINE

Es importante ver que ese flag funcione bien, descartá el uso del ADC y probá con un contador:

C:
#define F_CPU 1000000UL
#include <avr/io.h>
#include <stdio.h>
#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "lcd.h"
#include "ADC.h"


void Config_TMR1(void);
void Config_Interrupt_TMR1(void);

unsigned char flag_muestra=0;

int main(void)
{
   float Temp = 0.0;
   char Str_Temp[6];
   unsigned char cont=0;

   Lcd_Init();
   Lcd_Clear();

   Config_TMR1();
   Config_Interrupt_TMR1();

   DDRB  |=  (1<<DDB2);  //B2 COMO SALIDA
   PORTB &= ~(1<<PORTB2); //B2 INICIA EN OFF

   while (1)
   {
        if(flag_muestra)
        {
            flag_muestra=0;         
          
            sprintf(Str_Temp, "%02d", cont);
          
            Lcd_Set_Cursor(1,1);
            Lcd_Write_String("Test");
            Lcd_Set_Cursor(2,1);
            Lcd_Write_String("Contador: ");
            Lcd_Write_String(Str_Temp);

            cont++;
            if(cont>=60)
              cont=0;
        }     
    }
}

ISR(TIMER1_OVF_vect){
    TCNT1 = 49911; //Cargar nuevamente al TMR1 para generar 1 segundo       
    
    //Aplicacion
    PORTB ^= (1<<PORTB2); 
    flag_muestra=1;
}

Deberías ver que el LCD actualiza el valor del contador hasta 59 y resetea.
 
Arriba