Informe de laboratorio Nº 3
1
Laboratorio 3 Microcontrolador (MCU): Conversión Análogo-Digital (Octubre 2012) Germán F. Cárdenas y Christopher A. Flores, Ingeniería Civil Biomédica, DIE Profesor: Miguel E. Figueroa Ayudante: Enrique I. Germany
Resumen—
Se implementará en el laboratorio la conversión digital de los voltajes generados por ángulos de un acelerómetro, cuyo proceasamiento se realizará en un microcontrolador ATmega64. Dichos ángulos se visualizarán a través de 4 displays 7 segmentos ánodo común. Se trabajará en cuatro modos: El primero representará el rango en que se encuentran los tres ángulos simultáneamente, simultáneamente, mientras que en los otros tres, se podrá visulaizar el valor de cado uno. Además 3 motores servo, darán información del ángulo entregado por el acelerómetro
II. OBJETIVOS -Programar un MCU Atmel ATmega64 en lenguaje C -Familiriarizarse con la utilización de Timers, para generar interrupciones periódicas -Familiriarizarse con la utilización de ondas PWM, para controlar la posición angular de un servomotor -Desplegar la información en los visores de 7 segmentos, utilizando barrido III. COMPONENTES Y EQUIPOS
Palabras
claves—
Atmega64,
diplay
7
segmentos,
acelerómetro, PWM
I. INTRODUCCIÓN
U
n microcontrolador es un dispositivo programable. Posee una Unidad Central de Procesamiento (CPU), memoria y periféricos de entrada/salida. El MCU utililizar en el laboratorio es el Atmel ATmega 64, que posee las siguientes características: Reloj de hasta 16 [MHz], 64 [Kb] de flash de programa, 2 [Kb] de EEPROM de datos, 4 [Kb] de RAM, registros generales (32) y especializados, entre otros, pudiendo ser programado en lenguaje C, assembly (bajo nivel) o Basic. B asic. Una interrupción es un llamado a procedimiento que afecta el actual estado de trabajo del MCU para ejecutar una nueva función. En el laboratorio, se utilizarán interrupciones de término del proceso de conversión análogo-digital para desplegar en pantalla el valor (voltaje) ingresado al MCU por un acelerómetro. Además, se utilizarán interrupciones periódicas por timmer, para todos los delays. A continuación se muestran los resultados obtenidos en el laboratorio.
- Microcontrolador Atmel ATmega 64 - Cable alimentación microcontrolador - Protoboard - 1 Push-button N.A - 10 Cable JTAG -4 transistores PNP -4 resistencias 1 [k Ω] -Cable USB - 4 display 7 segmentos ánodo común -Acelerómetro ADXL335 -3 potenciómetros 1 K Ω -3 servomotores - Cables -Alicates -Multímero -Ordenador -Software AVR Studio -Software AVR Programmer IV. MATERIALES YMÉTODO -Simular programa Assembly en Proteus -Montar en protoboard displays correspondientes y el push botton -Cargar en MCU el programa -Encender el MCU y verificar funcionamiento
Informe de laboratorio Nº 3 V. DESARROLLO A.1 Solución propuesta
El sistema constará con cuatro estados que serán intercambiados por la acción de un botón. El sistema comenzará en el primer modo, donde se podrá visualizar en 4 displays el rango del ángulo correspondiente a cada entrada del conversor. Para ello, se hará uso de una estrucutra (lenguaje C) que almacena los rangos en hexadecimal. Los demás modos entregarán información del valor del ángulo en forma individual para cada entrada. Para su conversión, se utilizará una fómula que entregará directamente el valor del ángulo, dependiendo del voltaje suministrado por el acelerómetro. Dos interrupciones afectarán al programa: Término de conversión y timmers (delays). Finalmente, tres motores servo, entregarán información física de los ángulos suministrados por el acelerómetro. En primera instancia, se trabajarán con potenciómetros, una vez que el circuito funcione correctamente, se procederá a reemplazarlos por el acelerómetro. Se muestra un esquema general de funcionamiento:
Fig.1 Esquema funcionamiento programa A.2 Código
Se definen las librerías a utilizar //=========================================== ==================== #include
//Libreria AVR #define F_CPU 8000000UL //Frecuencia micro #define REBOTE 2 #define ESPERA 10 #include //Libreía timmer #include //Librería interrupciones #include //Librería funciones matemáticas
2
Se definen los prototipos de las funciones, para luego desarrolarlas al término del loop principal, para tener una mejor organización del código. Además se definen vectores que almacenan los valores decimales u de rangos, que permiten la conversión de los voltaje suminsitrados por el acelerómetro. //=========================================== ==================== void init_boton (void); //Prototipo inicia boton uint8_t procesa_boton (void); //Prototipo prcesamiento boton void init_display (void); //Prototipo inicia display void display (uint8_t bcd1, uint8_t bcd0); //Prototipo display void init_conversor(void); //Prototipo inicia conversor void modos (void); //Prototipo modos void display2(uint8_t bcd2); //Protoipo display modo1, visorA void display1(uint8_t bcd1); //Protoipo display modo1, visorC void display0(uint8_t bcd0); //Protoipo display modo1, visorD uint8_t promedio1(uint8_t vector[256]); //Prototipo promedio const uint8_t BCD_7seg[] = { (Tabla) 0xC0, // 0 0xF9, // 1 0xA4, // 2 0xB0, // 3 0x99, // 4 0x92, // 5 0x82, // 6 0xF8, // 7 0x80, // 8 0x90, // 9 0x88, // A 0x83, // B 0xC6, // C 0xA1, // D 0x86, // E 0x8E // F };
//Estructura almacena digitos
const uint8_t BCD_MODO1[] = { 0x00, 0x01, 0x23, 0x63, 0x77, 0x7f, 0xff, 0xf7, 0xe3,
Informe de laboratorio Nº 3 0xa3, 0x81, 0x80 }; //=========================================== ==================== uint8_t i; uint8_t contador = 1; Esta sección de código es anterior al loop principal del programa y se ejecuta una sola vez. Aquí se inicializan los botones, el conversor y los displays. Además se activan las interrupciones. int main(void) { init_display(); //Display configurado init_boton(); //Boton configurado init_conversor(); //Conversor configurado sei(); //Interrupciones activadas Esta sección de código corresponde al loop principal. Se lee el botón, para detectar si fue pulsado o no. Para el primer caso, se incrementa un contador, que está incializado en 1, de esta forma, el programa, comienza en el modo 1. Luego, el programa retorna al loop principal, para acceder a los distintos modos., según el contador. while(1) {
//Loop [f(0) = 1/x]
procesa_boton(); //Llama a botón _delay_ms(REBOTE); //Espera 5 ms antirrebote modos(); _delay_ms(ESPERA); //Espera 10 ms conversion } } //=========================================== ========================================= En esta sección de código, se inicializa el botón, configurando el puerto B como entrada //INCIALIZACION BOTON void init_boton (void) { DDRB = 0x00; //Puerto B es entrada PORTB = 0xff; //Activa pull ups } En esta sección de código, se inicializan los displays, configurando el puerto A, C y D como salida. Sin embargo, esto será modificado para el barrido (sólo puerto A).
3
//INCIALIZACION DISPLAY void init_display (void) { DDRA = 0xff; //Puerto A es salida DDRC = 0xff; //Puerto C es salida DDRD = 0xff; //Puerto D es salida } En esta sección de código, se inicializa el conversor, configurando el puerto F como entrada //INCIALIZACION CONVERSOR void init_conversor(void) { DDRF = 0x00; //Puerto F es entrada conversor ADCSRB = 0x00; } //LECTURA BOTON uint8_t procesa_boton(void) { static uint8_t boton = 0x00; boton <<=1; boton |= (PINB & 0x01); boton &= 0x03; if (boton == 0x02) contador = contador +1; else contador = contador; if (contador==5) { contador = 1; } } //MUESTRA DISPLAY MODOS 2,3 Y 4 void display (uint8_t bcd1, uint8_t bcd0) { PORTA = BCD_7seg[bcd1]; PORTC = BCD_7seg[bcd0]; } //MUESTRA DISPLAY MOD01, VISOR A void display2(uint8_t bcd2) { PORTA = BCD_MODO1[bcd2]; } //MUESTRA DISPLAY MOD01, VISOR C void display1(uint8_t bcd1) { PORTC = BCD_MODO1[bcd1]; } //MUESTRA DISPLAY MOD01, VISOR D void display0(uint8_t bcd0) { PORTD = BCD_MODO1[bcd0]; }
Informe de laboratorio Nº 3
4
En esta sección de código se selecciona el modo según el contador.
return promedio; }
//SELECCIÓN MODOS Y CONVERSOR void modos (void) {
Al término de cada conversión se llama a la función mostrar en displays y luego vuelve al estado anterior.
if (contador == 1) {
//modo1
ADMUX = 0b01100000; ADCSRA = 0b11011111; _delay_ms(ESPERA); ADMUX = 0b01100001; ADCSRA = 0b11011111; _delay_ms(ESPERA); ADMUX = 0b01100010; ADCSRA = 0b11011111; _delay_ms(ESPERA); } if (contador == 2) //modo2 { ADMUX = 0b01100000; ADCSRA = 0b11011111; } if (contador == 3) //modo3 { ADMUX = 0b01100001; ADCSRA = 0b11011111; } if (contador == 4) //modo4 { ADMUX = 0b01100010; ADCSRA = 0b11011111; } } La función promedio, tiene como argumento el buffer de datos que es llenado por el ADCH y retorna el promedio de dichos valores. //PROMEDIO MODO 1 uint8_t promedio1(uint8_t vector[]) { uint16_t suma=0; uint8_t promedio; for (i=0;i<128;i++) { suma = vector[i]+suma; } promedio = suma/128;
//INTERRUPCIÓN FIN CONVERSION ISR(ADC_vect) { uint8_t angle = asin((ADCH-127.5)/127.5); uint8_t temporal = angle; uint8_t digit0 = (temporal & 0x0F); // Obtener los 4 bits menos significativos de la conversión uint8_t digit1 = (temporal >> 4) & 0x0F; // Obtener los 4 bits mas significativos de la conversión uint8_t buffer[128]; for(i=0;i<128;i++) { buffer[i] = ADCH; } uint8_t tmp = promedio1(buffer); if(tmp>=0 && tmp <21) tmp = 0; if(tmp>=21 && tmp <42) tmp = 1; if(tmp>=42 && tmp <63) tmp = 2; if(tmp>=63 && tmp <84) tmp = 3; if(tmp>=84 && tmp <105) tmp = 4; if(tmp>=105 && tmp <126) tmp = 5; if(tmp>=126 && tmp <147) tmp = 6; if(tmp>=147 && tmp <168) tmp = 7; if(tmp>=168 && tmp <189) tmp = 8; if(tmp>=189 && tmp <210) tmp = 9; if(tmp>=210 && tmp <231) tmp = 10; if(tmp>=231 && tmp <=255) tmp = 11; if (contador==1) //MODO 1 { if(ADMUX == 0b01100000) //canal1 en modo1 { display0(tmp);
Informe de laboratorio Nº 3 } if(ADMUX == 0b01100001) //canal2 en modo1 { display1(tmp); } if(ADMUX == 0b01100010) //canal3 en modo1 { display2(tmp); } } if (contador>1 && contador<=4) //MODO 2, 3 y 4 { display(digit1, digit0); // Desplegar dígitos en los displays } }
A.3 Códigos de prueba Código hecho en clases despliega en display BCD, tras la acción de un push botton
void init_displays( void ) { DDRC = 0xFF; // Puerto C es salida. Diplay más significativo DDRA = 0xFF; // Puerto A es salida. Diplay menos significativo } void displayDigits( uint8_t bcd1, uint8_t bcd0 ) { PORTA = BCD_7seg[bcd1]; //Busca en estructura BCD PORTC = BCD_7seg[bcd0]; //Busca en estrucutra BCD } void incDigits( uint8_t *pbcd1, uint8_t *pbcd0) { if( *pbcd0 != 9 ) // Si dígito menos significativo no ha llegado a 9 (máximo) { (*pbcd0)++; // Incrementar normalmente } else // Dígito mas significativo llegó a 9 (máximo) { (*pbcd0) = 0; // volverlo a 0 // Realizar lo mismo, pero con el dígito mas significativo if (*pbcd1 != 9) { (*pbcd1)++; } else (*pbcd1) = 0;
#include //Librería AVR #define F_CPU 8000000UL //Frecuencia MCU #include //Librería timmer #define DELAY 5 //Delay antirrebote //Crear tabla de conversión //Estructura BCD const uint8_t BCD_7seg[] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E };
5
} } void init_boton (void) { DDRG = 0x00; // Puerto G es entrada botón PORTG = 0xFF; // Pull-ups activos } uint8_t proc_boton( void ) //Procesa boton { // Inicializar variable (solo una vez): Botón no presionado (estado anterior) static uint8_t boton = 0x01; // Realizar desplazamiento a la izquierda // Estado anterior del botón queda almacenado en el segundo bit de "boton" boton <<= 1;
Informe de laboratorio Nº 3 // Leer bit 0 del puerto G y hacer un OR con la variable "boton" // Almacena estado actual del botón en primer bit de la variable "boton" boton |= (PING & 0x01); // Descartar los 6 bits mas significativos (solo los 2 menos significativos interesan) boton &= 0x03; // Retornar 1 solo si botón no estaba presionado, y ahorá lo está (1b...10) if( boton == 0x02 ) return 1; // Retornar 0 en otro caso; Botón no presionado, o ya lo estaba return 0; }
int main(void) { uint8_t bcd1 = 0, bcd0 = 0;
#define PW_MIN 500 // ancho de pulso mínimo es 500us con div. por 8 #define PW_MAX 2000 // ancho de pulso máximo es 2ms con div. por 8 int main(void) { TCCR0 = 0b00000100; // FOC0 = 0; WGM01:0 = 00 (normal); COM01:0 = 00 (OC0 desconectado); // CS02:1 = 100 (f/64)--> 8us/tic, perÌodo 2.048ms TIMSK = 0b00000001; // OCIE0 = 0; TOIE0 = 1; habilitar interr. por overflow DDRB |= _BV(5) | _BV(6) | _BV(7); OC1C (PB5, PV6, PB7) salidas OCR1A = PW_MIN; OCR1B = PW_MIN; OCR1C = PW_MIN; ICR1 = TOP; TCCR1A = 0b10101010; TCCR1B = 0b00011010; TCCR1C = 0;
// Desplegar nueva cuenta displayDigits( bcd1, bcd0 ); } // Esperar (antirebote) _delay_ms( DELAY ); } return 1;
// OC1A, OC1B,
DDRF = 0x00; // Puerta F es entrada (ADC y conectores servo)
init_displays(); //Inicializa display init_boton(); //Inicializa boton displayDigits( bcd1, bcd0 ); // Desplegar digitos por primera vez while( 1 ) { if( proc_boton() ) // Si se ha presionado el botón { // Incrememntar cuenta incDigits( &bcd1, &bcd0 );
6
sei(); while (1) { } return 1; } ISR(TIMER0_OVF_vect) { static uint8_t contador = 0; contador++; if (contador > (50/2.048)) { contador = 0; ADMUX = 0b01100000; // REFS1:0 = 01: ref. es AVCC; ADLAR = 1: ajuste izquierda; Canal 0 seleccionado ADCSRA = 0b11001111; // ADEN=1; ADC habilitado, ADSC=1: iniciar conversiÛn, } }
} Código hecho acciona un motor sevo, dependiendo del valor ingresado por conversor #include #include #define TOP 20000 // período es de 20ms con un divisor por 8
ISR(ADC_vect) { uint32_t angulo = PW_MIN + ((uint32_t)ADCH*(PW_MAX-PW_MIN))/255; if ((ADMUX & 0x03) == 0) { OCR1A = angulo; ADMUX = 0b01100001; // REFS1:0 = 01: ref. es AVCC; ADLAR = 1: ajuste izquierda; Canal 0 seleccionado ADCSRA = 0b11001111; // ADEN=1; ADC habilitado, ADSC=1: iniciar conversiÛn,
Informe de laboratorio Nº 3 } else if ((ADMUX & 0x03) == 1) { OCR1B = angulo; ADMUX = 0b01100010; // REFS1:0 = 01: ref. es AVCC; ADLAR = 1: ajuste izquierda; Canal 0 seleccionado ADCSRA = 0b11001111; // ADEN=1; ADC habilitado, ADSC=1: iniciar conversiÛn, } else if ((ADMUX & 0x03) == 2) { OCR1C = angulo; } }
7 VI. CONCLUSIÓN
Los microcontroladores (MCU) son de gran utilidad a la hora de implementar circuitos digitales o analógicos, previo uso de conversores A/D. Su fundamento se basa en el estudio del lenguaje a utilizar, sea éste el C, assembly, Basic, entre otros. La diferencia radica en la cercanía (nivel) con el microcontrolador, lo que se traducirá en el grado de control. En el caso del lenguaje Assembly se tiene un mayor control bit a bit de los registros a utilizar, lo que logra un aprovechamiento eficiente de los recursos del MCU. Sin embargo,el lenguaje C, permite una mayor fluidez de código, ya que muchas funciones ya están implementadas (timmers).
A.4 Implementación de circuito
VII. REFERENCIAS El circuito a implementar consiste en 4 displays conectados en paralelo al puerto A del microcontrolador, cuya activación será mediante barrido. Para ello, se utilizarán 4 transistores PNP, cuyas bases serán activadas por l puerto C. Además un push button, conectado en el puerto B, controlará los cambios de modo. Finalmente, 3 motores servo, entregarán información del acelerómetro conectado en el puerto F.
Fig.2 Circuito a implementar en laboratorio
[1] Laboratorio de microcontroladores: Guía de laboratorio N° 3, Universidad de Concepción, Departamento de Ingeniería Eléctrica, 2011, pp 1-3