Начинаем продолжать…
Код: Выделить всё
static NOINLINE void EEWrite(uint8_t addr, uint8_t data) { … }
static NOINLINE uint8_t EERead(uint8_t addr) { … }
Библиотечные функции работы с EEPROM принимают «как бы указатель» на переменную в EEPROM. Оно выглядит красиво, но EEMEM только атрибут размещения и всё равно тот указатель как указатель не работает (в gcc 4.7 добавлены пространства памяти и в avr-gcc они использованы, но это другая история).
Соответственно в функции eeprom_* нужно передавать 16-битный аргумент и для вычисления адресов используется 16-разрядная арифметика. Но ведь в ATtiny13 всего 64 байта EEPROM!
Не ленимся и пишем свои функции, принимающие 8-битный номер ячейки. Тоже NOINLINE, так как иначе некоторые версии компилятора их встраивают (особенно Read, она совсем короткая).
Теперь иллюстрация к моим словам «т.е. у С-шной программы даже без желания автора обычно несколько другой функционал.»
Тут с некоторым желанием, но всё же.
Исходно:
Код: Выделить всё
unsigned char EEData[EEDataLen] EEMEM;
unsigned char EEPnt EEMEM;
for (unsigned char i=0; i<EEDataLen; i++)
eeprom_write_byte(&EEData [i], 0);
eeprom_write_byte(&EEPnt, 0);
Всё вроде бы нормально. Ассемблерная программа зануляла не глядя всю EEPROM-ину, а тут одна из переменных зануляется отдельно. Лишний вызов (а в исходном варианте ещё и с передачей 16-разрядного адреса). И сохранить «высокоуровневость» с занулением только необходимой части, причём не обязательно начиная с нулевого адреса, и сэкономить код можно объединением массива и индекса в структуру (имена такие с целью упрощения редактирования текста). Зануляем всю структуру одним махом:
Код: Выделить всё
#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, но, опять таки, не уверен, что на обработчики преріваний это повлияет.
Лень в виде мании величия: «ты гений, зачем стараться?». В виде комплекса: «всё равно не выйдет, зачем упираться?». Как логика: «если достаточно, зачем знать и уметь больше?». Цель одна: остановить. Не любит тепло работающих мышц и шум работающего мозга.