Interruptor automático de antena com controle MK

imagem

Na prática de rádio amador, às vezes é necessário fazer algo no microcontrolador. Se você não faz esse tipo de trabalho o tempo todo, precisa pesquisar no Google por um longo tempo a solução de circuito necessária e as bibliotecas adequadas para o MK, permitindo que você resolva rapidamente o problema. Recentemente, eu queria fazer uma troca automática de antena. No processo, tive que usar muitos dos recursos do Atmega MK em um projeto compacto. Quem começa a estudar o AVR, muda do arduino ou ocasionalmente programa o MK pode ser um código útil usado por mim no projeto.

Pensei no interruptor da antena como um dispositivo que conecta automaticamente a antena ao transceptor, o que é mais adequado para a faixa de trabalho de ondas curtas. Eu tenho duas antenas: Invertido V e Ground Plane, elas são conectadas ao sintonizador de antena MFJ, na qual podem ser comutadas remotamente. Existe um interruptor manual da marca MFJ, que eu queria substituir.

imagem

Para comutação operacional de antenas, um botão é conectado ao MK. Eu a adaptei para lembrar a antena preferida para cada faixa: quando você pressiona o botão por mais de 3 segundos, a antena selecionada é lembrada e selecionada corretamente automaticamente após a próxima inicialização do dispositivo. Informações sobre a faixa atual, a antena selecionada e o estado de sua sintonia são exibidas em um visor LCD de linha única.

Você pode descobrir em que faixa o transceptor está trabalhando atualmente de diferentes maneiras: você pode medir a frequência do sinal, receber dados via interface CAT, mas a coisa mais simples para mim é usar a interface do transceptor YAESU para conectar um amplificador externo. Possui 4 linhas de sinal, em código binário, indicando a faixa atual. Eles emitem um sinal lógico de 0 a 5 volts e podem ser conectados às pernas do MK através de um par de resistores de terminação.

imagem

Isso não é tudo. No modo de transmissão, os sinais PTT e ALC são transmitidos através da mesma interface. Este é um sinal lógico sobre ligar o transmissor (puxado para o chão) e um sinal analógico de 0 a -4V sobre a operação do sistema de controle de potência do transmissor automático. Também decidi medi-lo e exibi-lo no LCD no modo de transmissão.

Além disso, o sintonizador MFJ pode transmitir sinais para o controle remoto de que está sintonizando e de que a antena está sintonizada. Para isso, o painel de controle da empresa MFJ possui dois LEDs de controle. Em vez de LEDs, conectei os acopladores ópticos e enviei um sinal deles para o MK, para que eu pudesse ver todas as informações em um monitor. O dispositivo final é assim.

imagem

Brevemente sobre caseiro como tudo. Agora sobre a parte do software. O código está escrito no Atmel Studio (download gratuito no site da Atmel). O projeto para iniciantes demonstra os seguintes recursos do uso do popular Atmega8 MK:

  1. Botão conectar
  2. Conecte a entrada de linha para o sinal digital do transceptor e sintonizador
  3. Conexão da saída de controle do relé de comutação da antena
  4. Conectando um visor LCD de linha única
  5. Conexão de campainha e saída de som
  6. Conexão da linha de entrada analógica ADC e medição de tensão
  7. Usando interrupções
  8. Usando um timer para contar o tempo que um botão é pressionado
  9. Usando o Watchdog
  10. Usando memória não volátil para armazenar antenas selecionadas
  11. Usando o UART para impressão de depuração
  12. Economizando energia no MK ocioso

Então, vamos começar. No decorrer do texto, haverá todos os tipos de nomes e constantes de registros característicos do MK aplicado. Isso não é arduino, aqui, infelizmente, você precisa ler a folha de dados no MK. Caso contrário, você não entenderá o significado de todos esses registros e como poderá alterar os valores deles. Mas a estrutura do programa como um todo permanecerá a mesma.

Primeiro, conecte o botão ao MK


Este é o mais simples. Conectamos um contato ao pé do MK, o segundo contato do botão ao chão. Para que o botão funcione, você precisará ativar o resistor pull-up no MK. Ele conectará o botão através da resistência ao barramento de + 5V. Para fazer isso é bastante simples:

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

Da mesma forma, todas as entradas digitais que são controladas por uma falta à terra (acopladores ópticos, linhas de sinal do transceptor, sinal PTT) são puxadas para o barramento de + 5V. Às vezes, é melhor soldar fisicamente um resistor menor (por exemplo, 10k) entre a entrada do MK e o barramento de + 5V, mas a discussão desse problema está além do escopo do artigo. Como todos os sinais de entrada no projeto raramente alteram os valores, eles são desviados para o solo por 10 capacitores de nanofarad para proteger contra interferências.

Agora, temos 1 lógico na entrada PB2 e, quando você pressiona o botão, será lógico 0. Quando você pressiona \ pressiona, precisa rastrear o salto do contato do botão, verificando se o nível do sinal não mudou ao longo do tempo, digamos 50 milissegundos. Isso é feito no programa assim:

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

Agora conecte o squeaker


Emite um sinal de confirmação de áudio de que a antena está gravada na memória MK. Um tweeter é apenas um elemento piezoelétrico. É conectado através de uma pequena resistência ao pé MK e por um segundo contato a + 5V. Para que esta campainha funcione, você deve primeiro configurar o pé MK para gerar dados.

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

Agora pode ser usado. Para fazer isso, uma pequena função é escrita que usa atrasos de tempo para mudar as pernas do MK de 0 para 1 e vice-versa. Comutar com os atrasos necessários, é possível gerar um sinal de áudio de 4 kHz com uma duração de cerca de um quarto de segundo na saída MK, que é o som do elemento piezoelétrico.

 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 as funções de atraso funcionem, não se esqueça de incluir o arquivo de cabeçalho e definir a velocidade do processador constante. É igual à frequência do ressonador de quartzo conectado ao MK. No meu caso, havia quartzo de 16MHz.

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

Conectamos às antenas de comutação do relé MK


Aqui você só precisa configurar o pé MK para trabalhar na saída. Um relé de palheta é conectado a essa perna através de um transistor amplificador de maneira padrão.
 void init_tuner_relay(void) { PORTB &= ~(1 << PB1); // relay DDRB |= (1 << PB1); // output PORTB &= ~(1 << PB1); } 

Conexão de exibição


Usei um monitor LCD de 1601 linhas e 16 caracteres, extraído de hardware antigo. Ele usa o conhecido controlador HD44780, para o gerenciamento do qual muitas bibliotecas estão disponíveis na rede. Alguém gentil escreveu uma biblioteca leve de controle de exibição, que eu usei no projeto. A configuração da biblioteca é reduzida para indicar no arquivo de cabeçalho HD44780_Config.h o número de pernas MK conectadas aos pinos de exibição desejados. Eu apliquei uma conexão de vídeo em 4 linhas de dados.

 #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 

Um recurso da minha instância de exibição era que uma linha na tela era exibida como duas linhas de 8 caracteres; portanto, um buffer intermediário de tela foi criado no programa para um trabalho mais conveniente com a tela.

 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); } } 

A função update_display () permite exibir o conteúdo do buffer na tela. Os valores de bytes no buffer são os códigos ASCII dos caracteres de saída.

Depurar a saída de impressão na porta COM


O MK possui o UART e eu o usei para depurar o programa. Ao conectar o MK ao computador, basta lembrar que os níveis de sinal na saída do MK estão no padrão TTL, e não no RS232, portanto, é necessário um adaptador simples. Eu usei um adaptador USB-Serial, semelhante totalmente no aliexpress. Qualquer programa terminal, por exemplo, do arduino, é adequado para a leitura de dados. Código de configuração da porta 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; 

Após configurar o fluxo de saída, você pode usar o printf usual para imprimir na porta:
 printf( "Start flag after reset = %u\r\n", mcusr_mirror ); 

O programa usa a impressão de números reais. As bibliotecas regulares não suportam esse modo de saída, então tive que conectar uma biblioteca completa ao vincular um projeto. É verdade que aumenta seriamente a quantidade de código, mas eu tinha um grande suprimento de memória, portanto não era crítico. Nas opções do vinculador, você precisa especificar a linha:

-Wl,-u,vfprintf -lprintf_flt

Trabalhar com timer e interrupções


Para contar intervalos de tempo em um programa, é importante ter um contador de tempo. É necessário rastrear que o botão seja pressionado por mais de 3 segundos e, portanto, é necessário lembrar as novas configurações na memória não volátil. Para medir o tempo no estilo AVR, você precisa configurar o contador de pulsos do gerador de relógio e a interrupção que será executada quando o contador atingir o valor definido. Eu ajusto o timer para que ele produza uma interrupção cerca de uma vez por segundo. O próprio manipulador de interrupção conta o número de segundos decorridos. A variável timer_on controla a ativação / desativação do timer. É importante não esquecer de declarar todas as variáveis ​​atualizadas no manipulador de interrupções como voláteis; caso contrário, o compilador pode "otimizá-las" e o programa não 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++; } 

O valor de pass_secs é verificado no loop principal do programa. Quando o botão é pressionado, o timer inicia e, no ciclo principal do programa, o valor do timer é verificado enquanto o botão é pressionado. Se esse valor exceder 3 segundos, a EEPROM será gravada e o temporizador será interrompido.

Por fim, mas não menos importante, depois de todas as inicializações, você precisa ativar as interrupções com o comando sei ().

Medição do nível da ALC


É feito usando o conversor analógico-digital (ADC) embutido. Eu medi a tensão na entrada do ADC7. Deve-se lembrar que você pode medir um valor de 0 a 2,5V. e minha tensão de entrada foi de -4V a 0V. Portanto, conectei o MK através do divisor de tensão mais simples dos resistores, para que o nível de tensão na entrada do MK estivesse em um determinado nível. Além disso, como eu não precisava de alta precisão, apliquei uma conversão de 8 bits (basta ler dados apenas do registro ADCH). Como fonte de referência, usei um íon interno a 2,56V, isso simplifica levemente os cálculos. Para que o ADC funcione, certifique-se de conectar um capacitor de 0,1 µF ao pé REF no chão.

A ADC, no meu caso, funciona continuamente, relatando o final da conversão chamando a interrupção ADC_vect. É uma boa prática calcular a média dos valores de vários ciclos de conversão para reduzir o erro. No meu caso, deduzo a média de 2500 transformações. Todo o código ADC é assim:

 //        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 é uma memória não volátil no MK. É conveniente usá-lo para armazenar todos os tipos de configurações, valores de correção, etc. No nosso caso, é usado apenas para armazenar a antena selecionada para o alcance desejado. Para esse propósito, uma matriz de 16 bytes é alocada na EEPROM. Mas você pode acessá-lo através de funções especiais definidas no arquivo de cabeçalho avr / eeprom.h. Na inicialização, o MK lê informações sobre as configurações salvas na RAM e liga a antena desejada, dependendo da faixa atual. Quando o botão é pressionado por um longo período, um novo valor é gravado na memória, acompanhado por um sinal sonoro. Ao escrever na EEPROM, as interrupções são desativadas por precaução. Código de inicialização da memória:

 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 } } 

Um trecho do código de processamento para pressionar um botão por 3 segundos e gravar na memória:

  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 o Watchdog


Não é segredo que, sob condições de forte interferência eletromagnética, o MK pode congelar. Quando o rádio está em operação, há tanta interferência que “os ferros começam a falar”, portanto, é necessário garantir uma reinicialização cuidadosa do MK em caso de paralisação. Um cronômetro de vigilância serve a esse propósito. Usá-lo é muito simples. Primeiro, inclua o arquivo de cabeçalho avr / wdt.h no projeto. No início do programa, após concluir todas as configurações, é necessário iniciar o timer chamando a função wdt_enable (WDTO_2S) e lembre-se de redefini-lo periodicamente chamando wdt_reset (), caso contrário, reiniciará o próprio MK. Para depuração, a fim de descobrir por que o MK foi reiniciado, você pode usar o valor do registro especial do MCUSR, cujo valor pode ser lembrado e, em seguida, enviado para a impressão de depuração.

 //        //     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 ); 

Economizando energia para os amantes do meio ambiente


Enquanto MK não estiver ocupado com nada, ele pode adormecer e aguardar a próxima interrupção. Nesse caso, um pouco de energia elétrica é economizada. Um pouco, mas por que não usá-lo em um projeto. Além disso, é muito simples. Inclua o arquivo de cabeçalho avr / sleep.h. O corpo do programa consiste em um loop infinito no qual você precisa chamar a função sleep_cpu (), após o que o MC adormece um pouco e o loop principal para até a próxima interrupção. Eles ocorrem durante a operação do timer e do ADC, portanto o MK não dorme por muito tempo. O modo de hibernação é determinado quando o MK é inicializado chamando duas funções:

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

Por enquanto é tudo. Eu mudei; ele funciona com sucesso na minha estação de rádio amador sem falhas. Espero que o material fornecido seja útil para iniciantes.

73 de R2AJP

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


All Articles