Главная > AVR > AVR-GCC: передаем аргументы в main() и используем ее возвращаемое значение

AVR-GCC: передаем аргументы в main() и используем ее возвращаемое значение

Любому здравомыслящему человеку ясно, что использовать при написании прошивки под контроллер без ОС запись main() с параметрами и возвращаемым значением совершенно бессмысленно — операционной системы нет, параметрам взяться неоткуда, и возвращать результат исполнения тоже некуда. Однако, если никак, но очень хочется, то можно. Как — сейчас покажу.

Итак, для начала немного философии — откуда вообще можно взять аргументы для main() в прошивке? При наличии ОС все ясно — это разобранная командная строка. У нас же командной строки нет. Единственное, что кажется логичным — добывать их извне, из портов. Ну, например, подключить к порту DIP-переключатели и передавать их состояние посредством argv[]. Или принимать данные через UART. В принципе, можно сохранить аргументы во FLASH, но это как-то неинтересно.

Конечно, проверять DIP-переключатели можно и в теле main(), но мы же не ищем легких путей, правда?🙂

Теперь собственно о том, как передать параметры в main(), если, по идее, в отсутствие ОС управление сразу передается именно туда. Дело в том, что что в реальности все не совсем так — до main() выполняется еще достаточно служебного кода — я досконально разбирал его в одной из прошлых статей. Более того, после main() линкер тоже всегда ставит заглушку. И, оказывается, существует способ вежливо попросить его включить в служебные секции произвольный код.

Собственно сами параметры передаются ровно так же, как и в любую другую функцию.

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

Упомянутые части кода писать будем на асме в отдельном файле, так как здесь требуется активная работа с регистрами общего назначения, что на С сделать трудно, а формат вставок в AVR-GCC уж больно заморочен. Но, в принципе, указывать размещение кода в служебных секциях можно и на С.

Чтобы линкер поместил код в нужную секцию, оный надо предварить такой директивой:

.section <секция>,"ax",@progbits

Нас интересуют секции .init8 (код ровно перед main() ) и .fini1 (сразу после main() ).

Сами параметры передаются в регистрах. Конкретно для нашего случая в R25:R24 должен лежать argc, в R23:R22 — указатель на argv[], т.е., указатель на начало массива указателей на байт.

Я решил остановиться на двух параметрах: первый — значение порта B, второй — порта D.

*(argv[0]) <- PINB
*(argv[1]) <- PIND

Таким образом, чтобы осуществить задуманное, надо сделать следующее:

1. Завести переменные в памяти для значений PINB и PIND и заполнить их соответствующими значениями, считанными из портов (разумеется, настроив порты).

2. Завести в памяти массив под argv[], и поместить в него указатели на вышеупомянутые переменные.

3. Положить адрес упомянутого массива в R23:R22.

4. Положить в R25:R24 значение argv, количества аргументов. Для нашего случая два.

5. Попросить линкер положить все это в область памяти перед вызовом main().

Итак, заводим переменные:

.section .bss

main_argv: .space 4

param0: .space 1

param1: .space 1

И далее все как написано:

.section .init8,"ax",@progbits

  ;Setting up argument ports as inputs

  CLR R25
  OUT ARGV0_DDR,R25
  OUT ARGV1_DDR,R25

  ;Setting up argv pointer

  LDI R23,hi8(main_argv)
  LDI R22,lo8(main_argv)

  ;Copying values from argument ports to memory

  LDI ZL,lo8(param0)
  LDI ZH,hi8(param0)
  LDS R24,ARGV0_PIN
  ST Z,R24

  LDI ZL,lo8(param1)
  LDI ZH,hi8(param1)
  LDS R24,ARGV1_PIN
  ST Z,R24

  ;Now placing parameter addresses to argv[]
  ;just as required by standard🙂

  MOVW R30,R22

  LDI R24,lo8(param0)
  ST Z+,R24
  LDI R24,hi8(param0)
  ST Z+,R24

  LDI R24,lo8(param1)
  ST Z+,R24
  LDI R24,hi8(param1)
  ST Z,R24

  LDI R24,CONST_ARGC

  ;Proceed to main()...

С отловом возвращаемого значения все еще проще — я решил выводить его в порт C (аналогичным образом просим линкер уложить этот код после main()) :

.section .fini1,"ax",@progbits

  ;Copying main() return value (low byte) into physical port

  SER R25
  STS RET_DDR,R25
  STS RET_PORT,R24

  ;Proceed to infinite loop...

Эксперименты проводились в Протеусе на модели ATmega8515:

К порту С подключены два протеусовских примитива семисегментных дисплеев, принимающих данные в BCD, программа на С написана с учетом этого.

Основная программа работает по следующему алгоритму: сравнивает значения аргументов, после чего выводит на дисплеи последовательно числа от меньшего до большего; в конце показывает буквы (шестнадцатеричное число, на самом деле) AE — Algorithm End. Если числа равны, то выводится EE. Это сделано, чтобы показать работу return.

Исходинк главной программы:

#include <avr/io.h>
#include <util/delay.h>

#define num_to_BCD(NUM)        (((NUM) % 10) | (((NUM) / 10) << 4))

int i,min,max;

int main(int argc, char* argv[])
{
  DDRC=0xFF;

  if ((*(argv[0]))>(*(argv[1])))
  {
    min=(*(argv[1]));
    max=(*(argv[0]));
  }

  if ((*(argv[1]))>(*(argv[0])))
  {
    min=(*(argv[0]));
    max=(*(argv[1]));
  }

  if ((*(argv[0]))==(*(argv[1])))
  {
    return 0xEE;
  }

  for (i=min; i<=max; i++)
  {
    PORTC=num_to_BCD(i);
    _delay_ms(1000);
  }

  return 0xAE;
}

Во, вот так. В архиве есть исходники и модель в Протеусе для желающих узреть все своими глазами.

Зачем может понадобиться такая экзотика? Не знаю. Но работает.

Рубрики:AVR
  1. Комментариев нет.
  1. No trackbacks yet.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s