Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Сопоставление проектов С и АСМ
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
Страницы: 1, 2
SasaVitebsk
Написал сначала проект на ASM в AVR Studio, а позже очень похожий на Си. И там и там вылизывал по скорости. Точнее особо не вылизывал. В обоих случаях камни с большим запасом. Естественно во втором проекте было всё намного грамотнее построено. Вопервых дробление шагов на основе ШИМ с большим коэффициентом, во вторых более красивое движение стрелок обеспечено. 18 скоростей с плавным переключением. Фильтрация более красивая. Обработка CAN некоторое место занимает. Тем не менее сравнивать можно

Результаты достаточно любопытны для неопределившихся. Попробовал свести в небольшую таблицу. smile.gif

Код
Камень       ! atmega8 ! at90can128 !
язык         !   asm   !    IAR C   !
-------------+---------+------------+
Число ШД     !    6    !     6      !
управл.      ! дрб 6   ! ШИМ дрб 16 !
Вх аналоговые!    6    !     3      !
Вх частотные !    0    !     1      !
Вх CAN       !    0    !     2      !
Цифр. флтр   !    +    ! + более слж!
Плавность    !    -    ! спец алг   !
Авар         !    +    !+ более слж !
-------------+---------+------------+
Размер кода  !  5019   !   6901     !
без доп табл !   -     !    672     !
Итого        !  5019   !   6229     !
Итого %      !   81    !    100     !
RAM          !  241    !    415     !
Итого %      !   58    !    100     !
-------------------------------------

Несколько попозже буду перетаскивать Этот проект на м8. То есть можно будет сопоставить практически 1 к 1 (с учётом значительных улучшений).
_Pasha
Для меня лично есть одно НО:
вот я использую мультипоточное программирование на асме, стек-фреймы и прочее - так проще и читабельнее выглядят программы. Диспетчеризация кооперативная. При переходе от потока к потоку в свопинге участвуют только содержимое PC и SP. Все крутится на минимальном наборе регистров, потому что большая их часть задействована в прерываниях, в которых тоже свопинга мало. И под такую Homebrew - ось я заточить "С" не смогу никак.
Вот вчера запустил ногодрыгалку на меге 640 по теме, которой я к Вам в личку напрашивался.
Из 15 возможных ШИМ используются все с частотой до 10кГц.
Из 16 АЦП используются все.
+ Дисплей 7 сегментный + клава + 2 модбаса + 1 RS232.
И рад бы я это на "С" написать, потому как времени на это немного, но прикинул, что с учетом неперенесенных наработок и определенной критической массы Time-critical sections, да еще и трудностей с мультипотоком, писанины будет раза в 3 больше.

А тут же параллельно силовая поделка на:
пульт на меге 8 (+модбас)
контроллер: мега48+PIC18f2431 общаются по I2C (+модбас)
Здесь без "С" вообще никуда.

А по качеству кода у меня и к Winavr претензий нет. И что самое приятное - чем тупее/тривиальнее/читабельнее код, тем лучше оптимизация.
KRS
Это все не показатель.
Если код на С заточить например под IARовский компилер и использовать его фичи:
#pragma inline=forced
__z
__z_x и т.д.
посмотреть как компилятся определенные конструкции
у меня например есть макросы работы с кольцевым буфером в 256 байт выравненым, которые компилятся в
Код
  LDS ZL, Head
  LDI ZH, HIGH(Buf)
  LD r??, Z+
  STS Head, ZL

или если несколько байт надо вытащить

  LDS ZL, Head
  LDI ZH, HIGH(Buf)
  LD r??, Z+
  LDI ZH, HIGH(Buf)
  LD r??, Z+
  STS Head, ZL


использовать временные перменные
Код
    {
         uint8_t tmp;
         if (tmp = TIFR1 & (1<<OCF1A)) {
             TIFR = tmp;
             ....
         }
    }


можно получить код не хуже чем на асме!
зато с большими функциями в которых используется много указателей и вычислений гемора не будет
(IAR прекрасно пересылает указатели movw ZL:ZH, .....)
SasaVitebsk
2 _pasha.
Да с наработками не хочется расставаться - согласен.
И, на небольшом проекте, действительно объём проги на ASMе меньше чем на Си. Особенно если писать грамотно.

Но вот такой вот пример. У меня там фильтрация цифровая есть, так вот при переходе с 8-ми битного режима АЦП на 10-ти битный изменилась:
1) Одна константа (KADMUX)
2) В двух местах заменил ADCH на ADC
3) После фильтрации сигнал поделил на 4. ((z0>>2)&0xff)

И всё!!!
Чтобы коэффициенты 16-ти битные были заменил одно объявление int8_t на int16_t.

На Си мне не приходится собираться с мыслями, чтобы решиться на координальную переделку проекта. Switec-овский алгоритм осилил за час
Rst7
Цитата
и использовать его фичи:#pragma inline=forced__z__z_x и т.д.


На самом деле, с точки зрения написания оптимального кода, данными модификаторами мы немного регулируем распределение регистров компилятором между сохраняемыми/несохраняемыми. Конечно, в пределе, хорошо бы иметь возможность крутить этот параметр самостоятельно куда в более широких пределах. Но и так хорошо, приятная полезность.
Qwertty
Цитата(SasaVitebsk @ Feb 18 2008, 18:45) *
Switec-овский алгоритм осилил за час

Извиняюсь за оффтоп, но что такое "Switec-овский алгоритм"?
SasaVitebsk
Цитата(Qwertty @ Feb 18 2008, 22:33) *
Извиняюсь за оффтоп, но что такое "Switec-овский алгоритм"?

Красивый алгоритм управления стрелками на ШД. Он обеспечивает плавность движения стрелок. Задача, на самом деле довольно сложная. Я реализовывал упрощённую модель.

Смысл в том чтобы плавно менять скорость движения стрелки в зависимости от ошибки по положению.
fmdost
ИМХО. Написать С код соизмеримый с асмом можно, но требует таких же трудозатрат.

А вот интеретно, использовали ли Вы такие приёмы?
Код
unsigned char a;
a = delay_key; //токмо для "оптимизации". Иар почему-то все переменные считает как volatile.
if (KEY_ON())
{
  if ( a != 255 )
    delay_key = a+1;
}
else if (a)
  delay_key = a-1
IgorKossak
Цитата(Т.Достоевский @ Feb 19 2008, 01:25) *
...Иар почему-то все переменные считает как volatile...

В первый раз подобное слышу (обычно народ жалуется, что наоборот).
Наверное потому, что оптимизацию никогда не выключаю.
Igor26
Цитата
Написать С код соизмеримый с асмом можно, но требует таких же трудозатрат.

...если толком не писал ни на том, ни на другом.
Rst7
Цитата
А вот интеретно, использовали ли Вы такие приёмы?


Дык это совершенно правильный код для помогания компилятору. В общем случае структура любой функции для камня с большим количеством регистров должна стремиться к такому виду:
Код
foo()
{
regvar1=memvar1;
regvar2=memvar2;
....
regvarn=memvarn;
....
собственно вычисления
....
memvar1=regvar1;
memvar2=regvar2;
...
memvarn=regvarn;
}


Другое дело, что сам компилятор должен стремиться к этому. Но не грех и помогать ручками. Конечно, пример вырожденный, но, я думаю, мысль мою описывает правильно...
Dog Pawlowa
Цитата(_Pasha @ Feb 18 2008, 11:58) *
Для меня лично есть одно НО:
вот я использую мультипоточное программирование на асме, стек-фреймы и прочее - так проще и читабельнее выглядят программы. ...

А что такое стек-фреймы?
Меня первый же безусловный переход вводит в ступор, а второй - в ярость.
Это я так, нисколько не собираясь споров затевать.
_Pasha
Цитата(Dog Pawlowa @ Feb 19 2008, 09:54) *
А что такое стек-фреймы?
Меня первый же безусловный переход вводит в ступор, а второй - в ярость.
Это я так, нисколько не собираясь споров затевать.

Ну, я сразу все не напишу.
Модель вкратце, остальное по уточняющим вопросам.
Код
.dseg
Entry_Point:   .byte 2
Save_SP:      .byte 2
Local_SP:      .byte 2
Local_Stack:  .byte my_Stack_Size

Var1:            .byte 1
;..............................
VarN:            .byte 2

.set Local_Var_Org = Var1


Это по данным.
Предположим, у нас уже Y адресует начало этого блока данных
Тогда пролог процедуры:
Код
ld zL,Y+
ld zh,Y+; load entry point
in r0,spl
in r1,sph
st Y+,r0; store stack pointer
st Y+,r1

ld r0,Y+
ld r1,Y+
cli
out spL,r0
out sph,r1
sei
; at this line Y points to LOCAL DATA
ijmp; resume procedure execution

Эпилог соответственно восстанавливает все обратно
Очень полезный макрос
Код
.macro leave; <new_entry_point_addr>
  ldi zL,low(@0)
  ldi zh,high(@0)
  jmp Epilogue
.endm

Итого в нашу пользу:
1. Имеем реентерабельные процедуры. Могу выложить пример, где 4 одинаковых процесса крутятся таким образом, выдают инфу каждый на свой индикатор (7-сегм), обрабатывают энкодер как орган управления (правда, плохонько, но претензий нет, я и не работаю над собой)
2. Читабельность зашкаливает
Вместо приснопамятных переменных State мы переопределяем точку входа в процедуру.
Что-нить проверили, кое-какое условие, затем просто пишем leave addr вместо rjmp
Код
Label1:
            call Test_Some_Conditions;
            breq  Label2;
            leave Label1; exits here, but next entry point will be Label1
Label2:; continue program execution


3. Если глубина локального стека позволяет, можно вызывать другие подпрограммы, и если в них использовать leave, все еще интереснее. Т.е. с точки зрения программиста нормальная логика выполнения программ не нарушается.
4. Локальные данные адресуются с помощью yL:yH, намертво закрепленных за указателем
Продолжать ?
Dog Pawlowa
Цитата(_Pasha @ Feb 19 2008, 11:59) *
Продолжать ?

Нет, спасибо.
Интересно, конечно, но я уже перешел на 'C' и возвращаться не имеет смысла.
Однажды видел проект (система продажи билетов на ЖД эпохи начала перестройки), в котором использование макросов и условной компиляции поражало воображение, но для этого нужна свежая молодая голова.
Too late smile.gif
_Pasha
Цитата(Dog Pawlowa @ Feb 19 2008, 13:42) *
для этого нужна свежая молодая голова.
Too late smile.gif

Извиняюсь, не могу смолчать.
1. У меня голова уже точно не молодая и не свежая - сын в институте...
2. У атмелов-то правильных макросредств только недавно появилось smile.gif

3. Немножко все-таки продолжу про минусы того,чего написал выше.
Главный минус - это как детский вопрос : "Можно ли прикрутить туда 1-wire ?" smile.gif
SasaVitebsk
Цитата(Т.Достоевский @ Feb 19 2008, 03:25) *
ИМХО. Написать С код соизмеримый с асмом можно, но требует таких же трудозатрат.


А вы анализировали глобально производительность программы в процессе оптимизации? Существует теория оптимизации (естественно).

Если исключить оптимизацию по коду (в большенстве случаев удобнее просто другой чип взять), а брать оптимизацию по скорости, то возникает следующая ситуация. Оптимизировать требуется только незначительную часть кода. Обычно - единицы процентов. Это даёт существенный результат. Оптимизация же остальной части сказывается не сильно.

Опыт показывает, что оптимизировать этот кусочек лучше именно в виде "подсказок" для компилятора. У меня это дало практически идеальный код. потери в сравнении с асмом - 5% не больше. К сожалению, такая оптимизация тоже получается (CPU-зависима). Выработав некоторые общие принципы, можно сразу писать так, что будет код очень эффективный.

Вот пример
Код
void Point(uint8_t X,uint8_t Y,uint8_t Color)            // Вывести точку
{
uint8_t    maska;                                        // Смещение    относительно байта
uint8_t    *addr;                                        // Адрес экраного байта

maska =0x3;
if((X & 2)==0)
{
   maska = 0x30;
   Color <<= 4;
}
if((X & 1)==0)
{
   maska <<=2;
   Color <<=2;
}
maska = ~maska;
if(NEkr>0) addr = &Ekr[SIZE_EKR];
else addr = Ekr;

addr += (Y*SizeXb)+(X>>2);
maska &= *addr;
maska += Color;
*addr = maska;
}
/*offset    = (X & 3) << 1;                                // выделить    смещение точки в байте
maska = ~(3<<offset);
addr = (Y*SizeXb)+(X>>2);
maska &= Ekr[NEkr][addr];
maska += Color << offset;
Ekr[NEkr][addr] = maska;
}*/

Результат - двукратный рост производительности.
_Pasha
Цитата(SasaVitebsk @ Feb 19 2008, 14:42) *
Вот пример

Ну, я бы еще для масок завел массив в памяти программ, но, не видя листинга, могу и ошибиться.
forever failure
Цитата(SasaVitebsk @ Feb 19 2008, 16:42) *
Результат - двукратный рост производительности.

И можно ещё быстрее и компактней...
SasaVitebsk
Цитата(forever failure @ Feb 19 2008, 17:13) *
И можно ещё быстрее и компактней...

Наверняка.
В данном примере просто наглядно видно что я вместо вызова п/п сдвига на N бит делаю анализ указателя сдвига с генерацией констант.

Табличный метод в данном месте хуже.
вот кусочек результирующего листинга
Код
    891           if(chBright<0)
   \   00000004   2322               TST     R18
   \   00000006   F422               BRPL    ??PointB_0
    892           {
    893             ColorX =0;
   \   00000008   E030               LDI     R19, 0
    894             ch = -chBright;
   \   0000000A   2F02               MOV     R16, R18
   \   0000000C   9501               NEG     R16
   \   0000000E   C002               RJMP    ??PointB_1
    895           }
    896           else
    897           {
    898             ColorX =3;
   \                     ??PointB_0:
   \   00000010   E033               LDI     R19, 3
    899             ch = chBright;
   \   00000012   2F02               MOV     R16, R18
    900           }
    901           maska =0x3;
   \                     ??PointB_1:
   \   00000014   E023               LDI     R18, 3
    902           if((X & 2)==0)
   \   00000016   FB41               BST     R20, 1
   \   00000018   F02E               BRTS    ??PointB_2
    903           {
    904             maska = 0x30;
   \   0000001A   E320               LDI     R18, 48
    905             ColorX <<= 4;
   \   0000001C   9532               SWAP    R19
   \   0000001E   7F30               ANDI    R19, 0xF0
    906             ch <<= 4;
   \   00000020   9502               SWAP    R16
   \   00000022   7F00               ANDI    R16, 0xF0
    907           }

Как видим вполне прилично. На асме у меня получился выигрыш в несколько тактов.
_Pasha
Теперь немного о другом. Рискую утомить читателя, поэтому-кратко.
0. Код под Winavr

1. В EEPROM имеется несколько наборов параметров, активный набор обозначен селектором:

Код
uint8_t EEMEM Selector=0x00;

#define Max_Applicable_Paramsets 4
typedef struct {
    uint16_t Cap_Chg_Time;         // ==0 then continuous mode
    uint16_t Cap_Charge_Volt;     // chg to volt
    uint16_t Cap_Discharge_Volt;     // disch downto this value (volts)
    uint16_t Operating_Current;    // ==0 then voltage mode
    uint8_t     Amplitude;        // used for voltage mode boost
    uint16_t PWMfreq;
    uint16_t Expose_Time;        // in 10ms step
    uint8_t  Rly_Mode;        // ==0 none rly is turned on at selection

    } App_Param_Record;
// eeprom implementation
App_Param_Record EEMEM Parameter_set[Max_Applicable_Paramsets] =
{{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0}};


2. В памяти программ есть дескрипторы этих параметров, но только для активной страницы

Код
//дескриптор параметра
typedef struct {
                      uint8_t xcmd; // параметр соответствует некоторой команде, получаемой извне
                      void *ojaddr; // адрес параметра
                      uint8_t ojsize;// размер данных+ здесь же добавил опции размещения (см ниже) этих данных
                     } Cm_Xref_;

typedef Cm_Xref_ PROGMEM Cm_Xref_t; // объявили
// опции размещения данных
#define Fitter_mask 0xC0
// размещено во флеше (т.е. данные будут только для чтения)
#define Flash_Fit 0x80
// размещено ЕЕ
#define EE_Fit 0xC0
// размещено в ОЗУ и только для чтения
#define Sram_Fit_RO 0x40  //fitted in sram and read only

// Размещено в буфере типа  App_Param_Record, в который будет копироваться страница параметров
#define Relative_Fit 0x00 // relative to struct

// Команды на чтение 0x00..0x7F на запись 0x80..0xFF
#define Inhibit_read_write_mask 0x7F

//собсно описания

static App_Param_Record Edited_Record; // sram shadow

#define Rel_Param(member) &Edited_Record.member,Relative_Fit+sizeof(Edited_Record.member)

static Cm_Xref_t Cm_Xref[Max_cmd+1]=
{
    {cm_Brief,0,0}, // version crc
    {cm_NetLocate,&Net_ID,EE_Fit+1},
    {cm_Set_Selector,&Selector,EE_Fit+1},
    {cm_Charge_Time,Rel_Param(Cap_Chg_Time)},
    {cm_Charge_Volt,Rel_Param(Cap_Charge_Volt)},
    {cm_Discharge_Volt,Rel_Param(Cap_Discharge_Volt)},
    {cm_Operating_Current,Rel_Param(Operating_Current)},
    {cm_Amplitude,Rel_Param(Amplitude) },
    {cm_Expose_Time,Rel_Param(Expose_Time)},
    {cm_Rly_Mode,Rel_Param(Rly_Mode)}
};


Сложняк ?
Да. Для описания параметров из активной страницы пришлось принять, что эту страницу мы будем копировать в буфер. По-другому никак. Потому что, иначе мы разрушаем переносимость программы.

3. Проглотил я, короче, эти сложности - в конце концов памяти программ я пока не потерял.
В общем, дело доходит до реализации. Обрезал я поиск команды. Оставил обращение типа Cm_Xref[command].
Привожу процедуру без купюр и особых комментов. Все названия сторонних функций - сами за себя говорят.

Код
void Track_Pult(void)
{
uint8_t buffer[8],Rsz,*addr,k;
Packet_Layout *pack;
Cm_Xref_ xref,*xr;

    Rsz = RS485_pack_ready();
    if (!Rsz) return;
    // <datasize> <receiver> <sender> <command> <data> <crc>
    pack = &RS_buff;

        if((pack->receiver) != Get_Net_ID()) goto Catch;

    // else -continue parsing received pack
    Sender_Addr = pack->sender;// store sender
    Rsz -= 5;


           k = pack->command & Inhibit_read_write_mask;

    if (k == cm_Remote_Start){
      Set_Start_Status();
      Send_error(er_OK); // :) no errors
      goto Catch; }

    if (k == cm_Remote_Stop) {
      Set_Stop_Status();
      Send_error(er_OK);
      goto Catch; }

    if (k >= Max_cmd) {Send_error(er_Unknown_cmd);goto Catch;}

        xr = memcpy_P(&xref,&(Cm_Xref[k]),sizeof(xref));

uint8_t options,objsize;
void *compaddr;

    options = xref.ojsize;
    objsize = options & (~Fitter_mask);
    options &= Fitter_mask;
    addr = xref.ojaddr;
    k = pack->command != k;
    if (Dev_Status.IsWorking) {Send_error(er_Busy); goto Catch;}
    if (Rsz != objsize) {Send_error(er_InvParam); goto Catch;}

    switch (options){
    case Flash_Fit:
        if (k) Send_error(er_Read_Only);
        else   Send_data(er_OK,memcpy_P(&buffer,addr,objsize),objsize);
        break;

    case EE_Fit:
        eeprom_read_block(&buffer,addr,objsize);
        if (k) {eeprom_write_block(addr,&(pack->data),objsize);Send_error(er_OK);}
         else Send_data(er_OK,&buffer,objsize);
        break;
    case Relative_Fit:
        compaddr = Paramset();
        eeprom_read_block(&Edited_Record,compaddr,sizeof(Edited_Record));
        if (k) {eeprom_write_block(&Edited_Record,compaddr,sizeof(Edited_Record)); Send_error(er_OK);}
            else Send_data(er_OK, addr,objsize);
        break;

        }
Catch:
    RS485_purge_received();
    return;
}


Утомил?
Могу сказать одно - на асме эта байда была бы существенно проще. Я уже не говорю про смысл записи в EEPROM значений, которые не изменились. smile.gif Если я еще чего туда наверну, мне памяти точно не хватит.
Если все-же кто-то "асилил" то, что я накрапал, подскажите, как бороться со сложняком в системе описания параметров. Через "C", ессно.
З.Ы. О цифрах забыл размер кода 364 байт. Время - пофиг. Оптимизация -Os
З.З.Ы. Подчистил грязь - исправил "caller" на "receiver"
defunct
Цитата(_Pasha @ Feb 20 2008, 10:15) *
Могу сказать одно - на асме эта байда была бы существенно проще. Я уже не говорю про смысл записи в EEPROM значений, которые не изменились. smile.gif

Стиль значит асмовый используется.. Попробуйте писАть на С, а не на asm сишными словами.
Одинаковые значения в eeprom тоже никто не заставляет писать, в функции записи байта сделать вычитку ячейки, совпадает с тем что пишем - выход.
SasaVitebsk
Не могу сказать, что я точно понял смысл, но попробую. Хотя если из другой оперы - то извиняй. (Мне кажется я делал что-то подобное)

1. Все команды строю однотипно типа так.
Код
struct AddrKomXx
{
uint8_t            Name;                    // Имя команды
uint16_t            TimeStart;                // Время начала исполнения команды
} *KomXx;

struct AddrKomC
{
uint8_t            Name;                    // Имя команды
uint16_t             TimeStart;                // Время начала исполнения команды
uint8_t             TimeMashtabTek,            // Текущее значение масштаба для времени исполнения
                    TimeMashtab;            // Масштаб для времени исполнения
uint16_t            TimeLife;                // Время исполнения команды с учётом масштаба
int16_t             BegX,BegY,                // Начало объекта (X,Y)
                    SizeX,SizeY;            // Размеры объекта (X,Y)
int8_t                VecX,VecY;                // вектор перемещения объекта (X,Y)
uint8_t            color;                    // цвет заливки
} *KomC;

struct AddrKomK
{
uint8_t            Name;                    // Имя команды
uint16_t             TimeStart;                // Время начала исполнения команды
uint8_t             KernTek;                 // Текущее значение масштаба для времени исполнения
} *KomK;


У меня их море, это кусочек. Причём первая AddrKomXx это несуществующая "виртуальная", для определения текущей. Определение делаю так.
Код
  KomXx    = (struct AddrKomXx*) AdrActiveKom[i];            // Прочитать адрес текущей активной    команды
  if(KomXx->TimeStart>Status.TekTime) continue;            // Если    не подошло время для исполнения    команды, то    пропустить данную команду
  switch (KomXx->Name) {
  /* Команда "END" - "Ролик    завершить" */
  case 'S':
  /* Команда "Stop-Kadr"    - "Остановить модификацию картинки" */
            KomS = (struct AddrKomS*) AdrActiveKom[i];    // Прочитать адрес текущей активной    команды
...

То есть сначала адрес начала команды "сопоставляю" с "виртуальной" командой все поля в которой в точности совпадают во всех командах и по ней осуществляю контроль команды, а потом "пересопоставляю" с уже реальной командой, где уже реальные поля и работаю с ней.
Если работаешь с сылками(адресами), как я, то без разницы где находится команда и её можно не пересылать вообще. Имей один буфер приёма - с ним и работай. Я например даже удаляю отработавшие и делаю "сборку мусора". zltigo ещё интереснее сделал, если я подосвобожусь тоже так попробую - он свой диспетчер кучи написал и работает с занятием/освобождением весьма грамотно. У меня, принципиально, возможны проколы в данном месте - я это знаю.
_Pasha
Цитата(defunct @ Feb 20 2008, 17:51) *
Стиль значит асмовый используется.. Попробуйте писАть на С, а не на asm сишными словами.
Одинаковые значения в eeprom тоже никто не заставляет писать, в функции записи байта сделать вычитку ячейки, совпадает с тем что пишем - выход.

1. Дописываю тот же кусок кода на асме. Для сравнения. Получается в полновесном варианте, со всеми обработками ошибок, с поиском правильной команды и с таблицей - меньше 200 байт. Завтра выложу в аттаче, дабы не превращать тему в "кодовый флуд". По поводу "писАть на С" - я "С" терпеть не могу еще с 1992 года smile.gif Проблемы-то начинаются с обращения к памяти программ за данными. Этот процесс нигде не автоматизирован, отсюда сложности.
2. Переопределять стандартные функции НЕ ХОЧУ.

Цитата(SasaVitebsk @ Feb 20 2008, 19:25) *
Не могу сказать, что я точно понял смысл, но попробую. Хотя если из другой оперы - то извиняй. (Мне кажется я делал что-то подобное)

Смысл: Хранение в девайсе настроечных параметров и обращение к ним, например, через команды по последовательному порту.

Цитата
... он свой диспетчер кучи написал и работает с занятием/освобождением весьма грамотно. У меня, принципиально, возможны проколы в данном месте - я это знаю.

Вот! Там, где дело касается ОЗУ - ессно "С" выиграет.
SasaVitebsk
Цитата(_Pasha @ Feb 20 2008, 22:49) *
Проблемы-то начинаются с обращения к памяти программ за данными. Этот процесс нигде не автоматизирован, отсюда сложности.
2. Переопределять стандартные функции НЕ ХОЧУ.
Смысл: Хранение в девайсе настроечных параметров и обращение к ним, например, через команды по последовательному порту.
Вот! Там, где дело касается ОЗУ - ессно "С" выиграет.

Никаких проблем при обращении к памяти программ за данными я не заметил. Обращаешься так же как и к обычной переменной. Более того код получается очень эффективным. Пересылать абсолютно ничего не надо. По крайней мере в IAR.
В редких случаях, когда идут разнотипные данные компилятору приходится явно указывать что идёт обращение к flash.
Например
Код
// Знакогенераторы
const    void    __flash   *symbol[MAX_FONTS] = {&fnt0_6x8,&fnt1_8x8,&fnt2_10x10,&fnt3_11x13,&fnt4_9x14,&fnt5_16x16,&fnt6_16x16,&fnt7_12x13,&fnt8_16x15,&fnt9_14x16};
....
uint8_t __flash  *addr;
.....
     addr =    ((uint8_t __flash *)symbol[tekfonts])+(Znak*Fonts[tekfonts][1]);
.....

А обычно и этого делать не приходится
Код
// структура данных по символам
struct CharZnak
{
uint16_t            sm;                        // Смещение от начала фонта
uint8_t            width;                    // Ширина символа
};

//extern             uint16_t                __checksum;        //    CHECKSUM;
//const __flash    uint16_t                EndFlashAdr @0xe5 = & __checksum;

extern const    uint8_t        __flash        Fonts[MAX_FONTS][2];
extern const __flash struct    CharZnak    struct_fnt[MAX_FONTS-2][223];
....
    symbolx=struct_fnt[tekfonts-2][Znak].width + tekkern;


Вот пример компиляции
Код
   1038              symbolx=struct_fnt[tekfonts-2][Znak].width + tekkern;
   \   0000002C   ....               LDI     R30, LOW((struct_fnt - 1336))
   \   0000002E   ....               LDI     R31, HIGH((struct_fnt - 1336))
   \   00000030   5221               SUBI    R18, 33
   \   00000032   E003               LDI     R16, 3
   \   00000034   9F02               MUL     R16, R18
   \   00000036   0DE0               ADD     R30, R0
   \   00000038   1DF1               ADC     R31, R1
   \   0000003A   E90D               LDI     R16, 157
   \   0000003C   E012               LDI     R17, 2
   \   0000003E   9F14               MUL     R17, R20
   \   00000040   2D10               MOV     R17, R0
   \   00000042   9F04               MUL     R16, R20
   \   00000044   0D11               ADD     R17, R1
   \   00000046   0DE0               ADD     R30, R0
   \   00000048   1FF1               ADC     R31, R17
   \   0000004A   9104               LPM     R16, Z
   \   0000004C   0F06               ADD     R16, R22
   \                     ??ZnakWidth_1:
_Pasha
Вот это совсем другое дело! smile.gif
Че-то WINAVR тормозит немного. А может я торможу - начитался мануалов, а уже все работает. Пойду проверю.
Насчет диспетчера кучи: я так понял, надо либо собрать все указатели, работающие с выделяемыми объектами, в одну секцию. Тогда при дефрагментации памяти сравнительно легко все перемещается. Либо не париться и держать дескрипторы.
AHTOXA
Цитата(_Pasha @ Feb 21 2008, 06:19) *
Вот это совсем другое дело! smile.gif
Че-то WINAVR тормозит немного.


WINAVR как раз в этом очень сильно тормозит. Работа с флешом в нём сделана мегакриво. Так как в примере SasaVitebsk-а, не выйдет.
_Pasha
Цитата(AHTOXA @ Feb 21 2008, 10:20) *
WINAVR как раз в этом очень сильно тормозит. Работа с флешом в нём сделана мегакриво.

... И похоже, что по политическим соображениям никогда не исправится.
Насчет кривизны:
Из предыдущего утомительного примера:
Код
static Cm_Xref_t Cm_Xref[Max_cmd+1]=
{
    {cm_Brief,0,0}, // version crc
    {cm_NetLocate,&Net_ID,EE_Fit+1},
    {cm_Set_Selector,&Selector,EE_Fit+1},
/*****************************************/
};
/******************************************/
uint8_t M;
M = Cm_Xref[2].ojsize;


Так constant propagation оно просчитывает. А стоит появиться адресному выражению - кирдык.
Цитата
Попробуйте писАть на С, а не на asm сишными словами...


Блин, вот зацепил...до сих пор помню.
ReAl
Цитата(_Pasha @ Feb 21 2008, 03:19) *
Вот это совсем другое дело! smile.gif
Че-то WINAVR тормозит немного.

Ну, у avr-gcc код как правило похуже, с этим не спорю.
Код
#include <stdint.h>
#include <avr/pgmspace.h>
#define MAX_FONTS 10 // от балды, в приведеннмо примере не было задано. Но оно неважно

// структура данных по символам
struct CharZnak
{
uint16_t            sm;                        // Смещение от начала фонта
uint8_t            width;                    // Ширина символа
};

extern const  uint8_t   Fonts[MAX_FONTS][2] PROGMEM;
extern const  struct    CharZnak    struct_fnt[MAX_FONTS-2][223] PROGMEM;
extern uint8_t tekfonts, tekkern;

uint8_t sx(uint8_t Znak)
{
    return pgm_read_byte( & struct_fnt[tekfonts-2][Znak].width ) + tekkern;
}


CODE
.file "tst.c"
__SREG__ = 0x3f
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__tmp_reg__ = 0
__zero_reg__ = 1
.global __do_copy_data
.global __do_clear_bss
.text
.global sx
.type sx, @function
sx:
/* prologue: frame size=0 */
/* prologue end (size=0) */
ldi r25,lo8(3)
mul r24,r25
movw r30,r0
clr r1
lds r24,tekfonts
ldi r25,lo8(0)
ldi r18,lo8(669)
ldi r19,hi8(669)
movw r20,r24
mul r20,r18
movw r24,r0
mul r20,r19
add r25,r0
mul r21,r18
add r25,r0
clr r1
add r30,r24
adc r31,r25
subi r30,lo8(-(struct_fnt-1336))
sbci r31,hi8(-(struct_fnt-1336))
/* #APP */
lpm r30, Z
/* #NOAPP */
lds r24,tekkern
add r30,r24
mov r24,r30
ldi r25,lo8(0)
/* epilogue: frame size=0 */
ret
/* epilogue end (size=1) */
/* function sx size 31 (30) */
.size sx, .-sx
/* File "tst.c": code 31 = 0x001f ( 30), prologues 0, epilogues 1 */

Катастрофической я бы разницу не назвал - ни в написании текста, ни в сгенерированном коде.
Сравнивать надо одинаковые фрагменты, в приведенном выше куске часть регистров уже "откуда ни возьмись" заполнены и используются, тут всё "автономно".


Цитата(_Pasha @ Feb 20 2008, 20:49) *
По поводу "писАть на С" - я "С" терпеть не могу еще с 1992 года smile.gif
Я года с 1987..88 использую и ничего. Что-то писалось чисто асмовое, что-то с делением "это пусть на С, а эта часть на асме".
Сейчас если очень хочется - пишу вставку на асме. В последнее время хочется всё реже.

Цитата
2. Переопределять стандартные функции НЕ ХОЧУ.
Ну извините... Так можно дойти до "а на асме я могу что-то другое делать, пока EEPROM пишется, а С-шная программа станет колом на всё время записи блока, во какой плохой язык С".
И начать требоавть стандартную функцию для решения всей своей задачи.
defunct
Цитата(_Pasha @ Feb 21 2008, 09:34) *
Блин, вот зацепил...до сих пор помню.

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

Цитата
Проблемы-то начинаются с обращения к памяти программ за данными. Этот процесс нигде не автоматизирован, отсюда сложности.

да и на асм он не автоматизирован, ровно в той же степени.
_Pasha
Вернемся к нашей баранине. "Не умеешь-научим, не хочешь-заставим." Заставили. Дописал функцию записи в ЕЕПРОМ, в аккурат по шаблону eeprom_write_block() .

Код
void EE_Proof_Write_Block(const void *pointer_ram,
              void *pointer_eeprom,
              size_t n)
{
size_t j;
uint8_t *src,*dst;

src = (uint8_t*)pointer_ram;
dst = (uint8_t*)pointer_eeprom;

for (j=0;j<n;j++)
{
  if (eeprom_read_byte(dst) != *src)
      eeprom_write_byte(dst,(uint8_t)*src);
  dst++;
  src++;
}
return;
}


ИТОГО такое: +56 байт. Имея ввиду, что была исходная eeprom_write_block(), вычтем, скажем, байт 16. В добавок, это все можно обрезать, задаваясь размером блока не size_t a uint8_t, если уж очень хотца. Это еще минус байта 4-6. Короче, +32..36 байт. Оптимайзер рулит!!!
Может, мы доживем до тех времен, когда свопинг лишний чистится будет...
Кстати, кто-нибудь знает, почему многопроходовая оптимизация в GCC не в фаворе?

Цитата(defunct @ Feb 21 2008, 14:52) *
Все тоже самое в Сишном стиле можно сделать красивее и быстрее чем на асм, и уже глядя на код нельзя будет его назвать "байдой".


Боюсь, что не получится. Команд будет поболее - до 30 в полновесном варианте. И надо, чтобы в идеале доступ был не по индексу, а по LookUp, т.к. коды команд могут меняться. Так что от таблицы никуда не денешься. Впрочем, если Вы пальцем ткнете, я пойму, что имеется ввиду...
singlskv
Цитата(_Pasha @ Feb 21 2008, 19:57) *
Код
void EE_Proof_Write_Block(const void *pointer_ram,
              void *pointer_eeprom,
              size_t n)
{
size_t j;
uint8_t *src,*dst;

src = (uint8_t*)pointer_ram;
dst = (uint8_t*)pointer_eeprom;

for (j=0;j<n;j++)
{
  if (eeprom_read_byte(dst) != *src)
      eeprom_write_byte(dst,(uint8_t)*src);
  dst++;
  src++;
}
return;
}

Несколько моментов про Ваш код:
- вместо цикла for лучше использовать do..while c проверкой size_t n == 0 только в самом начале..
- написав отдельно src++ и вызывая стандартные функции Вы сами себя лишили возможности
пользоваться автоинкрементом при доступе к данным в RAM
- каждая из функций eeprom_xxx будет ждать флага освобождения EEPROM
- каждая из функций eeprom_xxx будет прописывать один и тот же адрес в EEAR

попробуйте это отрихтовать и удивитесь лаконичности полученого кода...
_Pasha
Цитата(defunct @ Feb 21 2008, 14:52) *
стиль кода Вашего примера не Сишный, и рад что вышло.


Нашел. Это паскалевский стиль. Обнаружился древний, почти безусловный рефлекс: как только в программе забрезжит хоть какой-то намек на ввод-вывод, бежим как от огня от перечислительных типов. Применительно к "С" это распространилось и на битовые поля. Так что, могу на любом языке, как на паскале. smile.gif

Кстати, был еще один рефлекс: клавишу F2 нажимать каждые 15 секунд. Ох и долго избавлялся!

Цитата(singlskv @ Feb 21 2008, 20:49) *
Несколько моментов про Ваш код:

попробуйте это отрихтовать и удивитесь лаконичности полученого кода...

Спасибо! Рихтуем, удивляемся... smile.gif
singlskv
Цитата(ReAl @ Feb 21 2008, 11:43) *
Ну извините... Так можно дойти до "а на асме я могу что-то другое делать, пока EEPROM пишется, а С-шная программа станет колом на всё время записи блока, во какой плохой язык С".
Вотъ, это тоже хотелось бы обсудить, хотя конкретно это, я уже пытался обсуждать,
но большинство опонентов высказалось в том смысле, что в IAR удобно обращаться к
таким(EEPROM) переменным и все тут...

А сейчас я вот задался интересным вопросом, если кто еще не заметил, в новых AVR (mega48/88/......)
есть такая фича, можно отдельно стирать ячейку и можно отдельно ее программировать...

Вопрос:
кто-нить уже пробовал записать в ячейку 2 раза без стирания ?
ИМХО, это открывает новые возможности для увеличения количества записи в eeprom,
но конечно, этот факт, требует дополнительной проверки, добровольцы есть ?
Artak
Ребята, может бить мой вопрос покажетса смехотwорным
но все- таки.
Если нужно умножить два восьмибитних числа и получить 16 битный результат достаточно написать
что то вроде
mul r16, r17 (конечно только для mega AVR)
и читать результат из пары r0,r1.
На C приходится писать
int result = (int)a * (int)b; или int result = (int)a * b; (a и b 8 битные)

Так как если написать int result = a * b то компилятор согласно правилам языка C сначала генерирует mul а потом обнуляет старший байт (результат умножения двух char получается в виде char и потом толко присвоивается int у)

А код скомплированный в результате int result = (int)a * (int)b; получаетса очень громоздкий, так как умножаяютса два 16 битных числа не обращая внимания на то что в старших байтах нули

чтобы решить проблему скорости мне пришлось в проекте написанном на C писать int result = a * b потом из сгенерированного асм листига вручную удалить clr r1 после mul-а и сгенерировать hex фаил в AVR Studio

А есть вообще грамотный способ решения таких проблем в C коде?
Rst7
Цитата
А есть вообще грамотный способ решения таких проблем в C коде?


В IAR'е можете написать __multiply_unsigned( a , b ) - это непосредственно сгенерит mul. Для портабельности не забудьте на этапе компиляции проверить текущий процессор и компилятор, и если это не AVR и не IAR, сделать #define __multiply_unsigned(var1,var1) ((var1)*(var2)). Про остальные умножения можете прочитать в доке по IAR'у, ключевое слово "Intrinsic functions"
Petka
Цитата(singlskv @ Feb 22 2008, 01:37) *
Вопрос:
кто-нить уже пробовал записать в ячейку 2 раза без стирания ?
ИМХО, это открывает новые возможности для увеличения количества записи в eeprom,
но конечно, этот факт, требует дополнительной проверки, добровольцы есть ?


Велосипед уже изобретён. Atmel AN "AVR103"
_Pasha
Цитата(singlskv @ Feb 21 2008, 20:49) *
Несколько моментов про Ваш код:
- вместо цикла for лучше использовать do..while c проверкой size_t n == 0 только в самом начале..
- написав отдельно src++ и вызывая стандартные функции Вы сами себя лишили возможности
пользоваться автоинкрементом при доступе к данным в RAM
- каждая из функций eeprom_xxx будет ждать флага освобождения EEPROM
- каждая из функций eeprom_xxx будет прописывать один и тот же адрес в EEAR

попробуйте это отрихтовать и удивитесь лаконичности полученого кода...


Поменял for() на while() и убрал лишнее копирование параметра size_t n . Т.е. while(n){}
Дальнейшее улучшение усиливает зависимость от железа, поэтому в данном случае (не библиотечном) не имеет смысла.
Бухгалтерия -12 байт. Итого в гору не более 24 байт на "крайне нежелательную" на первый взгляд функцию.
singlskv
Цитата(Petka @ Feb 22 2008, 10:25) *
Велосипед уже изобретён. Atmel AN "AVR103"
Спасибо за наводку,
этот апликайшн как-то мимо меня пролетел...
_Pasha
Теперь про асм.
В контексте многопоточного программирования очень полезно использование итераторов.
Пример:
Код
Iterate_16:
; input xL:xH = counter
; zL:zH = pointer to procedure/iteration body
    push    zL
    push    zh

    push    xL
    push    xH
    icall
    pop    xH
    pop    xL

    sbiw    xL,1
    brne    iterate_16_1    

    pop    zh
    pop    zL
    ret    

iterate_16_1:
    push    xL
    push    xh
    leave(Iterate_16_2)

Iterate_16_2:
    pop    xh
    pop    xL
    pop    zh
    pop    zL
    rjmp    Iterate_16


Данный итератор позволяет создавать циклы произвольной вложенности (в зависимости от размеров стека Вашего процесса). При этом подпрограмма -тело цикла вызывается косвенно, и может быть как атомарной, так и передавать управление системе, т.к. все параметры цикла как конструкции находятся в локальном стеке процесса.
Пример применения:
Код
func1:
; blah,blah,blah
    ret    
;...................................
func2:
    ldi16    xL,xh,0x100
    ldi16    zL,zh,func1;  
    jmp    Iterate_16
;...................................
func3:
    ldi16    xL,xh,0x0010
    ldi16    zL,zh,func2
    jmp    Iterate_16


В приведенном примере внешний цикл описан в func3, в котором вызывается внутренний цикл, описанный func2. Примерный сишный эквивалент
Код
int i,j;
for(i=0x0010;i >= 0;i--)
  for(j=0x100;j>=0;j--) func1();

... но в условиях кооперативного распределения ресурсов

P.S. Исправил примеры func2, func3. Была ошибка в загрузке Z. Кому интересно, обратите внимание.
ReAl
Цитата(Artak @ Feb 22 2008, 01:25) *
На C приходится писать
int result = (int)a * (int)b; или int result = (int)a * b; (a и b 8 битные)
Так как если написать int result = a * b то компилятор согласно правилам языка C сначала генерирует mul а потом обнуляет старший байт (результат умножения двух char получается в виде char и потом толко присвоивается int у)
По стандарту языка С те два a,b обязаны быть приведены к int до выполнения команды умножения. Т.е. явне приведения не нужны, результат должен быть правильным. Иначе - в топку компилятор, С хорош стандартом, если для каждого компилятора надо менять привычки - то тогда проще на асме писать.

Цитата(Artak @ Feb 22 2008, 01:25) *
А код скомплированный в результате int result = (int)a * (int)b; получаетса очень громоздкий, так как умножаяютса два 16 битных числа не обращая внимания на то что в старших байтах нули
чтобы решить проблему скорости мне пришлось в проекте написанном на C писать int result = a * b потом из сгенерированного асм листига вручную удалить clr r1 после mul-а и сгенерировать hex фаил в AVR Studio


Код
#include <stdint.h>

uint16_t mul88(uint8_t a, uint8_t b)
{
    return a*b;
}


avr-gcc -O2 -S -mmcu=atmega8

Код
    .text
.global    mul88
    .type    mul88, @function
mul88:
/* prologue: frame size=0 */
/* prologue end (size=0) */
    mul r22,r24
    movw r24,r0
    clr r1
/* epilogue: frame size=0 */
    ret
Ну да, clr r1... Но уже после переноса результата умножения в пару R25:R24, так как по соглашениям avr-gcc регистр R1 содержит константу 0 (я бы предложил попробовать переделать это хозяйство на два временных регистра __tem_reg_l__, __temp_reg_h__ и R2 как константу 0, чтобы не мучиться каждый раз после умножения, но неизвестно, как занятие R2 скажется на остальном).
_Pasha
В общем, наигрался я с "С". Память заканчивается, а проект лежит. Теперь переписываю на асме. Размер кода обещает быть в 2 раза короче (к слову о больших проектах). Да и пишется все по-быстрому, если знаешь, что делаешь. После чего все эти компиляторы, расплодившиеся в последнее время у меня в компе, летят в корзину. То же самое, кстати, касается и pic18 и dspic30/33 и HCS08, коими я иногда страдаю. Компилятор-то, оказывается, в голове...
defunct
Цитата(_Pasha @ Feb 23 2008, 01:10) *
В общем, наигрался я с "С". Память заканчивается, а проект лежит. Теперь переписываю на асме.

Совет для того чтобы память не заканчивалась в проектах на "C"
1. Делать стек большим, а глобальных переменных поменьше.
2. Использовать heap.
singlskv
Цитата(_Pasha @ Feb 23 2008, 02:10) *
В общем, наигрался я с "С". Память заканчивается, а проект лежит. Теперь переписываю на асме. Размер кода обещает быть в 2 раза короче (к слову о больших проектах).

Такая разница может получиться ТОЛЬКО на вычислительных алгоритмах и в основном
в местах где при использовании С необходимо делать преобразование типов,
ну или для вариантов когда просто в С нет соответствующего размера типа,
например в С нет 3-байтовых переменных...

Для остальных вариантов..., ну показывайте код что ли, думаю что после некоторой оптимизации
он Вас опять же сможет удивить...


Цитата(defunct @ Feb 23 2008, 02:37) *
Совет для того чтобы память не заканчивалась в проектах на "C"
1. Делать стек большим, а глобальных переменных поменьше.
2. Использовать heap.
Вот здесь хотелось бы разъяснений и поподробнее...(без подколок...),
я не понимаю как уменьшить количество глобальных переменных.
Я не понимаю как можно использовать heap для уменьшения расхода памяти вместо
использования глобальных переменных...
Alex B._
Цитата(singlskv @ Feb 23 2008, 02:55) *
я не понимаю как уменьшить количество глобальных переменных.
Я не понимаю как можно использовать heap для уменьшения расхода памяти вместо
использования глобальных переменных...

хм, а чего тут не понятного? sad.gif
singlskv
Цитата(Alex B._ @ Feb 23 2008, 03:03) *
хм, а чего тут не понятного? sad.gif
Содержательный ответ, а примерчик не подкините ?
defunct
Цитата(singlskv @ Feb 23 2008, 01:55) *
Вот здесь хотелось бы разъяснений и поподробнее...(без подколок...),
я не понимаю как уменьшить количество глобальных переменных.

А что Вы храните в глобальных переменных?
Ведь кроме конфигурации там больше нечего хранить.
Под все остальное память можно выделить либо в стеке, либо в куче.

Цитата
Я не понимаю как можно использовать heap для уменьшения расхода памяти вместо
использования глобальных переменных...

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

Еще пример:
допустим по i2c раз в секунду надо что-то записать/прочитать.
Выделили буфер из heap - сказали i2c драйверу - читать/писать данные в него.
Все остальное время - эта память будет использоваться под что-то другое.

и так многие места где используются значительные объемы памяти можно разделять во времени с помощью heap'a - пользовать память эффективно (одну и ту же область для разных целей в разное время).
singlskv
Цитата(defunct @ Feb 23 2008, 03:10) *
А что Вы храните в глобальных переменных?
Ведь кроме конфигурации там больше нечего хранить.
Как это нечего ?
Вот вполне реальный пример, нужно по 4 каналам АЦП делать скользящее среднее по 8 измерениям,
ИТОГО:
4 * 8 * 2(байта на измерение) = 64 байта
+ 4(канала) * 2(байта) = 8 байт для хранения суммы
+ 1 байт на хранение номера канала
+ пару байт на всякие сервисные штучки
ИТОГО: ~75 байт глобальных переменных..

Пример N2:
Есть ~250байт которые есть суть текущее состояние системы, эти параметры могут быть запрошены
в любой момент например по протоколу модбас,
допустим что модбас работает на скорости 345600, можем ли мы хранить где-нить кроме глобальных
переменных эти данные ?
SasaVitebsk
А мне просто супер нравится С. По-моему, для МК это совершенно оптимальный уровень абстракции. Здесь чётко видишь что такое объект или структура, что будет делать компилятор. Если необходимо вставить ASM кусок - пожалуйста, сразу видно как к сишным переменным обращаться. С другой стороны - совершенно наплевать как он память распределяет, - пусть сам думает. Как раз с этим не хочется возиться.

Я тоже наигрался с Си пытаясь добиться от него оптимального (как сделал бы сам на асме) кода. Ну кое где проколы есть у него, но он зато выигрывает при большом количестве глобальных переменных. Я не смог тут его сделать.

Не знаю как у вас получается "в два раза" - у меня 10-20%.

Единственное где можно капитально "надрать" компилятор, это если большое количество основных переменных в регистры влезает. Но это ведёт к сложности модификации программы. Что мы и имеем.

С другой стороны - какая песня иметь легко модифицируемый чёткий и ясный проект!!! Вот вы применяете кастрированную ОС своего исполнения. А если применить нормальную. Со всеми наворотами? Ну хорошо "не влазит задача". А можно спросить - куда она не влазит? Если, к примеру в m8, то есть m168/328. Разница - копейки. Если речь идёт о какой-нибудь m640, то ещё меньше разница на m1280/2560. Или на соответствующий ARM перейти со старшей модели тоже не фокус из под С. Короче, безусловно, дело вкуса, но очень спорный момент.

Когда просматриваешь кусочек, то да - кода много. А если сделать полный проект, то ваш выигрыш будет копеечный, а в замен сложная модифицируемость проекта. Си-шный сопровождать - на порядок легче. Это, конечно, моё личное мнение.
defunct
Цитата(singlskv @ Feb 23 2008, 02:43) *
Как это нечего ?
Вот вполне реальный пример, нужно по 4 каналам АЦП делать скользящее среднее по 8 измерениям,
ИТОГО:
4 * 8 * 2(байта на измерение) = 64 байта

Я конечно не знаю вашей задачи полностью и возможно у вас есть некие строгие требования к сохранению фазы сигнала поэтому нужен КИХ, а я не брезгую БИХ'ом - ибо эффективней с т.з. памяти..
У меня скользящее по восьми измерениям занимает 2* U16 данных и кот наплакал кода.
итого 4 канала - 16 байт:
код для усреднения по 8-ми точкам:

Код
pChan->vAverage = (pChan->vSum + 4) >> 3;
pChan->vSum += val - pChan->vAverage;

но я не меняя размер данных могу и по 16-ти точкам усреднять и по 32-м и по 1024.
меняться будет только формула..
Конечно, это не тот случай когда heap чем-то помог, но все же думать об экономии там где это возможно - стоит.

Цитата
Пример N2:
Есть ~250байт которые есть суть текущее состояние системы, эти параметры могут быть запрошены
в любой момент например по протоколу модбас,
допустим что модбас работает на скорости 345600, можем ли мы хранить где-нить кроме глобальных
переменных эти данные ?

Статистика / параметры системы - это есть конфигурация...
Стало быть легально делать их глобальными.
SasaVitebsk
Из приведенного мной первого поста видно, что проигрыш по памяти - почти двукратный.
Правда ....
1) я не оптимизировал ...
2) На оба стэка выделил значительно больше памяти, чтобы не искать потом мучительно ....
3) На асме стек не учитывал ...

Тем не менее, если это всё поучитывать + взять равноценные проекты = всё равно коэффициент 1.3~1.4 думаю найболее реальный. Но памяти сейчас в микроконтроллерах вполне достаточно. Как правило, на большинстве задач на асме она остаётся, а на Си её просто достаточно. В тех же задачах где требуется много памяти, - не спасает использование ASMа, надо вешать внешнюю.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.