dxa_rotor_ctl/fw/main.c

512 lines
24 KiB
C
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* USB rotor card
*
* Created: 30.05.2022
* Update: 11.06.2022 00:34:00
* Author : R5CA
* Support only GS232B 450deg
*/
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
#define BAUD 9600
#define MYUBRR F_CPU/16/BAUD-1
volatile uint8_t uartRXBuf[16]; // Буфер для данных принятых по UART
volatile uint8_t uartRXBufNum = 0; // Кол-во принятых байт по UART
volatile uint8_t uartRXC = 0; // Флаг окончания приема данных по UART
#define ROTOR_DDR DDRD
#define ROTOR_PORT PORTD
#define ROTOR_CW PD5
#define ROTOR_CCW PD6
#define ROTOR_AZ_IN 0 // Канал АЦП измерения азимута
#define ROTOR_SPEED_DDR DDRB // DDR регулировки скорости
#define ROTOR_SPEED_PORT PORTB // Порт регулировки скорости
#define ROTOR_SPEED_PINNUM PB3 // Номер пина регулировки скорости(обязательно OC2A)
#define STOP 0
#define SLOW_START 1
#define ROTARY 2
#define SLOW_DOWN 3
#define SLOW_DOWN_AZ 20 // Расстояние начала плавной остановки
uint16_t EEMEM EEPcal0 = 0; // EEProm значение калибровки 0 градусов
uint16_t EEMEM EEPcal90 = 0; // EEProm значение калибровки 90 градусов
uint16_t EEMEM EEPcal180 = 0; // EEProm значение калибровки 180 градусов
uint16_t EEMEM EEPcal270 = 0; // EEProm значение калибровки 270 градусов
uint16_t EEMEM EEPcal360 = 0; // EEProm значение калибровки 360 градусов
uint16_t EEMEM EEPcal450 = 0; // EEProm значение калибровки 450 градусов
uint8_t EEMEM EEPspeed = 128; // EEProm скорость вращения
uint16_t cal0 = 0; // Значение калибровки 0 градусов
uint16_t cal90 = 0; // Значение калибровки 90 градусов
uint16_t cal180 = 0; // Значение калибровки 180 градусов
uint16_t cal270 = 0; // Значение калибровки 270 градусов
uint16_t cal360 = 0; // Значение калибровки 360 градусов
uint16_t cal450 = 0; // Значение калибровки 450 градусов
uint8_t rot_cw = 0; // Вращение по часовой
uint8_t rot_ccw = 0; // Вращение против часовой
volatile uint8_t timer = 0; // Переменная таймера задержки плавного старта/остановки
volatile uint16_t timer2 = 0; // Переменная таймера
void uartbuf_zero(void) // Обнуление буфера UART
{
for(uint8_t i=0; i<16; i++) // Обнуление буфера UART
{
uartRXBuf[i] = 0x00; // Обнуление буфера UART
}
}
void usart_init(unsigned int ubrr) // Инициализация USART
{
UBRR0H = (unsigned char) (ubrr >> 8); // Установка скорости
UBRR0L = (unsigned char) ubrr; // Установка скорости
UCSR0B = (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0); // Включаем приемник, передатчик и прерывание по приходу байта
UCSR0C = (1<<UCSZ00)|(1<<UCSZ01); //
}
void usart_tx(uint8_t data) // Передача байта по USART
{
while (!(UCSR0A & (1<<UDRE0))){}; // Проверяем ушел-ли предыдущий байт
UDR0 = data; // отправляем байт
}
static int uart_putchar(char c, FILE *stream);
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
static int uart_putchar(char c, FILE *stream)
{
if (c == '\n')
uart_putchar('\r', stream);
loop_until_bit_is_set(UCSR0A, UDRE0);
UDR0 = c;
return 0;
}
ISR(USART_RX_vect) // Прерывание по приходу байта в USART
{
uartRXBuf[uartRXBufNum] = UDR0; // Чтение байта
if(uartRXBuf[uartRXBufNum] == 0x0D) // Если пришел символ конца строки
{
uartRXBuf[uartRXBufNum] = 0x00; // То заменяем его на 0х00
uartRXC = 1; // Ставим флаг замершения приема строки
}
else if(uartRXBuf[uartRXBufNum] == 0x18) // Если надо в бут
{
wdt_disable(); // Ребутимся
wdt_enable(WDTO_15MS); // Ребутимся
while(1) {}; // Ребутимся
}
uartRXBufNum++; // Счетчик принятых символов
}
void adc_init(void) // Инициализация АЦП
{
ADMUX |= (1<<REFS0); // Опора - AVCC + конденсатор на AREF
ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1); // Включаем АЦП и устанавливаем делитель тактовой частоты
}
uint16_t adc_read(uint8_t ch) // Чтение значения АЦП
{
ch &= 0b00000111; // Выбор канала АЦП
ADMUX = (ADMUX & 0xF8)|ch; // Выбор канала АЦП
ADCSRA |= (1<<ADSC); // Старт измерения
while(ADCSRA & (1<<ADSC)){}; // Ожидание завершения измерения
return (ADC); // Возврат измеренного значения
}
uint16_t az_raw(void) // Определение текущего азимута
{
uint16_t adc_val; // Переменная для хранения значения АЦП
adc_val = adc_read(ROTOR_AZ_IN); // Чтение АЦП
int16_t az_raw_data; // Переменная для хранения азимута
az_raw_data = lround((adc_val - cal0)*450.0/(cal450-cal0)); // Приблизительный расчет по полной шкале 0-450
if(adc_val <= cal0) // Если считанное значение АЦП меньше 0-ой калибровки
{
return 0; // То возвращаем 0
}
else if(adc_val >= cal450) // Если считанное значение АЦП больше 450-ой калибровки
{
return 450; // То возвращаем 450
}
else if(az_raw_data > 0 && az_raw_data < 90) // Если приблизительный азимут от 0 до 90 градусов
{
az_raw_data = lround((adc_val - cal0)*90.0/(cal90-cal0)); // То делаем уточненный расчет по части шкалы в 90 градусов(0-90) с использованием дополнительных калибровок
return az_raw_data; // Возвращаем значчение азимута
}
else if(az_raw_data >= 90 && az_raw_data < 180) // Если приблизительный азимут от 90 до 180 градусов
{
az_raw_data = lround(90.0+((adc_val - cal90)*90.0/(cal180-cal90))); // То делаем уточненный расчет по части шкалы в 90 градусов(90-180) с использованием дополнительных калибровок
return az_raw_data; // Возвращаем значчение азимута
}
else if(az_raw_data >= 180 && az_raw_data < 270) // Если приблизительный азимут от 180 до 270 градусов
{
az_raw_data = lround(180.0+((adc_val - cal180)*90.0/(cal270-cal180))); // То делаем уточненный расчет по части шкалы в 90 градусов(180-270) с использованием дополнительных калибровок
return az_raw_data; // Возвращаем значчение азимута
}
else if(az_raw_data >= 270 && az_raw_data < 360) // Если приблизительный азимут от 270 до 360 градусов
{
az_raw_data = lround(270.0+((adc_val - cal270)*90.0/(cal360-cal270))); // То делаем уточненный расчет по части шкалы в 90 градусов(270-360) с использованием дополнительных калибровок
return az_raw_data; // Возвращаем значчение азимута
}
else if(az_raw_data >= 360 && az_raw_data <= 450) // Если приблизительный азимут от 360 до 450 градусов
{
az_raw_data = lround(360.0+((adc_val - cal360)*90.0/(cal450-cal360))); // То делаем уточненный расчет по части шкалы в 90 градусов(360-450) с использованием дополнительных калибровок
return az_raw_data; // Возвращаем значчение азимута
}
else
{
return az_raw_data;
}
}
void set_az(uint16_t az_r, uint16_t az_n) // Расчет кратчайшего пути для поворота антенны
{
int16_t cw; // Переменная для длины пути по часовой стрелке
int16_t ccw; // Переменная для длины пути против часовой стрелки
ccw = az_r - az_n; // Расчет пути против часовой стрелки
if(ccw < 0) // Расчет пути против часовой стрелки
{
ccw += 360; // Расчет пути против часовой стрелки
}
cw = az_n - az_r; // Расчет пути по часовой стрелке
if(cw < 0) // Расчет пути по часовой стрелке
{
cw += 360; // Расчет пути по часовой стрелке
}
if(cw < ccw) // Если по часовой короче, чем против
{
if(cw + az_r <= 450) // и получившийся азимут не выходит за возможности редуктора
{
rot_cw = 1; // То вращаем по часовой стрелке
}
else // Иначе
{
rot_ccw = 1; // против часовой стрелки
}
}
else // Если против часовой короче, чем по
{
if((int16_t)az_r - (int16_t)ccw > 0) // и получившийся азимут не выходит за возможности редуктора
{
rot_ccw = 1; // То вращаем против часовой
}
else // Иначе
{
rot_cw = 1; // По часовой
}
}
}
void pwm_init(void) // Инициализация ШИМ
{
TCCR2A |= (1<<COM2A1)|(1<<WGM21)|(1<<WGM20); // OC2A, Fast PWM
TCCR2B |= (1<<CS20); // Установка делителя тактовой частоты
}
ISR(TIMER0_OVF_vect) // Прерывание по переполнению таймера 0
{
if(timer < 255)
{
timer++;
}
if(timer2 > 0)
{
timer2--;
}
}
void timer0_stop(void) // Остановка таймера 0
{
TCCR0B &= ~(1<<CS02); // Отключаем тактирование
TIMSK0 &= ~(1<<TOIE0); // Запрещаем прерывание
timer = 0; // Обнуляем переменную
}
void timer0_start(void) // Запуск таймера 0
{
TCCR0B |= (1<<CS02); // Включаем тактирование и устанавливаем делитель тактовой частоты
TIMSK0 |= (1<<TOIE0); // Разрешаем прерывание по переполнению таймера
}
int main(void)
{
wdt_enable(WDTO_2S);
wdt_reset();
uint8_t step_val = 31; // Значение ШИМ для регулировки скорости
uint8_t manual = 0; // Ручное вращение - 0, установка по азимуту - 1
uint8_t rot_stat = 0; // Текущее состояние, 0 - остановле, 1 - плавный пуск, 2 - вращение на установленной скорости, 3 - плавная остановка
uint8_t speed = 0; // Скорость вращения, значения для ШИМ
uint16_t raw_az = 0; // Текущий азимут 0-450
uint16_t new_az = 0; // Азимут установки
uint8_t need_stop = 0;
ROTOR_DDR |= (1<<ROTOR_CW)|(1<<ROTOR_CCW); // Настройка портов
ROTOR_PORT &= ~((1<<ROTOR_CW)|(1<<ROTOR_CCW)); // Настройка портов
ROTOR_SPEED_DDR |= (1<<ROTOR_SPEED_PINNUM); // Настройка портов
ROTOR_SPEED_PORT &= ~(1<<ROTOR_SPEED_PINNUM); // Настройка портов
adc_init(); // Инициализация АЦП
usart_init(MYUBRR); // Инициализация UART
stdout = &mystdout;
cal0 = eeprom_read_word(&EEPcal0); // Чтение калибровок
cal90 = eeprom_read_word(&EEPcal90); // Чтение калибровок
cal180 = eeprom_read_word(&EEPcal180); // Чтение калибровок
cal270 = eeprom_read_word(&EEPcal270); // Чтение калибровок
cal360 = eeprom_read_word(&EEPcal360); // Чтение калибровок
cal450 = eeprom_read_word(&EEPcal450); // Чтение калибровок
speed = eeprom_read_byte(&EEPspeed); // Чтение максимальной скорости
pwm_init(); // Инициализация ШИМ
OCR2A = 0; // Скважность ШИМ - 0
printf_P(PSTR("YAESU DXA ROTOR CARD by R5CA\n")); // Печатаем в порт
sei(); // Глобально разрешаем прерывания
timer0_start(); // Запускаем таймер
wdt_reset();
while(1)
{
wdt_reset();
raw_az = az_raw(); // Считывание и расчет азимута
if(rot_cw == 1 && (new_az - raw_az) < 1 && manual == 0) // Если разница между текущим азимутом и заданным меньше 1 градуса
{
rot_cw = 0; // Останавливаем вращение
}
if(rot_ccw == 1 && (raw_az - new_az) < 1 && manual == 0) // Если разница между текущим азимутом и заданным меньше 1 градуса
{
rot_ccw = 0; // Останавливаем вращение
}
if(rot_stat != STOP && rot_cw == 0 && rot_ccw == 0) // Ротор не остановлен и задание на вращение закончилось
{
ROTOR_PORT &= ~((1<<ROTOR_CW)|(1<<ROTOR_CCW)); // Останавливаем вращение
rot_stat = STOP; // Меняем статус
OCR2A = 0; // Скважность ШИМ - 0
//timer0_stop(); // Останавливаем таймер
}
if(timer2 == 0)
{
if(rot_stat == STOP && rot_cw == 1) // Ротор остановлен и получено задание на вращение по часовой стрелке
{
ROTOR_PORT |= (1<<ROTOR_CW); // Начинаем вращение
rot_stat = SLOW_START; // Меняем статус
step_val = 31; // Устанавливаем начальную скорость
OCR2A = step_val; // Устанавливаем начальную скорость
//timer0_start(); // Запускаем таймер
}
else if(rot_stat == STOP && rot_ccw == 1) // Ротор остановлен и получено задание на вращение против часовой стрелки
{
ROTOR_PORT |= (1<<ROTOR_CCW); // Начинаем вращение
rot_stat = SLOW_START; // Меняем статус
step_val = 31; // Устанавливаем начальную скорость
OCR2A = step_val; // Устанавливаем начальную скорость
//timer0_start(); // Запускаем таймер
}
}
if(rot_stat == SLOW_START) // Плавный пуск
{
if(step_val < speed && timer > 20) // Текущая скорость меньше максимальной и таймер дотикал до переключения
{
step_val += 16; // чуть добавляем скорость
OCR2A = step_val; // чуть добавляем скорость
timer = 0; // обнуляем таймер
}
if(step_val == speed) // Если достигли максимальной скорости
{
rot_stat = ROTARY; // Меняем статус
}
}
if((rot_stat == SLOW_START || rot_stat == ROTARY) && ((rot_ccw == 1 && ((raw_az - new_az) < SLOW_DOWN_AZ)) || (rot_cw == 1 && ((new_az - raw_az) < SLOW_DOWN_AZ))) && manual == 0) // Разница между текущим и заданным азимутом меньше, чем задана для плавной остановки
{
rot_stat = SLOW_DOWN; // Меняем статус
}
if(rot_stat == SLOW_DOWN) // Плавная остановка
{
if(step_val > 31 && timer > 15) // Если значение скорости больше минимальной и таймер дотикал до переключения
{
OCR2A = step_val; // Чуть уменьшаем скорость
step_val -= 16; // Чуть уменьшаем скорость
timer = 0; // обнуляем таймер
}
if(step_val <= 31 && need_stop == 1)
{
ROTOR_PORT &= ~((1<<ROTOR_CW)|(1<<ROTOR_CCW)); // Останавливаем вращение
rot_stat = STOP; // Меняем статус
OCR2A = 0; // Скважность ШИМ - 0
need_stop = 0;
}
}
if(uartRXC == 1) // Принята строка по UART
{
char uart_tmp[10];
if(strcmp_P((char*)uartRXBuf, PSTR("BOOT")) == 0) // Получено слово для перехода в boot
{
wdt_disable(); // Ребутимся
wdt_enable(WDTO_15MS); // Ребутимся
while(1) {}; // Ребутимся
}
else if(strcmp_P((char*)uartRXBuf, PSTR("CAL0")) == 0) // Калибровка 0 градусов
{
cal0 = adc_read(ROTOR_AZ_IN); // Чтение АЦП
eeprom_write_word(&EEPcal0, cal0); // Запись в EEPROM
printf_P(PSTR("OK\n")); // Печатаем в порт
}
else if(strcmp_P((char*)uartRXBuf, PSTR("CAL90")) == 0) // Калибровка 90 градусов
{
cal90 = adc_read(ROTOR_AZ_IN); // Чтение АЦП
eeprom_write_word(&EEPcal90, cal90); // Запись в EEPROM
printf_P(PSTR("OK\n")); // Печатаем в порт
}
else if(strcmp_P((char*)uartRXBuf, PSTR("CAL180")) == 0) // Калибровка 180 градусов
{
cal180 = adc_read(ROTOR_AZ_IN); // Чтение АЦП
eeprom_write_word(&EEPcal180, cal180); // Запись в EEPROM
printf_P(PSTR("OK\n")); // Печатаем в порт
}
else if(strcmp_P((char*)uartRXBuf, PSTR("CAL270")) == 0) // Калибровка 270 градусов
{
cal270 = adc_read(ROTOR_AZ_IN); // Чтение АЦП
eeprom_write_word(&EEPcal270, cal270); // Запись в EEPROM
printf_P(PSTR("OK\n")); // Печатаем в порт
}
else if(strcmp_P((char*)uartRXBuf, PSTR("CAL360")) == 0) // Калибровка 360 градусов
{
cal360 = adc_read(ROTOR_AZ_IN); // Чтение АЦП
eeprom_write_word(&EEPcal360, cal360); // Запись в EEPROM
printf_P(PSTR("OK\n")); // Печатаем в порт
}
else if(strcmp_P((char*)uartRXBuf, PSTR("CAL450")) == 0) // Калибровка 450 градусов
{
cal450 = adc_read(ROTOR_AZ_IN); // Чтение АЦП
eeprom_write_word(&EEPcal450, cal450); // Запись в EEPROM
printf_P(PSTR("OK\n")); // Печатаем в порт
}
else if(strlen((char*)uartRXBuf) == 1)
{
if(((char*) uartRXBuf)[0] == 'C') // Получен запрос азимута
{
usart_tx('A'); // Печатаем в порт
usart_tx('Z'); // Печатаем в порт
usart_tx('='); // Печатаем в порт
memset(uart_tmp, 0x00, 10); // Очищаем строку
sprintf(uart_tmp, "%03d", raw_az); // печатаем азимут в строку
for(uint8_t i=0; i < strlen(uart_tmp); i++) // Отправляем в порт строку
{
usart_tx(uart_tmp[i]); // Печатаем в порт
}
usart_tx(0x0D); // Печатаем в порт
}
else if(((char*) uartRXBuf)[0] == 'S') // остановка
{
rot_cw = 0; // Отменяем задание на вращение
rot_ccw = 0; // Отменяем задание на вращение
usart_tx(0x0D); // Печатаем в порт
}
else if(((char*) uartRXBuf)[0] == 'L' && rot_stat == STOP) // вращение против часовой
{
rot_ccw = 1; // Задание на вращение против часовой
manual = 1; // Ручной режим
usart_tx(0x0D); // Печатаем в порт
}
else if(((char*) uartRXBuf)[0] == 'R' && rot_stat == STOP) // вращение по часовой
{
rot_cw = 1; // Задание на вращение по часовой
manual = 1; // Ручной режим
usart_tx(0x0D); // Печатаем в порт
}
}
else if(strlen((char*)uartRXBuf) == 4 && ((char*) uartRXBuf)[0] == 'M') // установка азимута
{
uartRXBuf[0] = uartRXBuf[1]; // Сдвиг в массиве
uartRXBuf[1] = uartRXBuf[2]; // Сдвиг в массиве
uartRXBuf[2] = uartRXBuf[3]; // Сдвиг в массиве
uartRXBuf[3] = 0x00; // Конец строки
usart_tx(0x0D); // Печатаем в порт
new_az = atoi((char*)uartRXBuf); // Преобразуем ASCII число в значение
uint8_t tmp1 = 0;
uint8_t tmp2 = 0;
if(rot_cw == 1)
{
tmp1 = 1;
}
else if(rot_ccw == 1)
{
tmp1 = 2;
}
rot_cw = 0;
rot_ccw = 0;
set_az(raw_az, new_az); // Устанавливаем азимут назначения
if(rot_cw == 1)
{
tmp2 = 1;
}
else if(rot_ccw == 1)
{
tmp2 = 2;
}
if(tmp1 != 0)
{
if(tmp1 != tmp2)
{
if(rot_stat != STOP)
{
rot_stat = SLOW_DOWN; // Меняем статус
need_stop = 1;
timer2 = 375;
}
}
else
{
if(rot_stat == SLOW_DOWN)
{
rot_stat = SLOW_START; // Меняем статус
}
}
}
manual = 0; // Автоматический режим
}
else if(strlen((char*)uartRXBuf) == 2)
{
if(strcmp_P((char*)uartRXBuf, PSTR("X1")) == 0) // Команда изменения максимальной скорости
{
usart_tx(0x0D); // Печатаем в порт
speed = 63; // Устанавливаем значение скорости(скважность ШИМ 0-255)
eeprom_write_byte(&EEPspeed, speed); // Запись в EEPROM
}
else if(strcmp_P((char*)uartRXBuf, PSTR("X2")) == 0) // Команда изменения максимальной скорости
{
usart_tx(0x0D); // Печатаем в порт
speed = 127; // Устанавливаем значение скорости(скважность ШИМ 0-255)
eeprom_write_byte(&EEPspeed, speed); // Запись в EEPROM
}
else if(strcmp_P((char*)uartRXBuf, PSTR("X3")) == 0) // Команда изменения максимальной скорости
{
usart_tx(0x0D); // Печатаем в порт
speed = 191; // Устанавливаем значение скорости(скважность ШИМ 0-255)
eeprom_write_byte(&EEPspeed, speed); // Запись в EEPROM
}
else if(strcmp_P((char*)uartRXBuf, PSTR("X4")) == 0) // Команда изменения максимальной скорости
{
usart_tx(0x0D); // Печатаем в порт
speed = 255; // Устанавливаем значение скорости(скважность ШИМ 0-255)
eeprom_write_byte(&EEPspeed, speed); // Запись в EEPROM
}
}
cli(); // Глобально запрещаем прерывания
uartbuf_zero(); // Обнуляем буфер UART
uartRXBufNum = 0; // Обнуляем кол-во принятых байт
uartRXC = 0; // Обнуляем флаг окончания приема строки
sei(); // Глобально разрешаем прерывания
}
}//
}