带MK控制的自动天线开关

图片

在业余无线电实践中,有时需要在微控制器上做一些事情。 如果您不是一直都在做这种工艺,那么您就必须长时间搜索必要的电路解决方案和适用于MK的库,以使您快速解决问题。 最近,我想做一个自动天线开关。 在此过程中,我不得不在一个紧凑项目中使用许多Atmega MK功能。 那些开始学习AVR,从arduino切换或偶尔编程MK的人可能是我在项目中使用的有用代码。

我认为天线开关是一种将天线自动连接到收发器的设备,最适合短波的工作范围。 我有两个天线:倒V型和地平面,它们连接到MFJ天线调谐器,可以在其中进行远程切换。 我想更换一个品牌的手动开关MFJ。

图片

为了进行天线的操作切换,将一个按钮连接到MK。 我对其进行了修改,以记住每个范围的首选天线:当您按下按钮3秒钟以上时,设备下次启动时会自动记住并正确选择选定的天线。 有关当前范围,所选天线及其调谐状态的信息显示在单行LCD显示屏上。

您可以通过不同的方式找出收发器当前正在工作的范围:可以测量信号频率,可以通过CAT接口接收数据,但是对我来说,最简单的方法是使用YAESU收发器接口连接外部放大器。 它有4条信号线(以二进制代码表示),指示当前范围。 它们提供0至5伏的逻辑信号,并且可以通过一对终端电阻器连接到MK的支路。

图片

这还不是全部。 在传输模式下,PTT和ALC信号通过同一接口传输。 这是关于打开发射机电源(将其拉到地面)的逻辑信号,以及关于自动发射机电源控制系统工作的从0到-4V的模拟信号。 我还决定对其进行测量并以传输模式显示在LCD上。

另外,MFJ调谐器可以将正在调谐和已调谐天线的信号发送到遥控器。 为此,公司控制面板MFJ具有两个控制LED。 我连接了光耦合器,而不是LED,并将信号从它们发送到MK,这样我就可以在一个显示器上看到所有信息。 成品设备看起来像这样。

图片

简要介绍一下自制的一切。 现在介绍软件部分。 该代码用Atmel Studio编写(可从Atmel网站免费下载)。 面向初学者的项目演示了使用流行的Atmega8 MK的以下功能:

  1. 连接按钮
  2. 连接来自收发器和调谐器的数字信号的线路输入
  3. 连接天线开关的控制输出
  4. 连接单行液晶显示器
  5. 蜂鸣器连接和声音输出
  6. ADC模拟输入线连接和电压测量
  7. 使用中断
  8. 使用计时器来计算按下按钮的时间
  9. 使用看门狗
  10. 使用非易失性存储器存储选定的天线
  11. 使用UART进行调试打印
  12. 节省闲置MK的能量

因此,让我们开始吧。 在本文中,将使用所应用的MK的各种寄存器名称和常数。 这不是arduino,不幸的是,在这里您必须阅读MK上的数据表。 否则,您将不了解所有这些寄存器的含义以及如何更改它们的值。 但是该程序的整体结构将保持不变。

首先,将按钮连接到MK


这是最简单的。 我们将一个触点连接到MK脚,将第二个按钮触点连接到地面。 为了使按钮起作用,您需要打开MK中的上拉电阻。 他将通过电阻将按钮连接到+ 5V总线。 要做到这一点很简单:

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

同样,所有受接地故障控制的数字输入(光耦合器,收发器的信号线,PTT信号)均被拉至+ 5V总线。 有时最好在MK的输入和+ 5V总线之间物理焊接一个较小的电阻(例如10k),但有关此问题的讨论不在本文讨论范围之内。 由于项目中的所有输入信号很少改变值,因此它们被10纳法拉电容器并联到地,以防止干扰。

现在我们在输入PB2处有逻辑1,当您按下按钮时,它将为逻辑0。当您按下\按下时,您需要跟踪按钮的触点弹跳,检查信号电平是否随时间(例如50毫秒)没有变化。 可以在这样的程序中完成:

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

现在连接尖叫声


它将发出音频确认信号,表明天线已记录在MK存储器中。 高音扬声器只是压电元件。 它通过一个小电阻连接到MK脚,并通过第二个触点连接到+ 5V。 为了使该蜂鸣器工作,必须首先配置MK脚以输出数据。

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

现在可以使用了。 为此,编写了一个小函数,该函数使用时间延迟将MK支路从0切换到1,反之亦然。 以必要的延迟进行切换可以在MK输出处生成持续时间约为四分之一秒的4 kHz音频信号,这是压电元件的声音。

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

为了使延迟功能起作用,请不要忘记包含头文件并设置处理器速度常数。 它等于连接到MK的石英谐振器的频率。 就我而言,有16MHz石英。

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

我们连接到MK中继开关天线


在这里,您只需要配置MK支脚即可继续工作。 簧片继电器通过放大晶体管以标准方式连接到该脚。
 void init_tuner_relay(void) { PORTB &= ~(1 << PB1); // relay DDRB |= (1 << PB1); // output PORTB &= ~(1 << PB1); } 

显示连接


我使用了从旧硬件中提取的1601单行16字符LCD显示器。 它使用著名的HD44780控制器进行管理,网络上有许多可用的库。 一位好心的人写了一个轻量级的显示控件库,我在项目中使用了它。 库的设置减少为在头文件HD44780_Config.h中指示连接到所需显示引脚的MK支路的数量。 我在4条数据线上应用了显示连接。

 #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 

我的显示实例的一个功能是,屏幕上的一行显示为两行,每行8个字符,因此在程序中创建了一个中间屏幕缓冲区,以便更方便地使用屏幕。

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

使用update_display()函数可以在屏幕上显示缓冲区的内容。 缓冲区中的字节值是输出字符的ASCII码。

调试打印输出到COM端口


MK有UART,我用它来调试程序。 将MK连接到计算机时,只需记住MK输出上的信号电平为TTL标准,而不是RS232,因此您需要一个简单的适配器。 我使用了USB串行适配器,与速卖通完全类似。 任何来自arduino的终端程序都适合读取数据。 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; 

设置输出流后,可以使用常规的printf打印到端口:
 printf( "Start flag after reset = %u\r\n", mcusr_mirror ); 

该程序使用实数打印。 常规库不支持此输出模式,因此在链接项目时必须连接完整的库。 是的,它确实增加了代码量,但是我拥有大量的内存,因此这不是很关键。 在链接器选项中,您需要指定以下行:

-Wl,-u,vfprintf -lprintf_flt

使用计时器和中断


要计算程序中的时间间隔,重要的是要有一个时间计数器。 需要跟踪按下按钮的时间超过3秒钟,因此,您需要记住非易失性存储器中的新设置。 要以AVR风格测量时间,您需要配置时钟发生器的脉冲计数器以及当计数器达到设置值时将执行的中断。 我设置了计时器,使其大约每秒产生一次中断。 中断处理程序本身会计算经过的秒数。 timer_on变量控制计时器的开/关。 重要的是不要忘记将在中断处理程序中更新的所有变量声明为volatile,否则编译器可以“优化”它们,并且程序将无法运行。

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

在程序的主循环中检查passed_secs的值。 当按下按钮时,计时器启动,然后在主程序循环中在按下按钮时检查计时器值。 如果该值超过3秒,则将写入EEPROM,并且计时器停止。

最后但并非最不重要的一点是,在所有初始化之后,您需要使用sei()命令启用中断。

ALC液位测量


它是使用内置的模数转换器(ADC)制成的。 我测量了ADC7输入端的电压。 必须记住,您可以测量0至2.5V的值。 我的输入电压是从-4V到0V 因此,我通过电阻器上最简单的分压器连接了MK,因此MK输入端的电压电平处于给定电平。 此外,我不需要高精度,因此我进行了8位转换(仅从ADCH寄存器读取数据就足够了)。 作为参考源,我使用了一个2.56V的内部离子,这稍微简化了计算。 为了使ADC正常工作,请确保在地面上的REF脚上连接一个0.1 µF电容器。

就我而言,ADC连续工作,通过调用ADC_vect中断报告转换结束。 最好对多个转换周期取平均值,以减少错误。 就我而言,我推断出平均2500次转换。 所有ADC代码如下所示:

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


这是MK中的非易失性存储器。 使用它来存储各种设置,校正值等非常方便。 在我们的情况下,它仅用于存储选定天线的所需范围。 为此,在EEPROM中分配了一个16字节的阵列。 但是您可以通过头文件avr / eeprom.h中定义的特殊功能来访问它。 在启动时,MK会根据当前范围将有关已保存设置的信息读入RAM并打开所需的天线。 长时间按下按钮时,新值会被记录在存储器中,并伴有声音信号。 写入EEPROM时,为了以防万一,禁止中断。 内存初始化代码:

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

按下按钮3秒钟并写入内存的处理代码片段:

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

使用看门狗


在强烈的电磁干扰条件下,MK可能会冻结,这已不是秘密。 当收音机工作时,会出现干扰,以至于“铁杆开始说话”,因此,在挂起时,您需要确保小心重启MK。 看门狗定时器用于此目的。 使用它非常简单。 首先,在项目中包含avr / wdt.h头文件。 在程序开始时,完成所有设置后,您需要通过调用wdt_enable(WDTO_2S)函数来启动计时器,然后记住要通过调用wdt_reset()定期对其进行重置,否则它将重新启动MK本身。 为了进行调试以便找出重新启动MK的原因,可以使用特殊的MCUSR寄存器的值,可以记住该值,然后将其输出到调试打印。

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

为环保爱好者节省能源


当MK不忙于任何事情时,他可以入睡并等待下一次中断。 在这种情况下,可以节省少量电能。 琐事,但为什么不在项目中使用它呢? 而且,这非常简单。 包括avr / sleep.h头文件。 该程序的主体由一个无限循环组成,您需要在其中调用sleep_cpu()函数,此后MC会稍微入睡,并且主循环会停止直到下一个中​​断发生。 它们在定时器和ADC的操作过程中发生,因此MK不会长时间休眠。 通过调用以下两个函数来初始化MK时,将确定休眠模式:

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

现在就这些了。 我进行了切换;它可以在我的业余广播电台成功运行而没有失败。 我希望所提供的材料对初学者有用。

73 de R2AJP

Source: https://habr.com/ru/post/zh-CN405581/


All Articles