Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Новичок на С
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
Страницы: 1, 2
osnwt
Цитата(SasaVitebsk @ Mar 7 2006, 03:26) *
Или как поступить в данном случае. Как обратится к байтам длинного числа не пребегая к операциям типа ">> 8 ...."

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

Вообще, очень полезно бывает смотреть на сгенерированный компилятором код, чтобы что-то там оптимизировать (если это хочется сделать). Пример:

Код
unsigned char CRC(...)
{
    int crc;
    ...
    return crc ? 1 : 0;    // 0 - все в порядке, не ноль - ошибка
}

Дает совершенно правильный, но не слишком оптимальный для AVR код (на IAR). Вычисление кода возврата по return компилируется в 10 байтов.

Можно улучшить ситуацию, записав вот так:

Код
    return (crc | (crc >> 8));    // 0 - все в порядке, не ноль - ошибка

При видимом усложнении кода получаем вместо 10-ти уже 6 байтов. Неплохо.

Но самое интересное будет, если переставить местами операнды и добавить лишние 0xff:

Код
    return ((crc >> 8) | (crc & 0xff));    // 0 - все в порядке, не ноль - ошибка

Это компилируется всего в 4 байта, несмотря на увеличение количества операций.

Поэтому крайне рекомендуется при необходимости писать оптимально для конкретного процессора изучать генерируемый компилятором код. Иногда можно обнаружить совершенно неочевидные вещи.
defunct
Цитата(osnwt @ Mar 7 2006, 22:56) *
Код
    return ((crc >> 8) | (crc & 0xff));    // 0 - все в порядке, не ноль - ошибка

Это компилируется всего в 4 байта, несмотря на увеличение количества операций.

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

на ассемблере для этой задачи (0- все в порядке, не 0 - ошибка crc16) требуется всего одна команда or al, ah (2 байта), поэтому при необходимости получить оптимальный код, неплохо бы изучить ассемблер конкретного процессора.. Вопрос, лишь в том оправдается ли затраченное время (немалое) на получение оптимального кода для конкретного процессора?
osnwt
Цитата(defunct @ Mar 8 2006, 00:22) *
Цитата(osnwt @ Mar 7 2006, 22:56) *

Код
    return ((crc >> 8) | (crc & 0xff));    // 0 - все в порядке, не ноль - ошибка

Это компилируется всего в 4 байта, несмотря на увеличение количества операций.

на ассемблере для этой задачи (0- все в порядке, не 0 - ошибка crc16) требуется всего одна команда or al, ah (2 байта), поэтому при необходимости получить оптимальный код, неплохо бы изучить ассемблер конкретного процессора.. Вопрос, лишь в том оправдается ли затраченное время (немалое) на получение оптимального кода для конкретного процессора?


Одна команда - это два байта. Вторые два байта - это, собственно, ret. Фрагмент листинга компилятора:

// 159
// 160 // returned 0 means CRC is OK
// 161 return ((crc >> 8) | (crc & 0xff));
OR R16, R17

Однозначно ассемблер надо знать, иначе смысла смотреть на генерируемый код нет никакого.

А насчет оправдания затрат - во первых, это надо сделать примерно раз и дальше использовать везде.

Во вторых, это дает результат, и не такой малый. Скажем, компилированная GCC ассемблерная часть USB драйвера от Objective Development получается на 166 байтов короче, чем она же, но скомпилированная IAR. А на написанном мной куске того же объема результат прямо противоположный, и при этом я не оптимизировал тот свой код вручную. Это - результат вполне определенной оптимизации исходника USB под GCC, о чем вполне определенно пишет автор в readme IAR порта (не уверен, появился ли тот порт на их сайте или еще нет, так как последняя версия лежит у меня, всё ход не дойдет ее дотестировать).

В третьих, всё подряд оптимизировать не имеет смысла. Но в ряде случаев это просто необходимо. Когда мой бут вылез за пределы 4-х кило на 200 байтов, мне пришлось приложить ряд усилий, чтобы вернуть его обратно. При этом ничем не было пожертвовано, и код не потерял читабельности. Правда, пришлось переписать часть кода Atmel, да и свой немного поправить. Зато всё осталось на C, и поддерживать его будет проще.

Я сам был поборником ассемблера, ибо в свое время общался больше чем с десятком-полутора разных вариантов. Но с некоторых пор мнение изменил. Проще поддерживать все же C код. А на ассемблер надо переносить только критически важные участки: ISR и т.п. Вот это реально будет экономить время на поддержке.
osnwt
Цитата
Я вот тут подумал, чем надеяться на оптимизация компилятора не проще ли просто писать, то что требуется:
Код
return crc != 0

Увы, нет. Сравните с предыдущим листингом:
Код
//  160     // returned 0 means CRC is OK
//  161 //    return ((crc >> 8) | (crc & 0xff));
//  162     return crc != 0;
        OR    R16, R17
        BREQ    ??crcApplicationCheck_1
        LDI    R16, 1
        RJMP    ??crcApplicationCheck_2
??crcApplicationCheck_1:
        LDI    R16, 0
??crcApplicationCheck_2:


Логическая операция должна вернуть 0 или 1. А побитовая - что угодно, лишь бы не ноль.

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

ЗЫ. В приведенном примере я сам поражен: и это - максимальная оптимизация в IAR??? Ведь LDI R16, 0 - совершенно излишняя команда в данном случае.
defunct
Цитата(osnwt @ Mar 8 2006, 13:27) *
Увы, нет. Сравните с предыдущим листингом:

После того как написал пост - проверил, заметил, потому и удалил пост...

Цитата
ЗЫ. В приведенном примере я сам поражен: и это - максимальная оптимизация в IAR??? Ведь LDI R16, 0 - совершенно излишняя команда в данном случае.

Да поразительно работает оптимизация..
osnwt
Цитата(defunct @ Mar 8 2006, 14:31) *
После того как написал пост - проверил, заметил, потому и удалил пост...

А я думаю: что за глюки форума? В почте - вот оно, на сайте - нет ничего smile.gif Потом уж догадался о природе вещей, но ответить Чемберлену-то нужно! (без обид - шутка - после бессонной ночи сборки очередного девайса сижу вот такой: blink.gif).
SasaVitebsk
Беседа была интересная. smile.gif И я планирую проверить некоторые вещи в плане генерации.
Пока я ещё не завершил написание проекта. Обычно я сначала пишу всё, и только потом вылизываю.
И всётаки вопрос: Если применяешь с компиллер, то оптимизация описанным путём, - это же явная привязка к конкретному компилятору. И даже, вероятней всего, к его конкретной версии. smile.gif

Второй вопрос. Зачем, конкретно Вы, используете два разных компилятора. Хотя бы коротко. smile.gif
Третий. Насколько сложно программу написанную на IAR "заставить" компилится на GCC. Может у кого есть спец. ".h" файлы.
osnwt
Если вопросы ко мне - попробую ответить.

Цитата(SasaVitebsk @ Mar 11 2006, 19:24) *
Если применяешь с компиллер, то оптимизация описанным путём, - это же явная привязка к конкретному компилятору. И даже, вероятней всего, к его конкретной версии. smile.gif

Верно, хотя это не привязка, так как таким образом оптимизированный код скомпилируется правильно и другим компилятором. Может, не столь же эффективно - но правильно. С другой стороны, то, что пишется под AVR, едва ли будет компилироваться десятками разных компиляторов под десятком платформ. С вероятностью процентов 95% этот код вообще не выйдет за пределы компьютера автора (авторов). Потому при сохранении общей наглядности кода на C такая оптимизация имеет право на жизнь, когда есть проблемы по быстродействию или объему. Мне для впихивания навороченного USB крипто-загрузчика в 4 килобайта бут-блока меги32 пришлось соптимизировать примерно 200 лишних байтов. Ясное дело, что не только таким способом - многое пришлось переписать по другому. Но результат был достигнут.

Причем, переписать неочевидным путем. Скажем, алгоритм дешифровки использует сравнение 4-х байтов, загружаемых через последовательные *p++ и сравниваемые с 4-мя байтами сигнатуры побайтно. Казалось бы - самое оптимальное, раз байты так извлекаются. НЕТ. Более оптимально оказалось сделать так (непортабельно из за выравнивания на других платформах, но это и не нужно): v = *(unsigned long *)p; p += 4; а потом сравнить v с unsigned long сигнатурой. Это дало приличную экономию, учитывая, что сигнатура в данном случае - константа.

Цитата
Второй вопрос. Зачем, конкретно Вы, используете два разных компилятора. Хотя бы коротко. smile.gif

Лично я не использую, я использую IAR. Однако, версия USB драйвера для AVR написана под GCC компилятор и ассемблер, и под них оптимизирована автором. Потому мне пришлось сравнить эти два компилятора, чтобы портировать все на IAR. Это оказалось весьма несложно - C код вообще не менялся, практически. Ассемблерный - тоже. Были символические нюансы по особенностям типа "PROGMEM меняется на __flash", и т.п. А также соглашения по вызову ассемблерных функций из C и оформлению процедур обработки прерываний.

И вот при всём этом при максимальных оптимизациях C-часть драйвера компилируется под IAR примерно в 750 байтов, а под GCC - 550. 200 байтов проигрывает IAR. Однако, алгоритм дешифровки, никак не оптимизированный, скомпилированный под IAR получился около 1000 байтов, а под GCC - 1400. Вот мы уже впереди.

Так что особенности того компилятора, под который пишем, знать всё же полезно. Однако, в худшем случае переносимый код скомпилируется и другим компилятором. А вот всякие ассемблерные вставки с разным их синтаксисом и особенностями распределения регистров уже совсем непереносимы. Для USB драйвера пришлось переписать пару процедур, вызываемых из C, именно из за другого соглашения по регистрам.

Цитата
Третий. Насколько сложно программу написанную на IAR "заставить" компилится на GCC. Может у кого есть спец. ".h" файлы.

Если программа написана "правильно", то очень просто. Особенности относятся, как правило, к расширениям языка для поддержки FLASH, SFR, и т.п. Для примера приведу файл iarcompat.h из неопубликованной пока версии USB драйвера, который включается сишной и ассемблерной частями вместо "avr/*.h" файлов, используемых GCC. Остальные изменения не сильно принципиальны. Главное - это по всем спорным вопросам расширений писать макро, которые можно потом просто переопределить.

Код
/* Name: iarcompat.h
* Project: AVR USB driver
* Author: Christian Starkjohann
* Creation Date: 2006-03-01
* Tabsize: 4
* Copyright: (c) 2006 by OBJECTIVE DEVELOPMENT Software GmbH
* License: Proprietary, free under certain conditions. See Documentation.
* This Revision: $Id$
*/

/*
General Description:
This header is included when we compile with the IAR C-compiler and assembler.
It defines macros for cross compatibility between gcc and IAR-cc.

Thanks to Oleg Semyonov for his help with the IAR tools port!
*/

#ifndef __iarcompat_h_INCLUDED__
#define __iarcompat_h_INCLUDED__

#if defined __IAR_SYSTEMS_ICC__ || defined __IAR_SYSTEMS_ASM__

/* Enable bit definitions */
#ifndef ENABLE_BIT_DEFINITIONS
#   define ENABLE_BIT_DEFINITIONS    1
#endif

/* Include IAR headers */
#include <ioavr.h>
#ifndef __IAR_SYSTEMS_ASM__
#   include <inavr.h>
#endif

#define __attribute__(arg)
#define IAR_SECTION(section)    @ section

#ifndef USB_BUFFER_SECTION
#   define  USB_BUFFER_SECTION  "TINY_Z"    /* if user has not selected a named section */
#endif

#ifdef __IAR_SYSTEMS_ASM__
#   define __ASSEMBLER__
#endif

#ifdef __HAS_ELPM__
#   define PROGMEM __farflash
#else
#   define PROGMEM __flash
#endif

#define PRG_RDB(addr)   (*(PROGMEM char *)(addr))

/* The following definitions are not needed by the driver, but may be of some
* help if you port a gcc based project to IAR.
*/
#define cli()       __disable_interrupt()
#define sei()       __enable_interrupt()
#define wdt_reset() __watchdog_reset()


#endif  /* defined __IAR_SYSTEMS_ICC__ || defined __IAR_SYSTEMS_ASM__ */
#endif  /* __iarcompat_h_INCLUDED__ */
beer_warrior
Цитата
Третий. Насколько сложно программу написанную на IAR "заставить" компилится на GCC. Может у кого есть спец. ".h" файлы.

Немножко дополню.
Кроме указанных osnwt макросов, по другому объявляются прерывания, что легко обходиться с помощью #ifdef #else #endif.
С флэшью gcc обходиться не так непринужденно как IAR, т.е. хитрые
фокусы со всякими связанными списками во флэши, требуют некоторых
ухищрений.
Вообще желательно писать с Шилдтом на столе, не привязываясь к соблазнительным фичам конкретного компилятора, и тогда переносимость будет вполне приличная.
osnwt
Цитата(beer_warrior @ Mar 11 2006, 23:51) *
Кроме указанных osnwt макросов, по другому объявляются прерывания, что легко обходиться с помощью #ifdef #else #endif.

Да. Я просто привел то, что реально использовалось в драйвере. А прерывание там находится в ассемблерной части, что тоже потребовало определенных #if-ов для корректного его описания.

Цитата
С флэшью gcc обходиться не так непринужденно как IAR

Как пояснил Кристиан, gcc не рассчитывался на гарвардскую архитектуру процессора. Потому чтобы поддержать в нем AVR, пришлось сделать немалые хаки.

А отсутствие возможности прямой работы с переменными во флэше (см. выше макро для чтения байта) было причиной того, что автор драйвера не согласился поддержать работу с переменными дескрипторами уникального серийного номера устройства вместо константных.
defunct
Цитата(SasaVitebsk @ Mar 11 2006, 19:24) *
И всётаки вопрос: Если применяешь с компиллер, то оптимизация описанным путём, - это же явная привязка к конкретному компилятору. И даже, вероятней всего, к его конкретной версии. smile.gif

вопроса-то вроде и нет, Вы сами все верно сказали. wink.gif

Еще дополню относительно третьего вопроса..
Цитата
Третий. Насколько сложно программу написанную на IAR "заставить" компилится на GCC. Может у кого есть спец. ".h" файлы.

Зависит от привязки к компилятору. Чем больше текста написано на plain C, без использования прагм, ключевых слов характерных для конкретного компилятора, тем проще будет "заставить" код компилироваться другим компилятором. Хотя с ключевыми словами просто - можно создать хедер с соответствеющими макросами и все (пример osnwt).. А прагм не так уж много.. ;>
Существенное отличие между IAR и GCC на мой взгляд заключается наверное в том, что GCC основан на стандарте C99 и позволяет определять переменные только в начале функций, т.о. выражения вида for(int i; i=0;....}, прекрасно воспринимаемые компилятором IAR, в gcc компилироваться не будут. Соответственно лучше сразу ими не пользоваться и в IAR...

Цитата
Мне для впихивания навороченного USB крипто-загрузчика в 4 килобайта бут-блока меги32 пришлось соптимизировать примерно 200 лишних байтов. Ясное дело, что не только таким способом - многое пришлось переписать по другому. Но результат был достигнут.

Я бы "навороченный" криптозагрузчик писал бы полностью на ассемблере, если бы мне потребовался действительно небольшой объем. А так (с упованием на оптимизацию компилятора C) могу только сказать "овчинка выделки не стоит". Появится новая версия компилятора и код, вполне возможно, уже не втиснется в 4k.
osnwt
Цитата(defunct @ Mar 12 2006, 01:51) *
Я бы "навороченный" криптозагрузчик писал бы полностью на ассемблере, если бы мне потребовался действительно небольшой объем. А так (с упованием на оптимизацию компилятора C) могу только сказать "овчинка выделки не стоит". Появится новая версия компилятора и код, вполне возможно, уже не втиснется в 4k.

Дело в том, что 2 килобайта - это USB драйвер, еще килобайт - это дешифровщик. Тем самым мы уже вылезли за пределы 2Кбайт. Остальное - это протокольная часть со всякими штуками.

Естественно, что можно все написать на асме, если писать с нуля. Но переписывать 3/4 кода просто из спортивного интереса я не могу себе позволить, поскольку это не моя работа, а всего лишь хобби. Я предпочту уделить это время написанию собственно того, что можно загружать этим загрузчиком, к примеру.

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

Иными словами, все определяется потребностями и возможностями. В рамках имеющихся потребностей мне хватило имеющихся возможностей реализовать желаемое с минимальными затратами времени (а переписывание загрузчика или USB драйвера займет на порядок-два больше усилий на отладку и поиск трудноуловимых глюков при малейшей ошибке). В другой ситуации будет лучше все писать с нуля и на асме, но не в этой. Естественно, это всё IMHO.
defunct
Цитата(osnwt @ Mar 12 2006, 03:38) *
Кроме того, бут - это такая штука, которая едва ли будет часто меняться или развиваться. Обычно базовых функций хватает. Скорее, снимут с производства AVR в пользу чего-то другого, чем потребуется что-то менять. Новый компилятор едва ли будет давать бОльший код, чем старый. При этом нормальная практика в серьезных проектах - хранить средства разработки под версионным контролем наряду с собственно разработкой. Так что всегда есть возможность откомпилироваться старым.

Вот именно, и это как раз тот случай когда применение ассемблера действительно оправдано!


Цитата(osnwt @ Mar 12 2006, 03:38) *
Дело в том, что 2 килобайта - это USB драйвер
....
В другой ситуации будет лучше все писать с нуля и на асме, но не в этой. Естественно, это всё IMHO.

Возможно, а возможно было бы целесообразно применить код Igor Cesko о котором Вы упоминали...
beer_warrior
2 osnwt
Цитата
Как пояснил Кристиан, gcc не рассчитывался на гарвардскую архитектуру процессора. Потому чтобы поддержать в нем AVR, пришлось сделать немалые хаки.

Вообще-то достаточно странно.Ведь по идее компилеру должно быть наплевать задан сегмент кода программно или аппаратно.
Тем более гарвардских процессоров добрая половина.
Интересно сравнить например с возможностями SDCC.

2defunct
Цитата
Существенное отличие между IAR и GCC на мой взгляд заключается наверное в том, что GCC основан на стандарте C99 и позволяет определять переменные только в начале функций, т.о. выражения вида for(int i; i=0;....}, прекрасно воспринимаемые компилятором IAR, в gcc компилироваться не будут. Соответственно лучше сразу ими не пользоваться и в IAR...


Ну во-первых обьявления в операторе for это как раз С99,
а во-вторых gcc поддерживает 4 стандарта С89-С99 и их расширения для GNU, тут как раз проблемы нет - С99 и вперед.

2SasaVitebsk
Конечная цель любой отпимизации свести код либо к минимальному размеру, либо к максимальной скорости, как правило несложные неоптимальности - использование промежуточных переменных, изменнение обычной переменной в цикле, чрезмерно сложные ветвления соптимизируються одинаково ибо приемы решения этих проблем общие.
По ссылке ниже - анализ компиляторов с примерами - примените их к AVRу и получите представление о сходстве и различии в их поведении.
http://www.rsdn.ru/article/devtools/devtools.xml
osnwt
Цитата(defunct @ Mar 12 2006, 03:53) *
Цитата(osnwt @ Mar 12 2006, 03:38) *

Кроме того, бут - это такая штука, которая едва ли будет часто меняться или развиваться.

Вот именно, и это как раз тот случай когда применение ассемблера действительно оправдано!

Если бы у меня стояла задача минимизировать цену в большой партии устройств, то я бы, возможно, и попытался. Однако, код USB занимает больше 1.5 килобайт. Втиснуть в оставшееся дешифровальщик и протокольную часть было бы проблематично, хотя шанс и есть. Но в моей ситуации это была бы неоправданная трата времени.

Цитата
Цитата(osnwt @ Mar 12 2006, 03:38) *

Дело в том, что 2 килобайта - это USB драйвер
....
В другой ситуации будет лучше все писать с нуля и на асме, но не в этой. Естественно, это всё IMHO.

Возможно, а возможно было бы целесообразно применить код Igor Cesko о котором Вы упоминали...


Цитата с сайта автора avr-usb (выделено мною):

Цитата
Advantages over other Firmware-Only Implementations

A similar driver for the AVR series of microcontrollers is available from Igor Češko. Our USB driver has the following advantages over Igor's driver:

* All customizable code written in ANSI-C and thus easier to maintain.
* Modular concept: easier to integrate into existing designs.
* Slightly smaller code size in spite of high level language modules.
* Faster: All encoding/decoding (USB requires NRZI coding and bit stuffing) is done in real-time, not in the main loop after storing away the raw data stream.

* AVR-USB comes with a free shared Vendor- / Product-ID pair.
* The level of standards conformance is documented (description of limitations and potential problems).
* Available for free with Open Source license. (See the file License.txt in the distribution.)

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


Цитата(beer_warrior @ Mar 12 2006, 13:23) *
Вообще-то достаточно странно.Ведь по идее компилеру должно быть наплевать задан сегмент кода программно или аппаратно.

Не вижу ничего странного. При различных методах адресации кода и данных нужно помнить разные типы указателей или использовать нечто вроде generic типа указателя в IAR, который крайне неэффективен. Доступ к данным тоже осуществляется совершенно по разному. Вроде, мелочь - но, видимо, существенная.
SasaVitebsk
Похоже даже максимального уровня оптимизации мне будет не достаточно. smile.gif
То что я увидел в обработчике самого быстрого прерывания (245мкс), трудно назвать программой в здравом уме. Читал некоторые посты по оптимизации. Применил всё что я вычитал оттуда. Также захотел "облегчить работу компилятора" заменив ряд логических операций, - таблицей перекодировки.
Теперь даже не знаю что былобы лучше.
И ещё скажите мне почему компилятор так упорно применяет команды типа "LDD R18, Z+61" вместо "lds r18,имя". Явно нагляднее последнее. (В Z компилятор заносит имя переменной вообще не имеющей отношения к процедуре прерывания). Учитывая использование данной инструкции, похоже необходимо переменные используемые в одном месте группировать? Может есть у кого рекомендации по оптимизации программы на С? Я пока чётко понял одну касающуюся необходимости правильного размещения или объявления процедуры прерывания.
Вопросов у меня несколько.
1) Как правильно объявить процедуру прерывания если я её полностью напишу на ASMе? Достаточно ли просто extern?
2) Где почитать по макросам для IAR ASM?
3) На объявление переменной типа:

struct
{
uint8_t RXOFF : 1, // Буфер переполнен, загрузка приостановлена
RMaster: 1, // Контроллер в режиме "Мастер"
RLoadActKom : 1, // Разрешена Загрузка команд в "активную зону"
cursiv: 1, // вывод текста курсивом
Color: 1, // цвет
Load485: 1, // Загрузка команд идёт с RS485
ErrLoadKom: 1; // Ошибка загрузки команды
// RActivPass: 1; // Прерывание завершено;
} Flag;

Компилятор выделил 11 (!) байт данных. Где у меня ошибка? Или я не правильно понял "возможность работы с битами" и эта "возможность" такая же как "boolean" под Паскалем?
beer_warrior
Цитата
почему компилятор так упорно применяет команды типа "LDD R18, Z+61" вместо "lds r18,имя".

Обычно такое случаеться при работе со сложными типами данных.
В Z - адрес, а потом ездит по смещению.
Цитата
Компилятор выделил 11 (!) байт данных. Где у меня ошибка? Или я не правильно понял "возможность работы с битами"

Есть две важных установки компилятора
1.Упаковка структур - т.е без выравнивания по адресам.
2.Разрешение работы с битовыми полями.
Устанавливаеться прагмами или в свойствах проекта.

Если такие проблемы - м.б. взять камень побыстрее?
vet
Цитата(SasaVitebsk @ Mar 17 2006, 00:19) *
И ещё скажите мне почему компилятор так упорно применяет команды типа "LDD R18, Z+61" вместо "lds r18,имя". Явно нагляднее последнее. (В Z компилятор заносит имя переменной вообще не имеющей отношения к процедуре прерывания).


LDS занимает 2 слова, а LDD - одно, при одинаковом времени выполнения. Очевидно, второй вариант предпочтительнее.
osnwt
Цитата(SasaVitebsk @ Mar 16 2006, 23:19) *
То что я увидел в обработчике самого быстрого прерывания (245мкс), трудно назвать программой в здравом уме. Читал некоторые посты по оптимизации.

Основной совет: минимизировать количество операций в прерывании, ограничившись, например, выставлением флага или записью байта в регистр.

Битовые флаги удобно делать регистровыми типа __regvar. Экономит сохранение регистров, да и все остальное.

Не менее важный совет: избегать вызова функций из процедуры обработки прерывания. Если последняя работает автономно со своими локальными или глобальными переменными, то код будет самым эффективным. Если она вызывает хоть одну другую функцию, определенную в этом же файле - мы нарываемся на сохранение и восстановление некоторых регистров, используемых той функцией. Если процедура обработки прерывания вызовет хоть одну функцию типа extern, описанную в другом файле или непродекларированную - будьте готовы к полному сохранению и восстановлению половины регистров (все scratch регистры, которые любая C функция имеет право портить).

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

Цитата
1) Как правильно объявить процедуру прерывания если я её полностью напишу на ASMе? Достаточно ли просто extern?

Вероятно, не extern, а public. Но ее вообще не нужно нигде объявлять, если это процедура обработки прерывания. C-компилятору вообще нет дела до этой процедуры, он ее не вызывает. Но при этом придется самому позаботиться о том, чтобы правильно разместить команду перехода на начало функции в таблице векторов прерываний. Пример можно посмотреть в исходных текстах USB драйвера. Там именно так и сделано.

Цитата
2) Где почитать по макросам для IAR ASM?

Вероятно, в прилагаемой к нему документации?

Дополнительно можно посмотреть в его *.h файлы. Можно найти кое-что полезное.

Цитата
3) На объявление переменной типа:
...
Компилятор выделил 11 (!) байт данных.

Как это было определено? С трудом себе это представляю. У меня, как и ожидается, 1 байт. Листинг ниже. А что у Вас в листинге?

Код
//   87 __root struct
//   88 {
//   89  uint8_t  RXOFF   : 1, // Буфер переполнен, загрузка приостановлена
//   90              RMaster: 1, // Контроллер в режиме "Мастер"
//   91              RLoadActKom : 1, // Разрешена Загрузка команд в "активную зону"
//   92              cursiv: 1, // вывод текста курсивом
//   93              Color: 1, // цвет
//   94              Load485: 1, // Загрузка команд идёт с RS485
//   95              ErrLoadKom: 1; // Ошибка загрузки команды
//   96 // RActivPass: 1; // Прерывание завершено;

        RSEG NEAR_Z:DATA:ROOT(0)
        REQUIRE `?<Segment init: NEAR_Z>`
//   97  } Flag;
Flag:
        DS 1

(__root - это чтобы компилятор не выкинул переменную, на которую нет ссылок в коде).
alekseykoj
У IAR синтаксис обычный для языка С. Т.е описание библиотек ты можешь найти в любой книжке по С. Приведенные Вами примеры функций - это обработчики прерываний. Они бодробно описаны в описании IAR. Как строить программу на C можно прочитать в любом самоучителе по С
osnwt
Цитата(alekseykoj @ Mar 17 2006, 16:19) *
У IAR синтаксис обычный для языка С. Т.е описание библиотек ты можешь найти в любой книжке по С. Приведенные Вами примеры функций - это обработчики прерываний. Они бодробно описаны в описании IAR. Как строить программу на C можно прочитать в любом самоучителе по С

Вопросы были не в том, как строить программу на C, а специфических особенностях IAR. В том числе:

Цитата
- как описать процедуру обработки прерывания на ассемблере (а не как построить программу на C).

Ответ: стандартно, так как C до нее нет никакого дела. Читать документацию по ассемблеру (именно по IAR ассемблеру - важно, как разместить rjmp по адресу вектора прерывания с использованием команд управления сегментами *SEG и счетчиком адреса ORG, которые частично отличаются по синтаксису от других ассемблеров).

Цитата
- где почитать по макросам IAR ассемблера (а не про стандартные C библиотеки).

Ответ: после чтения документации полезно посмотреть не стандартные, а именно специфические для IAR *.h файлы, поскольку они используются и для C-программ, и для ассемблерных программ. Ассемблер IAR отличается от ассемблера Atmel синтаксисом большинства директив, а также использованием препроцессора C по умолчанию. Стандартные библиотеки C тут не причем.

Цитата
- почему стандартное битовое поле из 8-ми битов превращается в 11 байтов вместо 1.

Ответ: и на самом деле, почему? Видимо, не так смотрелось. Нужно смотреть на листинг, во что превращается объявление. Этого ни при каком выравнивании не может быть, так как 8 байтов из 8 битов - еще понять можно, но 11...

То есть, даны ответы, в общем-то, правильные, но совсем не на заданные вопросы...
SasaVitebsk
Я извиняюсь перед всеми. Надо было более детально разобраться в произошедшем, а потом писать. Спасибо всем высказавшимся. smile.gif
Скажу что изучал когда-то очень давно теорию компиляторов и ОС. smile.gif Но тогда мне это было не нужно и безинтересно. Тогда безгранично годсподствовала ОС ЕС ЭВМ и только0-только появилась СВМ! С каким бы я интересом послушил бы сейчас того препода! Такой класный был преподаватель ...

Теперь по сути. Компилятор, очевидно, очень хороший. Местами великолепно "угадывает" как надо делать и прекрассно компилирует. Но некоторые фичи я пока не понимаю.
1) В процедуре обработки прерывания я не вызываю функций. (Прочитал в других ветках форума). Но если я пользуюсь переменными, объявленными как статические глобальные (вверху файла), то код получается значительно больший. Похоже компилятор пытается выделять локальные экземпляры переменных или что-то в этом духе. Обращение к этим переменным осуществляется ч/з указатель. Не совсем понимаю зачем это? Может чтобы дважды можно было вызвать? Если объявляешь статическими внутри функции, то код намного понятнее. А как надо объявлять? Я хочу чтобы от вызова к вызову переменные сохраняли своё значение.
2) Наблюдаю картину, что если арифметическое выражение сделать сложным, то компилятор начинает генерить сложный код. Получается что лучше разбивать операторы на более простые?
3) Мне необходимо в прерывании выполнить определённую операцию 18 раз. Можно сделать её путём логических операций. Но что бы ускорить процесс я решил воспользоваться табличным методом. Для этого сгенерировал соответсвующий массив. Вне данного прерывания этот массив использоваться не будет. Я объявил его в другом файле вместе с другими таблицами а в перывании объявил его как внешний. (Пробовал объявить его внутри прерывания, - компилятор ругается). Так вот обращение к этому массиву какое-то мудрёное. Может кто подскажет в чём дело?
4) На счёт одинадцати байт. smile.gif И правда и не совсем. smile.gif С одной стороны при обращении к битам происходит обращение только к одному байту! Причём обращение красивое!!! Но в распечатке листинга (релиз) я сейчас вижу следующее:
// 70 } Flag;
Flag:
DS 1
DS 1

А раньше выделялось 11 байт. Вроде ничего не менял. Пробовал воткнуть STATIC, - не получилось.
Кстати ко многим переменным добавляется ещё один байт. Это зачем?

Пятое и пока последнее. Спасибо всем откликающимся. Я конечно постепенно приду к определённым результатам и самостоятельно, не отрывая Вас от Ваших дел, но мне кажется что данный форум читаю не только я, но и другие начинающие программисты С. И, мне хочется надеятся, что он будет полезен для многих, поскольку я пытаюсь ставить вопросы, так сказать, из жизни.
Old1
Цитата(SasaVitebsk @ Mar 17 2006, 21:59) *
...
Но некоторые фичи я пока не понимаю.
1) В процедуре обработки прерывания я не вызываю функций. (Прочитал в других ветках форума). Но если я пользуюсь переменными, объявленными как статические глобальные (вверху файла), то код получается значительно больший. Похоже компилятор пытается выделять локальные экземпляры переменных или что-то в этом духе. Обращение к этим переменным осуществляется ч/з указатель. Не совсем понимаю зачем это? Может чтобы дважды можно было вызвать? Если объявляешь статическими внутри функции, то код намного понятнее. А как надо объявлять? Я хочу чтобы от вызова к вызову переменные сохраняли своё значение.
...

Компилятор глобальные переменные по умолчанию помещает в SRAM, поэтому и обращается к ним через указатель, времени это занимает больше, если сравнивать с обращениями к регистровому файлу или к io-регистрам (поэтому обычно рекомендуют стараться меньше использовать глобальные переменные). Для того чтобы уменьшить время выполнения обработчика прерывания можно посоветовать объявить глобальную переменную как регистровую или разместить ее в неиспользуемых io-регистрах.
SasaVitebsk
Цитата(Old1 @ Mar 17 2006, 22:53) *
Компилятор глобальные переменные по умолчанию помещает в SRAM, поэтому и обращается к ним через указатель, времени это занимает больше, если сравнивать с обращениями к регистровому файлу или к io-регистрам (поэтому обычно рекомендуют стараться меньше использовать глобальные переменные). Для того чтобы уменьшить время выполнения обработчика прерывания можно посоветовать объявить глобальную переменную как регистровую или разместить ее в неиспользуемых io-регистрах.

Компилятор в любом случае помещает эти переменные в SRAM. Но в первом описанном случае помещает ещё и указатели на эти переменные. Ну и обращается к ним ... соответственно.
osnwt
Цитата(Old1 @ Mar 17 2006, 20:53) *
Компилятор глобальные переменные по умолчанию помещает в SRAM, поэтому и обращается к ним через указатель, времени это занимает больше, если сравнивать с обращениями к регистровому файлу или к io-регистрам (поэтому обычно рекомендуют стараться меньше использовать глобальные переменные). Для того чтобы уменьшить время выполнения обработчика прерывания можно посоветовать объявить глобальную переменную как регистровую или разместить ее в неиспользуемых io-регистрах.

Ответ большей частью верный, но непонятный для начинающих. Попробую пояснить.

Имеем три вида переменных в языке C:

1) Глобальные, объявленные на внешнем уровне.
2) Локальные статические, объявленные внутри функции и сохраняющие свои значения между вызовами.
3) Локальные автоматические, объявленные внутри функции и не сохраняющие своих значений.

Переменные (1) компилятор размещает в SRAM. Слово register конкретным компилятором для глобальных переменных не будет удовлетворено, так как неизвестно, что с этими регистрами сделают другие функции. Для размещения глобальной переменной в регистре есть опция конкретного компилятора lock registers и ключевое слово __regvar (см. документацию). Залочив несколько регистров (то есть, зарезервировав их под статические переменные), вся программа и используемые библиотеки должна быть откомпилирована с такой же опцией, иначе на стадии линковки будет ошибка. Есть еще вариант размещения констант в памяти программ - const __flash, но это не относится к изменяемым переменным.

Кстати, зарезервировать регистры под переменные мало: надо еще и явно разместить их там (см. документацию). Иначе будет только хуже или, как минимум, не лучше. А хуже потому, что компилятор ограничен в использовании всех свободных регистров, а если там ничего не разместили, то выигрыша не будет. Не все переменные следует делать регистровыми - это зависит от их использования, надо пробовать. Иногда на одной переменной выигрыш десятки байтов, на другой - почти ничего, а на третьей получаем даже проигрыш.

Локальные статические переменные (2) компилятор также размещает в SRAM (из ответа можно понять, что нет). Отличие от (1) - только в области видимости (конкретная функция). Все справедливо и в отношении игнорирования слова register. __regvar использовать, скорее всего, тоже не удастся: насколько помню, компилятор допускает его применение только для глобальных переменных. Почему обращение к локальным static менее эффективно, чем к глобальным - не скажу, не видя фрагмента кода.

Локальные автоматические переменные (3) - вот тут возможны варианты. Классически эти переменные размещаются в стеке, расположенном тоже в SRAM. Однако в данном случае компилятор и без всяких слов register будет пытаться разместить максимум автоматических переменных в регистрах. Сохранять их между вызовами не нужно, а есть достаточное количество регистров, которые функция может использовать и не обязана сохранять (см. документацию). Работа с регистрами обычно эффективнее, это верно. Но использовать минимум глобальных переменных рекомендуют вовсе не по этой причине, а из соображений стиля программирования и читабельности программ. Остальное более-менее вторично и зависит от конкретных особенностей компилятора.

Тут есть много нюансов. Например, единственный вызов функции без параметров, занимающий слово (2 байта) может очень неслабо увеличить размер некоей функции. Почему? А потому что вызванная функция может использовать половину регистров, и весь выигрыш от размещения локальных auto переменных в регистрах будет съеден необходимостью их сохранить перед этим вызовом, а потом восстановить.

Почему компилятор использует обращение через "указатель"? Ряд команд специально сделан для работы с индексированными данными. Например, в регистровую пару Z может быть загружен начальный адрес структуры, а потом к ее членам можно обращаться одной командой через Z+смещение. Компактно и удобно. Этот прием компилятор может использовать и для доступа к локальным переменным, и для глобальных с целью экономии кода.

Насчет сложного кода в выражениях: заочно ответить нельзя. В таких случаях надо писать иначе: вот фрагмент 1 (кусок кода), вот фрагмент 2 (кусок кода). Первый занимает на 20 байтов больше. В чем причина? Иногда ответить на этот вопрос вообще нельзя, не видя описания переменных и т.п. Часто просто удобнее писать по частям, заводя по ходу дела промежуточные переменные. Не следует беспокоиться: при высоких уровнях оптимизации компилятор так или иначе не будет хранить то, что дальше по тексту не требуется. А читабельность это может поднять.

Про массивы в прерывании. То же самое. Не видя кода, нельзя сказать, почему компилятор "ругается". Как минимум, следует указывать сообщение об ошибке. А сэкономить можно много как. Например, объявить таблицу как const __flash, разместив ее тем самым не в SRAM (которое заполняется кодом инициализации из того же flash при запуске программы), а непосредственно во flash. Правда, доступ будет чуть сложнее, но зато таблица не займет оперативной памяти, что иногда важнее. А, кстати, а как на самом деле объявлялась таблица внутри? Надеюсь, что static? Иначе бедному компилятору пришлось бы ее инициализировать при каждом входе в функцию... Может, это и не понравилось? В общем, код в студию.

11 байт: то же самое. Бесцельно компилятор не выделяет лишние байты в конце. Причина обычно всегда есть. Но без фрагмента текста понять ее сложно. Мало кто на этом форуме умеет гадать на кофейной гуще smile.gif


Цитата(SasaVitebsk @ Mar 17 2006, 21:38) *
Цитата(Old1 @ Mar 17 2006, 22:53) *

Компилятор глобальные переменные по умолчанию помещает в SRAM

Компилятор в любом случае помещает эти переменные в SRAM. Но в первом описанном случае помещает ещё и указатели на эти переменные. Ну и обращается к ним ... соответственно.

Компилятор не помещает их в SRAM, если явно указано, например,

char __regvar __noinit c @ 15;

При этом глобальная переменная c будет размещена не в SRAM, а в регистре, который мы обязаны залочить на уровне всего проекта опцией компилятора или IDE. Тут ответ был совершенно правильный.

А размещать для статических переменных еще и указатели на них, помимо самих переменных... помилуйте, ради чего? Повторю, что не видя конкретного кода в конкретном контексте, сказать на 100% просто невозможно, можно лишь гадать.
SasaVitebsk
osnwt спасибо за расширенный ответ. Детали и нюансы не почерпнёшь в мануалах. Там всё лаконично.
Массив пересчёта я поместил в const __flash, бузусловно. Использовать 256 байт озу под это для меня слишком расточительно. Помещать Вам на рассмотрение ещё и фрагменты текста, по-моему это уж слишком. smile.gif Это уж самом-самом крайнем случае. Буду разбираться сам. Ваши советы мне помогли обрести уверенность. Я теперь больше доверяю компилятору и меньше себе. smile.gif Если что-то не так, - более детально разбираю программу.
На счёт "легендарных" 11 байт. biggrin.gif
Ещё раз внимательно всё просмотрел (в который уже раз). У меня в программе очень много различных данных находится в sram. И разнотипных к тому же.
Чё делает компилятор? - Он выбирает группу переменных (возможно использующихся в одном месте) и для обращения к переменным использует одно имя. А к остальным переменным обращается LDD Rx, Z+ (смещение в группе). Вот так и получилось 11 байт. laugh.gif На самом деле это определённое количество переменных начинающихся с переменной "flag".

В моём случае вычисление типа:
Oport0 &= ((EkrToPort[*AdrShowEkr++ ^ Cycl_Ekr] & 0xf) | (EkrToPort[*AdrShowEkr++ ^ Cycl_Ekr] & 0xf0));
Заняло больше места чем
l = (EkrToPort[*AdrShowEkr++ ^ Cycl_Ekr] & 0xf);
Oport0 &= (l | (EkrToPort[*AdrShowEkr++ ^ Cycl_Ekr] & 0xf0));
(l - переменная локальная)
osnwt
Цитата(SasaVitebsk @ Mar 19 2006, 15:05) *
В моём случае вычисление типа:
Oport0 &= ((EkrToPort[*AdrShowEkr++ ^ Cycl_Ekr] & 0xf) | (EkrToPort[*AdrShowEkr++ ^ Cycl_Ekr] & 0xf0));
Заняло больше места чем
l = (EkrToPort[*AdrShowEkr++ ^ Cycl_Ekr] & 0xf);
Oport0 &= (l | (EkrToPort[*AdrShowEkr++ ^ Cycl_Ekr] & 0xf0));
(l - переменная локальная)

Хочу заметить, что первое (сложное) выражение, помимо того, что не очень наглядно, еще и неоднозначно с точки зрения порядка вычислений (иногда при этом можно получить и разную длину кода).

Использованное там выражение *p++ имеет побочный эффект в виде инкремента указателя. В выражении e1 | e2 порядок вычисления выражений e1 и e2 вообще не определен (в отличие от e1 || e2, где вычисление идет слева направо - смысл операций, разумеется, разный). Потому в общем случае компилятор может сначала посчитать правое выражение, а потом левое. Очевидно, что результаты будут различные, так как *p++ (где p - это EkrToPort) даст разные результаты в этих двух случаях.

Потому в таких случаях особенно важно писать однозначно и наглядно.
defunct
Как говорит уважаемый АП - лучше написать два раза if чем, слепив условие для одного оператора if, получить неоднозначность..
osnwt
Цитата(osnwt @ Mar 19 2006, 16:32) *
*p++ (где p - это EkrToPort)

Ошибка: имелось в виду AdrShowEkr в выражении
Код
Oport0 &= ((EkrToPort[*AdrShowEkr++ ^ Cycl_Ekr] & 0xf) | (EkrToPort[*AdrShowEkr++ ^ Cycl_Ekr] & 0xf0));
SasaVitebsk
Я это обнаружил с самого начала, но решил посмотреть как это сделает компилятор. Потом убрал чтобы не было неоднозначности. Написанная мной подпрограмма прерывания на ASMе оказалась быстрее примерно в четыре раза. Я конечно ещё поэкспериментирую с СИ (программой прерывания), но вероятней всего в конечном варианте будет программа на асемблере. (Самый быстрый вариант на СИ тоже оставлю для возможного перехода на другой МП)

Я приступил к отладке, поскольку в настоящий момент начинаю "терять из под контроля программу". smile.gif (Не знаю как у кого, а у меня при большой длине проги наступает такой момент, когда она живёт своей жизнью)
После того как я отлажу (в симуляторе) некоторые независимые части, то начну её дописывать. Осталось процентов 20-25. И вот сейчас у меня непонятная ошибка.

TekAdrActive = LastAdrActive = malloc(SIZE_FREE); // Âûäåëèòü ïàìÿòü

При отладке в указатели заносится значение при SIZE_FREE менее 10. Фишка в том, что я пишу под atmega2560 (sram 8k). Компилятор выдаёт следующие значения занятой программы.

//
// 43 bytes in segment ABSOLUTE
// 2 668 bytes in segment CODE
// 3 002 bytes in segment FARCODE
// 7 bytes in segment INITTAB
// 12 bytes in segment INTVEC
// 855 bytes in segment NEAR_Z
//
// 5 670 bytes of CODE memory (+ 19 bytes shared)
// 855 bytes of DATA memory (+ 43 bytes shared)

И ещё указатель на свободный адрес она выдаёт типа 229h. Может быть там и есть дырка байт 10. smile.gif Но потом идут массивы и прочее. Короче менее 5bfh этот адрес не должен быть. Где-то ошибка. Может быть это связано с сегментами? Дело в том что я с ними не работал и пока убей-непонимаю зачем они нужны (применительно к AVR. На процессоре 80x8х когда-то сталкивался под ассемблером. Там это очевидно). Помогите разобраться.
osnwt
Цитата(SasaVitebsk @ Mar 19 2006, 22:27) *
TekAdrActive = LastAdrActive = malloc(SIZE_FREE); // Âûäåëèòü ïàìÿòü

При отладке в указатели заносится значение при SIZE_FREE менее 10. Фишка в том, что я пишу под atmega2560 (sram 8k). Компилятор выдаёт следующие значения занятой программы.
...
Может быть это связано с сегментами? Дело в том что я с ними не работал и пока убей-непонимаю зачем они нужны (применительно к AVR. На процессоре 80x8х когда-то сталкивался под ассемблером. Там это очевидно). Помогите разобраться.

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

Раскладыванием сегментов по адресам занимается линкер под управлением файла конфигурации *.lnk. По умолчанию в IAR среда определяет основные размеры областей, а дальше используется файл конфигурации для соответствующего контроллера. Там указано, что сегмент такой-то занимает адреса такие-то. Это делается (по умолчанию) с учетом заданных в конфигурации проекта размеров стеков, кучи и т.п.

При работе с *alloc функциями надо выделить достаточный размер "кучи" (heap) в конфигурации проекта. А потом посмотреть, где линкер разместит сегменты HEAP и NEAR_HEAP. Если выделяемый адрес не попадает в эти области, то что-то не так с программой.

Подробнее RTFM по линкеру, там все хорошо расписано. В данном случае сегменты не имеют ничего общего с сегментами x86, это просто средство управления распределением адресов под код и данные при окончательной компоновке.
SasaVitebsk
Спасибо всем помогавшим. Сегодня в общих чертах проект закончен. 22кб из них 8кб прога. Основные моменты проверены на AVR Studio. smile.gif Даже полностью написанный на Си (без ассемблерных вставок) укладывается по времени с учётом выбраной частоты (С хорошим запасом).
Время написания достаточно малое, с учётом обучения. Очеть наглядная программа. По этому параметру программа на Си значительно лучше чем на ASMе.
Короче я очень доволен и перехожу к следующим стадиям проекта. smile.gif
SasaVitebsk
Подскажите спецы (IAR). Компилятор очищает переменные до запуска main, но после __low_level_init.
Поэтому переменные которые я инициализирую в __low_level_init теряются. Я стал их инициализировать при объявлении. Это помогло. Но у меня есть переменные которые должны инициализироваться в начале __low_level_init, т.к по ним потом иниц. оборудование.
Вопрос. Как объявить переменную, чтобы компилятор не инициализировал её? volatile вроде не для этих целей
SasaVitebsk
Подскажите спецы (IAR). Компилятор очищает переменные до запуска main, но после __low_level_init.
Поэтому переменные которые я инициализирую в __low_level_init теряются. Я стал их инициализировать при объявлении. Это помогло. Но у меня есть переменные которые должны инициализироваться в начале __low_level_init, т.к по ним потом иниц. оборудование.
Вопрос. Как объявить переменную, чтобы компилятор не инициализировал её? volatile вроде не для этих целей
defunct
__no_init ?
SasaVitebsk
Спасибо.
Сегодня ине глючит у меня. Поэтому топик попал дважды да не весь. smile.gif Передаю вторую часть
Подскажите спецы (IAR).
Более сложный вопрос. smile.gif
Переменные объявлены следующим образом:

void static *AdrActiveKom[MAX_ACTIVE_KOM+1]; // Адреса указателей на начало активных команд
uint8_t i, j, c, *adrnpict;

Имею такой цикл (или он меня smile.gif )
j= (uint8_t *)AdrActiveKom[i+1]-(uint8_t *)AdrActiveKom[i]; // Высчитать длину команды
for(adrnpict=(AdrActiveKom[i]);adrnpict<(AdrActiveKom[NumbActiveKom+1]-j);adrnpict++)
*adrnpict = *(adrnpict+j);// перенесли команду

Если цикл описать так, то синтаксич. ошибка несовместимости типов, а если так как ниже то всё OK
j= (uint8_t *)AdrActiveKom[i+1]-(uint8_t *)AdrActiveKom[i]; // Высчитать длину команды
for(adrnpict=((uint8_t *)AdrActiveKom[i]);adrnpict<((uint8_t *)AdrActiveKom[NumbActiveKom+1]-j);adrnpict++)
*adrnpict = *(adrnpict+j);// перенесли команду

Но при вычислениях (не в теле цикла, а при выч. переменных цикла) компилятор использует адреса а не содержимое массива.
Можно конечно поизголятся используя адресную арифметику, но я хочу выяснить где у меня ошибка? Что я не правильно понимаю?
С уважением.
defunct
Цитата(SasaVitebsk @ Mar 22 2006, 14:29) *
Можно конечно поизголятся используя адресную арифметику, но я хочу выяснить где у меня ошибка?

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

На самом деле команд много и поступают непрерывно. Команды имеют переменную длину и структуру. Заносятся последовательно и там проблем не возникает. Исполняются только те команды которым подошло время. Т.н. активные. Исполняются пока не завершится время исполнения. С исполнением тоже проблем нет.
То что Вы видите, - это не занесение в буфер а "смерть" команды. Т.е. она отработала, и её надо выкинуть из буфера. Я конечно могу просто удалить указатель на данную команду, но тогда в памяти появится "дырка". Короче стандартная ситуация "сборки мусора", которая, как говорят, эффективно решена в UNIX, но плохо в Windows. smile.gif Известно и её решение в файловой системе диска начиная с FAT. По известной причине данные решения я применить не могу. smile.gif Поэтому я переписываю все команды следующие за удаляемой на место начиная с освобождённого. А также пересчитываю массив указателей на начало команд и уменьшаю их число. Всё это на PC работает и здесь ошибки нет, но компилятор почему-то не верно компилирует (точнее не так как я от него хочу).
defunct
1. избавляться от "дыр" в массиве можно скопировав последний элемент массива на место удаленного элемента и уменьшив размер массива на еденицу.
2. Для того чтобы было все гладко и смотрелось красиво, то неплохо бы для чистки "кучи" (если вы ее имели в виду) пользоваться прототипами функций memcopy, fillchar и т.п.
SasaVitebsk
В массиве только указатели на "команды". Команда представляет собой структуру. Разные команды - разные структуры. Одна занимает 14 байт, - другая - 150 байт. Друг в друга ни как не скопируешь. Насчёт мемcopy посмотрю. Не видел такой функции.
А всётаки что на счёт программы. Там 4 строчки. Описания переменных я дал. Смысл того что хочу сделать вроде объяснил. Где ошибка?
Алгоритм мой оценивать сложно так как вы видите очень малую часть общей картины.
defunct
Цитата(SasaVitebsk @ Mar 23 2006, 23:39) *
В массиве только указатели на "команды". Команда представляет собой структуру. Разные команды - разные структуры. Одна занимает 14 байт, - другая - 150 байт. Друг в друга ни как не скопируешь.

Сходу посоветовал бы включить в тело команды поле "размер команды" 1 байт. Чтобы избавиться от вычисления размера.

Цитата
Насчёт мемcopy посмотрю. Не видел такой функции.

ну начните с такого прототипа:
int CopyArray(char *dest, char *source, int count);

копирует count байт из массива source в массив dest, возвращается число реально скопированных байт. Реализацию думаю Вы легко сможете написать..

а потом перейдите к такому (чтобы копировать что угодно и куда угодно):
int CopyMemory(void *dest, void *source, int count);
копирует count байт c адреса source по адресу dest, возвращается число реально скопированных байт. Имея реализацию первой функции, написать реализацию второй - по времени займет не больше одной минуты.

Цитата
А всётаки что на счёт программы. Там 4 строчки. Описания переменных я дал. Смысл того что хочу сделать вроде объяснил. Где ошибка?

ошибка всплывет, если упростить выражение вида for(adrnpict=(AdrActiveKom[i]);adrnpict<(AdrActiveKom[NumbActiveKom+1]-j);adrnpict++).... до такой записи:

Код
for(i=0; i<x; i++)
{
  dest[i] = source[i];
}

типы переменных (x, dest и source) и их значения задать Вам будет проще, чем мне гадать..

Цитата
Алгоритм мой оценивать сложно так как вы видите очень малую часть общей картины.

Да, оценить, а точнее - понять Ваш алгоритм по этой малой части общей картины, действительно нельзя.. Но вот, заметить что алгоритм через чур сложный - можно.
SasaVitebsk
Нашёл функцию "memcpy" скорее всего это сокращение от memcopy. С ней всё пашет. Спасибо.

Цитата(defunct @ Mar 24 2006, 03:30) *
Да, оценить, а точнее - понять Ваш алгоритм по этой малой части общей картины, действительно нельзя.. Но вот, заметить что алгоритм через чур сложный - можно.


Не спорю. Может даже ещё сложнее чем Вы подозреваете. smile.gif Но с одной стороны - это решение поставленной задачи. А с другой - это МОЁ решение. smile.gif Возможно пока я усложняю простые вещи. Или просто не вижу простого решения. Мой опыт программиста говорит, что ,как правило, находишь красивые решения, а потом их используешь многократно. Для этого, в первую очередь, требуется опыт. Я - оптимист, - опыт наработаем. biggrin.gif Было бы желание и работоспособность.

За помощь премного благодарю. В последнее время задавать вопросы и даже просто посоветоваться было не с кем. Хорошо что есть инет! smile.gif Так что не обижайтесь, я Вас ещё побеспокою.

Вопрос. Если я пишу п/п прерывания на asm, то необходимо как-то указывать компилятору С о наличии такой подпрограммы? Или это (что прерывание и расположено с такого вектора) указывается в модуле ASM и используется только линковщиком?
defunct
/offtop

Цитата
А всётаки что на счёт программы. Там 4 строчки. Описания переменных я дал. Смысл того что хочу сделать вроде объяснил. Где ошибка?


Цитата
Нашёл функцию "memcpy" скорее всего это сокращение от memcopy. С ней всё пашет. Спасибо.

Как видите не всегда рационально искать ошибку там где ее можно обойти ;>

Цитата
Вопрос. Если я пишу п/п прерывания на asm, то необходимо как-то указывать компилятору С о наличии такой подпрограммы? Или это (что прерывание и расположено с такого вектора) указывается в модуле ASM и используется только линковщиком?

Если в модуле с п/п на ассемблере указан ORG с адресом вектора прерывания, то безусловно это будет учтено на стадии линковки. Компилятору C главное не указывать директив #pragma vector с номером вектора который уже "занят" модулем на ASM.
osnwt
Цитата(SasaVitebsk @ Mar 24 2006, 16:42) *
Вопрос. Если я пишу п/п прерывания на asm, то необходимо как-то указывать компилятору С о наличии такой подпрограммы? Или это (что прерывание и расположено с такого вектора) указывается в модуле ASM и используется только линковщиком?

На C нужно разрешить указанное прерывание, установив соответствующие биты в регистрах, ответственных за прерывание, и глобально разрешив прерывания (__enable_interrupt() для IAR).
А на ассемблере - вот так, например (для IAR):

Код
; Переход с вектора прерывания на процедуру обработки
; ISR (interrupt service routine)

    COMMON  INTVEC
    ORG     INT0_vect
    rjmp    ISR_Int0

; Процедура обработки прерывания

    RSEG    CODE

ISR_Int0:
; сохранение статуса и регистров при необходимости
; ...
; ...
; восстановление регистров и статуса

    reti
SasaVitebsk
Цитата(defunct @ Mar 24 2006, 19:42) *
Как видите не всегда рационально искать ошибку там где ее можно обойти ;>


Когда "прикручивал" memcpy нашёл и там ошибку. smile.gif Ошибка была моя и компилятор (оно и понятно) был не причём. Впрочем, вариант на memcpy работает быстрее почти в два раза. Поэтому оставил memcpy.

osnwt спасибо за пример.
SasaVitebsk
А вот вопрос для понимания. Почему компилятор сохраняет регистры используя LD/ST -Y/Y+ а не PUSH/POP? Я вижу след. варианты ответов.
1) Чтобы контролировать переполнение
2) Чтобы удобнее было работать с переменными сохранёными в стеке (например в пп)

Может кто знает правильный ответ? Возможно это пригодится при работе asm/c.
beer_warrior
Правильный ответ 2.
Индексной адресацией удобно бегать по списку аргументов.
Обратите внимание, когда регистр надо просто сохранить используется PUSH/POP
SasaVitebsk
Такой вот вопрос:
При входе в подпрограмму передаются два параметра. Они типа uint16_t. Но после определённых действий значимая их часть становится 8. И дальше для арифметических операций не требуется 16 бит. Как указать это компилятору? Чтобы результирующий код оптимальнее был? Можно конечно объявить локальные переменные и переприсвоить. Но вроде не очень красиво. Может возможно напрямую компилятору указать?

Ещё раз повторю вопрос старый. Как указать компилятору не инициализировать переменную __no_init у меня не работает. Может хедер какой нужно подключить?

Заранее благодарю.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.