Interruptor automático de antena con control MK

imagen

En la práctica de la radioafición, a veces es necesario hacer algo en el microcontrolador. Si no hace este tipo de manualidades todo el tiempo, debe buscar en Google durante mucho tiempo la solución de circuito necesaria y las bibliotecas adecuadas para MK, lo que le permite resolver rápidamente el problema. Recientemente quería hacer un cambio automático de antena. En el proceso, tuve que usar muchas de las funciones de Atmega MK en un proyecto compacto. Aquellos que comienzan a estudiar AVR, cambian de arduino u ocasionalmente programan MK pueden ser piezas útiles de código que utilicé en el proyecto.

Pensé en el interruptor de antena como un dispositivo que conecta automáticamente la antena al transceptor, que es el más adecuado para el rango de trabajo de ondas cortas. Tengo dos antenas: V invertida y plano de tierra, están conectadas al sintonizador de antena MFJ, en el que se pueden conmutar de forma remota. Hay un interruptor manual de marca MFJ, que quería reemplazar.

imagen

Para la conmutación operativa de antenas, un botón está conectado al MK. Lo adapté para recordar la antena preferida para cada rango: cuando presiona el botón durante más de 3 segundos, la antena seleccionada se recuerda y se selecciona correctamente automáticamente después del siguiente encendido del dispositivo. La información sobre el alcance actual, la antena seleccionada y el estado de su sintonización se muestra en una pantalla LCD de una sola línea.

Puede averiguar en qué rango está trabajando actualmente el transceptor de diferentes maneras: puede medir la frecuencia de la señal, puede recibir datos a través de la interfaz CAT, pero lo más simple para mí es usar la interfaz del transceptor YAESU para conectar un amplificador externo. Tiene 4 líneas de señal, en código binario, que indican el rango actual. Dan una señal lógica de 0 a 5 voltios y se pueden conectar a las patas del MK a través de un par de resistencias terminales.

imagen

Eso no es todo. En el modo de transmisión, las señales PTT y ALC se transmiten a través de la misma interfaz. Esta es una señal lógica acerca de encender el transmisor (tirado al suelo) y una señal analógica de 0 a -4 V sobre el funcionamiento del sistema de control automático de potencia del transmisor. También decidí medirlo y mostrarlo en la pantalla LCD en modo de transmisión.

Además, el sintonizador MFJ puede transmitir señales al control remoto que está sintonizando y que la antena está sintonizada. Para hacer esto, el panel de control de la compañía MFJ tiene dos LED de control. En lugar de LED, conecté los optoacopladores y envié una señal de ellos al MK, para poder ver toda la información en una pantalla. El dispositivo terminado se ve así.

imagen

Brevemente sobre casero como todo. Ahora sobre la parte del software. El código está escrito en Atmel Studio (descarga gratuita desde el sitio web de Atmel). El proyecto para principiantes demuestra las siguientes características del uso del popular Atmega8 MK:

  1. Botón de conexión
  2. Conecte la entrada de línea para la señal digital del transceptor y el sintonizador
  3. Conexión de la salida de control del relé de conmutación de antena
  4. Conexión de una pantalla LCD de una sola línea
  5. Conexión de zumbador y salida de sonido.
  6. Conexión de línea de entrada analógica ADC y medición de voltaje
  7. Usando interrupciones
  8. Usar un temporizador para contar el tiempo que se presiona un botón
  9. Usando Watchdog
  10. Uso de memoria no volátil para almacenar antenas seleccionadas
  11. Uso de UART para la impresión de depuración
  12. Ahorro de energía en inactivo MK

Entonces comencemos. En el curso del texto, habrá todo tipo de nombres de registros y constantes característicos de la MK aplicada. Esto no es arduino, aquí, desafortunadamente, tienes que leer la hoja de datos en MK. De lo contrario, no comprende lo que significan todos estos registros y cómo puede cambiar sus valores. Pero la estructura del programa en su conjunto seguirá siendo la misma.

Primero, conecte el botón al MK


Este es el más simple. Conectamos un contacto al pie MK, el segundo contacto del botón al suelo. Para que el botón funcione, deberá encender la resistencia pull-up en MK. Conectará el botón a través de la resistencia al bus + 5V. Hacer esto es bastante simple:

PORTB |= (1 << PB2); // pullup resistor   

Del mismo modo, todas las entradas digitales que están controladas por una falla a tierra (optoacopladores, líneas de señal desde el transceptor, señal PTT) se dirigen al bus + 5V. A veces es mejor soldar físicamente una resistencia tan pequeña (por ejemplo, 10k) entre la entrada del MK y el bus + 5V, pero la discusión de este tema está más allá del alcance del artículo. Dado que todas las señales de entrada en el proyecto rara vez cambian de valor, son derivadas al suelo por condensadores de 10 nanofaradios para proteger contra interferencias.

Ahora tenemos 1 lógico en la entrada PB2, y cuando presiona el botón, será lógico 0. Cuando presiona \ deprimir, necesita rastrear el rebote de contacto del botón, verificando que el nivel de señal no ha cambiado con el tiempo, digamos 50 milisegundos. Esto se hace en el programa así:

  if(!(PINB&(1<<PINB2)) && !timer_on) { //    _delay_ms(50); if( !(PINB&(1<<PINB2)) ) { //        -   passed_secs = 0; timer_on = 1; } } 

Ahora conecta el chirrido


Dará una señal de confirmación de audio de que la antena está grabada en la memoria MK. Un tweeter es solo un elemento piezoeléctrico. Está conectado a través de una pequeña resistencia al pie MK, y por un segundo contacto a + 5V. Para que este zumbador funcione, primero debe configurar el pie MK para generar datos.

 void init_buzzer(void) { PORTB &= ~(1 << PB0); // buzzer DDRB |= (1 << PB0); // output PORTB &= ~(1 << PB0); } 

Ahora se puede usar. Para hacer esto, se escribe una pequeña función que utiliza retrasos de tiempo para cambiar las piernas MK de 0 a 1 y viceversa. El cambio con los retrasos necesarios hace posible generar una señal de audio de 4 kHz con una duración de aproximadamente un cuarto de segundo en la salida MK, que es el sonido del elemento piezoeléctrico.

 void buzz(void) { //    4 0,25  for(int i=0; i<1000; i++) { wdt_reset(); //    PORTB |= (1 << PB0); _delay_us(125); PORTB &= ~(1 << PB0); _delay_us(125); } } 

Para que las funciones de retraso funcionen, no olvide incluir el archivo de encabezado y establecer la velocidad constante del procesador. Es igual a la frecuencia del resonador de cuarzo conectado al MK. En mi caso, había 16MHz de cuarzo.

 #ifndef F_CPU # define F_CPU 16000000UL #endif #include <util/delay.h> 

Nos conectamos a las antenas de conmutación de relé MK


Aquí solo necesita configurar el pie MK para que funcione al salir. Un relé de láminas está conectado a esta pata a través de un transistor amplificador de manera estándar.
 void init_tuner_relay(void) { PORTB &= ~(1 << PB1); // relay DDRB |= (1 << PB1); // output PORTB &= ~(1 << PB1); } 

Conexión de pantalla


Utilicé una pantalla LCD 1601 de 16 caracteres y una línea, extraída del hardware antiguo. Utiliza el conocido controlador HD44780, para cuya administración hay muchas bibliotecas disponibles en la red. Una persona amable escribió una biblioteca de control de pantalla ligera, que utilicé en el proyecto. La configuración de la biblioteca se reduce a indicar en el archivo de encabezado HD44780_Config.h el número de patas MK conectadas a los pines de pantalla deseados. Apliqué una conexión de pantalla a través de 4 líneas de datos.

 #define Data_Length 0 #define NumberOfLines 1 #define Font 1 #define PORT_Strob_Signal_E PORTC #define PIN_Strob_Signal_E 5 #define PORT_Strob_Signal_RS PORTC #define PIN_Strob_Signal_RS 4 #define PORT_bus_4 PORTC #define PIN_bus_4 0 #define PORT_bus_5 PORTC #define PIN_bus_5 1 #define PORT_bus_6 PORTC #define PIN_bus_6 2 #define PORT_bus_7 PORTC #define PIN_bus_7 3 

Una característica de mi instancia de visualización fue que una línea en la pantalla se mostraba como dos líneas de 8 caracteres, por lo que se creó un búfer de pantalla intermedio en el programa para un trabajo más conveniente con la pantalla.

 void init_display(void) { PORTC &= ~(1 << PC0); // display DDRC |= (1 << PC0); // output PORTC &= ~(1 << PC0); PORTC &= ~(1 << PC1); // display DDRC |= (1 << PC1); // output PORTC &= ~(1 << PC1); PORTC &= ~(1 << PC2); // display DDRC |= (1 << PC2); // output PORTC &= ~(1 << PC2); PORTC &= ~(1 << PC3); // display DDRC |= (1 << PC3); // output PORTC &= ~(1 << PC3); PORTC &= ~(1 << PC4); // display DDRC |= (1 << PC4); // output PORTC &= ~(1 << PC4); PORTC &= ~(1 << PC5); // display DDRC |= (1 << PC5); // output PORTC &= ~(1 << PC5); LCD_Init(); LCD_DisplEnable_CursOnOffBlink(1,0,0); } /*   16  0-3   40M     4-8   A:GP  A:IV     9-15    : TUNING=, TUNED==, HI-SWR= */ uchar display_buffer[]={' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}; // 16    void update_display() { LCD_Init(); LCD_DisplEnable_CursOnOffBlink(1,0,0); //   16      8         LCD for (uchar i=0; i<8; i++){ LCD_Show(display_buffer[i],1,i); LCD_Show(display_buffer[i+8],2,i); } } 

La función update_display () le permite mostrar el contenido del búfer en la pantalla. Los valores de bytes en el búfer son los códigos ASCII de los caracteres de salida.

Depurar la salida de impresión al puerto COM


MK tiene UART y lo usé para depurar el programa. Al conectar el MK a la computadora, solo necesita recordar que los niveles de señal en la salida MK están en el estándar TTL y no en el RS232, por lo que necesita un adaptador simple. Utilicé un adaptador de serie USB, completamente similar en aliexpress. Cualquier programa de terminal, por ejemplo de arduino, es adecuado para leer datos. Código de configuración del puerto UART:

 #define BAUD 9600 #include <stdio.h> #include <stdlib.h> #include <avr/io.h> // UART      RS232 void uart_init( void ) { /* //   UBRRH = 0; UBRRL = 103; //9600   16  */ #include <util/setbaud.h> UBRRH = UBRRH_VALUE; UBRRL = UBRRL_VALUE; #if USE_2X UCSRA |= (1 << U2X); #else UCSRA &= ~(1 << U2X); #endif //8  , 1  ,    UCSRC = ( 1 << URSEL ) | ( 1 << UCSZ1 ) | ( 1 << UCSZ0 ); //     // UCSRB = ( 1 << TXEN ) | ( 1 <<RXEN ); UCSRB = ( 1 << TXEN ); } int uart_putc( char c, FILE *file ) { //     while( ( UCSRA & ( 1 << UDRE ) ) == 0 ); UDR = c; wdt_reset(); return 0; } FILE uart_stream = FDEV_SETUP_STREAM( uart_putc, NULL, _FDEV_SETUP_WRITE ); stdout = &uart_stream; 

Después de configurar el flujo de salida, puede usar la impresión habitual para imprimir en el puerto:
 printf( "Start flag after reset = %u\r\n", mcusr_mirror ); 

El programa utiliza la impresión de números reales. Las bibliotecas normales no admiten este modo de salida, por lo que tuve que conectar una biblioteca completa al vincular un proyecto. Es cierto que aumenta considerablemente la cantidad de código, pero tenía una gran cantidad de memoria, por lo que no fue crítico. En las opciones del vinculador, debe especificar la línea:

-Wl,-u,vfprintf -lprintf_flt

Trabaja con temporizador e interrupciones


Para contar los intervalos de tiempo en un programa, es importante tener un contador de tiempo. Es necesario hacer un seguimiento de que el botón se presiona durante más de 3 segundos y, por lo tanto, debe recordar las nuevas configuraciones en la memoria no volátil. Para medir el tiempo en el estilo AVR, debe configurar el contador de pulsos del generador de reloj y la interrupción que se ejecutará cuando el contador alcance el valor establecido. Configuré el temporizador para que produzca una interrupción aproximadamente una vez por segundo. El propio controlador de interrupciones cuenta el número de segundos transcurridos. La variable timer_on controla el encendido / apagado del temporizador. Es importante no olvidar declarar todas las variables que se actualizan en el controlador de interrupciones como volátiles, de lo contrario el compilador puede "optimizarlas" y el programa no funcionará.

 //   1    -     void timer1_init( void ) { TCCR1A = 0; //    1 -   /* 16000000 / 1024 = 15625 ,     15625      1  */ //  CTC, ICP1 interrupt sense (falling)(not used) + prescale /1024 +    (not used) TCCR1B = (0 << WGM13) | (1 << WGM12) | (0 << ICES1) | ((1 << CS12) | (0 << CS11) | (1 << CS10)) | (0 << ICNC1); OCR1A = 15625; //  TIMSK |= (1 << OCIE1A); } uchar timer_on = 0; volatile uchar passed_secs = 0; //      e ISR(TIMER1_COMPA_vect) { if (timer_on) passed_secs++; } 

El valor de pass_secs se verifica en el bucle principal del programa. Cuando se presiona el botón, el temporizador comienza y luego, en el ciclo del programa principal, se verifica el valor del temporizador mientras se presiona el botón. Si este valor supera los 3 segundos, se escribe la EEPROM y el temporizador se detiene.

Por último, pero no menos importante, después de todas las inicializaciones, debe habilitar las interrupciones con el comando sei ().

Medición de nivel de ALC


Se realiza utilizando el convertidor analógico a digital (ADC) incorporado. Medí el voltaje en la entrada del ADC7. Debe recordarse que puede medir un valor de 0 a 2.5V. y mi voltaje de entrada fue de -4V a 0V. Por lo tanto, conecté el MK a través del divisor de voltaje más simple en las resistencias, de modo que el nivel de voltaje en la entrada MK estaba en un nivel dado. Además, no necesitaba una alta precisión, por lo que apliqué una conversión de 8 bits (es suficiente para leer datos solo del registro ADCH). Como fuente de referencia, utilicé un ion interno a 2.56V, esto simplifica ligeramente los cálculos. Para que funcione el ADC, asegúrese de conectar un capacitor de 0.1 µF al pie REF en el suelo.

ADC en mi caso funciona continuamente, informando el final de la conversión llamando a la interrupción ADC_vect. Es una buena práctica promediar los valores de varios ciclos de conversión para reducir el error. En mi caso, infiero el promedio de 2500 transformaciones. Todo el código ADC se ve así:

 //        ALC #define SAMPLES 2500 //    #define REFERENCEV 2.56 //       #define DIVIDER 2.0 double realV = 0; //     ALC double current_realV = 0; volatile int sampleCount = 0; volatile unsigned long tempVoltage = 0; //     volatile unsigned long sumVoltage = 0; //         void ADC_init() // ADC7 { //   2,56, 8 bit  -   ADCH ADMUX = (1 << REFS0) | (1 << REFS1) | (1 << ADLAR) | (0 << MUX3) | (1 << MUX2) | (1 << MUX1) | (1 << MUX0); // ADC7 // , free running,   ADCSRA = (1 << ADEN) | (1 << ADFR) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); //  128 ADCSRA |= (1 << ADSC); // Start ADC Conversion } ISR(ADC_vect) //     2500  { if (sampleCount++) //    tempVoltage += ADCH; if (sampleCount >= SAMPLES) { sampleCount = 0; sumVoltage = tempVoltage; tempVoltage = 0; } ADCSRA |=(1 << ADIF); // Acknowledge the ADC Interrupt Flag } realV = -1.0*(DIVIDER * ((sumVoltage * REFERENCEV) / 256) / SAMPLES - 5.0); //   ALC if (realV < 0.0) realV = 0.0; printf("ALC= -%4.2f\r\n", realV); //      

Usando EEPROM


Esta es una memoria no volátil en MK. Es conveniente usarlo para almacenar todo tipo de configuraciones, valores de corrección, etc. En nuestro caso, se usa solo para almacenar la antena seleccionada para el rango deseado. Para este propósito, se asigna una matriz de 16 bytes en la EEPROM. Pero puede acceder a él a través de funciones especiales definidas en el archivo de encabezado avr / eeprom.h. Al inicio, el MK lee información sobre la configuración guardada en la RAM y enciende la antena deseada, dependiendo del rango actual. Cuando se presiona el botón durante mucho tiempo, se graba un nuevo valor en la memoria, acompañado de una señal de sonido. Al escribir en la EEPROM, las interrupciones se desactivan por si acaso. Código de inicialización de memoria:

 EEMEM unsigned char ee_bands[16]; //         unsigned char avr_bands[16]; void EEPROM_init(void) { for(int i=0; i<16; i++) { avr_bands[i] = eeprom_read_byte(&ee_bands[i]); if (avr_bands[i] > 1) avr_bands[i] = ANT_IV; //    EEPROM   ,     FF } } 

Un fragmento del código de procesamiento para presionar un botón durante 3 segundos y escribir en la memoria:

  if (!(PINB&(1<<PINB2)) && passed_secs >= 3) { //    3  timer_on = 0; //   read_ant = avr_bands[read_band]; //     cli(); EEPROM_init(); //          sei(); if (read_ant) { avr_bands[read_band] = ANT_GP; } else { avr_bands[read_band] = ANT_IV; } cli(); eeprom_write_byte(&ee_bands[read_band], avr_bands[read_band]); //    EEPROM sei(); buzz(); } 

Usando Watchdog


No es ningún secreto que bajo condiciones de fuerte interferencia electromagnética, el MK puede congelarse. Cuando la radio está en funcionamiento, existe tal interferencia que "los hierros comienzan a hablar", por lo que debe asegurarse de reiniciar cuidadosamente el MK en caso de que se cuelgue. Un temporizador de vigilancia sirve para este propósito. Usarlo es muy simple. Primero, incluya el archivo de encabezado avr / wdt.h en el proyecto. Al comienzo del programa, después de completar todas las configuraciones, debe iniciar el temporizador llamando a la función wdt_enable (WDTO_2S), y luego recordar restablecerlo periódicamente llamando a wdt_reset (), de lo contrario, reiniciará el MK. Para la depuración con el fin de descubrir por qué se reinició el MK, puede usar el valor del registro especial MCUSR, cuyo valor puede recordarse y luego imprimirse en la impresión de depuración.

 //        //     uint8_t mcusr_mirror __attribute__ ((section (".noinit"))); void get_mcusr(void) \ __attribute__((naked)) \ __attribute__((section(".init3"))); void get_mcusr(void) { mcusr_mirror = MCUSR; MCUSR = 0; wdt_disable(); } printf( "Start flag after reset = %u\r\n", mcusr_mirror ); 

Ahorro de energía para los amantes del medio ambiente.


Si bien MK no está ocupado con nada, puede quedarse dormido y esperar la próxima interrupción. En este caso, se ahorra un poco de energía eléctrica. Un poco, pero ¿por qué no usarlo en un proyecto? Además, es muy simple. Incluya el archivo de encabezado avr / sleep.h. El cuerpo del programa consta de un bucle infinito en el que debe llamar a la función sleep_cpu (), después de lo cual el MC se queda dormido un poco y el bucle principal se detiene hasta que se produce la siguiente interrupción. Se producen durante el funcionamiento del temporizador y el ADC, por lo que MK no dormirá durante mucho tiempo. El modo de hibernación se determina cuando MK se inicializa llamando a dos funciones:

  set_sleep_mode(SLEEP_MODE_IDLE); //     IDLE sleep_enable(); 

Eso es todo por ahora. Hice el cambio; funciona con éxito en mi estación de radio amateur sin fallas. Espero que el material proporcionado sea útil para principiantes.

73 de R2AJP

Source: https://habr.com/ru/post/es405581/


All Articles