Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: IAR AVR (Cи). вопрос о указателе на глобальную структуру
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
ibiza11
Доброго времени суток уважаемые форумчане. На этом форуме давно, но отписываюсь не часто, практически по всем вопросам есть информация, за что всем посетителям и администрации поклон в ноги.
При написании кода всегда обращаю внимание на генерируемый ассемблерный листинг, при этом не заострял внимания на указатели.
Недавно обратил внимание на оч. интересный документ AVR035:Эффетивное программирование на Си для AVR (очень жалею, что давно его не прочел) sad.gif
В нем написан интересный способ уменьшения объема кода в случае использования множетсва глобальных переменных. Там предлагается объявить эти переменные как глобальную структуру и обращаться к каждой переменной
как к элементу структуры, при этом указатель на структуру всегда используется один и тот же, а переменные считываются командой LDD r16,Z+30 (соответственно командой STD записываются обратно). плюс
этой команды - использование считывания/записи со смещением, не изменяя при этом регистр Z.
я попробовал повторить пример из этой Application Note, все получилось как надо:
Цитата
typedef struct
{
int hour;
char min;
int sec;
}t;
t global;

__C_task void main1(void)
{
t *time=&global;
if(++time->sec==60)
{
time->sec=90;
time->min=70;
}
}

а это ассемблерный листинг, который генерируется в итоге:
Цитата
// 10 {
// 11 t *time=&global;

LDI R16, LOW(global)
LDI R17, (global) >> 8
MOVW R31:R30, R17:R16
// 12 if(++time->sec==60)
LDD R16, Z+2
INC R16
STD Z+2, R16
CPI R16, 60
BRNE ??main1_0
// 13 {
// 14 time->sec=90;

LDI R16, 90
STD Z+2, R16
// 15 time->min=70;
LDI R16, 70
STD Z+1, R16
// 16 }
// 17 }

Обратите внимание, что указатель инициализируется сразу в регистры r30,r31 (Z-регистр) и далее вообще не изменяется, а только используется с командами LDD, STD.
НО только я добавляю в цикл функцию delay, все кардинально меняется!!! blink.gif
Вот исходник
Цитата
typedef struct
{
int hour;
char min;
int sec;
}t;
t global;
extern void delay(unsigned int);

__C_task void main1(void)
{
delay(107);
t *time=&global;
if(++time->sec==60)
{
time->sec=90;
time->min=70;
}
}

А вот ассемблерный листинг
Цитата
// 12 delay(107);
FUNCALL main1, delay
LOCFRAME CSTACK, 0, STACK
LOCFRAME RSTACK, 2, STACK
ARGFRAME RSTACK, 0, STACK
LDI R16, 107
LDI R17, 0
RCALL delay
// 13 t *time=&global;
LDI R16, LOW(global)
LDI R17, (global) >> 8
MOVW R27:R26, R17:R16
// 14 if(++time->sec==60)
ADIW R27:R26, 2
LD R16, X

SBIW R27:R26, 2
INC R16
ADIW R27:R26, 2
ST X, R16
SBIW R27:R26, 2
CPI R16, 60
BRNE ??main1_0
// 15 {
// 16 time->sec=90;
LDI R16, 90
ADIW R27:R26, 2
ST X, R16

SBIW R27:R26, 2
// 17 time->min=70;
LDI R16, 70
ADIW R27:R26, 1
ST X, R16

SBIW R27:R26, 1
// 18 }
// 19 }
Обратите внимание, что теперь компилятору нафиг не сдался удобный регистр Z и команды LDD и STD вместе взятые. Вместо них он использует регистр X и каждый раз инкрементирует его на необходимый сдвиг, а считывает и записывает командами LD, ST, что естественно занимает больше времени чем команды LDD и STD. При оптимизации по скорости выполнения вообще заменяет указатель константой и обращается к каждому элементу структуры через команды STS LDS (занимающие в два раза больше программной памяти).
Вот код функции delay:
Цитата
void delay(volatile unsigned int ticks)
{
while(ticks)
ticks--;
}

volatile добавлен исключительно для того, чтобы компилятор не выбросил задержку из кода при оптимизации.
Теперь вопрос: Объясните почему так поступает компилятор с командами LDD и STD, и как ему указать что так делать не нужно?
Спасибо за внимание и понимание)

p.s. Если разместил тему не в том разделе, прошу модераторов перенести ее туда, где будет актуальнее.
уважаемые модераторы, изменил <codebox> на <quote> удобнее читать и есть возможность изменять цвет. в <code> нет возможности менять цвет текста.
pwl
Цитата(ibiza11 @ Apr 1 2011, 19:12) *
НО только я добавляю в цикл функцию delay, все кардинально меняется!!! blink.gif

Видимо, как и любую другую. Ибо:

What registers are used by the C compiler?
Data types:
char is 8 bits, int is 16 bits, long is 32 bits, long long is 64 bits, float and double are 32 bits (this is the only supported floating point format), pointers are 16 bits (function pointers are word addresses, to allow addressing up to 128K program memory space). There is a -mint8 option (see Options for the C compiler avr-gcc) to make int 8 bits, but that is not supported by avr-libc and violates C standards (int must be at least 16 bits). It may be removed in a future release.
Call-used registers (r18-r27, r30-r31):
May be allocated by gcc for local data. You may use them freely in assembler subroutines. Calling C subroutines can clobber any of them - the caller is responsible for saving and restoring.
Call-saved registers (r2-r17, r28-r29):
May be allocated by gcc for local data. Calling C subroutines leaves them unchanged. Assembler subroutines are responsible for saving and restoring these registers, if changed. r29:r28 (Y pointer) is used as a frame pointer (points to local data on stack) if necessary. The requirement for the callee to save/preserve the contents of these registers even applies in situations where the compiler assigns them for argument passing.
Fixed registers (r0, r1):
Never allocated by gcc for local data, but often used for fixed purposes:

это про gcc, но вроде у них похожие abi.
можно попробовать объявить delay как static или inline. может поможет.
ibiza11
Спасибо за ответ! sm.gif единственный из всех, кто просмотрел) Теряюсь в догадках как
внешнюю функцию объявить static или inline, только если в исходном файле, где находится тело функции, но ведь тогда она везде будет инлайновая/статическая.
В общем не очень помогло sad.gif
Да и довольно странно, что функция, после выхода из нее оставляет свои переменные в регистре Z. Даже если она и оставляет какие то статические переменные в памяти, то уж явно не в регистре, а запишет в ОЗУ. Думаю дело не в этом.
В то же время не знаю в чем дело.... Помогите, кто чем может... или никто никогда об этом не задумывался?
Блин, хоть на ассемблере пиши... wacko.gif
pwl
Цитата(ibiza11 @ Apr 2 2011, 03:04) *
Да и довольно странно, что функция, после выхода из нее оставляет свои переменные в регистре Z. Даже если она и оставляет какие то статические переменные в памяти, то уж явно не в регистре, а запишет в ОЗУ. Думаю дело не в этом.

нет. не в этом. а в том, что регистр Z (r30:r31), не обязан быть сохранен вызываемой функцией (входит в Call-used registers). поэтому не имея дополнительных сведений о функции, компилятор не имеет права предпологать что она не испортит значение регистра. а значит должен будет его загрузить заново после каждого вызова. вот если-бы функция была локальной (static), то он мог-бы попытаться применить inter-procedural optimization. а для внешней функции он обязан предпологать худшее.

Цитата(ibiza11)
Блин, хоть на ассемблере пиши...

ну дык. time critical части и положено на нем писать..
ibiza11
Так ведь указатель инициализируется только после функции, пусть функция делает что хочет со своими регистрами, потом то функция не вызывается....
И почему заново инициализация не происходит именно в Z после каждого вызова?
pwl
Цитата(ibiza11 @ Apr 2 2011, 13:33) *
Так ведь указатель инициализируется только после функции, пусть функция делает что хочет со своими регистрами, потом то функция не вызывается....
И почему заново инициализация не происходит именно в Z после каждого вызова?
пардон. не углядел. положился на фразу:

НО только я добавляю в цикл функцию delay, все кардинально меняется!!!

ну чо. глюкавый оптимизтор у IAR. больше сказать нечего.
ibiza11
не знаю как бороться с этим, придется пока смириться. но вопрос остается открытым, так что если еще кому-нибудь есть что-нибудь сказать по теме, милости прошу)
SasaVitebsk
Во-первых необязательно глобальные переменные объединять в структуру. Компилятор и так в большинстве случаев будет использовать LDD/STD.
Во-вторых работа с часами, ну никак не тянет на критически по времени важный участок программы. Притянуто за уши.
В-третьих. По какой-то причине компилятор посчитал, что эффективней работать именно так а не иначе. Причину такого решения трудно понять по нескольким строчкам. Я не помню чтобы компилятор у меня так поступал. Что-то повлияло, но вряд-ли простое применение п/п. У меня десятки п/п и при этом компилятор в большинстве случаев применяет LDD.
В-четвёртых. Да действительно можно повлиять на результат компиляции программы. В некоторых случаях даже введение промежуточной локальной переменной приводит к изменению компиляции. Применение указателей тоже порой ведёт к оверхеду а иногда наоборот. (Я имею ввиду что применение указателей либо массивов с индексами, в разных случаях дают разные эффекты.) В общем случае хорошее знание стуктуры процессора и его системы комманд, часто позволяют оценивать предварительно как именно надо писать программу.
Но ... самое главное ... не стоит на этом зацикливаться. Надо понимать что таким образом мы привязываем программу к определённой архитектуре. По-моему, лучше этого не делать. Лучше оценить программу на критическую по времени часть и некритическую. Минимизировать критическую. Обособить её. Оценить потребность в вылизывании её и только после этого приступать к её вылизыванию. А не делать огульные мероприятия. Основной критерий красоты программы - есть её ЯСНОСТЬ. пусть и в ущерб эффективности.

ibiza11
Спасибо за ответ.
Отвечать буду тоже по пунктам (если есть что добавить или поправить)
1) Странно, но лично у меня IAR чаще использует LDS/STS (с оптимизацией по скорости выполнения), LD/ST (без оптимизации и глобальной структуры), а не LDD/STD
2) Программа была из Appnote AVR035, просто в качестве примера, на самом деле из-за часов я не начинал бы подобной дискуссии
3) Повлиял именно ввод вызова функции перед основным циклом. Листинг приведен полностью, можете попробовать у себя скомпилить, посмотреть что выдаст? И может сюда отпишете, если будет что-то отличаться?
4) Был приведен пример именно из Аппноута от производителя микроконтроллеров AVR. Именно они рекомендовали так структурировать переменные исходя из своей архитектуры.
Все-таки порой эффективность стоит раньше в списке требований, нежели ясность.
=GM=
2ibiza11
Как-то некрасиво себя IAR ведёт. В структуре вы описали sec как int sec, а IAR работает с ней, как с байтовой переменной:
// 12 if(++time->sec==60)
LDD R16, Z+2
INC R16
STD Z+2, R16

Что не есть гут. WINAVR более корректен, раз sec двухбайтовая переменная, то он так и работает с ней.
if(++time->sec==60)
be: 80 91 93 01 lds r24, 0x0193
c2: 90 91 94 01 lds r25, 0x0194
c6: 01 96 adiw r24, 0x01 ; 1
c8: 90 93 94 01 sts 0x0194, r25
cc: 80 93 93 01 sts 0x0193, r24
d0: cc 97 sbiw r24, 0x3c ; 60
d2: 49 f4 brne .+18 ; 0xe6 <main+0x5c>
ibiza11
to =GM=, спасибо) blush.gif возможно, что я пробовал менять типы данных, чтобы посмотреть как ведет себя при этом IAR, при этом возможно, что я дизассемблер простого варианта скопипастил, а код уже нового варианта.
В вашем примере от WinAVR применена какая-нибудь оптимизация или это дебаг вариант? WinAVR также не использовал косвенную адресацию, а именно прямую (LDS/STS), как и IAR при оптимизации кода по скорости выполнения.
=GM=
Ну так, приведите соответствующий код. Оптимизация в WinAVR была максимальная -Os.
ibiza11
Не понимаю, зачем Вам это, с таким упреком сказали "выложите соответствующий код". Тип переменной не повлияет на обращение с указателем на структуру.
Вот не поленился: код с закоментированной функцией delay1
Цитата
typedef struct
{
_int hour;
_char min;
_int sec;
}t;
t global;
void delay1(unsigned int);//прототип функции, тело функции ниже

__C_task void main1(void)
{
_t *time=&global;
_//delay1(107);
_if(++time->sec==60)
_{
___time->sec=90;
___time->min=70;
_}
}
Ассемблерный листинг:
Цитата
// 13 t *time=&global;
........LDI R16, LOW(global)
........LDI R17, (global) >> 8
........MOVW R31:R30, R17:R16
// 14 //delay1(107);
// 15 if(++time->sec==60)

........LDD R24, Z+3
........LDD R25, Z+4
........ADIW R25:R24, 1
........STD Z+3, R24
........STD Z+4, R25
........CPI R24, 60
........LDI R16, 0
........CPC R25, R16
........BRNE ??main1_0
// 16 {
// 17 time->sec=90;

........LDI R16, 90
........LDI R17, 0
........STD Z+3, R16
........STD Z+4, R17
// 18 time->min=70;
........LDI R16, 70
........STD Z+2, R16
// 19 }
// 20 }



Ассемблерный листинг с функцией delay1:
Цитата
// 13 t *time=&global;
........LDI R16, LOW(global)
........LDI R17, (global) >> 8
........MOVW R27:R26, R17:R16
// 14 delay1(107);
........LDI R16, 107
........LDI R17, 0
........RCALL delay1
// 15 if(++time->sec==60)
........ADIW R27:R26, 3
........LD R24, X+
........LD R25, X
........SBIW R27:R26, 4
........ADIW R25:R24, 1
........ADIW R27:R26, 3
........ST X+, R24
........ST X, R25
........SBIW R27:R26, 4
........CPI R24, 60
........LDI R16, 0
........CPC R25, R16
........BRNE ??main1_0
// 16 {
// 17 time->sec=90;

........LDI R16, 90
........LDI R17, 0
........ADIW R27:R26, 3
........ST X+, R16
........ST X, R17
........SBIW R27:R26, 4
// 18 time->min=70;
........LDI R16, 70
........ADIW R27:R26, 2
........ST X, R16
........SBIW R27:R26, 2
// 19 }
// 20 }

как видите, симптомы те же)
=GM=
Цитата(ibiza11 @ Apr 5 2011, 05:56) *
Не понимаю, зачем Вам это, с таким упреком сказали "выложите соответствующий код"

Да ну, какие упрёки. Если бы вы не сказали "возможно, что я дизассемблер простого варианта скопипастил, а код уже нового варианта", а просто проверили бы коды на соответствие и сообщили бы, то и моего поста не было бы. А так, я уж было подумал, что компилятор иар не совсем адекватный, решил посмотреть, что там унутре...

Тем не менее, нет худа без добра. После оптимизации на данном фрагменте winavr дал 7 слов кода, а iar - 12 слов, несмотря на использование z-регистра. Конечно, это может ничего не значить, может быть в другом месте iar вырвется вперёд, но может и стоит задуматься. Тут соглашусь с СашейВитебским, не надо выкручивать руки си-компилятору и добиваться от него сиюминутной оптимизации, которая однако может сделать вашу программу зависимой не только от компилятора, но даже от его версии. Для себя я решил так, критичные куски выделяю в ассемблерную подпрограмму, остальное на си. Даже представить себе не можете, как упростилась жизнь эмбеддера :-) А раньше я пыхтел, вставляя инлайновые куски, кто только их придумал..

Ещё подумал, как горю помочь. Попробуйте переставить операторы

_t *time=&global;
_delay1(107);

вот так

_delay1(107);
_t *time=&global;

Компиляторы время от времени бывают туповаты, глядишь и сработает.
ibiza11
Простите за недопониманиеsm.gif Не хотел обидеть sm.gif
Я тоже думаю писать все-таки на Ассемблере. Спасибо за ответ) IAR не туповат, это я не уследил просто. Изменение порядка объявления указателя и вызова функции ничего не далоsm.gif В общем думаю вопрос исчерпан, надо смириться с величиной Си кода в угоду его удобности.
Цитата
После оптимизации на данном фрагменте winavr дал 7 слов кода, а iar - 12 слов, несмотря на использование z-регистра.
здесь не соглашусь. IAR получил 9 слов кода при использовании Z-регистра, а WinAVR получил 11 слов, поскольку инструкции LDS/STS занимают по два слова.
IAR:
Цитата
// 15 if(++time->sec==60)
LDD R24, Z+3
LDD R25, Z+4
ADIW R25:R24, 1
STD Z+3, R24
STD Z+4, R25
CPI R24, 60
LDI R16, 0
CPC R25, R16
BRNE ??main1_0

WinAVR:
Цитата
if(++time->sec==60)
be: 80 91 93 01 lds r24, 0x0193
c2: 90 91 94 01 lds r25, 0x0194
c6: 01 96 adiw r24, 0x01 ; 1
c8: 90 93 94 01 sts 0x0194, r25
cc: 80 93 93 01 sts 0x0193, r24
d0: cc 97 sbiw r24, 0x3c ; 60
d2: 49 f4 brne .+18 ; 0xe6 <main+0x5c>
SasaVitebsk
Код
     20            delay(107);
   \   00000002   E60B               LDI     R16, 107
   \   00000004   E010               LDI     R17, 0
   \   00000006   8308               ST      Y, R16
   \   00000008   8319               STD     Y+1, R17
   \                     ??main_0:
   \   0000000A   8108               LD      R16, Y
   \   0000000C   8119               LDD     R17, Y+1
   \   0000000E   2B01               OR      R16, R17
   \   00000010   F7E1               BRNE    ??main_0
   \   00000012   8108               LD      R16, Y
   \   00000014   8119               LDD     R17, Y+1
   \   00000016   5001               SUBI    R16, 1
   \   00000018   4010               SBCI    R17, 0
   \   0000001A   8308               ST      Y, R16
   \   0000001C   8319               STD     Y+1, R17
     21            t *time=&global;
     22            if(++time->sec==60)
   \   0000001E   ....               LDI     R30, LOW(global)
   \   00000020   ....               LDI     R31, (global) >> 8
   \   00000022   8103               LDD     R16, Z+3
   \   00000024   8114               LDD     R17, Z+4
   \   00000026   5F0F               SUBI    R16, 255
   \   00000028   4F1F               SBCI    R17, 255
   \   0000002A   8303               STD     Z+3, R16
   \   0000002C   8314               STD     Z+4, R17
   \   0000002E   330C               CPI     R16, 60
   \   00000030   E020               LDI     R18, 0
   \   00000032   4010               SBCI    R17, 0
   \   00000034   F429               BRNE    ??main_1
     23            {
     24              time->sec=90;
   \   00000036   E50A               LDI     R16, 90
   \   00000038   8303               STD     Z+3, R16
   \   0000003A   8324               STD     Z+4, R18
     25              time->min=70;
   \   0000003C   E406               LDI     R16, 70
   \   0000003E   8302               STD     Z+2, R16
     26            }
     27          }

От замеса зависит. ))
ibiza11
SasaVitebsk, странно, у Вас компилятор не вызвал функцию delay(), она у Вас инлайновая? При этом вполне исправно использует Z-регистр. Какой у Вас компилятор?
=GM=
Цитата(ibiza11 @ Apr 5 2011, 11:42) *
..поскольку инструкции LDS/STS занимают по два слова

Есть модификация LDS/STS, занимает одно слово. Зависит от того, где в памяти размещены переменные.
ibiza11
Цитата(=GM= @ Apr 5 2011, 17:45) *
Есть модификация LDS/STS, занимает одно слово. Зависит от того, где в памяти размещены переменные.
что за модификация? не слышал об этом ничего... можно подробнее?
=GM=
Документ doc0856i (тыц), с.96. Я не говорил, что иар туповат, я сказал "комиляторЫ иногда туповаты" (или почти всегда, в настоящее время)

Вот CVAVR немного притупил
Цитата(SasaVitebsk @ Apr 5 2011, 12:20) *
Код
delay(107);
       LDI        R16,107
       LDI        R17,0
       ST         Y,R16
       STD        Y+1,R17
main0: LD         R16,Y
       LDD        R17,Y+1
       OR         R16,R17
       BRNE       main0
       LD         R16,Y
       LDD        R17,Y+1
       SUBI       R16,1
       SBCI       R17,0
       ST         Y,R16
       STD        Y+1,R17

WINAVR делает задержку более элегантно
Код
delay(107);
        ldi       r24,0x6B     ; 107
        ldi       r25,0x00     ; 0
        rjmp      L2        ; 0xee <main+0x3c>
L1:     sbiw      r24,0x01     ; ticks--
L2:     sbiw      r24,0x00     ; while(ticks)
        brne      L1        ;
ibiza11
Спасибо sm.gif всегда думал, что я знаю этот документ))) оказалось я пропустил важный момент. получается если переменная в памяти находится по адресу от 0х40 до 0хBF и используется регистр от 16 до 31, то используется однословная команда LDS sm.gif это стоит учесть при написании ассемблерных модулейsm.gif
=GM=
Если в памяти, то от 0х60
SasaVitebsk
Цитата(ibiza11 @ Apr 5 2011, 17:38) *
SasaVitebsk, странно, у Вас компилятор не вызвал функцию delay(), она у Вас инлайновая? При этом вполне исправно использует Z-регистр. Какой у Вас компилятор?

Прошу прощения. Просто не глянул. Кинул функцию в main.c файл. Вот она и заинклудилась. )) Не прочитал, что это для вас принципиально.
Исправил. Показываю
Код
   \                                 In  segment CODE, align 2, keep-with-next
     13          __C_task void main(void)
   \                     main:
     14          {
     15            delay(107);
   \   00000000   E60B               LDI     R16, 107
   \   00000002   E010               LDI     R17, 0
   \   00000004   ....               RCALL   delay
     16            t *time=&global;
     17            if(++time->sec==60)
   \   00000006   ....               LDI     R30, LOW(global)
   \   00000008   ....               LDI     R31, (global) >> 8
   \   0000000A   8103               LDD     R16, Z+3
   \   0000000C   8114               LDD     R17, Z+4
   \   0000000E   5F0F               SUBI    R16, 255
   \   00000010   4F1F               SBCI    R17, 255
   \   00000012   8303               STD     Z+3, R16
   \   00000014   8314               STD     Z+4, R17
   \   00000016   330C               CPI     R16, 60
   \   00000018   E020               LDI     R18, 0
   \   0000001A   4010               SBCI    R17, 0
   \   0000001C   F429               BRNE    ??main_0
     18            {
     19              time->sec=90;
   \   0000001E   E50A               LDI     R16, 90
   \   00000020   8303               STD     Z+3, R16
   \   00000022   8324               STD     Z+4, R18
     20              time->min=70;
   \   00000024   E406               LDI     R16, 70
   \   00000026   8302               STD     Z+2, R16
     21            }
     22          }
   \                     ??main_0:
   \   00000028   9508               RET

Компилятор 5.11
Задержка
Код
      1          void delay(volatile unsigned int ticks)
   \                     delay:
      2          {
   \   00000000   9722               SBIW    R29:R28, 2
   \   00000002   C004               RJMP    ??delay_0
      3            while(ticks) ticks--;
   \                     ??delay_1:
   \   00000004   8108               LD      R16, Y
   \   00000006   8119               LDD     R17, Y+1
   \   00000008   5001               SUBI    R16, 1
   \   0000000A   4010               SBCI    R17, 0
   \                     ??delay_0:
   \   0000000C   8308               ST      Y, R16
   \   0000000E   8319               STD     Y+1, R17
   \   00000010   8108               LD      R16, Y
   \   00000012   8119               LDD     R17, Y+1
   \   00000014   2B01               OR      R16, R17
   \   00000016   F7B1               BRNE    ??delay_1
      4          }
   \   00000018   9622               ADIW    R29:R28, 2
   \   0000001A   9508               RET
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.