Automatischer Antennenschalter mit MK-Steuerung

Bild

In der Amateurfunkpraxis besteht manchmal die Notwendigkeit, etwas am Mikrocontroller zu tun. Wenn Sie diese Art des Bastelns nicht ständig ausführen, müssen Sie lange Zeit die erforderliche Schaltungslösung und geeignete Bibliotheken für MK googeln, damit Sie das Problem schnell lösen können. Vor kurzem wollte ich einen automatischen Antennenschalter machen. Dabei musste ich viele der Atmega MK-Funktionen in einem kompakten Projekt verwenden. Diejenigen, die anfangen, AVR zu studieren, von Arduino zu wechseln oder gelegentlich MK zu programmieren, können nützliche Codeteile sein, die von mir im Projekt verwendet werden.

Ich stellte mir den Antennenschalter als ein Gerät vor, das die Antenne automatisch mit dem Transceiver verbindet, der für den Arbeitsbereich von Kurzwellen am besten geeignet ist. Ich habe zwei Antennen: Inverted V und Ground Plane. Sie sind mit dem MFJ-Antennentuner verbunden, in dem sie ferngeschaltet werden können. Es gibt einen Marken-Handschalter MFJ, den ich ersetzen wollte.

Bild

Zum betrieblichen Schalten von Antennen ist eine Taste mit dem MK verbunden. Ich habe es angepasst, um die bevorzugte Antenne für jeden Bereich zu speichern: Wenn Sie die Taste länger als 3 Sekunden drücken, wird die ausgewählte Antenne gespeichert und nach dem nächsten Einschalten des Geräts automatisch korrekt ausgewählt. Informationen über den aktuellen Bereich, die ausgewählte Antenne und den Status ihrer Abstimmung werden auf einem einzeiligen LCD-Display angezeigt.

Sie können auf verschiedene Arten herausfinden, an welchem ​​Bereich der Transceiver gerade arbeitet: Sie können die Signalfrequenz messen, Sie können Daten über die CAT-Schnittstelle empfangen, aber für mich ist es am einfachsten, die YAESU-Transceiver-Schnittstelle zum Anschließen eines externen Verstärkers zu verwenden. Es hat 4 Signalleitungen im Binärcode, die den aktuellen Bereich anzeigen. Sie geben ein logisches Signal von 0 bis 5 Volt und können über ein Paar Abschlusswiderstände mit den Beinen des MK verbunden werden.

Bild

Das ist noch nicht alles. Im Übertragungsmodus werden die PTT- und ALC-Signale über dieselbe Schnittstelle übertragen. Dies ist ein logisches Signal zum Einschalten des Senders (auf den Boden gezogen) und ein analoges Signal von 0 bis -4 V über den Betrieb des Leistungssteuerungssystems des automatischen Senders. Ich habe mich auch entschlossen, es zu messen und im Übertragungsmodus auf dem LCD anzuzeigen.

Darüber hinaus kann der MFJ-Tuner Signale an die Fernbedienung senden, dass er abstimmt und die Antenne abgestimmt ist. Zu diesem Zweck verfügt das Unternehmensbedienfeld MFJ über zwei Steuer-LEDs. Anstelle von LEDs habe ich die Optokoppler angeschlossen und ein Signal von ihnen an den MK gesendet, damit ich alle Informationen auf einem Display sehen konnte. Das fertige Gerät sieht so aus.

Bild

Kurz über hausgemacht wie alles. Nun zum Software-Teil. Der Code ist in Atmel Studio geschrieben (kostenloser Download von der Atmel-Website). Das Projekt für Anfänger demonstriert die folgenden Funktionen der Verwendung des beliebten Atmega8 MK:

  1. Verbindungstaste
  2. Schließen Sie den Leitungseingang für das digitale Signal von Transceiver und Tuner an
  3. Anschließen des Steuerausgangs des Antennenschaltrelais
  4. Anschließen eines einzeiligen LCD-Displays
  5. Summeranschluss und Tonausgabe
  6. Analoge ADC-Eingangsleitung und Spannungsmessung
  7. Interrupts verwenden
  8. Verwenden eines Timers zum Zählen der Zeit, die eine Taste gedrückt wird
  9. Watchdog verwenden
  10. Verwenden eines nichtflüchtigen Speichers zum Speichern ausgewählter Antennen
  11. Verwenden von UART für den Debug-Druck
  12. Energie sparen im Leerlauf MK

Also fangen wir an. Im Verlauf des Textes gibt es alle Arten von Registernamen und Konstanten, die für das angewendete MK charakteristisch sind. Dies ist kein Arduino, hier muss man leider das Datenblatt auf MK lesen. Andernfalls verstehen Sie nicht, was all diese Register bedeuten und wie Sie ihre Werte ändern können. Die Struktur des gesamten Programms bleibt jedoch unverändert.

Verbinden Sie zuerst den Knopf mit dem MK


Das ist das einfachste. Wir verbinden einen Kontakt mit dem MK-Fuß, den zweiten Knopfkontakt mit dem Boden. Damit die Taste funktioniert, müssen Sie den Pull-up-Widerstand in MK einschalten. Er verbindet den Knopf über den Widerstand mit dem + 5V-Bus. Dies zu tun ist ganz einfach:

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

Ebenso werden alle digitalen Eingänge, die durch einen Erdschluss gesteuert werden (Optokoppler, Signalleitungen vom Transceiver, PTT-Signal), auf den + 5V-Bus gezogen. Manchmal ist es besser, einen so kleineren Widerstand (z. B. 10k) zwischen dem Eingang des MK und dem + 5V-Bus physikalisch zu löten, aber die Diskussion dieses Problems geht über den Rahmen des Artikels hinaus. Da alle Eingangssignale im Projekt selten Werte ändern, werden sie zum Schutz vor Interferenzen von 10 Nanofarad-Kondensatoren auf den Boden geleitet.

Jetzt haben wir die logische 1 am Eingang PB2, und wenn Sie die Taste drücken, ist sie logisch 0. Wenn Sie \ drücken, müssen Sie den Kontaktsprung der Taste verfolgen und überprüfen, ob sich der Signalpegel im Laufe der Zeit nicht geändert hat, z. B. 50 Millisekunden. Dies geschieht im Programm wie folgt:

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

Schließen Sie nun den Quietscher an


Es wird ein Audio-Bestätigungssignal ausgegeben, dass die Antenne im MK-Speicher aufgezeichnet ist. Ein Hochtöner ist nur ein piezoelektrisches Element. Es ist durch einen kleinen Widerstand mit dem MK-Fuß und durch einen zweiten Kontakt mit +5 V verbunden. Damit dieser Summer funktioniert, müssen Sie zuerst den MK-Fuß für die Ausgabe von Daten konfigurieren.

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

Jetzt kann es verwendet werden. Zu diesem Zweck wird eine kleine Funktion geschrieben, die Zeitverzögerungen verwendet, um die MK-Beine von 0 auf 1 und umgekehrt umzuschalten. Durch Umschalten mit den erforderlichen Verzögerungen kann am MK-Ausgang, dem Klang des piezoelektrischen Elements, ein 4-kHz-Audiosignal mit einer Dauer von etwa einer Viertelsekunde erzeugt werden.

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

Vergessen Sie nicht, die Header-Datei einzuschließen und die Prozessorgeschwindigkeitskonstante einzustellen, damit die Verzögerungsfunktionen funktionieren. Sie entspricht der Frequenz des an den MK angeschlossenen Quarzresonators. In meinem Fall gab es 16 MHz Quarz.

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

Wir schließen an die MK-Relais-Schaltantennen an


Hier müssen Sie nur den MK-Fuß konfigurieren, um auf dem Weg nach draußen zu arbeiten. Ein Reed-Relais ist in üblicher Weise über einen Verstärkungstransistor mit diesem Zweig verbunden.
 void init_tuner_relay(void) { PORTB &= ~(1 << PB1); // relay DDRB |= (1 << PB1); // output PORTB &= ~(1 << PB1); } 

Verbindung anzeigen


Ich habe ein einzeiliges 16-Zeichen-LCD-Display mit 1601 verwendet, das aus alter Hardware extrahiert wurde. Es verwendet den bekannten HD44780-Controller, für dessen Verwaltung viele Bibliotheken im Netzwerk verfügbar sind. Eine freundliche Person hat eine leichtgewichtige Display-Steuerungsbibliothek geschrieben, die ich im Projekt verwendet habe. Das Einrichten der Bibliothek reduziert sich darauf, in der Header-Datei HD44780_Config.h die Anzahl der MK-Beine anzugeben, die mit den gewünschten Anzeigestiften verbunden sind. Ich habe eine Displayverbindung über 4 Datenleitungen angelegt.

 #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 

Ein Merkmal meiner Anzeigeinstanz war, dass eine Zeile auf dem Bildschirm als zwei Zeilen mit 8 Zeichen angezeigt wurde, sodass im Programm ein Zwischenbildschirmpuffer erstellt wurde, um die Arbeit mit dem Bildschirm zu vereinfachen.

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

Mit der Funktion update_display () können Sie den Inhalt des Puffers auf dem Bildschirm anzeigen. Die Bytewerte im Puffer sind die ASCII-Codes der Ausgabezeichen.

Debuggen der Druckausgabe an den COM-Anschluss


MK hat UART und ich habe es verwendet, um das Programm zu debuggen. Wenn Sie den MK an den Computer anschließen, müssen Sie nur berücksichtigen, dass die Signalpegel am MK-Ausgang dem TTL-Standard und nicht RS232 entsprechen. Daher benötigen Sie einen einfachen Adapter. Ich habe einen USB-Serial-Adapter verwendet, ähnlich wie bei aliexpress. Jedes Terminalprogramm, zum Beispiel von Arduino, ist zum Lesen von Daten geeignet. Setup-Code für den UART-Port:

 #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; 

Nach dem Einrichten des Ausgabestreams können Sie mit dem üblichen printf an den Port drucken:
 printf( "Start flag after reset = %u\r\n", mcusr_mirror ); 

Das Programm verwendet das Drucken von reellen Zahlen. Normale Bibliotheken unterstützen diesen Ausgabemodus nicht, daher musste ich beim Verknüpfen eines Projekts eine vollständige Bibliothek verbinden. Es stimmt, es erhöht die Menge an Code ernsthaft, aber ich hatte einen großen Speichervorrat, also war es unkritisch. In den Linker-Optionen müssen Sie die Zeile angeben:

-Wl,-u,vfprintf -lprintf_flt

Arbeiten Sie mit Timer und Interrupts


Um Zeitintervalle in einem Programm zu zählen, ist es wichtig, einen Zeitzähler zu haben. Es muss nachverfolgt werden, dass die Taste länger als 3 Sekunden gedrückt wurde. Daher müssen Sie sich die neuen Einstellungen im nichtflüchtigen Speicher merken. Um die Zeit im AVR-Stil zu messen, müssen Sie den Impulszähler des Taktgenerators und den Interrupt konfigurieren, der ausgeführt wird, wenn der Zähler den eingestellten Wert erreicht. Ich habe den Timer so eingestellt, dass er ungefähr einmal pro Sekunde einen Interrupt erzeugt. Der Interrupt-Handler selbst zählt die Anzahl der verstrichenen Sekunden. Die Variable timer_on steuert das Ein- und Ausschalten des Timers. Es ist wichtig, nicht zu vergessen, alle im Interrupt-Handler aktualisierten Variablen als flüchtig zu deklarieren, da der Compiler sie sonst „optimieren“ kann und das Programm nicht funktioniert.

 //   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++; } 

Der Wert von übergeben_secs wird in der Hauptschleife des Programms überprüft. Wenn die Taste gedrückt wird, startet der Timer und im Hauptprogrammzyklus wird der Timerwert überprüft, während die Taste gedrückt wird. Wenn dieser Wert 3 Sekunden überschreitet, wird das EEPROM geschrieben und der Timer stoppt.

Last but not least müssen Sie nach allen Initialisierungen Interrupts mit dem Befehl sei () aktivieren.

ALC-Füllstandsmessung


Es wird mit dem eingebauten Analog-Digital-Wandler (ADC) hergestellt. Ich habe die Spannung am Eingang des ADC7 gemessen. Es muss beachtet werden, dass Sie einen Wert von 0 bis 2,5 V messen können. und meine Eingangsspannung war von -4V bis 0V. Daher habe ich den MK über den einfachsten Spannungsteiler an den Widerständen angeschlossen, so dass der Spannungspegel am MK-Eingang auf einem bestimmten Pegel lag. Außerdem brauchte ich keine hohe Genauigkeit, also habe ich eine 8-Bit-Konvertierung angewendet (es reicht aus, nur Daten aus dem ADCH-Register zu lesen). Als Referenzquelle habe ich ein internes Ion bei 2,56 V verwendet, was die Berechnungen etwas vereinfacht. Damit der ADC funktioniert, müssen Sie einen 0,1-µF-Kondensator an den REF-Fuß am Boden anschließen.

In meinem Fall arbeitet ADC kontinuierlich und meldet das Ende der Konvertierung durch Aufrufen des Interrupts ADC_vect. Es wird empfohlen, die Werte mehrerer Konvertierungszyklen zu mitteln, um den Fehler zu verringern. In meinem Fall schließe ich den Durchschnitt von 2500 Transformationen ab. Der gesamte ADC-Code sieht folgendermaßen aus:

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

EEPROM verwenden


Dies ist ein nichtflüchtiger Speicher in MK. Es ist praktisch, es zum Speichern aller Arten von Einstellungen, Korrekturwerten usw. zu verwenden. In unserem Fall wird es nur verwendet, um die ausgewählte Antenne für den gewünschten Bereich zu speichern. Zu diesem Zweck wird im EEPROM ein 16-Byte-Array zugewiesen. Sie können jedoch über spezielle Funktionen darauf zugreifen, die in der Header-Datei avr / eeprom.h definiert sind. Beim Start liest der MK Informationen über die gespeicherten Einstellungen in den RAM und schaltet die gewünschte Antenne je nach aktuellem Bereich ein. Wenn die Taste längere Zeit gedrückt wird, wird ein neuer Wert im Speicher aufgezeichnet, der von einem Tonsignal begleitet wird. Beim Schreiben in das EEPROM werden Interrupts für alle Fälle deaktiviert. Speicherinitialisierungscode:

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

Ein Ausschnitt aus dem Verarbeitungscode zum Drücken einer Taste für 3 Sekunden und zum Schreiben in den Speicher:

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

Watchdog verwenden


Es ist kein Geheimnis, dass der MK unter Bedingungen starker elektromagnetischer Störungen einfrieren kann. Wenn das Radio in Betrieb ist, gibt es solche Störungen, dass „die Eisen anfangen zu sprechen“. Daher müssen Sie im Falle eines Hängens einen sorgfältigen Neustart des MK sicherstellen. Ein Watchdog-Timer dient diesem Zweck. Die Verwendung ist sehr einfach. Fügen Sie zunächst die Header-Datei avr / wdt.h in das Projekt ein. Zu Beginn des Programms müssen Sie nach Abschluss aller Einstellungen den Timer durch Aufrufen der Funktion wdt_enable (WDTO_2S) starten und dann durch Aufrufen von wdt_reset () regelmäßig zurücksetzen, da sonst der MK selbst neu gestartet wird. Zum Debuggen können Sie den Wert des speziellen MCUSR-Registers verwenden, dessen Wert gespeichert und dann an den Debug-Druck ausgegeben werden kann, um herauszufinden, warum der MK neu gestartet wurde.

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

Energie sparen für Umweltliebhaber


Während MK mit nichts beschäftigt ist, kann er einschlafen und auf die nächste Unterbrechung warten. In diesem Fall wird ein wenig elektrische Energie gespart. Eine Kleinigkeit, aber warum nicht in einem Projekt verwenden. Darüber hinaus ist es sehr einfach. Fügen Sie die Header-Datei avr / sleep.h hinzu. Der Hauptteil des Programms besteht aus einer Endlosschleife, in der Sie die Funktion sleep_cpu () aufrufen müssen. Danach schläft der MC ein wenig ein und die Hauptschleife stoppt, bis der nächste Interrupt auftritt. Sie treten während des Betriebs des Timers und des ADC auf, sodass MK lange Zeit nicht schläft. Der Ruhezustand wird festgelegt, wenn der MK durch Aufrufen von zwei Funktionen initialisiert wird:

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

Das ist alles für jetzt. Ich habe den Wechsel vorgenommen, er funktioniert erfolgreich bei meinem Amateurfunk ohne Fehler. Ich hoffe, dass das bereitgestellte Material für Anfänger nützlich ist.

73 de R2AJP

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


All Articles