| Форум РадиоКот https://radiokot.ru/forum/ |
|
| C для AVR -- пишем аккуратно. https://radiokot.ru/forum/viewtopic.php?f=57&t=77142 |
Страница 1 из 6 |
| Автор: | avreal [ Пт авг 24, 2012 13:35:36 ] | ||
| Заголовок сообщения: | C для AVR -- пишем аккуратно. | ||
Выделено из темы про ассемблер AVR. Начало тут. Цитаты оттуда: shads писал(а): Сообразил тут декодер радиоканала на тиньке 13-й, в двух вариантах, на С и на асме, так вот мож кому интересно будет узнать, разница в весе кода - больше чем в 2 раза, на С - 870 байт, а на асме тоже самое - 420 байт..... Помоему асма остается актуальной для таких мелкашек как tiny13 http://asis-kbr.ru/forum/viewtopic.php?f=9&t=122 avreal писал(а): Я даже больше скажу -- если написать на асме программу размером байт десять, то С-шный вариант проиграет раз в пять-восемь, так как С (по крайней мере WinAVR) и всю таблицу прерываний заполняет переходом на ловушку необработанных прерываний, и прочие «общеподготовительные» вещи могут место занять. Т.е. у С-шной программы даже без желания автора обычно несколько другой функционал. ... Увидел всё по линку, гляну по свободе. На носу куча выходных Ну вот и глянул. Табличка результатов на разных версиях avr-gcc. orig -- код по ссылке patch -- то, что я с ним сделал. Ключи компиляции везде одинаковые, взяты из стандартного проекта. Для 4.7.1 ещё добавлялся ключ -fno-ivopts, чтобы он менше чудил с оптимизацией induction variables в циклах. Они там что-то в «корневом» gcc похимичили, для процессоров с развитыми адресациями точно стало лучше, для AVR компилятор иногда слишком «мудрит» и сам себя перехитрить умудряется. Код: version orig patch avr-gcc build «MobileChessBoard» -- это такие сборки свежего avr-gcc для Windows.3.4.6 884 702 WinAVR-20060421 4.2.2 874 678 WinAVR-20071221 4.3.3 872 658 WinAVR-20100110 4.7.1 844 674 MobileChessBoard 4.7.1rc1 (-fno-ivopts -> 668) 4.3.4 894 680 Ubuntu-10.04 4.5.3 860 660 Ubuntu-12.04 Т.е. проигрыш у ассемблера уже не два, а полтора раза (675 / 450) Но сейчас я не о том, насколько проигрывает C ассемблеру на проектах такого размера, а о том, что можно сделать с нормальным в принципе С-шным кодом для уменьшения размера прошивки байт так с 870-ти до байт так 670-ти (больше, чем на четверть). Причём смотрел «обще-микроконтроллерно-С-шные» вещи, там осталась одна большая беда именно avr-gcc, IAR должен бы ещё короче сделать на несколько десятков байт, но мне лень проверять. Пришиваю новый исходник, надеюсь, оно будет работать Немного позже (сейчас гости пришли) напишу немного по принципы — «общие» и что там осталось специфического для avr-gcc
|
|||
| Автор: | shads [ Пт авг 24, 2012 15:46:05 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Гы..... С трудом узнал свою программу..... Даже не верилось что она заработает, НО..... она работает..... Спасибо за уделенное внимание. Хотя для понимания она стала сложнее (по крайней мере для меня, т.к. я тока начал писать на С), но надо будет обязательно разобраться, чего это вы там такого понаписали |
|
| Автор: | BOB51 [ Пт авг 24, 2012 15:52:32 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
однако в конце-концов всеравно все сводится к машинным кодам корректность программы на С во многом зависит от качества компилятора (и знаний правильных приемов работы с оным), а в ассемблере львиная доля - знание аппаратной структуры и схемотехники устройств в идеале - знание того и другого (да еще минимум на всех базовых семействах) из С более перспективным выглядит микроси - единый подход ко всем 4-м "ходовым" семействам.... но... полные версии уж "оччень платные" |
|
| Автор: | YS [ Пт авг 24, 2012 19:49:31 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Я, кстати, достаточно подробно разбирал, чего генерирует AVR-GCC в нагрузку к основному коду. |
|
| Автор: | avreal [ Пт авг 24, 2012 20:05:55 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
shads писал(а): Гы..... С трудом узнал свою программу..... Так я же в ней ничего принципиального не менял Так, точечно, «тактически»., а «стратегически» все осталось как было. Итак, пошли сверху вниз по тексту. Код: #define INLINE inline __attribute__((__always_inline__)) GCC-шные штуки, эквивалентные IAR-овским #pragma inline и т.п. Команды компилятору принудительно «инлайнить» (встраивать по месту как макросы) и ни в коем случае не инлайнить определённые функции. Будут использованы ниже.#define NOINLINE __attribute__((__noinline__)) Код: //прототипы функций Каждая функция используются только в данном файле, поэтому её стоит объявить как static. При этом компилятор знает, что снаружи она не может быть вызвана и поступает с ней по собственному усмотрению. Если функция вызывается из одного места, то он практически гарантированно её встроит по месту, а не будет вызывать. Если функция вызывается из нескольких мест, но она короткая — тоже может встроить.static void Receive(void); static void Int100Hz(void); При встраивании единожды вызываемой функции экономи размера кода небольшая, но есть. Для небольшой функции без static может быть вообще плохо — он её и по месту встроит (маленькая, экономия на саом вызове и на стандартной передаче параметров может быть больше размера функции), но и отдельно тело тоже оставит. Так как при компиляции из .c в .o компилятор не знает, что файл один, оставляет функцию на случай, если ее кто-то снаружи вызовет. Т.е. функция займёт место во флеше дважды. Кроме того, static-объекты не захламляют глобальное пространство имён, не занимают место (и время) в соответствующих таблицах при работе компилятора и линкера. Объявлять как static все функции и переменные, которые не используются за пределами файла — «правило хорошего тона». Код: enum { DW_B0 = 0, DW_B1 = 1, DW_B2 = 2, DW_B3 = 3 }; // LITTLE ENDIAN CPU Я уже об этом писал, для приёма битового потока удобно объявить объединение, т.е. наложение в памяти двух переменных: 32-битного целого и 4-бійтового массива. Слдвиг делается в 32-битовое поле dw, а разбор потом — из байтового массива.// enum { DW_B0 = 3, DW_B1 = 2, DW_B2 = 1, DW_B3 = 0 }; // BIG ENDIAN CPU union { uint32_t dw; uint8_t b[4]; } ReceiveData; //4 байта для чтения кода с радиоканала Перед ним «для порядку» объявлено перечислимый тип, который описывает порядок байтов в 32-битном слове. Надо было бы ещё и #ifdef LITTLE_ENDIAN или там #if BYTE_ORDER == LITTLE_ENDIAN, но тут я так, для демонстрации того, что такую вещь можно при желании сделать переносимой и она скомпилируется для любого процессора. Это место сэкономило довольно много кода — при записи в EEPROM и при сравнивании с таблицей кодов пропали >> 8, >> 16, которые хоть и делаются пересылкой байтов, но все же… Было: Код: eeprom_write_byte (&EEData[EEPntTmp +0], (unsigned char) (ReceiveData >> 0)); Стало:eeprom_write_byte (&EEData[EEPntTmp +1], (unsigned char) (ReceiveData >> 8)); eeprom_write_byte (&EEData[EEPntTmp +2], (unsigned char) (ReceiveData >> 16UL)); Код: EEWrite(ee_addr, ReceiveData.b[DW_B0]); (а временная переменная ee_addr практически только для удобства записи, на размер кода не влияет).EEWrite(++ee_addr, ReceiveData.b[DW_B1]); EEWrite(++ee_addr, ReceiveData.b[DW_B2]); Код: static NOINLINE void delay(uint8_t ticks10ms) В ассемблерном варианте для задержки везде вызівается функция с параметром времени в 10мс квантах. В С-шном везде было _delay_ms(). Но это макрос, который по месту вставляет все те вложенные циклы, при многократном применении флеш разлетается ужасающим образом. Это для малых, микросекундных задержек там три команды выходит, не так и жалко.{ while(ticks10ms--) _delay_ms(10); } Но вот тут понадобилось NOINLINE, так как функция короткая и некоторые из оттестированных версий компиляторов норовили вставить тело по месту. У меня NOINLINE и компания вместе с уже упоминавшимися INIT() сидят в gcc_macros.h и я для таких небольших функций со специально вынесенным отдельно кодом ставлю практически рефлекторно. Пойду попью чаю… |
|
| Автор: | avreal [ Пт авг 24, 2012 20:23:50 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
YS писал(а): Я, кстати, достаточно подробно разбирал, чего генерирует AVR-GCC в нагрузку к основному коду. О, значит про это можно не писать Только добавлю (недавно писал на этом форуме), что обработчик незадействованого прерывания можно определить в своей программе, он заменит тот по умолчанию переход на старт. |
|
| Автор: | YS [ Пт авг 24, 2012 20:34:21 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Цитата: обработчик незадействованого прерывания можно определить в своей программе Ага. BADISR_vect его вектор. #pragma offtopic А AVReal еще не поддерживает FTBB? |
|
| Автор: | shads [ Пт авг 24, 2012 20:50:21 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Ооо.... Спасибо avreal, многое постараюсь применить, я тут как раз одну страшную весч затеял, думаю в итоге под 8кб коду будет (mega8), так что надо будет экономить. Пока только начало, нашкрябал 1,5кб..... СпойлерКод: //####################################################################################################################### //####################################################################################################################### //ВКЛЮЧАЕМЫЕ ФАЙЛЫ #define F_CPU 8000000 #include <util/delay.h> #include <avr/io.h> #include <avr/interrupt.h> #include <avr/eeprom.h> //eeprom_read_byte(), eeprom_write_byte(), eeprom_read_word(), eeprom_write_word() //ДЕФАЙНЫ #define CtrlPortOut PORTC /*порт светодиода для отладки*/ #define CtrlPortIn PINC #define CtrlPin (1<<0) /*светодиод для отладки*/ #define DisplayType 255 /*0 - дисплей с OK, 255 - дисплей с OA*/ #define DispDataPort PORTB /*порт данных дисплея*/ #define DispStrobePort PORTD /*порт стробов дисплея*/ #define DispStrb0 (1<<4) /*линия строба 0*/ #define DispStrb1 (1<<5) /*линия строба 1*/ #define DispStrb2 (1<<6) /*линия строба 2*/ #define DispStrb3 (1<<7) /*линия строба 3*/ #define BtnPort PINC /*порт чтения кнопок*/ #define BtnPin (1<<2) /*пин чтения кнопок*/ #define BuzPort PORTC /*порт баззера*/ #define BuzPin (1<<3) /*пин баззера*/ #define LPortOut PORTC /*порт записи линии 1,2*/ #define LPortIn PINC /*порт чтения линии 1,2*/ #define LPin1 (1<<0) /*пин линии 1*/ #define LPin2 (1<<1) /*пин линии 2*/ #define LinePortOut PORTD /*порт записи в линии*/ #define LinePinOut (1<<3) /*пин записи в линию*/ #define LinePortIn PIND /*порт чтения с линии*/ #define LinePinIn (1<<2) /*пин чтения с линии*/ //ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ volatile char volatilei; /*вставка для борьбы с оптимизацией*/ unsigned char Flags1; /*байт флагов №1*/ #define b1DispFlash (1<<0) /*бит мигающего режима дисплея*/ unsigned char ButtonFlags; unsigned char *pButtonFlags = &ButtonFlags; /*АДРЕС БАЙТА ФЛАГОВ НАЖАТЫХ КЛАВИШ*/ #define ShortLeft (1<<0) /*бит короткого нажатия кнопки вверх*/ #define ShortDown (1<<1) /*бит короткого нажатия кнопки вниз*/ #define ShortRight (1<<2) /*бит короткого нажатия кнопки влево*/ #define ShortUp (1<<3) /*бит короткого нажатия кнопки вправо*/ #define LongLeft (1<<4) /*бит длиного нажатия кнопки вверх*/ #define LongDown (1<<5) /*бит длиного нажатия кнопки вниз*/ #define LongRight (1<<6) /*бит длиного нажатия кнопки влево*/ #define LongUp (1<<7) /*бит длиного нажатия кнопки вправо*/ unsigned char DispFlash; /*переменная режима индикатора FLASH (счет 0-99 за секунду)*/ unsigned char DispData [] = {0,0,0,0}; /*БУФЕР ВЫВОДА ДАННЫХ НА ДИСПЛЕЙ*/ #define BitPoint (1<<3) /*бит точки дисплея*/ const unsigned char SimbMass [] = /*символьный массив (не используются K,M,V,W,X,Z)*/ {0xf5,0x05,0x73,0x57,0x87, /*01234*/ 0xd6,0xf6,0x45,0xf7,0xd7, /*56789*/ 0x00,0xe7,0xb6,0xf0,0x37, /* ABCD*/ 0xf2,0xe2,0xf4,0xa7,0x05, /*EFGHI*/ 0x35,0x00,0xb0,0x00,0x26, /*JKLMN*/ 0x36,0xe3,0xc7,0x22,0xd6, /*OPQRS*/ 0xb2,0x34,0x00,0x00,0x00, /*TUVWX*/ 0x97,0x00,0xa0,0x23,0x05, /*YZ[/]*/ 0x02,0x10}; /*-_ */ unsigned char TxtSure [] = {"sure"}; /*МАССИВ СООБЩЕНИЙ ДЛЯ ВЫВОДА НА ДИСПЛЕЙ*/ unsigned char TxtDone [] = {"done"}; unsigned char TxtStup [] = {"stup"}; unsigned char TxtDflt [] = {"dflt"}; unsigned char TxtRet [] = {" ret"}; unsigned char TxtSpace[] = {" "}; unsigned char TxtHallo[] = {"^^^^"}; unsigned char TxtUp [] = {" up"}; unsigned char TxtDown [] = {" dn"}; unsigned char TxtLeft [] = {"left"}; unsigned char TxtRight[] = {"rght"}; unsigned char TxtLU [] = {" lu"}; unsigned char TxtLD [] = {" ld"}; unsigned char TxtLL [] = {" ll"}; unsigned char TxtLR [] = {" lr"}; //#define EEDataLen 60 /*длина буфера данных в EEPROM*/ //unsigned char EEData[EEDataLen] EEMEM; //массив данных в EEPROM //unsigned char EEPnt EEMEM; //указатель на байт массива в EEPROM (0-59) //прототипы функций void DispFresh (void); void DispSimbShow (unsigned char, unsigned char); void DispMessageShow (unsigned char*); void DispIntShow (unsigned int); void DispIntEdit (unsigned int*, unsigned int, unsigned int); void ButtonGet (unsigned char); unsigned char div (unsigned int* , unsigned int); unsigned char MenuBtnPressWait (unsigned char*, unsigned char*); unsigned char BtnPressWait1Sec (void); //####################################################################################################################### //####################################################################################################################### //Главная функция int main (void) { DDRB = 0xff; //линии порта на вывод (данные дисплея) PORTB = 0; //подтяжки нет DDRC = (BuzPin|CtrlPin); //линия порта на вывод для buzzer PORTC = (BtnPin & (~DisplayType)); //подтяжка для кнопки с учетом типа дисплея DDRD = (DispStrb0|DispStrb1|DispStrb2|DispStrb3|LinePinOut); //линий порта на вывод PORTD = 0; //подтяжки нет TIMSK = 0x80; //режим работы - прерывание по совпадению TCCR2 = 0x0e; //режим таймера T2 = CTC (0x08 сброс при совпадении)(0x06 предделитель на 256) OCR2 = 77; //регистр сравнения (сброс таймера счетч по совп) sei (); //разрешаем глобально прерывания unsigned int integer = 500; DispMessageShow (TxtHallo); while(1) //бесконечный цикл { //volatilei ++; unsigned char BtnValue = BtnPressWait1Sec (); //ждем нажатия кнопки if ( BtnValue == ShortUp) DispMessageShow (TxtUp); if ( BtnValue == ShortDown) DispMessageShow (TxtDown); if ( BtnValue == LongRight) { DispIntEdit (&integer, 0, 9999); DispMessageShow (TxtHallo); } } } //---------- //функция вывода сообщения на дисплей void DispMessageShow (unsigned char *pAdrTxt) { for (unsigned char position =0; position<4; position++) DispSimbShow (*pAdrTxt++,position); } //---------- //функция вывода символа на дисплей //аргумент 1 - символ "0-9,A-Z" //аргумент 2 - знакоместо (0-3) void DispSimbShow (unsigned char simbol, unsigned char position) { unsigned char tmp =10; if (simbol >= 96) simbol -= 32; //преобразуем знаки a-z в знаки A-Z if (simbol < 10) tmp = simbol; //значения 0-9 if ((simbol >= 48)&&(simbol < 58)) tmp = (simbol -48); //символы 0-9 if ((simbol >= 64)&&(simbol < 96)) tmp = (simbol -64 +10); //символы A-Z DispData[position] = SimbMass[tmp]; } //---------- //функция вывода на дисплей числа 0-9999 void DispIntShow (unsigned int integer) { unsigned char position = 0; //позиция печати на дисплее if (integer < 1000) DispData [position ++] = 0; //очистить тысячи if (integer < 100) DispData [position ++] = 0; //очистить сотни if (integer < 10) DispData [position ++] = 0; //очистить десятки switch (position) { case 0: DispSimbShow (div (&integer, 1000), position ++); case 1: DispSimbShow (div (&integer, 100), position ++); case 2: DispSimbShow (div (&integer, 10), position ++); case 3: DispSimbShow (integer, position); } } //ф-ция деления возвращает результат деления //аргумент 1 - делимое (указатель) //аргумент 2 - делитель unsigned char div (unsigned int *pDivident, unsigned int divisor) { unsigned char result =0; while (*pDivident >= divisor) { *pDivident -= divisor; result ++; } return result; } //---------- //функция редактирования числа //аргумент 1 - указатель на редактируемые данные //аргумент 2 - максимально возможно нижнее значение //аргумент 3 - максимально возможное верхнее значение void DispIntEdit (unsigned int* pInteger, unsigned int IntMin, unsigned int IntMax) { unsigned int IntegerTemp = *pInteger; //редактируем пока промежуточную IntegerTemp while (1) { Flags1 |= b1DispFlash; //включить режим дисплея - FLASH DispIntShow (IntegerTemp); //вывести значение на дисплей unsigned char BtnValue = MenuBtnPressWait (0, 0);//ожидаем нажатия кнопки Flags1 &= ~b1DispFlash; //выключить режим дисплея - FLASH if (BtnValue == LongRight){ *pInteger = IntegerTemp; //выход с сохранением данных DispMessageShow (TxtDone); _delay_ms(1500); return; } if ((!BtnValue)||(BtnValue == LongLeft)){ DispMessageShow (TxtRet); //выход кнопкой без сохранения и по таймауту _delay_ms(1500); return; } if (BtnValue == ShortUp){ if (IntegerTemp < IntMax) //увеличить значение IntegerTemp ++; } if (BtnValue == ShortDown){ if (IntegerTemp > IntMin) //уменьшить значение IntegerTemp --; } if (BtnValue == LongUp){ if (IntegerTemp < IntMax) //увеличить значение IntegerTemp ++; } if (BtnValue == LongDown){ if (IntegerTemp > IntMin) //уменьшить значение IntegerTemp --; } } } //---------- //функция индикации строк меню, и ожидание нажатия кнопки //аргумент 1 - строка меню №1 (если 0 - не выводится) //аргумент 2 - строка меню №2 (если 0 - не выводится) //значение на выходе - байт маска нажатой кнопки (если 0 - выход по таймауту) unsigned char MenuBtnPressWait (unsigned char *pMenu1, unsigned char *pMenu2) { #define TimeOut 10 /*если указанное кол-во секунд, кнопка не нажимается, то выход из ф-ции*/ unsigned char Button; //регистр маски нажатой кнопки for (unsigned char i =0; i<(TimeOut/2); i++) { if (pMenu1) DispMessageShow (pMenu1); //выводим строку 1 Button = BtnPressWait1Sec(); if (Button) return Button; //кнопка нажата, возвращаем маску кнопок if (pMenu2) DispMessageShow (pMenu2); //выводим строку 2 Button = BtnPressWait1Sec(); if (Button) return Button; //кнопка нажата, возвращаем маску кнопок } return 0; //выход по таймауту } //---------- //функция ожидания нажатия кнопки в течение 1 сек //значение на выходе - маска кнопок *pButtonFlags (если timeout, то на выходе 0) unsigned char BtnPressWait1Sec (void) { for (unsigned char i=0; i<10; i++) //крутимся 10 раз по 100мс т.е. 1 сек { volatilei ++; if (*pButtonFlags) { unsigned char temp = *pButtonFlags; //кнопка нажата *pButtonFlags = 0; return temp; //возвращаем маску нажатой кнопки } _delay_ms(100); } return 0; //секунда закончилась, возвращаем значение 0 } //####################################################################################################################### //####################################################################################################################### //обработка прерывания (частота вызова 400 Гц) ISR (TIMER2_COMP_vect) { DispFresh (); //динамическое обновление дисплея } //---------- //функция динамического отображения данных на 4-х разрядном дисплее. // void DispFresh (void) { static unsigned char strobe; //переменная активного строба дисплея и опроса кнопок (0-3) ButtonGet (strobe); //ф-ция обработки опроса кнопок (аргумент - номер включенного строба индикатора 0-3) if (++ strobe >= 4){ //инкремент строба знакоместа strobe =0; //здесь обслуживание переменных с частотой 100Гц if (++ DispFlash == 100) //инкремент счетчика FLASH (за секунду счет 0-99) DispFlash = 0; } if (DisplayType) { //обработка дисплея с ОА DispStrobePort &= ~(DispStrb0 |DispStrb1 |DispStrb2 |DispStrb3);//погасить все разряды DispDataPort = ~(DispData [strobe]); //вывод данныx if (Flags1 & b1DispFlash){ //проверка включен ли режим FLASH if (DispFlash >= 50) DispDataPort = DisplayType; //при счетчике 0-49 индикация данных, 50-99 погасить дисплей } switch (strobe){ //включение обновленного знакоместа case 0: DispStrobePort |= DispStrb0; break; case 1: DispStrobePort |= DispStrb1; break; case 2: DispStrobePort |= DispStrb2; break; case 3: DispStrobePort |= DispStrb3; } } else { //обработка дисплея с ОК DispStrobePort |= (DispStrb0 |DispStrb1 |DispStrb2 |DispStrb3);//погасить все разряды DispDataPort = DispData [strobe]; //вывод данных if (Flags1 & b1DispFlash){ //проверка включен ли режим FLASH if (DispFlash >= 50) DispDataPort = DisplayType; //при счетчике 0-49 индикация данных, 50-99 погасить дисплей } switch (strobe){ //включение обновленного знакоместа case 0: DispStrobePort &= ~DispStrb0; break; case 1: DispStrobePort &= ~DispStrb1; break; case 2: DispStrobePort &= ~DispStrb2; break; case 3: DispStrobePort &= ~DispStrb3; } } } //---------- //ф-ция обработки нажатий клавиш (вызывается с частотой 400 Гц) //аргумент - номер включенного строба индикатора 0-3 (0 - left, 1 - down, 2 - right, 3 - up) void ButtonGet (unsigned char strobe) { static unsigned char BtnLockBit; //ащелка (защита от дребезга) static unsigned char BtnLockCoun; //счетчик защелки (защита от дребезга) static unsigned char BtnLongCoun; //счетчик длинного нажатия static unsigned char BtnStack; //здесь накапливаем за время опроса 4-х стробов, маску нажатых кнопок static unsigned char BtnTemp; //бит последней нажатой кнопки #define BtnLock 30 /*время обработки дребезга в милисекундах (10-100)*/ #define BtnLongPress 2000 /*время фиксации длинного нажатия в милисекундах (1000 - 2500)*/ if (!((BtnPort ^ DisplayType) & BtnPin)) //грубый опрос кнопок во время обработки стробов, с учетом типа индикатора (ОК, ОА) BtnStack = (1 << strobe); if (strobe <3) return; //выход первые 3 строба (обработка данных только после последнего 4-го строба) if (BtnStack) { //клавиша нажата BtnTemp = BtnStack; BtnStack = 0; if (BtnLockCoun < (BtnLock/10)){ BtnLockCoun++; return; } BtnLockBit=1; //нажатие зафиксировано if (BtnLongCoun >= (BtnLongPress/10)) return; BtnLongCoun ++; if (BtnLongCoun >= (BtnLongPress/10)) *pButtonFlags |= (BtnTemp << 4); //установка бита длинного нажатия (старшие 4 бита флагов ButtonByte) } else { //клавиша отжата if (BtnLockCoun != 0){ BtnLockCoun --; return; } if (! (BtnLockBit)) //отжатие зафиксировано return; BtnLockBit =0; if (BtnLongCoun < (BtnLongPress/10)){ *pButtonFlags |= (BtnTemp); //установка бита короткого нажатия (младшие 4 бита флагов ButtonByte) DispFlash = 0; //сбросить на начало цикл FLASH дисплея } BtnLongCoun = 0; } } //####################################################################################################################### //####################################################################################################################### /* //индикация светиком if (*pButtonFlags) CtrlPortOut |= CtrlPin; else CtrlPortOut &= ~CtrlPin; */ |
|
| Автор: | avreal [ Пт авг 24, 2012 21:33:35 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Начинаем продолжать… Код: static NOINLINE void EEWrite(uint8_t addr, uint8_t data) { … } Библиотечные функции работы с EEPROM принимают «как бы указатель» на переменную в EEPROM. Оно выглядит красиво, но EEMEM только атрибут размещения и всё равно тот указатель как указатель не работает (в gcc 4.7 добавлены пространства памяти и в avr-gcc они использованы, но это другая история).static NOINLINE uint8_t EERead(uint8_t addr) { … } Соответственно в функции eeprom_* нужно передавать 16-битный аргумент и для вычисления адресов используется 16-разрядная арифметика. Но ведь в ATtiny13 всего 64 байта EEPROM! Не ленимся и пишем свои функции, принимающие 8-битный номер ячейки. Тоже NOINLINE, так как иначе некоторые версии компилятора их встраивают (особенно Read, она совсем короткая). Теперь иллюстрация к моим словам «т.е. у С-шной программы даже без желания автора обычно несколько другой функционал.» Тут с некоторым желанием, но всё же. Исходно: Код: unsigned char EEData[EEDataLen] EEMEM; Всё вроде бы нормально. Ассемблерная программа зануляла не глядя всю EEPROM-ину, а тут одна из переменных зануляется отдельно. Лишний вызов (а в исходном варианте ещё и с передачей 16-разрядного адреса). И сохранить «высокоуровневость» с занулением только необходимой части, причём не обязательно начиная с нулевого адреса, и сэкономить код можно объединением массива и индекса в структуру (имена такие с целью упрощения редактирования текста). Зануляем всю структуру одним махом:unsigned char EEPnt EEMEM; for (unsigned char i=0; i<EEDataLen; i++) eeprom_write_byte(&EEData [i], 0); eeprom_write_byte(&EEPnt, 0); Код: #define EEDataLen 60 /*длина буфера данных в EEPROM */ При обращении используем адреса полейstruct { unsigned char Data[EEDataLen]; //массив данных в EEPROM unsigned char Pnt; //указатель на байт массива в EEPROM (0-59) } EE EEMEM; uint8_t from = (unsigned)&EE; uint8_t to = (unsigned)&EE + sizeof(EE); do { EEWrite(from, 0); ++from; } while(from < to); //цикл стирания данных EEPROM Код: EEWrite((unsigned)&EE.Pnt, EEPntTmp+3); uint8_t ee_addr = (unsigned)&EE.Data[EEPntTmp]; Дальше мелочи. Код: //сдвиг буфера и запись в младший разряд принятого бита об этом я тоже уже писал — компилятору тут желательна подсказка в виде временной переменной и он перестаёт делать лишнее сохранение после сдвига.unsigned long ultemp = ReceiveData.dw << 1; if (BitLoPartCoun < BitHiPartCoun) ultemp |= 1; ReceiveData.dw = ultemp; Код: //ReceiveData &= 0x00ffffff; //очистить лишние старшие 8 бит Это я сразу закомментировал, еще до создания union для приёмного буфера. Старший байт в программе нигде не анализировался, ничего страшного, если там поболтается мусор.Ну вот, собственно, и всё… Не так уж и много изменил. Сделать было быстрее, чем описать Что осталось: 1. Обще-С-шная беда, особенно сильно проявляющаяся у AVR -- у него много регистров, а при обработке прерывания их нужно сохранить. Если обработчик делает всё «сам», то компилятор знает, какие регистры он задействовал и сохраняет/восстанавливает только их. Если из обработчика вызывается какая-то функция, то в обработчике сохраняются все регистры, которые вызываемая функция не обязана сохранять. Даже если она их не использует — компилятор-то про это «не знает» и сохраняет на всякий случай все *) Из-за обилия регистров у AVR стоит избегать вызовов не-inline функций из обработчиков. Вот даже тут если поменять функции EERead/EEwrite на INLINE, то код увеличится, но ненамного, так как пропадёт с десяток push/pop (т.е. десятка четыре байт). Для avr-gcc 4.3.4 из убунты 10.04 NOINLINE даёт 680 байт, а INLINE, размноживший тела функций по месту, 712. 2. Чисто avr-gcc-шная беда. Он почему-то экономит указательные регистры и предпочитает 4-байтовые lds/sts, которых там в обработчике немеряно. Даже если собрать все используемые в Receive() переменные в одну структуру, завести в подпрограмме указатель на эту структуру (под использование ldd/std), то он всё равно радостно скажет «да это же не какая-то неизвестная и каждій раз другая структура, это вот она одна конкретная фиксированная я ее вижу», выбросит указатель и будет напрямую lds/sts использовать. IAR, насколько я знаю, умеет сам собирать рядом объявленные переменные в структуру и использовать смещение к адресной регистровой паре. Прикидочно, на этом в данной программе можно сэкономить ещё около полусотни байт. avr-gcc иногда удаётся убедить использовать указатель, но это если вообще не поленюсь, то уж не в эти выходные. *) Keil/MCS51 умеет вкупе с линкером делать глобальную регистровую оптимизацию, анализируя по дереву вызовов использование регистров. Не помню, работает ли это с обработчиками прерываний, они у меня на асме написаны были. На 25-килобайтной программе эта оптимизация давала около килобайта выиграша (для всех своих ассмблерных подпрограмм я тоже дал описания используемых регистров, иначе от них вверх по дереву шло «неизвестно, а значит надо беречь всё»). Возможно, в GCC что-то поправится в связи с LTO, но, опять таки, не уверен, что на обработчики преріваний это повлияет. |
|
| Автор: | avreal [ Пт авг 24, 2012 21:34:36 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
YS писал(а): #pragma offtopic Ну вот как меньше буду по форумам сидеть, так и найду время А AVReal еще не поддерживает FTBB? |
|
| Автор: | ILYAUL [ Сб авг 25, 2012 10:22:27 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Ну так это уже на статью тянет |
|
| Автор: | avreal [ Сб авг 25, 2012 11:51:25 ] | ||
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. | ||
«Охота пуще неволи» («Начинаем продолжать - 2»). Мелкая правка: Поменял имена типа LedPin на LedMask и завёл LedPIN как именно регистр PIN, запись в него инвертирует ножку. Таким образом мигание светодиодом ещё укоротилось (это же и для асм-варианта полезно). Тяжёлая артиллерия (заставляем avr-gcc работать со структурой через указатель и смещения — struct_receiver.c): Объединил переменные состояния приёма и буфер приёма в одну структуру (связанные логикой программы переменные я чаще всего сразу объединяю в структуру), добавил в Receive() переменную-указатель и макрос для предзагрузки указателя (там в исходниках ссылка на сайт, где обсуждается этот фокус). После столь убедительной просьбы avr-gcc начинает таки использовать ldd/std и размер программы для всех версий, кроме 3.4.6, упал до значений в диапазоне 600-620 байт, в минимуме 598. Уже только на треть больше 450-байтового ассемблерного варианта у которого все переменные в регистрах и прямая работа с ними на месте. Было бы переменных больше и они не лезли бы все в регистры, так разница была бы еще меньше. Хотя асмовый вариант тоже можно немного сократить. Если ещё такое же сделать для Int100Hz(), причём в ту же структуру затолкать и счётчик Div100, то размер упадёт ниже отметки 600 байт уже для большинства версий компилятора. Веником его, веником (reg_struct_receiver.c): Почти все глобальные переменные размещены в регистрах при помощи конструкций вида Код: register uint8_t RelayOnCoun asm("r2"); Нужно в ключах добавитьКод: -ffixed-r2 -ffixed-r3 -ffixed-r4 -ffixed-r5 -ffixed-r6 -ffixed-r7 -ffixed-r8 При этом компилятор все равно иногда работает с ними плохо («I like to move it move it»), вместо dec r2 может влупить mov r16, r2 $ subi r16, 1 $ mov r2, r16, но всё равно код выходит короче и быстрее.Повтор таблички с новой колонкой ptr для результатов со структурой и указателем и с колонкой reg для принудительного размещения переменных в регистрах. Не зря я не люблю насиловать компилятор размещением в регистрах. Две версии (*** в соответствующей колонке) просто взглюкнули, выбросив почти весь код receive(): Код: avr-gcc orig patch ptr reg avr-gcc build 3.4.6 884 702 646 616 WinAVR-20060421 4.2.2 874 678 620 586 WinAVR-20071221 4.3.3 872 658 598 *** WinAVR-20100110 4.7.1 844 674 612 576 MobileChessBoard 4.7.1rc1 4.7.1 844 668 606 570 MobileChessBoard 4.7.1rc1 -fno-ivopts 4.3.4 894 680 620 *** Ubuntu-10.04 4.5.3 860 660 598 566 Ubuntu-12.04 И новые исходники
|
|||
| Автор: | avreal [ Сб авг 25, 2012 12:06:40 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
ILYAUL писал(а): Ну так это уже на статью тянет Так это ж нужно садиться и делать хоть маленькую, но дополнительную работу Надо сначала отлежаться/отлизаться |
|
| Автор: | shads [ Сб авг 25, 2012 13:37:47 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Уххххх...... Высший пилотаж! Я уж и боюсь вникать во все эти тонкости, стока их..... Для начала попробую разобраться, как это вы просите компилятор использовать ldd/std вместо команд прямой загрузки, ато он немерено жрет ресурсов чтобы переменную загрузить и выгрузить. Я на асме вообще всегда резервировал регистровую пару с адресом начала оперативки, и всегда пользовался ldd/std. Честно говоря сначала в шоке был когда увидел что на обращение к переменной С тратит целых 8 байт..... это кроме операций с самой переменной..... Со структурами пока страшновато, постепенно думаю буду вникать в ваши уроки. Еще раз спасибо! |
|
| Автор: | YS [ Сб авг 25, 2012 14:37:07 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Приятно посмотреть на качественную работу с кодом!
|
|
| Автор: | avreal [ Сб авг 25, 2012 15:10:55 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
shads писал(а): Для начала попробую разобраться, как это вы просите компилятор использовать ldd/std вместо команд прямой загрузки Вот когда на асме регистровая пара на начало оперативки — фактически, все переменные начинают характеризоваться не своим абсолютным адресом, а смещением относительно того начала.... Я на асме вообще всегда резервировал регистровую пару с адресом начала оперативки, и всегда пользовался ldd/std. ... Со структурами пока страшновато, постепенно думаю буду вникать в ваши уроки. Структуры в С чем-то на это похожи. Объединяя переменные в структуру мы говорим компилятору «они должны лежать вместе, рядом, нас будут интересовать смещения относительно начала структуры». Причём самих переменны=-структур может быть несколько одинаковых, при работе с каждой пользуемся смещениями относительно её начала. Адресация к «полям» — элементам структуры (переменной соответствующего типа) идёт через точку, например Код: имя_структуры.имя_поля = 0; Это кже неплохая подсказка компилятору о том, что можно в указательную пару загрузить адрес начала структуры и работать дальше по смещению.Но в оптимизаторе gcc/avr-gcc где-то недоработка. Возможно, несколько неправильно расставлены «стоимости» операций / «ценность» регистров. И он почему-то считает нежелательным занимать пару регистров. Приходится обращаться через указатель Код: имя_указателя_на_структуру->имя_поля = 0; и загружать вручную указатель в пару макросом PRELOAD()После ручной загрузки регистровая пара «уже потрачена» и компилятор её использует. |
|
| Автор: | HHIMERA [ Сб авг 25, 2012 16:16:35 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Подобные костыли и порождают трудноуловимые глюки... Не потому ли компилерописатели "пропустили" подобные "обороты"??? )))))))) |
|
| Автор: | avreal [ Сб авг 25, 2012 16:58:05 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
HHIMERA писал(а): Подобные костыли и порождают трудноуловимые глюки... А подробнее? Конкретно что за костыль, какие, к примеру, глюки? Из собственного либо чужого опыта…HHIMERA писал(а): Не потому ли компилерописатели "пропустили" подобные "обороты"??? )))))))) Шо, и тут всемирный заговор?
|
|
| Автор: | HHIMERA [ Сб авг 25, 2012 17:31:30 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Мне проще посчитать свой пост вопросом, а не утверждением... чем впадать в полемику... (Просто мне лениво)... |
|
| Автор: | avreal [ Сб авг 25, 2012 18:41:01 ] |
| Заголовок сообщения: | Re: C для AVR -- пишем аккуратно. |
Понятно. Ляпнуть было не лениво, а отвечать — лениво. Вы — трепло. Можете считать мой пост вопросом, а не утверждением, так Вам будет проще, не надо будет отвечать за свои слова. |
|
| Страница 1 из 6 | Часовой пояс: UTC + 3 часа |
| Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group http://www.phpbb.com/ |
|


