Самодельный передатчик (ver. by Nick_Shl)

Nick_Shl

При написании кода, надо стараться, что бы структура его была логична и понятна. Например разбить на файлы, соответствующим образом именованные.
Например все(почти все) определения я вынес в файл defs.h:

// *****************************************************************************
// ***   Общие определения   ***************************************************
// *****************************************************************************
#define AVR_Clock_Freq 16000000     // Частота работы МК
#define TimerDevider 8              // Делитель главного таймера
#define TASK_TICK_TIME 20           // Время одного тика таймера задач(в мс)
#define BAUD_RATE 115200            // Частота работы порта UART0

// Если задан - режим отладки. Применяется для вывода отладочных сообщений
// в UART0.
//#define DEBUG

// Если задан - экран рисуется перевёрнутым  - для установки экрана вверх тормашками.
#define GFX_REVERSED

// ***   Количество точек в кривой   *******************************************
#define CURVE_NODES   7
// ***   Количество моделей   **************************************************
#define MAX_MODELS    5
// ***   Длинна названия модели (максимум n символов + нуль-терминатор)   ******
#define MODEL_NAME_LEN (12 + 1)
// ***   Длинна названия полётного режима (n символов + нуль-терминатор)   *****
#define MODE_NAME_LEN  (10 + 1)

// *****************************************************************************
// ***   Триммеры   ************************************************************
// *****************************************************************************
#define trim_0up        !PINE.7
#define trim_0down      !PINE.6
#define trim_1up        !PINB.2
#define trim_1down      !PINB.3
#define trim_2up        0// У меня отсутствует
#define trim_2down      0// У меня отсутствует
#define trim_3up        !PINE.4
#define trim_3down      !PINE.5


// *****************************************************************************
// ***   Кнопки навигации   ****************************************************
// *****************************************************************************
#define HB_UP              !PIND.3
#define HB_DOWN            !PIND.1
#define HB_LEFT            !PIND.2
#define HB_RIGHT           !PIND.0
#define HB_BACK            !PIND.4
#define HB_ENTER           !PIND.5

// *****************************************************************************
// ***   Стрелочный индикатор                                                ***
// ***   Значения 0x00 - 0xFF                                                ***
// *****************************************************************************
#define GAUGE(x)        OCR0 = x

// *****************************************************************************
// ***   Зуммер                                                              ***
// ***   "1" включен, "0" выключен. PORTG.2 отсутствует :(                   ***
// *****************************************************************************
//#define BUZ             PORTG.2
//#define BUZ(x)          PORTG = (PORTG & (~(1 << 2))) | (x << 2)
#define BUZ(x)          PORTB.7 = x

// *****************************************************************************
// ***   Светодиоды   **********************************************************
// *****************************************************************************
#define LED1(x)
#define LED2(x)         PORTB.4 = x

// *****************************************************************************
// ***   Переключатели   *******************************************************
// *****************************************************************************
#define MODE_KEY1       !PINE.2
#define MODE_KEY2       !PINE.3

// Возможно использование SW1 как трехпозиционный, так и как двухпозиционный
// Ксли определен SW1 - преключатель двухпозиционный
//#define SW1             !PIND.7
// Иначе(должены быть определёны SW1_1 и SW1_2) - трехпозиционный
#define SW1_1           !PINA.1
#define SW1_2           !PINA.2
#define SW2             !(PING&(1 << 1))
#define SW3             0
#define SW4             0

#define Tcut_KEY        !(PING&(1 << 0))
#define DUAL_AIL        !PIND.6
#define DUAL_ELE        !PIND.7
#define DUAL_RUD        0

// *****************************************************************************
// ***   Каналы АЦП   **********************************************************
// *****************************************************************************
enum
{
    ADC_AUX2 = 0,
    ADC_AUX3,
    ADC_AUX1,
    ADC_AIL,
    ADC_ELE,
    ADC_THR,
    ADC_RUD,
    ADC_BAT,
    MAX_ADC
};

// *****************************************************************************
// ***   Элементы управления   *************************************************
// *****************************************************************************
enum
{
    CTRL_AIL = 0, // \
    CTRL_ELE,     // | Должны быть первыми, потому как триммеры
    CTRL_THR,     // | добавляются к первым 4-м каналам
    CTRL_RUD,     // /
    CTRL_SW1,
    CTRL_SW2,
    CTRL_SW3,
    CTRL_AUX1,
    CTRL_V1, // Виртуальные каналы должны быть последними, CTRL_V1 должен быть первым из
    CTRL_V2, // них, т.к. всё каналы что ниже его нельзя выбрать в качестве источника.
    CTRL_TRIM,
    MAX_CONTROLS
};

// *****************************************************************************
// ***   Параметры управляющих элементов   *************************************
// *****************************************************************************
enum
{
    CTRLS_REV = 0,
    CTRLS_MAXR,
    CTRLS_MINR,
    CTRLS_MAXDR,
    CTRLS_MINDR,
    CTRLS_TCUT,
    CTRLS_FROM,
    CTRLS_CURVE,
    MAX_CTRL_SETTINGS
};

// *****************************************************************************
// ***   Trimmers   ************************************************************
// *****************************************************************************
enum
{
    TRM_AIL = 0,
    TRM_ELE,
    TRM_THR,
    TRM_RUD,
    MAX_TRIMMERS
};

// *****************************************************************************
// ***   Каналы PPM   **********************************************************
// *****************************************************************************
enum
{
    CH_AIL = 0,
    CH_ELE,
    CH_THR,
    CH_RUD,
    CH_AUX1,
    CH_AUX2,
    CH_AUX3,
    CH_BAT,
    MAX_CHANNELS
};

// *****************************************************************************
// ***   Model types   *********************************************************
// *****************************************************************************
enum
{
    TYPE_PLANE = 0,
    TYPE_HELI,
    TYPE_GLIDER,
    MAX_TYPES
};

// *****************************************************************************
// ***   Fly modes   ***********************************************************
// *****************************************************************************
enum
{
    M_NORMAL = 0,
    M_IDLEUP,
    M_THOLD,
    MAX_MODES
};

// *****************************************************************************
// ***   TX Modulation types   *************************************************
// *****************************************************************************
enum
{
    MODUL_PPM = 0,
    MODUL_IPPM,
    MODUL_PCM,
    MAX_MODULS
};

// *****************************************************************************
// *****************************************************************************
// ***   Определения ниже - платформонезависимые - НЕ МЕНЯТЬ !!!   *************
// *****************************************************************************
// *****************************************************************************

// ***   Количество тиков таймера за n сек   ***********************************
#define TimerClockPerSec(s) ((unsigned long)(s * AVR_Clock_Freq) / TimerDevider)

// ***   Подсчет количества элементов массива   ********************************
#define NumberOf(x) (sizeof(x)/sizeof(x[0]))

// ***   Определения   *********************************************************
#define TRUE  1
#define FALSE 0
#define ON    1
#define OFF   0
#define On    1
#define Off   0
#define High  1
#define Low   0

// *****************************************************************************
// ***   Описание типов указателей   *******************************************
// *****************************************************************************
enum
{
    // String (char) ptrs
    PTR_FLASH = 0,
    PTR_SRAM,
    PTR_EEPROM,
    // Variables ptrs
    PTR_CHAR,
    PTR_UCHAR,
    PTR_INT,
    PTR_UINT,
    MAX_PTR_TYPES
};

// *****************************************************************************
// *****************************************************************************
// *****************************************************************************
// *****************************************************************************
// *****************************************************************************

Первым делом идут определения частоты, делителя и т.д. Зачем? Первое с чем я столкнулся в кодере focus’а - жесточайший хардкодинг: где только можно были вписаны числовые значения, соответственно прошивка заточена под 12 МГц кварц, что был у focus’а. Мне же сахотелось поставить на 16 МГц - если чип допускает, почему бы не использовать всю мощь?
Используя эти определения рассчитываются на этапе компиляции необходимые значения(делители для таймеров и т.д.).

Далее идут определения понятные по комментариям, потом кнопочки, потом стрелочный индикатор который у меня был - подключался на порт связанный с таймером и управлялся ШИМом, далее определение динамика(которого у меня тоже нет - использую ШИМ и могу играть мелодии), светодиоды, кнопки.
Хочу отметить, что полной модульности не получилось, т.к. надо править инициализацию потров(назначение входов-выходов). Зато заглянув сюда всегда можно вспомнить что есть и куда подключено.

А вот дальше пошли перечисления. Фактически это именования связанные с числами. Первое именование приравнивается к нулю, второе - к единице и т.д. Очень удобно, например при обращении к массиву - пишем не число пытаясь вспомнить что же в этой позиции лежит, а пишем имя. Если надо добавить что-то - просто добавляем в перечисления. В результате ошибок с тем, что что-то взяли не из того положения в массиве будет меньше.

В конце идут несколько определений, которые используются по всей программе для улучшения читаемости. Последнее перечисление нужно для передачи в функции указателей. AVR имеет гарвардскую структуру, а значит мало передать в функцию указатель на данные, нужно еще передать куда он указывает - на флешь, ОЗУ или еепром(если у нас данные могут находится в разных областях памяти). Изменять их нет надобности о чем говорится в заголовке блока.

Продолжение следует… надеюсь 😃

Nick_Shl

Теперь пришло время рассмотреть главный файл:

/*******************************************************************************
*  Coder.c
*
*  Радиоуправление: Главный файл
*
*       Copyright (c) 2007-2008 Nick Shl, focus
*           All rights reserved.
*
*
*  Изменения:
*
*  Apr 20, 2009  Nick_Shl  Переработка
*  Apr 10, 2008  Nick_Shl  Форматирование, комментарии.
*  *** **, ****  focus     Первоначальная версия
*
*  Chip type           : ATmega128
*  Program type        : Application
*  Clock frequency     : 16,000000 MHz
*  Memory model        : Small
*  External SRAM size  : 0
*  Data Stack size     : 1024
*/// ***************************************************************************

// *****************************************************************************
// ***   Системные инклюды   ***************************************************
// *****************************************************************************
#include <mega128.h>
#include <delay.h>
#include <stdio.h>

// *****************************************************************************
// ***   Работа с COM портом - сгенерирована CodeVisionAVR   *******************
// *****************************************************************************
#define RXB8 1
#define TXB8 0
#define UPE 2
#define OVR 3
#define FE 4
#define UDRE 5
#define RXC 7

#define FRAMING_ERROR (1 << FE)
#define PARITY_ERROR (1 << UPE)
#define DATA_OVERRUN (1 << OVR)
#define DATA_REGISTER_EMPTY (1 << UDRE)
#define RX_COMPLETE (1 << RXC)

// USART0 Receiver buffer
#define RX_BUFFER_SIZE0 32
char rx_buffer0[RX_BUFFER_SIZE0];

#if RX_BUFFER_SIZE0 < 256
    unsigned char rx_wr_index0, rx_rd_index0, rx_counter0;
#else
    unsigned int rx_wr_index0, rx_rd_index0, rx_counter0;
#endif

// This flag is set on USART0 Receiver buffer overflow
bit rx_buffer_overflow0;

// USART0 Receiver interrupt service routine
interrupt [USART0_RXC] void usart0_rx_isr(void)
{
    char status, data;
    status = UCSR0A;
    data = UDR0;
    if((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN)) == 0)
    {
        rx_buffer0[rx_wr_index0] = data;
        if(++rx_wr_index0 == RX_BUFFER_SIZE0) rx_wr_index0 = 0;
        if(++rx_counter0 == RX_BUFFER_SIZE0)
        {
            rx_counter0 = 0;
            rx_buffer_overflow0 = 1;
        };
    };
}

#ifndef _DEBUG_TERMINAL_IO_
// Get a character from the USART0 Receiver buffer
#define _ALTERNATE_GETCHAR_
#pragma used+
char getchar(void)
{
    char data;
    while(rx_counter0 == 0);
    data = rx_buffer0[rx_rd_index0];
    if (++rx_rd_index0 == RX_BUFFER_SIZE0) rx_rd_index0 = 0;
    #asm("cli")
    --rx_counter0;
    #asm("sei")
    return data;
}
#pragma used-
#endif

// USART0 Transmitter buffer
#define TX_BUFFER_SIZE0 32
char tx_buffer0[TX_BUFFER_SIZE0];

#if TX_BUFFER_SIZE0 < 256
    unsigned char tx_wr_index0, tx_rd_index0, tx_counter0;
#else
    unsigned int tx_wr_index0, tx_rd_index0, tx_counter0;
#endif

// USART0 Transmitter interrupt service routine
interrupt [USART0_TXC] void usart0_tx_isr(void)
{
    if(tx_counter0)
    {
        --tx_counter0;
        UDR0 = tx_buffer0[tx_rd_index0];
        if(++tx_rd_index0 == TX_BUFFER_SIZE0) tx_rd_index0 = 0;
    };
}

#ifndef _DEBUG_TERMINAL_IO_
// Write a character to the USART0 Transmitter buffer
#define _ALTERNATE_PUTCHAR_
#pragma used+
void putchar(char c)
{
    while(tx_counter0 == TX_BUFFER_SIZE0);
    #asm("cli")
    if(tx_counter0 || ((UCSR0A & DATA_REGISTER_EMPTY) == 0))
    {
        tx_buffer0[tx_wr_index0] = c;
        if(++tx_wr_index0 == TX_BUFFER_SIZE0) tx_wr_index0 = 0;
        ++tx_counter0;
    }
    else UDR0 = c;
    #asm("sei")
}
#pragma used-
#endif

// *****************************************************************************
// ***   Конец кода работы с COM портом   **************************************
// *****************************************************************************

// *****************************************************************************
// ***   Пользовательские инклюды   ********************************************
// *****************************************************************************
#include "Def.h"
#include "Hardware.h"
#include "Variables.h"
#include "Graphic.h"
#include "Sound.h"
#include "Math.h"
#include "System.h"
#include "Crc.h"
#include "Tasks.h"
#include "UI_Engine.h"
#include "User_Interface.h"

// *****************************************************************************
// ***   Глобальные переменные   ***********************************************
// *****************************************************************************
static unsigned long TickCount = 0;
flash unsigned int TxStartSound[] = {784<<4 + 2, 660<<4 + 1, 523<<4 + 1, 784<<4 + 2};

// *****************************************************************************
// ***   Прототипы функций   ***************************************************
// *****************************************************************************

// *****************************************************************************
// ***   Определения   *********************************************************
// *****************************************************************************

// *****************************************************************************
// ***   Возвращает количество тиков с момента запуска устройства   ************
// *****************************************************************************
inline unsigned long GetTickCount(void)
{
#ifdef DEBUG
    return(TickCount / TASK_TICK_TIME);
#else
    return(TickCount);
#endif
}

// *****************************************************************************
// ***   Возвращает количество мс с момента запуска устройства   ***************
// *****************************************************************************
inline unsigned long GetRunningTime(void)
{
#ifdef DEBUG
    return(TickCount);
#else
    return(TickCount * TASK_TICK_TIME);
#endif
}

// *****************************************************************************
// ***   Прерывание таймера                                                  ***
// ***   В котором собственно говоря и идет вычисление импульсов :)          ***
// *****************************************************************************
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
    // Номер текущего канала
    static unsigned char nb = 0;
    // Длительность паузы между пачками PPM импульсов
    static unsigned int del = 0;

#ifdef DEBUG
    // Для отладки
    static unsigned char gauge = 0; // Для отладки
    static char dir = 1;            // Для отладки
#endif

    if(nb < CurModel.num_ch)
    {
        // Рассчет канального импульса - микширование
        math_CalcChannel(nb);

        ICR1H = output[nb] >> 8;         // Заливаем длительность канала (Hi)
        ICR1L = output[nb] & 0b11111111; // Заливаем длительность канала (Lo)

        del += output[nb]; // Добавляем длительность канала к суммарному значению канальных импульсов

        nb++; // Увеличиваем номер канала
    }
    else
    {
        del = TimerClockPerSec(0.020) - del; // Длительность паузы: количество отсчетов
                                             // за 0.020 секунды(20 мс) - время канальных импульсов
        ICR1H = del >> 8;                    // Заливаем длительность паузы (Hi)
        ICR1L = del & 0b11111111;            // Заливаем длительность паузы (Lo)

        del = 0; // Обнуляем суммарное значение канальных импульсов
        nb = 0;  // Сбрасываем номер канала

#ifdef DEBUG
        // Код нужен для отладки - гоняние стрелочного индикатора туда-сюда.
        if(gauge == 0x00) dir = +1;
        if(gauge == 0xFF) dir = -1;
        GAUGE(gauge);
        gauge += dir;
#endif

        // Получение статуса управляющих элементов
        math_CalcControls();

        // Читаем и обрабатываем значение опорного напряжения:
        // (напряжение на встроенном ИОН * разрядность АЦП) / прочитанное значение встроенного ИОН
        Aref = (123L * 1023L) / (long)read_adc(0x1E);
    }
}

// *****************************************************************************
// ***   Прерывание таймера                                                  ***
// ***   Данное прерывание необходимо для реализации второстепенных задач,   ***
// ***   таких как опрос клавиатуры, пищание при нажатии на кнопки и т.д.    ***
// ***   Предусматривается не возможность повторного входа в прерывание.     ***
// ***   FIX ME: Возможно стоит переделать в функцию и вызвать в конце       ***
// ***   timer1_compa_isr с помощью строчки:                                 ***
// ***   if(nb == 0) timer3_compa_isr();                                     ***
// *****************************************************************************
interrupt [TIM3_COMPA] void timer3_compa_isr(void)
{
    static unsigned char InterruptEnterFlag = 0;
    static unsigned char MissedIntCount = 0;
    unsigned char MissedInterruptsCount = 0;

    // Увеличиваем счетчик системного времени в TASK_TICK_TIME или в мс(DEBUG) интервалах
    TickCount++;

#ifdef DEBUG
    if(TickCount % TASK_TICK_TIME) return;
#endif

    // Если мы уже находимся в этом прерывании - увеличиваем счетчик количества
    // пропущенных прерываний и выходим.
    if(InterruptEnterFlag)
    {
        MissedIntCount++;
        return;
    }

    // Иначе устанавливаем флаг, что мы уже находимся в данном прерывании
    InterruptEnterFlag = 1;
    // Копируем количество пропущенных прерываний в дополнительную переменную,
    // т.к. счетчик будет увеличиватся при повторном вызове данного прерывания.
    MissedInterruptsCount = MissedIntCount;
    // Обнуляем счетчик пропущенных прерываний.
    MissedIntCount = 0;

    // Разрешаем прерывания для более приоритетной задачи - опрос АЦП и передачи
    #asm("sei")

    // Вызываем драйвер клавиатуры
    Keyboard_Driver_Task(MissedInterruptsCount);

    // Вызываем задачу подсчета таймера
    Timer_Task(MissedInterruptsCount);

    // Вызываем задачу проигрывания мелодии
    PlaySound_Task(MissedInterruptsCount);

    // Вызываем драйвер батареи
    Battery_Driver_Task(MissedInterruptsCount);

    // Задача сохранение триммеров
    Trimmers_Save_Task(MissedInterruptsCount);

    // Вызываем задачу обновления экрана
//    gfx_AutoRefreshTask(MissedInterruptsCount);

    // Запрещаем прерывания
    #asm("cli")
    //  Cбрасываем флаг, что бы в следующий раз зайти в прерывание
    InterruptEnterFlag = 0;

    return;
}

// *****************************************************************************
// ***   Главная функция   *****************************************************
// *****************************************************************************
void main(void)
{
    // Время последнего обновления главного экрана
    unsigned long LastRefreshTime = 0;
    // Переменная для хранения значения кнопок
    unsigned char Kbd = 0;

// *** Инициализация железа   **************************************************
    hwl_InitPorts(); // Ports initialization
    hwl_InitTimers(); // Timers/Counters initialization
    hwl_InitUSART0(BAUD_RATE); // USART0 initialization
    hwl_InitMisc(); // All other hardware initialization

    // Информация для отладки
#ifdef DEBUG
    printf("Started program...\r");
    printf("CRC FLASH:  0x%04X\r", Crc16_flash((unsigned char flash *)0, 0x1000 - sizeof(unsigned short)));
    printf("CRC EEPROM: 0x%04X ", Get_EEPROM_CRC());
    if(Is_EEPROM_CRC_Correct() == TRUE) printf("CORRECT\r");
    else printf("INCORRECT\r");
    if(sizeof(EEPROM_MODEL_SETTINGS) != sizeof(MODEL_SETTINGS)) printf("WARNING!!! ");
    printf("EEPROM_MODEL_SETTINGS(%d) == MODEL_SETTINGS(%d);\r", sizeof(EEPROM_MODEL_SETTINGS), sizeof(MODEL_SETTINGS));
    delay_ms(1);
#endif

    // Инициализация графической подсистемы
    gfx_Init();
    // Очищаем экран
    gfx_ClearBuf();
    // Выводим запрос подтверждения сброса
    MsgBoxF("Starting...", NULL, "TRANSMITTER", NULL);

    // Если первое включение, то сбросить все модели и вызвать калибровку
    if((EEPROM_SETTINGS.FirstON != 1) || (HB_BACK && HB_ENTER)) TX_Reset();
    // Если на проверка контрольной суммы EEPROM не пройдена
    if(Is_EEPROM_CRC_Correct() == FALSE)
    {
        // Выводим запрос подтверждения сброса
        MsgBoxF("EEPROM CRC ERROR\nReset to factory\ndefaults ?", &Font_6x8, "ERROR", NULL);

        // Ждем отпускания кнопок, если были нажаты
        while(HB_ENTER || HB_BACK);
        // Ждем нажатия кнопок
        while(!HB_ENTER && !HB_BACK);
        // Eсли нажали ВВОД - сбрасываем
        if(HB_ENTER) TX_Reset();
    }

    #asm("cli")
        // Инициализируем кодер (чтение установок из EEPROM)
        TX_Init();
        // Разрешение выработки прерываний таймером выходных импульсов
        TIMSK = 0x10;
        // Разрешение выработки прерываний таймером задач
        ETIMSK = 0x10;
        // Первоначальный рассчет значений управляющих элементов
        math_CalcControls();
    #asm("sei")

    // Пауза для гарантированной отработки всех драйверов
    delay_ms(50);

    // Мелодия включения
    PlaySound(TxStartSound, NumberOf(TxStartSound));

#ifdef DEBUG
//    StartTime = GetRunningTime();
//    printf("Execute time = %d ms\r", GetRunningTime() - LastRefreshTime);
#endif

    while(1)
    {
        // Засыпаем до первого прерывания
        Sleep();

        trim_sound();

        // Этот цикл будет выполнятся как минимум один раз. Если ранее были нажаты кнопки,
        // цикл будет выполнятся до тех пор, пока их не отпустят. Нужно для того, что бы
        // экран не подвисал на время удержания кнопок.
        do
        {
            // Если прошло 100 мс с момента последней отрисовки
            if(LastRefreshTime + 100 <= GetRunningTime())
            {
                // Запоминаем время последней отрисовки
                LastRefreshTime = GetRunningTime();
                // Отрисовываем главный экран
                MainScreen();
#ifdef DEBUG
//printf("MainScreen() execute time = %d ms (%d Ticks)\r", (GetTickCount() - LastRefreshTime) * TICK_TIME, GetTickCount() - LastRefreshTime);
#endif
            }
        }
        while(Kbd && AskButtons());

        // Получаем состояние кнопок
        Kbd = AskButtons();

        // Если таймер включил звук и нажали кнопку
        if(GetTimerSoundStatus() == On && Kbd)
        {
            // Выключаем заук таймера
            TimerSoundOff();
            // пропускаем последующий анализ кнопок
            continue;
        }

        // Останов счета и переинициализация таймера кнопкой ВЛЕВО
        if(Kbd & B_LEFT) Timer_Init();
        // Запуск таймера кнопкой ВВЕРХ
        if(Kbd & B_UP) Timer_Stop();
        // Останов таймера кнопкой ВНИЗ
        if(Kbd & B_DOWN) Timer_Start();

        // Управляем подсветкой на главном экране кнопкой BACK
        // При нажати BACK подсветка включается/выключается
        // Изменение сохраняется только в SRAM
        if(Kbd == B_BACK)
        {
            if(Settings.BacklightFlag == On) gfx_BackLight(Off);
            else                             gfx_BackLight(On);
        }

        // Вход в главное меню
        if(Kbd == B_ENTER) MainMenu();
    };
}

Первым делом в нем идут системные инклюды и код сгенерированный CodeVisionAVR для работы с UART портом. В дальнейшем его можно было бы использовать для подключения к компьютеру, но я его использовал только для отладки - выводил в него сообщения, которые просматривал в терминале. Говоря про определения я не рассказал про определение “#define DEBUG” - именно оно определяет, собираем прошивку с отладкой или без. Анализируя это определение можно указать какой код должен быть учтен на этапе компиляции, а какой - нет. В прочем в этом файле вы это увидите.

Дальше идет подключение пользовательских заголовков(обратите внимание на количество и названия) и глобальные переменные. Их всего две. Кстати, оперирование глобальными переменными - плохая практика. Например переменная TickCount. Напрямую к ней обращаться не стоит - для этого есть функции GetTickCount() и GetRunningTime() которые вам ее вернут. Первая возвращает количество тиков, вторая - время в мс. Это первое место, где мы сталкиваемся с анализом определения DEBUG. В целях отладки я устанавливал срабатывания таймера каждую мс, а в релизной версии он срабатывает каждые 20 мс. Поэтому в отладочной версии мы подсчитываем количество тиков, а в релизной - количество мс.

Дальше идет прерывание формирующее сигнал PPM. В нем так же есть код для отладки - в режиме отладки имеющийся у меня стрелочный индикатор я гонял туда-сюда. В релизной версии предполагал использовать как индикатор батарей.

Дальше идет очень интересное прерывание, но о нем чуть ниже, а пока рассмотрим функцию main(). Это главная функция, которая крутится в фоновом режиме. Сначала мы делаем инициализацию железа. Она вынесена в отдельный файл hardware.с и не занимает много места - код более читабельный. В отладочном блоке выводится всякая информация, дальше идет инициализация графики, очистка экрана, вывод сообщения о старте. Если включение первое или зажаты одновременно кнопки “Back” и “Enter” - сбрасываем передатчик к заводским значениям. После проверяем CRC eeprom. Если оно не верно - предлагаем сбросить к заводским настройкам. Дальше инициализируется передатчик, играется мелодия и идем в главный цикл.

В главном цикле стоит засыпание до прерывания, вывод звука если нажат триммер, цикл отрисовки экрана. Данный цикл будет выполнятся до тех пор, пока нажаты кнопки, которые были уже обработаны. Дальше идет опрос клавиатуры. Опрос делается функцией AskButtons(), а не прямым чтением портов. Дальше идет проверка сработавшего сигнала таймера, и если он сработал и нажата кнопка - мы его выключаем.
После идет код управления таймером(сброс, запуск, останов), управление подсветкой и вход в главное меню, которое реализовано функцией. О нем мы поговорим позже.

Теперь про “интересное прерывание”. Это прерывание поддерживает обработку “задач” и подсчет системного времени в тиках. Кроме того, данное прерывание разрешает прерывания, что бы на время его выполнения могли формироваться импульсы PPM. При этом, если предыдущее прерывание не успеет отработать к следующему вызову, то задачи повторно запущены не будут, но для времязависимых задач(таймер например) ведется подсчет пропущеных прерывний и передает это значение задачам.

Какие задачи тут имеются:

  • Драйвер” клавиатуры меню(обрабатывает стрелки и “Back” с “Enter”)
  • Таймер
  • Проигрывание мелодии в фоне
  • Драйвер” батареи
  • Задача по сохранению триммеров
  • Задача по автоматическому обновлению экрана - признана бесполезной

Вот о задачах, наверное и поговорим в следующий раз…

Aleksey_Gorelikov

Дисплей там на TLS8201, практически аналог st7565r Шина паралельная. Подробности тут (и кусок схемы в том числе я выкладывал). rcopen.com/forum/f8/topic152759

Запустить то нет проблем. Вопрос что дальше? Есть менее удобная, но более функциональная - ер9х. При использовании еепе неудобства нивелируются. Сомневаюсь, что кому-нибудь это нужно.

RW9UAO

гыгы. Сергея код понятен почти сразу. попробуйте в коде менюшек er9x разобраться. вот там ад и израиль.
Николай, вы всетаки решились причесать. это очень хорошо. может станет еще легче ее портировать.

Nick_Shl
RW9UAO:

Николай, вы всетаки решились причесать. это очень хорошо. может станет еще легче ее портировать.

Это не причесывание, это почти полная переработка. Большинство вещей не имеет ничего общего с остальными тут представленными версиями.

Aleksey_Gorelikov:

Запустить то нет проблем. Вопрос что дальше?

Дальше упражнятся в программировании… впрочем, судя по всему этот раздел не для таких, а для тех, кто лишь повторяет чужие разработки. Тогда действительно, делать нечего.

Aleksey_Gorelikov:

Сомневаюсь, что кому-нибудь это нужно.

Действительно не нужно. И это - очередное тому подтверждение, что распинаюсь я тут зря. А потому заканчиваю.

Если кому что понадобится - интересующее спрашивать может тут.

Иван
Nick_Shl:

Действительно не нужно. И это - очередное тому подтверждение, что распинаюсь я тут зря. А потому заканчиваю. Если кому что понадобится - интересующее спрашивать может тут.

Это вы зря, при всём своём уважении к Вашей работе.
У меня по этому поводу вопрос есть - на сколько сложно перевести проект msv под предлагаемый вами способ разработки? если не сильно всё трудно и Автор проекта будет не против я поучаствую в этом деле.

Nick_Shl
Иван:

Это вы зря, при всём своём уважении к Вашей работе.

Ну никому же не интересно, разве не так? А кому интересно будет - может задать вопрос.

Иван:

У меня по этому поводу вопрос есть - на сколько сложно перевести проект msv под предлагаемый вами способ разработки? если не сильно всё трудно и Автор проекта будет не против я поучаствую в этом деле.

Абсолютно не сложно. Скачиваете TortoiseSVN, устанавливаете, создаете каталог для репозитория(например “D:\Repository”, заходите в него проводником, нажимаете правую кнопку мыши, выбираете “TortoiseSVN -> Create repository here”. Может спросить про создание структуры директорий в репозитории - можете не жать “Create …”, а сразу жать “Exit”.
Потом идете проводником к каталогу, где у вас лежит проект. Пусть это будет “D:\OSD” для примера. На нем нажимаем правую кнопку мыши и выбираем “TortoiseSVN -> Import”/ В открывшемся окне вводим URL репозитория, добавив название папки(т.к. в репозиторий кладется только содержимое папки), например “file:///D:/Repository/OSD”. Чуть ниже вводим комментарий, например “Первоначальная версия” и жмем ОК. Теперь код лежит там. Нужно только взять его из репозитория, а как и как дальше работать расписано в первом посте этой темы.

Иван

в качестве репозитория можно сетевый ресурс использовать и получить общественный проект:) ?
Спасибо за развёрнутый ответ.

Nick_Shl
Иван:

в качестве репозитория можно сетевый ресурс использовать и получить общественный проект:) ?
Спасибо за развёрнутый ответ.

Можно, но как- я не разбирался… попробуйте разобраться с Google Project Hosting например. Там при создании проекта можно выбрать Subversion или Git(тогда используем TortoiseGit).

Dinotron

Ох не зря я про вьетнам с китаем ляпнул. 😃 Разобраться с гуглем, общественный проект, ваши наработки для затравки, на ARM перевести, краудфайндинг ну и…

minhthien1988

hello Nick_scl

TX can setup program ''slow servo ‘’ for any channel ? i want to use for a FLAP , so have to control speed servo .

Nick_Shl
Dinotron:

Ох не зря я про вьетнам с китаем ляпнул. 😃 Разобраться с гуглем, общественный проект, ваши наработки для затравки, на ARM перевести, краудфайндинг ну и…

Заморочек много. А когда нет нормального железа и не используешь его постоянно…
Может закажу себе как-нибудь все-таки 9XR. Надо только самолет достроить, а то крылья уже несколько лет валяются…
Но тут уже китайцы козлы… 9X с модулями за вменяемые деньги есть, но в ней нет ISP, надо лезть с паяльником - в общем гемор. А 9XR идет только без модулей, а если еще и модули купить, то слишком уж получается…

minhthien1988:

TX can setup program ‘‘slow servo’’ for any channel ? i want to use for a FLAP , so have to control speed servo .

I dont know what mean “program ''slow servo"”. But I think it possible to programm it, if it needed 😃

1 month later
6 months later
Alibaba

вопрос к Nick_Shl у вас существует прошивка или нет под вашу схему

5 months later
Вовуся

Можно ли использовать платы Ардуино для приемника и передатчика?

Dinotron
Вовуся:

Можно ли использовать платы Ардуино для приемника и передатчика?

Да в лёгкую! Тремя командами со стандартными библиотеками. Они ж под это и заточены. www.ianjohnston.com/index.php?option=com_content&v…
А там хоть любую чушь туда с терминалом гнать. А уж принимать-то. PulseInarduino.ua/ru/prog/PulseIn Короче третье после мигания светодиодиком и часов. 😃

Dinotron
Nick_Shl:

А 9XR идет только без модулей, а если еще и модули купить, то слишком уж получается…

Кстати, насчёт модуля под 9XR. Только не смейтесь и не бейте сильно. 😃 От Esky напильником доточил. Подумал, жаль выбрасывать.

1 month later
Nick_Shl

Давненько я сюда не заходил…

Alibaba:

вопрос к Nick_Shl у вас существует прошивка или нет под вашу схему

Так прошивка тут выложена же. Достаточно скачать репозиториий, вынуть её оттуда и скомпилировать. Если схема другая - подправить Def файл. Могут быть проблемы только с подключением экрана - все что к нему относится кажись в файле “драйвера” экрана.

P.S. подвязок в Hobbico(они же “Башня”) ни у кого нет? Не в плане покупок, а в плане трудоустройства? 😉

Alibaba
Nick_Shl:

Так прошивка тут выложена же. Достаточно скачать репозиториий, вынуть её оттуда и скомпилировать.

слово репозиториий это что, потом мне нужно искать к-то софт, чтоб это собрать. ну и вопрос: а нельзя ли это в виде гекс файла под конкретную схему: скажем та что приведена.

Nick_Shl

C hex проблемка… вернее не с ним, а со схемой. Это первый вариант, потом я сделал другую схему, но не рисовал её, а просто развел плату. Потом подключается я смотрел на какой пин что подключается и вбивал в Def.h

Выложил последнюю версию кода. Там же и Hex.

Про подключение. Начнем с LCD. Его подключение описано в MT12864.c:

#define LCD_DATA_PORT PORTC
#define LCD_DATA_DDR  DDRC
#define LCD_DATA_PIN  PINC

#define LCD_BL(x)  PORTA.0 = x // Подсветка ЖКИ
#define LCD_E(x)   PORTA.3 = x // Линия E ЖКИ (строб)
#define LCD_A0(x)  PORTA.4 = x // Линия A0 ЖКИ (данные/команда)
#define LCD_RW(x)  PORTA.5 = x // Линия RW ЖКИ (запись/чтение)
#define LCD_RES(x) PORTA.6 = x // Линия RES ЖКИ (сброс)

// Линии CS ЖКИ (выборка)
#define LCD_C1(x)  PORTG = (PORTG & (~(1 << 2))) | (x << 2) // PORTG.2
#define LCD_C2(x)  PORTA.7 = x 

Линии Data на LCD подключаются на PORTC 1 к 1: 0 к 0 … 7 к 7
Остальные линии смотрите на название порта и номер пина после точки(нумеруются с 0).

Далее остальное:

// *****************************************************************************
// ***   Триммеры   ************************************************************
// *****************************************************************************
#define trim_0up        !PINE.7
#define trim_0down      !PINE.6
#define trim_1up        !PINB.2
#define trim_1down      !PINB.3
#define trim_2up        0// У меня отсутствует
#define trim_2down      0// У меня отсутствует
#define trim_3up        !PINE.4
#define trim_3down      !PINE.5


// *****************************************************************************
// ***   Кнопки навигации   ****************************************************
// *****************************************************************************
#define HB_UP              !PIND.3
#define HB_DOWN            !PIND.1
#define HB_LEFT            !PIND.2
#define HB_RIGHT           !PIND.0
#define HB_BACK            !PIND.4
#define HB_ENTER           !PIND.5

// *****************************************************************************
// ***   Стрелочный индикатор                                                ***
// ***   Значения 0x00 - 0xFF                                                ***
// *****************************************************************************
#define GAUGE(x)        OCR0 = x

// *****************************************************************************
// ***   Зуммер                                                              ***
// ***   "1" включен, "0" выключен. PORTG.2 отсутствует :(                   ***
// *****************************************************************************
#define BUZ(x)          PORTB.7 = x

// *****************************************************************************
// ***   Светодиоды   **********************************************************
// *****************************************************************************
#define LED1(x)
#define LED2(x)         PORTB.4 = x

// *****************************************************************************
// ***   Переключатели   *******************************************************
// *****************************************************************************
#define MODE_KEY1       !PINE.2
#define MODE_KEY2       !PINE.3

// Возможно использование SW1 как трехпозиционный, так и как двухпозиционный
// Ксли определен SW1 - преключатель двухпозиционный
//#define SW1             !PIND.7
// Иначе(должены быть определёны SW1_1 и SW1_2) - трехпозиционный
#define SW1_1           !PINA.1
#define SW1_2           !PINA.2
#define SW2             !(PING&(1 << 1)) // PING.1
#define SW3             0
#define SW4             0

#define Tcut_KEY        !(PING&(1 << 0)) // PING.0
#define DUAL_AIL        !PIND.6
#define DUAL_ELE        !PIND.7
#define DUAL_RUD        0

CVProject_My.zip