IAR AVR. Си + Ассемблер

01/12/2009 - 22:57

Итак, скачиваем проект generator_IAR.rar, распаковываем его и открываем в IARe.
Создаем новый файл File > New > File.
Называем его encoder.asm и сохраняем в папке проекта.
Подключаем этот файл к проекту Project > Add Files..
Сишный файл encoder.c удаляем из проекта Project >Remove
Сохраняем проект.

Для работы с энкодером у нас три функции:

void ENC_InitEncoder(void)
void ENC_PollEncoder(void)
unsigned char
ENC_GetStateEncoder(void)

Я добавил еще одну - для загрузки значений в буфер. Это только для того, чтобы показать как из сишной программы передаются аргументы в ассемблерную подпрограмму/функцию.

void ENC_SetStateEncoder(unsigned char var)

Сейчас мы напишем ассемблерные аналоги этих функций. Сразу вас предупрежу, синтаксис IAR`овского ассемблера несколько отличается от AVR`овского. Взять хотя бы такую простую вещь — в IAR`e ассемблерные команды должны писаться с отступом. Если этого не сделать, компилятор выдаст ошибку. 


Структура ассемблерного модуля

   Ассемблерные подпрограммы должны находиться внутри модуля. Модуль имеет следующую структуру:

MODULE имя_модуля
   //подключение заголовочных файлов

   RSEG DATA //сегмент данных
   //здесь можно объявить переменные

   RSEG CODE //сегмент кода
   //здесь будут ассемблерные подпрограммы

ENDMOD
END


Жирным шрифтом выделены директивы.
END – директива, обозначающая конца файла. Все что находится после нее, компилятор игнорирует.

Назовем наш модуль encoder и напишем его каркас.

MODULE encoder
  #include
  #include "encoder.h"

  RSEG NEAR_Z

  RSEG CODE
   
//инициализация порта
ENC_InitEncoder:
      ret

//опрос энкодера
ENC_PollEncoder:
     ret

//возвращает значение буфера  
ENC_GetStateEncoder:
    ret

//загружает значение в буфер
ENC_SetStateEncoder:
    ret

ENDMOD
END


ioavr.h мы подключили, потому что в программе используются имена регистров AVRа.
encoder.h – это заголовочный файл от сишного исходника. Его нужно немного изменить - удалить подключение ioavr.h и прототипы всех функций.

NEAR_Z — это название области памяти данных (ОЗУ) используемой для статических и глобальных переменных инициализируемых нулем.

Дальше вроде все понятно - 4 пустых подпрограммы.

В исходнике энкодера используются 2 глобальных переменных. В ассемблерном файле они нам тоже нужны. Объявим их:

  RSEG NEAR_Z

stateEnc  DS
bufEnc    DS 1   


stateEnc – название переменной, DS – директива для резервирования оперативной памяти под байтовую переменную, 1 — количество резервируемых байтов.
Заметьте, никаких адресов ОЗУ мы не указываем - за нас это сделает компилятор.

Для того чтобы наши подпрограммы были доступны в других модулях нужно добавить еще одно объявление:

MODULE encoder
  #include
  #include "encoder.h"

  PUBLIC ENC_InitEncoder
  PUBLIC ENC_PollEncoder
  PUBLIC ENC_GetStateEncoder
  PUBLIC ENC_SetStateEncoder
...

Теперь нужно наполнить ассемблерные подпрограммы/функции содержимым.

Функция инициализации

ENC_InitEncoder:
  cbi DDR_Enc, Pin1_Enc
  cbi DDR_Enc, Pin2_Enc
  sbi PORT_Enc, Pin1_Enc
  sbi PORT_Enc, Pin2_Enc
  ret

DDR_Enc,  PORT_Enc … это макроопределения из файла encoder.h

Функция записи в буфер

   IAR может передавать аргументы в функцию двумя способами - старым, который использовался в IAR`e версии 1.х и новым, который используется в старших версиях. При написании смешанных проектов  рекомендуется использовать старый способ.  Для этого перед  функциями пишется ключевое слово __version_1. Ниже об этом еще пойдет речь.
   Перед вызовом функции, значения аргументов копируются в регистры R16 - R23. В описании на компилер приводится такая табличка:
регистры используемые компилятором IAR для передачи аргументов в функцию
Где b – 8-ми разрядные переменные, w – 16-ти разрядные, l – 32 разрядные.
 
Она дает нам информацию, какие регистры используются для различных типов данных. 
Если выделенных регистров для передачи аргументов не хватает, компилер использует стек, но я с этой темой пока не разбирался
 
Нашей функции записи при вызове передается байтовая переменная, значит компилятор (согласно таблице) поместит ее значение в R16. Нам остается только забрать его оттуда и записать в буфер.

ENC_SetStateEncoder:
  sts bufEnc, r16
  ret

Функция чтения буфера

Для возврата значений компилятор IAR использует регистры R16 – R19. В табличке ниже расписаны варианты для разных типов данных.
регистры используемые комиплятором IAR для возврата значений
Функция чтения должна считать значение буфера, поместить его в R16 (согласно таблице), очистить буфер и выйти.

ENC_GetStateEncoder:
  lds r16, bufEnc
  clr r17
  sts bufEnc, r17
  ret

Функция опроса энкодера

   Эта функция ничего не возвращает и не принимает, но зато делает самую главную работу — опрашивает энкодер. Для выполнения различных операций "внутри" функции IAR использует регистры  R0–R3, R16–R23 и R30–R31 (так называемые scratch registers).

ENC_PollEncoder:
  clr r16                           //очищаем R16 потому что он нам понадобится ниже
  sbic PIN_Enc, Pin1_Enc   //записываем состояние выводов энкодера в r16
  sbr r16, 1                     
  sbic PIN_Enc, Pin2_Enc

  sbr r16, (1<<1)            
  lds r17, stateEnc            //считываем переменную, в которой хранятся старые  
  andi r17, b'00000011     //состояния энкодера и выделяем последнее
  cp r16, r17                    //сравниваем с текущим  
  breq Exit                       //если равны, значит ничего не изм. — выходим
  lds r17, stateEnc            //если не равны, считываем   stateEnc,
  lsl r17                           //сдвигам влево на 2 разряда
  lsl r17                           //чтобы добавить текущее состояние
  or r16, r17                    //добавляем с помощью операции ИЛИ
  sts stateEnc, r16            //сохраняем в ОЗУ
  cpi r16, b'11100001       //сравниваем получившуюся последовательность
  breq LeftSpin                 //с константами, переходим если равно
  cpi r16, b'11010010
  breq RightSpin
  ret

LeftSpin:
  ldi r16, LEFT_SPIN     //если вращение влево, записываем в r16 константу    
  rjmp LoadToRam        // LEFT_SPIN и прыгаем на метку

RightSpin:
  ldi r16, RIGHT_SPIN   //если вправо, записываем в r16 RIGHT_SPIN
 
LoadToRam:
  sts bufEnc, r16          //сохраняем r16 в буфер
 
Exit:
  ret

Все. Модуль написан - IAR AVR. Ассемблерный модуль.
Последний штрих - нужно сообщить компилятору, что используемые нами функции находятся в другом модуле и указать, что вызов функций должен осуществляться старым методом. Для этого используются ключевые слова extern  и __version_1 соответственно. Добавляем перед main`ом.

extern void ENC_InitEncoder(void);
extern void ENC_PollEncoder(void);
extern unsigned char ENC_GetStateEncoder(void);
__version_1 extern void ENC_SetStateEncoder(unsigned char val);

Всего одна функция у нас принимает аргументы, поэтому  __version_1 я добавил только перед ней.
Запускаем компиляцию и сборку - F7....  Грузим прошивку в микроконтроллер и проверяем ее работоспособность... Все работает. Продолжение следует...

Файлы

IAR AVR. Си+Ассемблер

Comments   

# foxit 2009-12-02 12:05
А для winavr что-то подобное есть?
# Pashgan 2009-12-02 16:28
есть, но надо разбираться... :-?
# foxit 2009-12-02 16:31
А какой?
# Guest 2009-12-02 17:36
foxit, смотри user manual на winavr - раздел avr-libc and assembler programs
# Guest 2010-05-09 21:13
Quote:
В исходнике энкодера используются 2 глобальных переменных. В ассемблерном файле они нам тоже нужны. Объявим их:

RSEG NEAR_Z

stateEnc DS 1
bufEnc DS 1
Двоеточий после имён переменных не надо ли?
# Pashgan 2010-05-10 09:03
Не обязательно.
# Guest 2010-09-21 07:56
как правильно определить функцию, чтобы адрес точки входа функции из ASM кода был в таблице векторов прерываний, в данном варианте следующая ошибка компайлера: “Error[Pe147]: declaration is incompatible with "void my_int_func()" (declared at line 31)” ?
# Guest 2010-09-21 08:17
Верхние строчки с вложениями которые я написал, прошу удалить :), вот прим.ер
еxtеrн vоid мy_iнt_fуnс (вoiд), в ASM, прописал как: РUВLIС мy_iнt_fуnс

рragma вectоr=номер
iнtеrruрt воid мy_iнt_fуnc (воiд), здесь комп. показал ошибку
# Pashgan 2010-09-29 20:32
Что то не пойму.. вопрос в том, как описать обработчик прерывания на асме?
# Shiuza 2010-10-13 13:02
Я тогда решил данную проблемму просто через:
ORG TIMER1_COMPB_ve ct
rjmp MY_test_func
….
Спасибо челу под ником BSVi (сайт bsvi_pp_ua) , подсказал в конце концов
:-)
# kotbimigot 2011-10-03 06:11
а для CVAVR подскажите пожалуйста как макроассемблерн ую вставочку сделать
# Andre 2012-09-03 17:22
Никак не получается написать обработчик прерывания на ассемблере.Так чтобы при появлении прерывания сразу вызывалась продпрограмма написанная на асемблере. Не подскажите как это сделать.
# Pashgan 2012-09-03 21:51
Вот пример прерывания на асме.
Code:
NAME TIM0_VECTOR
#include <iom16.h>

COMMON INTVEC
ORG TIMER0_COMP_vect
rjmp Timer0_comp


RSEG CODE
Timer0_comp:

st -Y,R16 ; Push used registers on stack
in R16,SREG ; Read status register
st -Y,R16 ; Push Status register
in R16,PORTB ; Load in value from port B

com R16 ; Invert it

out PORTB,R16 ; Output inverted value to port B
ld R16,Y+ ; Pop status register
out SREG,R16 ; Store status register
ld R16,Y+ ; Pop Register R16

reti

ENDMOD
END


Тут смысл такой.
Определяешь в сегменте таблицы прерываний COMMON INTVEC ассемблерный переход на обработчик прерывания.
Сам обработчик описываешь в сегменте RCEG CODE.
Данный код нужно сохранить в ассемблерном файле и подключить его в IAR`овскому проекту.
# Andre 2012-09-19 04:39
Спасибо за подсказку по написанию обработчика прерывания на ассемблере. Моя ошибка заключалась в том что я ассемблерный модуль оформлял как подпрограмму (текст программы начинался со слова: MODULE)...... А ассемблерный переход я определял директивой ORG.
# Andre 2012-09-23 17:56
Не подскажите как лучше организовать обмен информацией с программой - обработчиком прерывания написанной на ассемблере.
# Pashgan 2012-09-29 12:51
Два варианта: через глобальную переменную или через внутреннюю переменную и функции доступа. Я предпочитаю использовать второй вариант.
Что вроде этого:
Code:
unsigned int timer = 0;
unsigned char state_timer = 0;

void TIM_SetTimer(unsigned int data)
{
unsigned int tmp = SREG;
__disable_interrupt();
state_timer = TIMER_WORK;
timer = data;
SREG = tmp;
}

unsigned char TIM_GetStateTimer(void)
{
if (state_timer == TIMER_EXPIRED) return 1;
return 0;
}

#pragma vector = TIMER0_OVF_vect
__interrupt void Timer0Ovf(void)
{
TCNT0 = 0xe6;

if (state_timer == TIMER_WORK){
if (timer != 0) {
timer--;
}
else{
state_timer = TIMER_EXPIRED;
}
}

BUT_Debrief();
IND_Update();
}



Только переписать это на ассемблере.
В этом примере доступ к переменной timer осуществляется через функции TIM_SetTimer() и TimGetStateTime r().
# STEM 2014-03-25 15:26
Здравствуйте. Подскажите пожалуйста, как разместить и использовать константы в памяти программ. Не то, когда записываю в регистры Z адрес начала блока данных помещенных в памяти программ, записывается совсем не тот адрес.
# Pashgan 2014-03-26 17:23
Понял только первые два предложения. Речь идет про ассемблерный модуль для IARa или просто про ассемблер AVR?
# STEM 2014-03-27 09:18
Речь шла про ассемблерный модуль для IARa.Я уже разобрался, для того чтобы вычитывать данные из флешь памяти, когда в Z пишешь адрес расположения данных, не нужно его умножать на два.
# Wolf 2016-08-12 08:05
Здравствуйте, а для C++, есть пример?

У вас недостаточно прав для комментирования.