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

Nick_Shl

Как-то все обещался рассказать, “как надо программить”. Но с учетом какой я тяжелый на подъем… в общем придется делать по другому.
Напомню про свой вариант кодера. Есть еще и приемник, кому интересно, но сейчас речь не о нем.

В этом посте я выкладываю исходный текст своего кодера. Но не просто код, а репозиторий.
Но для его использования у меня есть условия. Их я озвучу в конце. Использование кода автоматом подтверждает принятие вами этих условий.
Файл имеет расширение zip, но на самом деле это архиватор 7z, потому что zip давал файл 1,4 МБ, а сайт пускает не больше 1 МБ. И не пускает файлы с расширением 7z - пришлось переименовать.

Управление версиями

Первое что надо сделать при желании что-нибудь написать - это реализовать управление версиями. Подход типа “А зачем мне старые версии? И последней хватит!” или “Я тут основные версии себе забэкапил” - НЕ ПОДХОДЯТ. Что позволяет система контроля версий? Позволяет она многое. Но я расскажу о базовых вещах, остальное почитаете в интернете.
Для групповой разработки лучше всего подойдет онлайновая система контроля версий, но с ней я не сталкивался, так что рассказать не смогу.
Для небольшой группы разработчиков имеющих локальную сеть нужно поставить сервер.
Программист одиночка может обойтись и без этого, а поможет в этом TortoiseSVN.
Данная утилита позволяет организовать локальный репозиторий. Ее придется установить для того, что бы достать код моего кодера.
Установив ее заходим через “Проводник” в любое удобное место, жмем правой кнопкой мыши, выбираем “SVN Checkout….”, открывается окно. В поле “URL of repository:” вводим(без кавычек) “file:///<путь к файлу>” и жмем кнопочку в три точечки рядом. У меня весь путь выглядит так: “file:///__D:/AVR/SVN_Rep/”.
В открывшемся окне слева кликаем на “CVProject_My”, жмем ОК и видим что в “URL of repository:” и в “Checkout directory:” добавилось “CVProject_My”. Жмем ОК и в этом окне и получаем сходный код моего кодера в папку “CVProject_My”.
Данная папка помечена зеленым значком - это значит, что файлы в ней не изменялись. Если вы поменяете файл, значок будет красный. Зайдя в папку такие же значки увидим на каждом файле.

Далее если вам интересно, что же я менял, то на папке “CVProject_My” нажимаем правой кнопкой мыши, выбираем пункт “TortoiseSVN”, а в открывшемся списке “Show log”. Видим все мои изменения с комментариями.
Нажав на конкретное изменение видим список измененных файлов. Дважды кликнув на файл из списка видим что же было в нем изменено. Стандартная программа сравнения мне не очень нравится, рекомендую установить Araxis Merge и настроить его в настройках TortoiseSVN как программу сравнения файлов.

Изменив какой-либо файл он помечается красным значком. Нажав на этом файле правой кнопкой мыши и выбрав пункт “SVN Commit…”, откроется окно для сохранения этого файла в репозиторий. Точно так же двойной клик на файле позволяет просмотреть что в нем было изменено. Перед заливкой обязательно вписывайте комментарий в соответствующее окошко!
Этот же подход работает и для директории. Можно выбрать “SVN Commit…”, тогда вы получите список изменённых файлов, которые будут добавлены за один коммит. Это удобно когда у вас какое-то одно изменение затрагивает несколько файлов(например при добавлении новой функциональности).

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

Форматированрие кода

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

Другие вопросы

Задавайте! По мере возможности буду отвечать, консультировать и т.д.

Условия использования:
1) При изменении использовать управление версиями.
2) При изменении поддерживать форматирование кода, причем такое же, как есть там сейчас.
3) Не использовать код в коммерческих проектах без моего согласия.

P.S. Я сейчас не занимаюсь кодером, этому есть пару причин:
Первая - это нет летающей модели. Хотя сделать ее смог бы - есть и двигатель, и регулятор, и батарея и вроде даже сервы. Есть и пара крыльев, сделанных давным-давно.
Вторая и основная - передатчик был сделан криво. Изначально это была модель с аналоговыми триммерами, сделано все “как смог”, триммеры глючили, что-то отваливалось, потом еще детишки добавили… в общем так и валяется он теперь.
А денег на новую игрушку в семейном бюджете не нашлось(на тот момент поглядывал на Turnigy 9X), сейчас наиболее интересный вариант Turnigy 9XR - хоть у нее и спорный дизайн, но она имеет разъем для программирования и экран 128x64, такой же как я использовал в своем кодере. То есть она не требует вмешательства в аппаратную часть.
Впрочем, денег на нее не находится и сейчас(на ХК она продается только без модулей, в отличие от 9X, что повышает цену готовой системы). Но если кто пожелает задарить мне такую штуку - сопротивляться не буду 😃

SVN_Rep.zip

Dinotron

Эх уважаемый Николай. Пром-акция удалась. Но

Nick_Shl:

это нет летающей модели

Nick_Shl:

передатчик был сделан криво.

Но вьетнам с китаем заинтересовались. Не продешевите. 😃

Nick_Shl
Dinotron:

Пром-акция удалась.

Это вы о чем? 5 лет он у меня просто лежала… не помню, почему именно не выложил… может быть потому, что никто так и не удосужился хотя бы “причесать” код, что бы он выглядело красиво и понятно новичкам. Все равно превратили бы в такую же “помойку” как и все остальное… наверное. Сейчас мне кажется, что надо было выложить и попробовать направить в правильное русло. В любом случае не вижу смысла, что бы он у меня лежал дальше.
ем более, что я об этом заикался, на что Aleksey_Gorelikov резонно заметил:

Aleksey_Gorelikov:

Да давно пора! Кчерту декларации намеряний! Сначало дело! 😃

Так что вот оно - дело.

Dinotron:

Но вьетнам с китаем заинтересовались. Не продешевите. 😃

Так наш англоязычный друг оттуда? Ну заинтересовались и ладно… за 5 лет она довольно сильно устарела - надо добавлять поддержку протокольных передатчиков(PPM уже прошлый век наверное) и поддержку телеметрии.

Dinotron

Извините, не очень хочется вступать с вами в дискуссию. Был бы живой проект-шло бы обсуждение, а так.

Aleksey_Gorelikov
Dinotron:

Был бы живой проект-шло бы обсуждение, а так.

Живой проект был 10 лет назад. Когда аппаратура стоила как чугунный мост и простым пионерам приходилось копить годами на какой-нибудь хайтек-фокус4, а тот же “компьютеризированный” флешь-5 был мечтой. Ныне китайское чудо сопостовимо по цене со стоимостью недельных завтраков первоклашки, в следствии чего все самодельные проекты умирают. Исключение- общественные проекты типа ер9х под “народное” китайское железо.

Коля критиковал код что фокуса, что мсв, я упрекнул его, что “покажи как надо личным примером!” и вот он, наконец-то делится своим опытом, примером для подражания и т.д. Вполне возможно, что кто-то присоединится именно к его проекту, ведь (наверно, я не смотрел) он более понятный. Использование контроля версий действительно значительно упрощает развитие проекта. Это же тоже плюс. Но главное, чем делится Николай - это своим опытом использования этого софта. Ведь, многие даже не понимают смысл этого и не могут найти исходники вполне себе распространненых открытых проектов. И основная идея сей ветки, как я понимаю именно в ключевых трех буквах SVN. Этому же посвещено и стартовое сообщение. Люди, пользуйтесь SVN, это удобно! Облегчите жизнь и себе и другим. Коля потратил время чтобы показать на собственном примере как это работает! Молодец, спасибо! Так держать! И… “давно пора было!” 😃

Nick_Shl
Aleksey_Gorelikov:

Живой проект был 10 лет назад. Когда аппаратура стоила как чугунный мост и простым пионерам приходилось копить годами на какой-нибудь хайтек-фокус4, а тот же “компьютеризированный” флешь-5 был мечтой. Ныне китайское чудо сопостовимо по цене со стоимостью недельных завтраков первоклашки, в следствии чего все самодельные проекты умирают. Исключение- общественные проекты типа ер9х под “народное” китайское железо.

C этим согласен по большей части. Просто проекты из аппаратно-программных сместились больше в программную часть - зачем самому делать платы, если можно взять ту же 9XR, где даже ISP разъем для программирования выведен наружу?
И на данный момент я не вижу никаких проблем запустить мой проект там, где запускается ер9х - если не ошибаюсь, экран там с контроллером KS108, такой же как использовал я. Если есть желание, могу попробовать найти описание что куда подключено, скомпилировать и выложить.

Aleksey_Gorelikov:

ведь (наверно, я не смотрел) он более понятный.

А жаль… так хочется что бы хоть кто-то оценил 😃

Aleksey_Gorelikov:

И основная идея сей ветки, как я понимаю именно в ключевых трех буквах SVN. Этому же посвещено и стартовое сообщение.

Это ошибочно. Просто вылоджен-то репозиторий, и безх SVN код не достанешь. А раз так, то пришлось все рассказать. Дальше попробую описать некоторые моменты, жаль только, что теги “CODE” не позволяют менять цвет фона и букв - расцветка тоже не маловажный фактор восприятия…

А про код пример. Вот первый попавшийся кусок от MSV:

//Исходные тексты клонированы от версии MSV v.1.9.
//Отличия от базовой версии (MSV v.1.9) описаны в файле Eagle_version.txt
//
//В интерфейсе программатора CVAVR фузы BODEN и BODLEVEL отмечены (=запрограммированы, при чтении =0).
//У остальных фузов отметка снята (=не запрограммированы, при чтении =1).
//См. также файлы Notes.txt и Coder.txt

//#define NoCheckThrottleOnStart //Раскомментировать, если требуется отменить проверку положения ручки газа при запуске аппы

//#define AntennaCtrlUse  //Раскомментировать, если требуется управление током антенны
    #ifdef AntennaCtrlUse
    #endif

#include <mega128.h>
#include <delay.h>
#include <stdio.h>
#include <math.h>
#include <i2c.h>
#include "def.h"
#include "LCD_3320.h"
#include "menu.h"
#include "displ.h"
#include "coder.h"

// I2C Bus functions
#asm
   .equ __i2c_port=0x15 ;PORTC
   .equ __sda_bit=5
   .equ __scl_bit=4
#endasm
// Сохраняемые данные в EEPROM'е
eeprom MODEL_SET EEPR_models[MAX_MODELS];
eeprom unsigned char EEPR_start_model;
eeprom unsigned char EEPR_f_sound_en;
eeprom unsigned char EEPR_BatCntr[3];
eeprom unsigned char EEPR_AntCntr;
eeprom int EEPR_ADmid[5], EEPR_ADmax[5], EEPR_ADmin[5];
eeprom unsigned char EEPR_FirstON, EEPR_Calibration;
eeprom signed int EPPR_CalibrTemperature[2];
//------------------------------------------------------
signed int out_control[MAX_CONTRS];
signed int ADmid[6];
signed int ADmin[6];
signed int ADmax[6];
signed int ADcur[6];
signed int out_ch[MAX_CHANS];
MODEL_SET cur_model;
unsigned char cur_model_ind;
unsigned char cur_mode_ind;
bit f_TCut;
unsigned int cur_Batt;
#ifdef AntennaCtrlUse
    unsigned int cur_Power;
    unsigned int cur_Antenna;
#endif
unsigned int cur_Temperature;
signed int dir_Temperature;
unsigned char wd_Temperature;
unsigned int div_trimmer;
unsigned char f_l;
unsigned char g_MainMenuMode;
extern bit f_timer_en;
unsigned int gRPMtmp=0;
unsigned int gRPM;
unsigned char gRPMn;
signed int g_pos_d[3];
unsigned char g_keys;
signed int gAddPropVal;
signed int gAddPropValOld;
bit gfca_start;

#define F_SW1 1
#define F_SW2_1 2
#define F_SW2_2 4
#define F_SW3 8
#define F_DUAL1 0x10
#define F_DUAL2 0x20
#define F_DUAL3 0x40
#define F_TCUT 0x80

#define UNI_KEY 0x5a
//------------------------------------------------------
//------------------------------------------------------
#define ADC_VREF_TYPE 0x00
unsigned int read_adc(unsigned char adc_input)
{
ADMUX=adc_input|ADC_VREF_TYPE;
delay_us(10);
ADCSRA|=0x40; // Start the AD conversion
while ((ADCSRA & 0x10)==0);
ADCSRA|=0x10; // Stop
return ADCW;
}
//------------------------------------------------------
//------------------------------------------------------
// (PD0-tachometr)
interrupt [EXT_INT0] void ext_int0_isr(void)
{
gRPMtmp++;
if(gRPMtmp==1024) EIMSK&=0xfe;
}
//------------------------------------------------------
void TimerTachometer(void)
{ // 50Hz
static unsigned char div;
div++;
if(div>=25)
  { // ~2Hz
  div=0;
  gRPM[gRPMn]=gRPMtmp;
  if(++gRPMn>=SIZE_RPM_BUFF) gRPMn=0;
  gRPMtmp=0;
  }
}
//------------------------------------------------------
//------------------------------------------------------
__flash const unsigned int notes[]= {//Note A 1-okt;
440, 466, 494, 523,    554,  588,  612,  660,    698,  740,  784,  830,
880, 932, 988, 1064,   1108, 1176, 1224, 1320,   1396, 1480, 1568, 1660};
BEEP_TRACK_DEF flash *beep_track;
unsigned int beep_length;
unsigned char beep_cnt;
unsigned char beep_cntn;
unsigned char beep_rep;
bit f_beep_timer;
bit f_beep_EEP;
bit f_beep_key;
bit f_beep_trimmer;
bit f_beep_trimmer_c;

extern flash const BEEP_TRACK_DEF hello_sound[];
//------------------------------------------------------
void beep(BEEP_TRACK_DEF flash *note)
{
unsigned long ln, freq;
unsigned int v;

freq=notes[note->note];
v=(unsigned int)((1500000L/2)/freq);
ICR3H=(unsigned char)(v>>8);
ICR3L=(unsigned char)v;
ln=note->ms*freq;
beep_length=ln/1000;
}
//------------------------------------------------------
// Timer 3 output compare A interrupt service routine
interrupt [TIM3_COMPA] void timer3_compa_isr(void)
{
bit f_con;

if(PINE.3) return;
if(--beep_length==0)
  {
  f_con=0;
  if(++beep_cntn<beep_cnt)  f_con=1;
  else if(beep_rep) { beep_rep--; beep_cntn=0; f_con=1; }
  if(f_con==0) { TCCR3B&=0xf8; return; }  // stop
  else beep((beep_track+beep_cntn));
  }
}
//------------------------------------------------------
void beep_start(BEEP_TRACK_DEF flash *notes, unsigned char cnt, unsigned char rep)
{
beep_track=notes;
beep_cnt=cnt;
beep_cntn=0;
beep_rep=rep;
beep(notes);
TCCR3B|=0x2;
}
//-------------------------------------------------------------
//-------------------------------------------------------------
//-------------------------------------------------------------
unsigned int CHcalc (char ch_ind)
{
signed long ln;
unsigned char i;
signed char *mix;
CHANNEL * channel;
signed int v, val;

mix=cur_model.modes[cur_mode_ind].mixer[ch_ind];
val=0;
for (i=0; i<MAX_CONTRS; i++)
  {
  if(mix[i])
    {
    ln=out_control[i]; ln*=mix[i]; ln/=100;
    val+=(signed int)ln;
    }
  }
channel=&cur_model.channels[ch_ind];
v=channel->subtrimmer; v*=5; //50*5 -> +-250
val+=v;
//limit
if(val<-900) val=-900;
if(val>900) val=900;
//--------------------------------
//#define EPA_MODE1
#ifdef EPA_MODE1
//epa 1 (Просто ограничение с обрезанием по epa)
v=-channel->epa[0]; v*=90; v/=12;
if(val<v) val=v;
v=channel->epa[1]; v*=90; v/=12;
if(val>v) val=v;
#else
#ifdef EPA_MODE2
//epa 2 (масштабирование раздельно на участках epa-<>0 и 0<>epa+)
ln=val;
if(val<0) ln*=channel->epa[0];
else ln*=channel->epa[1];
val=(signed int)(ln/120);
#else
//epa 3 (мастабирование линейное epa-<>epa+ со сдвигом центра)
ln=channel->epa[0]; ln+=channel->epa[1]; ln*=val;
val=(signed int)(ln/240);
v=channel->epa[1]; v-=channel->epa[0]; v*=90; v/=24;
val+=v;
#endif
#endif
//----------------------------------
val*=(signed int)channel->reverse;
val+=2250; // 1,5 ms
return (unsigned int)val;
}
//-------------------------------------------------------------
signed int interpol(signed int val, signed char* nodes )
{
unsigned char n;
signed int n1, n2, vmin;
signed long ln;

ln=val; ln*=6; n=ln/1500;
n1=*(nodes+n); n1=(n1*75)/10;
n2=*(nodes+n+1); n2=(n2*75)/10;
vmin=n; vmin*=250;
ln=(val-vmin); ln*=(n2-n1); ln/=250;
return ln + n1;
}
//-------------------------------------------------------------
signed int rate_calc(signed int val, unsigned char min, unsigned char max)
{
unsigned char rate;
signed long v;

if(val<0) rate=min;
else rate=max;
v=val; v*=rate; v/=100;
return v;
}
//-------------------------------------------------------------
unsigned char ReadKeys(void)
{
unsigned char f;

f=0;
if(!SW1) f|=F_SW1;
if(!SW2_1) f|=F_SW2_1;
if(!SW2_2) f|=F_SW2_2;
if(!SW3) f|=F_SW3;
if(!DUAL_ail) f|=F_DUAL1;
if(!DUAL_elev) f|=F_DUAL2;
if(!DUAL_rud) f|=F_DUAL3;
if(!Tcut_KEY) f|=F_TCUT;
return f;
}
//-------------------------------------------------------------
void Calc (void)
{ // 50Hz (20ms)
static unsigned char div_trim;
signed char *trim;
signed int input[10];
unsigned int i, n;
signed long vl;
const unsigned char acntr[]={ ch_Ailerons, ch_Elevator, ch_Throttle, ch_Rudder, ch_Rpop_contr };
signed int vi;
unsigned char mn, mx;
DEF_MODE *mode;
unsigned char f_dual[3];
unsigned char keys;
unsigned char f;

//опрос переключателя режимов
if(!MODE_KEY2)           i=0;
else if(!MODE_KEY1)      i=2;
else                     i=1;
if(cur_mode_ind!=i) { BEEP; save_trimmer(); cur_mode_ind=i; }

//опрос триммеров
trim=cur_model.modes[cur_mode_ind].trimmers;
if (div_trim)
  {
  f=0;
  if(!trim_0up && trim[0]<80) { trim[0]++; f|=1;  }
  if(!trim_0down && trim[0]>-80) { trim[0]--;  f|=1; }
  if(!trim_1up && trim[1]<80) { trim[1]++; f|=2; }
  if(!trim_1down && trim[1]>-80) { trim[1]--; f|=2; }
  if(!trim_2up && trim[2]<80) { trim[2]++;   f|=4; }
  if(!trim_2down && trim[2]>-80) { trim[2]--; f|=4; }
  if(!trim_3up && trim[3]<80) { trim[3]++; f|=8; }
  if(!trim_3down && trim[3]>-80) { trim[3]--; f|=8; }
  if((f & 1) && trim[0]==0) f_beep_trimmer_c=1;
  if((f & 2) && trim[1]==0) f_beep_trimmer_c=1;
  if((f & 4) && trim[2]==0) f_beep_trimmer_c=1;
  if((f & 8) && trim[3]==0) f_beep_trimmer_c=1;
  if(f)
    {
    div_trimmer=1500;
    if(f_beep_trimmer_c==0) f_beep_trimmer=1;
    }
  }
div_trim=!div_trim;

mode=&cur_model.modes[cur_mode_ind];
// исходные данные
for(i=0; i<5; i++)
  {
  n=i; if(i==4) n=7;
  vl=read_adc(acntr[i]);
  if(vl<ADmid[i])
    {
    if(ADmin[i]>vl) ADmin[i]=vl;
    vl-=ADmin[i]; vl*=750; input[n]=vl/(ADmid[i]-ADmin[i]);
    }
  else
    {
    if(ADmax[i]<vl) ADmax[i]=vl;
    vl-=ADmid[i]; vl*=750; input[n]=750+(vl/(ADmax[i]-ADmid[i]));
    }
  }
gAddPropVal=input[7];
keys=ReadKeys(); if(keys!=g_keys) { BEEP; g_keys=keys; }
if(keys & F_SW1) input[4]=0; else input[4]=1500;
if(keys & F_SW2_1) input[5]=0;
else if(keys & F_SW2_2) input[5]=1500;
else input[5]=750;
if(keys & F_SW3) input[6]=0; else input[6]=1500;
for(i=0; i<3; i++)
  {
  vi=mode->contr_d[i].delay;
  if(vi==0) g_pos_d[i]=input[4+i];
  else if(g_pos_d[i]!=input[4+i])
    {
    vl=g_pos_d[i]; vl*=vi; vl/=(300/2); vl++; vl/=2;
    if(g_pos_d[i]<input[4+i])
      {
      vl++; vl*=(300*2); vl/=vi; vl++; vl/=2;
      if(vl>input[4+i]) vl=input[4+i];
      }
    else
      {
      vl--; vl*=(300*2); vl/=vi; vl++; vl/=2;
      if(vl<input[4+i]) vl=input[4+i];
      }
      g_pos_d[i]=vl; input[4+i]=vl;
    }
  }
input[8]=input[mode->contr_v[0].from];
input[9]=input[mode->contr_v[1].from];
//опрос кнопки выключения двигателя
if (keys & F_TCUT) f_TCut=1; else f_TCut=0;
// Двойные расходы
f_dual[0]=(keys & F_DUAL1);
f_dual[1]=(keys & F_DUAL2);
f_dual[2]=(keys & F_DUAL3);
// Вся математика
for(i=0; i<3; i++)
  {
  n=i; if(i==2) n++;
  vi=interpol(input[n], mode->contr_ab[i].cur_nodes+1);
  if(f_dual[i]) { mn=mode->contr_ab[i].minDRates; mx=mode->contr_ab[i].maxDRates; }
  else { mn=mode->contr_ab[i].minRates; mx=mode->contr_ab[i].maxRates; }
  out_control[n]=rate_calc(vi, mn, mx);
  vi=mode->trimmers[n]; vi*=5; out_control[n]+=vi;
  }
if(f_TCut) { vi=mode->contr_tr.tcut; vi*=-75; out_control[2]=vi/10; }
else
  {
  vi=interpol(input[2], mode->contr_tr.cur_nodes+1);
  out_control[2]=rate_calc(vi, mode->contr_tr.minRates, mode->contr_tr.maxRates);
  vi=mode->trimmers[2]; vi*=5; out_control[2]+=vi;
  }
if(cur_model.timer_mode==2) { if(!f_TCut && input[2]>150) f_timer_en=1; else f_timer_en=0; }

    for(i=0; i<3; i++)
    {
    vi=input[4+i]-750;
    out_control[4+i]=rate_calc(vi, mode->contr_d[i].minRates, mode->contr_d[i].maxRates);
    }

    vi=interpol(input[7], mode->contr_ap.cur_nodes+1);
    out_control[7]=rate_calc(vi, mode->contr_ap.minRates, mode->contr_ap.maxRates);

    for(i=0; i<2; i++)
    {
        vi=interpol(input[8+i], mode->contr_v[i].cur_nodes+1);
        out_control[8+i]=rate_calc(vi, mode->contr_v[i].minRates, mode->contr_v[i].maxRates);
    }
}
//------------------------------------------------------

Вроде этот кусок отвечает за математику. А вот так она сделана у меня(вынесена в отдельный файл):

/*******************************************************************************
*  Math.c
*
*  Радиоуправление: Математика
*
*       Copyright (c) 2007-2008 Nick Shl, focus
*           All rights reserved.
*
*
*  Изменения:
*
*  Apr 20, 2009  Nick_Shl  Переработка
*  Apr 10, 2008  Nick_Shl  Форматирование, комментарии
*  *** **, ****  focus     Первоначальная версия
*
*/// ***************************************************************************

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

// *****************************************************************************
// ***   Пользовательские инклюды   ********************************************
// *****************************************************************************
#include "Def.h"
#include "Coder.h"
#include "Sound.h"
#include "Tasks.h"
#include "Variables.h"

// *****************************************************************************
// ***   Рассчет времени канальных импульсов для вывода (Микширование)  ********
// *****************************************************************************
void math_CalcChannel(char CH)
{
    char i;
    signed long Factor;

    // Если передали отритцательный номер канала - выходим
    if(CH < 0) return;
    // Если передали номер канала превышающий количество каналов - выходим
    if(CH > MAX_CHANNELS) return;

    // Обнуляем значение длительности канального импульса
    output[CH] = 0;

    // Формирование выходного канала
    for(i=0; i < MAX_CONTROLS; i++)
    {
        // Если влияние управляющего элемента i на канал CH отсутстует - идем дальше
        if(CurModel.Mode[FLY_MODE].Chanels[CH][i] != 0)
        {
            // Получаем влияние управляющего элемента i на канал CH
            Factor = CurModel.Mode[FLY_MODE].Chanels[CH][i];
            // Добавляем управляющий элемент i к каналу CH
            output[CH] += out_cur[i] * Factor / 100;
        }
    }

    // Длительность импульса:
    // значение канала(+-0,5 мс) + центральное положение(1,5 мс)
    output[CH] += TimerClockPerSec(0.0015);
    // приведение к int нужно для оптимизации: вычисленное на этапе компиляции число использовать как int
    // забавно получилось: 1,5 мс это 1/666,6(6) секунды...

    // Если получившийся импульс меньше 0,0009 сек (0,9 мс) - ограничиваем
    if(output[CH] < TimerClockPerSec(0.0009)) output[CH] = TimerClockPerSec(0.0009);

    // Если получившийся импульс больше 0,0021 сек (2,1 мс) - ограничиваем
    if(output[CH] > TimerClockPerSec(0.0021)) output[CH] = TimerClockPerSec(0.0021);
}

// *****************************************************************************
// ***   Расширенный рассчет кривой   ******************************************
// *****************************************************************************
signed int math_InterPolEx(signed int Val, signed int Max, signed int K, signed char * Nodes, signed char NodesCount)
{
    unsigned char n;
    signed int n1, n2, ln;
    // Размер интервала. Должен быть long, потому что в дальнейшем рассчете
    // получатся числа большой размерности.
    signed long InterPoolSize;

    // Если не передали указатель на массив точек кривой - возвращаем переданное значение
    if(Nodes == NULL) return(Val);
    // Если передали размер массива точек кривой 0 и меньше - возвращаем переданное значение
    if(NodesCount <= 0) return(Val);

    // Если переданное значение окажется меньше 0 - нулим
    if(Val < 0) Val = 0;
    // Если переданное значение окажется больше или равно Max
    // выйдем за границы массива - корректируем
    if(Val >= Max) Val = Max - 1;

    // Вычисляем размер интервала - максимальное значение / количество точек кривой
    InterPoolSize = Max / (NodesCount - 1);

    // Вычисляем интервал в который попали:
    // значение умножаем на количество интервалов и делим на максимальное значение
    n  = (Val * (NodesCount - 1)) / Max;
    // Вычисляем значение одной границы интервала
    n1 = (*(Nodes + n)     * K) / 10;
    // Вычисляем значение другой границы интервала
    n2 = (*(Nodes + n + 1) * K) / 10;
    // Вычисляем смещение
    // Значение относительно начала интервала умножается на величину интервала(Y)
    // и делится на величину интервала(X)
    ln = (Val - n * InterPoolSize) * (n2 - n1) / InterPoolSize;

    return(n1 + ln);
}

// *****************************************************************************
// ***   Рассчет кривой   ******************************************************
// *****************************************************************************
static inline signed int math_CurveInterPol(signed int Val, signed char * Nodes)
{
    // Добавляем 0,5 мс - сдвигаем в положительную область
    Val += TimerClockPerSec(0.0005);
    // Выполняем рассчет кривой с использованием универсальной функции
    return(math_InterPolEx(Val, TimerClockPerSec(0.001), TimerClockPerSec(0.0005) / 10, Nodes, CURVE_NODES));
}

// *****************************************************************************
// ***   Общий рассчет кривой   ************************************************
// *****************************************************************************
// Может использоватся внешними задачами для реализации своих нужд. Например
// при выводе информации на стрелочный индикатор проградуирыванный не линейно.
// Или для приблизительного расчета оставшейся емкости по напряжению батареи.
inline signed int math_InterPol(signed int Val, signed int Max, signed char * Nodes, signed char NodesCount)
{
    // Выполняем общий рассчет кривой с использованием универсальной функции
    return(math_InterPolEx(Val, Max, 10, Nodes, NodesCount));
}

// *****************************************************************************
// ***   Изменение триммеров   *************************************************
// *****************************************************************************
void math_ChangeTrimmer(signed char * trim, signed char dir)
{
    if(dir == 0) return;
    if((dir > 0) && (*trim + dir >  96)) return;
    if((dir < 0) && (*trim + dir < -96)) return;
    *trim += dir;
    Trimmers_Need_Save(15);
    trim_sound_en = 1;
}

// *****************************************************************************
// ***   Опрос устройств ввода и рассчет их значений   *************************
// *****************************************************************************
void math_CalcControls(void)
{
    char i, Dual[MAX_CONTROLS];
    int TrimFactor;
    signed long Factor;
    unsigned char NewTrimMask = 0;
    static unsigned char TrimMask = 0;

    //опрос переключателя режимов
    if(MODE_KEY2)                FLY_MODE = 1;
    if(!MODE_KEY1 && !MODE_KEY2) FLY_MODE = 0;
    if(MODE_KEY1)                FLY_MODE = 2;

    // Загоняем все нажатые триммера в битовую маску
    if(trim_0up)   NewTrimMask |= 0x01;
    if(trim_0down) NewTrimMask |= 0x02;
    if(trim_1up)   NewTrimMask |= 0x04;
    if(trim_1down) NewTrimMask |= 0x08;
    if(trim_2up)   NewTrimMask |= 0x10;
    if(trim_2down) NewTrimMask |= 0x20;
    if(trim_3up)   NewTrimMask |= 0x40;
    if(trim_3down) NewTrimMask |= 0x80;
    // Если состояние маски изменилось - запоминаем его
    if(NewTrimMask != TrimMask)
    {
        TrimMask = NewTrimMask;
    }
    // Если не изменилось и присутствуют нажатые триммера - обрабатываем
    else if(TrimMask != 0)
    {
        // 8 кнопок триммеров и 8 бит в переменной
        for(i=0; i<8; i++)
        {
            // Если в маске установлен i-тый бит
            if(TrimMask & (1 << i))
                // И i - нечёт нужно уменьшить значение триммера
                if(i & 0x01) math_ChangeTrimmer(&CurModel.Mode[FLY_MODE].trimmers[i/2], -1);
                // Иначе нужно увеличить значение триммера
                else         math_ChangeTrimmer(&CurModel.Mode[FLY_MODE].trimmers[i/2],  1);
        }
    }

    //опрос кнопки выключения двигателя
    if(Tcut_KEY) Cut_enable = 1;
    else         Cut_enable = 0;

    // Чтение АЦП и нормализация полученных данных
    for(i=0; i < MAX_ADC; i++)
    {
        AD[i] = read_adc(i);
        // Если ADmid unsigned возникает проблема в вычислениях при отритцательных
        // числах. Не понятно почему так происходит...
        if(AD[i] > Settings.ADmid[i]) AD_NORM[i] = ((AD[i] - Settings.ADmid[i]) * Settings.Kmax[i]) / 10;
        else                          AD_NORM[i] = ((AD[i] - Settings.ADmid[i]) * Settings.Kmin[i]) / 10;
    }

    // Копирование нормализованных данных с АЦП в управляющие элементы
    // для дальнейшей обработки
    input[CTRL_AIL]  = AD_NORM[ADC_AIL];
    input[CTRL_ELE]  = AD_NORM[ADC_ELE];
    input[CTRL_THR]  = AD_NORM[ADC_THR];
    input[CTRL_RUD]  = AD_NORM[ADC_RUD];
    input[CTRL_AUX1] = AD_NORM[ADC_AUX1];

#ifdef SW1
    // Опрос и установка значения переключателя SW1 (двухрозиционный)
    if(SW1) input[CTRL_SW1] =   TimerClockPerSec(0.0005); // +0,5 мс
    else    input[CTRL_SW1] = - TimerClockPerSec(0.0005); // -0,5 мс
#else
    // Опрос и установка значения переключателя SW1 (трехрозиционный)
    if(SW1_1)            input[CTRL_SW1] =   TimerClockPerSec(0.0005); // +0,5 мс
    if(!SW1_1 && !SW1_2) input[CTRL_SW1] =   0;                     //  0   мс
    if(SW1_2)            input[CTRL_SW1] = - TimerClockPerSec(0.0005); // -0,5 мс
#endif

    // Опрос и установка значения переключателя SW2 (двухрозиционный)
    if(SW2) input[CTRL_SW2] =   TimerClockPerSec(0.0005); // +0,5 мс
    else    input[CTRL_SW2] = - TimerClockPerSec(0.0005); // -0,5 мс

    // Опрос и установка значения переключателя SW3 (двухрозиционный)
    if(SW3) input[CTRL_SW3] =   TimerClockPerSec(0.0005); // +0,5 мс
    else    input[CTRL_SW3] = - TimerClockPerSec(0.0005); // -0,5 мс

    // Копирование виртуальных каналов из реальных
    input[CTRL_V1] = input[CurModel.Mode[FLY_MODE].Control[CTRL_V1].from];
    input[CTRL_V2] = input[CurModel.Mode[FLY_MODE].Control[CTRL_V2].from];

    // Это значение триммера задаваемого в меню. Всегда равно 0,5 мс.
    // В результате влияние зависит только от заданного коэффициента.
    input[CTRL_TRIM] = TimerClockPerSec(0.0005);

    // Нулим массив двойных расходов
    memset(Dual, 0, sizeof(Dual));
    // Опрос двойных расходов
    Dual[CTRL_AIL] = DUAL_AIL;
    Dual[CTRL_ELE] = DUAL_ELE;
    Dual[CTRL_RUD] = DUAL_RUD;

    for(i=0; i < MAX_CONTROLS; i++)
    {
        // Для этих каналов нет кривых - мы их просто копируем на выход.
        if((i == CTRL_SW1) ||
           (i == CTRL_SW2) ||
           (i == CTRL_SW3) ||
           (i == CTRL_TRIM))
        {
            out_cur[i] = input[i];
        }
        else
        {
            // Рассчет значения с использованием кривой
            out_cur[i] = math_CurveInterPol(input[i], CurModel.Mode[FLY_MODE].Control[i].nodes);
        }

        // Расходы
        if(Dual[i])
        {
            if(input[i] < 0) Factor = CurModel.Mode[FLY_MODE].Control[i].minDRates;
            else             Factor = CurModel.Mode[FLY_MODE].Control[i].maxDRates;
        }
        else
        {
            if(input[i] < 0) Factor = CurModel.Mode[FLY_MODE].Control[i].minRates;
            else             Factor = CurModel.Mode[FLY_MODE].Control[i].maxRates;
        }
        if(Factor != 100) out_cur[i] = out_cur[i] * Factor / 100;

        // Если включен T.Cut
        if((i == CTRL_THR) && Cut_enable)
        {
            Factor = CurModel.Mode[FLY_MODE].Control[i].minDRates; // minDRates в канале газа - положение при T.Cut
            out_cur[i] = (- TimerClockPerSec(0.0005)) * Factor / 100;
        }

        // Прибавляем триммера к основным управляющим каналам
        // FIX ME: доделать триммеры через enum
        if(i < MAX_TRIMMERS)
        {
            TrimFactor = CurModel.Mode[FLY_MODE].trimmers[i];
            out_cur[i] += TrimFactor * 2; // FIX ME: нужно вычисляемое число, зависящее от частоты
        }

        // Применение реверса канала
        out_cur[i] *= CurModel.Mode[FLY_MODE].Control[i].reverse;
    }
}

Это просто про оформление… а интересного там гораздо больше: и кооперативная многозадачность(отдельные задачи вызываемые по прерыванию таймера), и пользовательский интерфейс, и много чего еще…

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 напильником доточил. Подумал, жаль выбрасывать.