|
Самозагрузчик |
|
|
|
Apr 7 2016, 06:51
|

Профессионал
    
Группа: Модераторы
Сообщений: 1 951
Регистрация: 27-08-04
Из: Санкт-Петербург
Пользователь №: 555

|
Цитата(inventor @ Apr 7 2016, 09:34)  после того, как бинарник записан по адресу 0x08001000 как мне LOADER - ом передать ему управление? Самый лучший способ IMHO - сделать хардварный reset с помощью watchdog! лоадер уже не обнаружит прошивку и передаст управление... Но есть еще нюанс - для того что бы лоадеру обнаружить файл на SD, необходимо частично проинитить периферию..., а передавать управление основной программе после этого не хорошо! Я ставлю метку в озу по определенному адресу (типа 0x55AAAA55) и если лоадер ее обнаруживает, то стирает и передает управление основной программе (установив адрес таблицы прерываний кончено)
|
|
|
|
|
Apr 7 2016, 07:05
|
Знающий
   
Группа: Свой
Сообщений: 524
Регистрация: 25-12-08
Из: Москва
Пользователь №: 42 748

|
Цитата(KRS @ Apr 7 2016, 10:51)  Самый лучший способ IMHO - сделать хардварный reset с помощью watchdog! лоадер уже не обнаружит прошивку и передаст управление...
Но есть еще нюанс - для того что бы лоадеру обнаружить файл на SD, необходимо частично проинитить периферию..., а передавать управление основной программе после этого не хорошо! Я ставлю метку в озу по определенному адресу (типа 0x55AAAA55) и если лоадер ее обнаруживает, то стирает и передает управление основной программе (установив адрес таблицы прерываний кончено) у меня вот такие вопросы, вектора должны находится по адресу 80000000 как мне сделать так чтобы у прошики эти вектора были переопределены и не накладывались на адреса векторов у лоадера.
|
|
|
|
|
Apr 7 2016, 07:11
|
Знающий
   
Группа: Свой
Сообщений: 771
Регистрация: 16-07-07
Из: Волгодонск
Пользователь №: 29 153

|
Цитата(inventor @ Apr 7 2016, 10:05)  у меня вот такие вопросы, вектора должны находится по адресу 80000000 как мне сделать так чтобы у прошики эти вектора были переопределены и не накладывались на адреса векторов у лоадера. Вся прошивка в целом линкуется с таким скриптом линкера, чтобы осталось пустое место под загрузчик. При этом вектора прошивки будут сдвинуты. Скажем на 0x8001000 - это зависит от размера загрузчика. Есть такой регистр - VTOR, который задает положение таблицы векторов Соответственно в загрузчике перед стартом прошивки его надо скорректировать, выставить стек и перейти по адресу сброса Код #define PO_START 0x8001000 SCB->VTOR = PO_START; __set_MSP(*(uint32_t*)PO_START); __DMB();
typedef void (* pf)(); (*((pf*)(PO_START+4)))();
|
|
|
|
|
Apr 7 2016, 07:46
|
Знающий
   
Группа: Свой
Сообщений: 524
Регистрация: 25-12-08
Из: Москва
Пользователь №: 42 748

|
то есть я делаю в файле линкера для прошивки исправления: define symbol __ICFEDIT_intvec_start__ = 0x08001000; define symbol __ICFEDIT_region_ROM_start__ = 0x08001000; про компиляции программы моя программа и векторы должны начинаться с адреса 0x08001000 теперь в лоадере я должен передать управление по новому адресу вектора сброса: 0x08001000 правильно я понял? вернее вектор старта + 4, там вроде в начале адрес стека идет
Сообщение отредактировал IgorKossak - Apr 7 2016, 18:33
Причина редактирования: бездумное цитирование
|
|
|
|
|
Apr 7 2016, 08:07
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(inventor @ Apr 7 2016, 12:34)  саму программу Loader я хочу разместить по адресу Flash 0x08000000 а рабочая прошивка, предположим, будет работать с адреса 0x08001000 после того, как бинарник записан по адресу 0x08001000 как мне LOADER - ом передать ему управление? В 0x1000 байт не впихнёте работу с файловой системой однозначно. Чтобы загрузчик был маленький, нужно работу с ФС вынести в основное ПО. Например: основное ПО обнаружив файл с новой прошивкой, формирует в ОЗУ информацию о цепочке кластеров файла прошивки и ставит флаг наличия. Дальше - перегружается (WDT). Дальше - загрузчик видит флаг, считывает прошивку из цепочки кластеров и шьёт её во флеш программ. Стартует новое ПО. Новое ПО, видя что обновилось, стирает исходный файл прошивки. Всё. Если нужно чтобы операция обновления ПО была устойчива к сбоям питания, то флаг и инфу о цепочку кластеров нужно сохранять в энергонезависимой памяти. Как передать управление из загрузчика в основное ПО тут уже 100500 раз обсуждалось и не ранее чем позавчера очередной раз. Учитесь искать.
|
|
|
|
|
Apr 7 2016, 08:09
|

Профессионал
    
Группа: Модераторы
Сообщений: 1 951
Регистрация: 27-08-04
Из: Санкт-Петербург
Пользователь №: 555

|
Цитата(inventor @ Apr 7 2016, 10:46)  вернее вектор старта + 4, там вроде в начале адрес стека идет да! и стек надо тоже проинитить у меня такой код, но это для LPC и IAR (просто у IAR в r1 = 0x1000 после присваивания VTOR) Код VTOR = (unsigned)0x1000; asm ("ldm r1, {r0,r1}\n" "mov r13, r0\n" "mov r15, r1"); while(1);
|
|
|
|
|
Apr 7 2016, 10:13
|
Знающий
   
Группа: Свой
Сообщений: 524
Регистрация: 25-12-08
Из: Москва
Пользователь №: 42 748

|
Цитата(jcxz @ Apr 7 2016, 11:07)  В 0x1000 байт не впихнёте работу с файловой системой однозначно. а 4 k не влезет но в 22 киболайт вполне влезает. с рюшками, uart для отладки и светодиодами
|
|
|
|
|
Apr 7 2016, 13:54
|
Знающий
   
Группа: Участник
Сообщений: 643
Регистрация: 29-05-09
Из: Германия
Пользователь №: 49 725

|
Цитата(inventor @ Apr 7 2016, 07:34)  имеется какой то прибор с SD картой на проце STM32 я хочу сделать для пользователя возможность самому менять прошивку. У меня есть такой загрузчик. Ему требуется примерно 12K флэша. Я зарезервировал 16К, то есть, приложение компилируется так, чтобы начинаться с 16К. Файловая система на FatFS, с которой выброшено все, кроме чтения (файл прошивки я не удаляю). Есть варианты для SPI и SDIO доступа к карте. Если при старте карта и файл с определенным именем на ней обнаружены, загрузчик считывает версию образа и сравнивает с текущей версией прошивки. Прошивка для этого содержит соответствующие поля, которые как данные расположены непосредственно за таблицей векторов. Загрузчик, конечно, знает, что и как искать. Если на карте новая версия, производится запись образа во флэш и его запуск. Во всех остальных случаях загрузчик запускает имеющуюся прошивку сразу.
|
|
|
|
|
Apr 7 2016, 15:07
|
Участник

Группа: Участник
Сообщений: 63
Регистрация: 25-11-11
Пользователь №: 68 515

|
Цитата(inventor @ Apr 7 2016, 09:34)  Подскажите как правильно сделать вот что: ......... саму программу Loader я хочу разместить по адресу Flash 0x08000000 а рабочая прошивка, предположим, будет работать с адреса 0x08001000 после того, как бинарник записан по адресу 0x08001000 как мне LOADER - ом передать ему управление? 1) делаете 2 отдельных проекта-загрузчика и основной программы. В каждом проекте задаете свой стартовый адрес (меню keil project->options for target->IROM1),чтобы компилятор генерировал код с данного адреса 2) в код загрузчика добавляете функцию Код __asm void run_main_programm() { mov32 r0,#MY_PROGRAMM_START_ADRESS ldr r1,[r0] mov sp,r1 ldr pc,[r0,#4] } 3) прошили, выход из загрузчика Код VTOR=MY_PROGRAM_START_ADRESS; run_main_programm(); перед присваиванием VTOR запрещаем прерывания всей используемой в загрузчике периферии+еще запрещаем их в контролеере NVIC Ну вот собственно и все...не забудьте закоментировать в функции system_init() в коде основной программы строку типа VTOR=FLASH_BASE_ADDRESS, потому как вы уже ему присвоили свою таблицу в коде загрузчика
|
|
|
|
|
Apr 7 2016, 20:01
|

Профессионал
    
Группа: Свой
Сообщений: 1 333
Регистрация: 27-10-08
Из: Планета Земля
Пользователь №: 41 226

|
Цитата(KnightIgor @ Apr 7 2016, 16:54)  У меня есть такой загрузчик. Ему требуется примерно 12K флэша. Я зарезервировал 16К, то есть, приложение компилируется так, чтобы начинаться с 16К. Файловая система на FatFS, с которой выброшено все, кроме чтения (файл прошивки я не удаляю). Есть варианты для SPI и SDIO доступа к карте. Если при старте карта и файл с определенным именем на ней обнаружены, загрузчик считывает версию образа и сравнивает с текущей версией прошивки. Прошивка для этого содержит соответствующие поля, которые как данные расположены непосредственно за таблицей векторов. Загрузчик, конечно, знает, что и как искать. Если на карте новая версия, производится запись образа во флэш и его запуск. Во всех остальных случаях загрузчик запускает имеющуюся прошивку сразу. Вот бы его на github'е увидеть !
|
|
|
|
|
Apr 7 2016, 21:06
|
практикующий тех. волшебник
    
Группа: Участник
Сообщений: 1 190
Регистрация: 9-09-05
Пользователь №: 8 417

|
Цитата(KnightIgor @ Apr 7 2016, 16:54)  У меня есть такой загрузчик... в своё время сделал немного по другому. прошивка сама себя меняет. есть понятие версия фулл, инкрементальная, модуля. каждый модуль может существовать во флэше несколько экземпляров. загрузчик подымает последнюю устойчивую версию и все модули которые в него входят. собирает статистику, и содержит маленький проверенный код записи во флэш. каналы апдэйтов - куча = микроСД, изернет, модбас, вайфай. благо флэша - как у дурака махорки  как то так (круглый)
Сообщение отредактировал kolobok0 - Apr 7 2016, 21:07
|
|
|
|
|
Apr 8 2016, 05:37
|
Знающий
   
Группа: Свой
Сообщений: 771
Регистрация: 16-07-07
Из: Волгодонск
Пользователь №: 29 153

|
Цитата(kolobok0 @ Apr 8 2016, 00:06)  в своё время сделал немного по другому. прошивка сама себя меняет. есть понятие версия фулл, инкрементальная, модуля. каждый модуль может существовать во флэше несколько экземпляров. загрузчик подымает последнюю устойчивую версию и все модули которые в него входят. собирает статистику, и содержит маленький проверенный код записи во флэш. каналы апдэйтов - куча = микроСД, изернет, модбас, вайфай. благо флэша - как у дурака махорки  Гм, т.е. вы сделали dll на МК. Главный вопрос - зачем  ? И еще кучка вопросов по реализации, интересно  * как тестировали? По идее надо проверять каждую версию с каждым набором версий других модулей * как линковали? Каждый модуль - как отдельный проект? * у вас код запускался из flash? Тогда как разруливались несколько экземпляров модулей (адреса же фиксированные на этапе линковки)? Или вы копировали в ОЗУ? Или не применяли команды, которые работают с фиксированными адресами, только по смещению? * где жила системная библиотека (общая для всех модулей)? * что происходит с глобальными и статическими переменными? У каждого модуля фиксированный регион RAM? Или все хранится в куче? * межмодульное взаимодействие как было организовано? Т.е. как позвать функцию из другого модуля?
|
|
|
|
|
Apr 8 2016, 09:55
|
Частый гость
 
Группа: Участник
Сообщений: 123
Регистрация: 12-06-15
Из: Земля
Пользователь №: 87 133

|
Вот мой скелет бутлоадераю Выделено под него 16К флеш. В любом случае прийдется выделить целое количество секторов, тк стереть часть сектора невозможно. CODE #define APP_ADDRESS (uint32_t)0x08004000
typedef void (*pFunction)(void); pFunction Jump_To_App; uint32_t JumpAddr;
int main(void) { init_hw(); // Hardware init
upgrade_firmware();
if(check_firmware()) { // Jump to application JumpAddr = *(__IO uint32_t*) (APP_ADDRESS + 4); Jump_To_App = (pFunction) JumpAddr; // Initialize application's Stack Pointer __set_MSP(*(__IO uint32_t*) APP_ADDRESS); Jump_To_App(); } else Fault();
return 0; } APP_ADDRESS - адрес, с которого начинается основное приложение. upgrade_firmware() - процедура записи приложения в флеш - ищем на SD, если находим, сверяем версию (чтоб не перезписывать одно и то же), обновляем. check_firmware() - проверяем наличие / валидность приложения. Я, например, считаю CRC, Вы можете изобрести другой метод (напимер ЭЦП). Fault() - говорит сам за себя - приложения нет / повреждено - "моргаем красным светодиодом". Основное приложение пишется как обычно, только адрес заггрузки указывается равный APP_ADDRESS. В Keil это делается в опциях проекта. Project -> Options for target -> Target поле IROM1 = 0x08004000 Кроме этого нужно указать адрес таблицы векторов прерываний равный APP_ADDRESS. Делается это правкой файла system_*.c. Ищем строку #define VECT_TAB_OFFSET 0x0 и заменяем на свой оффсет #define VECT_TAB_OFFSET 0x4000
|
|
|
|
|
Apr 8 2016, 14:07
|
практикующий тех. волшебник
    
Группа: Участник
Сообщений: 1 190
Регистрация: 9-09-05
Пользователь №: 8 417

|
Цитата(Непомнящий Евгений @ Apr 8 2016, 08:37)  ...* как тестировали? ..Каждый модуль - как отдельный проект?...у вас код запускался из flash? ...Или не применяли команды, которые работают с фиксированными адресами, только по смещению? * где жила системная библиотека (общая для всех модулей)? ...Или все хранится в куче? ... как позвать функцию из другого модуля? - стандартный отладчик только на уровне азма конечно же, кроме загрузчика. юзается в основном дебажный вывод и мощная система логгирования при фатальных ошибках. - каждый модуль отдельный проект, работает из флэша. - вся адресация относительная. - все библиотеки = модули(фрииртос, фаты, стандартная либа, менеджэр памяти - их вообщето два, но для модулей - так-же в модуле отдельном) - куча используется - загрузчик делает позднее связывание модулей. т.е. есть понятия импортируемых и экспортируемых функций, необходимых аппаратных прерываний, версии модуля и запрос версии. - функции зовутся как обычно (в тексте это имеет маленький префикс - вот и всё отличие) зачем = повышенная отказоустойчивость. и минимум требований к фазе дистанционной апгрэйта кода. (круглый)
|
|
|
|
|
Apr 12 2016, 12:44
|
Знающий
   
Группа: Свой
Сообщений: 524
Регистрация: 25-12-08
Из: Москва
Пользователь №: 42 748

|
Сделал, но чего то не получается запустить основную программу в чем может быть проблема? утилитой ST Link проверяю - файл записался и лежит правильно в памяти Загрузчик у меня такой: CODE #define FILE_NAME "loader.bin" #define APP_ADDRESS (uint32_t)0x08008000
do { /* Монтируем. Если нет файла - выходим на загрузку */ if ((rc = f_mount(0, &fatfs)) == 0) { PRINTF("INFO: mount OK\r\n"); } else { PRINTF("ERROR: mount fs\n"); break; }
/* Открыть на чтение */ rc = f_open(&fil, FILE_NAME, FA_READ); if (rc == 0) { PRINTF("INFO: Loader file %s open OK\n", FILE_NAME); } else { PRINTF("INFO: Loader file %s doesn't exist. Continue booting", FILE_NAME); break; }
/* стираем секторы 2...4*/ FLASH_Unlock(); delay_ms(50);
if(FLASH_COMPLETE == FLASH_EraseSector(FLASH_Sector_2, VoltageRange_3)) { PRINTF("Erase sector OK\n"); led_toggle(LED3); } if(FLASH_COMPLETE == FLASH_EraseSector(FLASH_Sector_3, VoltageRange_3)) { PRINTF("Erase sector OK\n"); led_toggle(LED3); }
if(FLASH_COMPLETE == FLASH_EraseSector(FLASH_Sector_4, VoltageRange_3)) { PRINTF("Erase sector OK\n"); led_toggle(LED3); }
/* Читаем файл и записываем в секторы flash */ bytes = 0; addr = APP_ADDRESS; do { bw = 0; rc = f_read(&fil, buf, sizeof(buf), &bw); if (rc == 0 && bw > 0) { bytes += bw; for(i = 0; i < bw; i++) { FLASH_ProgramByte(addr, buf[i]); addr++; } PRINTF("%d bytes written\n", bytes); led_toggle(LED4); delay_ms(50); } } while (bw);
rc = f_close(&fil); // Стираем файл /* Если файл существует - удалим его */ // rc = f_unlink(FILE_NAME); } while (0);
/* Disable all interrupts */ RCC->CIR = 0x00000000; /* Делаем старт по адресу */ JumpAddr = *(__IO uint32_t*) (APP_ADDRESS + 4); Jump_To_App = (pFunction) JumpAddr;
/* Initialize application's Stack Pointer */ __set_MSP(*(__IO uint32_t*) APP_ADDRESS); Jump_To_App();
Я вижу отладчиком, что до джампа он доходит - а дальше аут основная прога использует такой скрипт линкера, где установлены адреса: define symbol __ICFEDIT_intvec_start__ = 0x08008000; define symbol __ICFEDIT_region_ROM_start__ = 0x08008000; + сделал коментарий в вызове из ассемблера SystenInit() - так это уже сделано в загрузчике компилю образ проги и получаю бинарник program.binCODE ;Reset_Handler
; LDR R0, =SystemInit ; BLX R0 LDR R0, =__iar_program_start BX R0
В чем может быть еще засада?
|
|
|
|
|
Apr 12 2016, 13:12
|
Знающий
   
Группа: Свой
Сообщений: 524
Регистрация: 25-12-08
Из: Москва
Пользователь №: 42 748

|
Цитата(scifi @ Apr 12 2016, 15:52)  А поподробнее? В окне дизассемблера нашли инструкцию, которая делает не то, что нужно? Посмотрели значения соответствующих регистров? делает branch BLX R8 тока в R8 почему то 0x08009f85
|
|
|
|
|
Apr 12 2016, 14:26
|

Профессионал
    
Группа: Модераторы
Сообщений: 1 951
Регистрация: 27-08-04
Из: Санкт-Петербург
Пользователь №: 555

|
Цитата(inventor @ Apr 12 2016, 15:44)  Код /* Disable all interrupts */ RCC->CIR = 0x00000000; /* Делаем старт по адресу */ JumpAddr = *(__IO uint32_t*) (APP_ADDRESS + 4); Jump_To_App = (pFunction) JumpAddr;
/* Initialize application's Stack Pointer */ __set_MSP(*(__IO uint32_t*) APP_ADDRESS); Jump_To_App(); Так делать некорректно! Во первых не устанавливается новый адрес таблицы векторов ( VTOR ) Во вторых указатель стека будет модифицирован и не будет соответствовать тому что лежит по APP_ADDRESS И если хотите отлаживать приложение без бутлоадера (если это разные проекты) то надо у IAR в макросах установить запись VTOR - тогда можно без проблем отлаживать.
|
|
|
|
|
Apr 12 2016, 14:49
|
Гуру
     
Группа: Свой
Сообщений: 10 713
Регистрация: 11-12-04
Пользователь №: 1 448

|
Цитата(KRS @ Apr 12 2016, 17:26)  Во первых не устанавливается новый адрес таблицы векторов ( VTOR ) По-моему, это все же дело приложения, а не загрузчика. Цитата(KRS @ Apr 12 2016, 17:26)  Во вторых указатель стека будет модифицирован и не будет соответствовать тому что лежит по APP_ADDRESS Где?
|
|
|
|
|
Apr 12 2016, 15:03
|
Гуру
     
Группа: Свой
Сообщений: 3 020
Регистрация: 7-02-07
Пользователь №: 25 136

|
Цитата(aaarrr @ Apr 12 2016, 17:49)  Где? Строго говоря, компилятору разрешается крутить указатель стека в любой момент, причём в любую сторону. Так что я бы сказал, что такой способ установки указателя стека предполагает некое везение. Ассемблерный код здесь идеологически более правильный. Как обычно, агитирую за свой вариант Код static const uint16_t jump2fw[] = { 0xF850, 0xDB04, /* LDR.W SP, [R0], #4 */ 0x6800, /* LDR.W R0, [R0] */ 0x4700, /* BX R0 */ }; ((void (*)(int))(1 + (int)jump2fw))(0x08010000);
|
|
|
|
|
Apr 12 2016, 16:49
|
Гуру
     
Группа: Свой
Сообщений: 10 713
Регистрация: 11-12-04
Пользователь №: 1 448

|
Цитата(KRS @ Apr 12 2016, 18:41)  Нет - загрузчика! Потому что старт приложения происходит по RESET вектору который лежит в таблице прерываний, соотв. VTOR обязан указывать туда... Как минимум спорно. Приложение может и не придерживаться этих правил, т.к. не предназначено для непосредственного исполнения после аппаратного сброса. Цитата(scifi @ Apr 12 2016, 18:03)  Как обычно, агитирую за свой вариант  А я - за свой  Код __set_MSP(*((uint32_t *)IMAGE_START)); (*((void (**)(void))(IMAGE_START + 4)))();
|
|
|
|
|
Apr 13 2016, 07:54
|
Знающий
   
Группа: Участник
Сообщений: 643
Регистрация: 29-05-09
Из: Германия
Пользователь №: 49 725

|
Цитата(inventor @ Apr 13 2016, 07:49)  вот мой загрузчик грузит только то, что есть на внутренней flash как быть с теми функциями и данными, которые должны располагаться на RAM памяти? кто должен их располагать правильно? надо ли так же перераспределить и память RAM, между загрузчиком и основной программой? Ничего специально делать не нужно. Если у Вас основная программа не bare bones (иными словами, написана в рамках среды разработки и с использованием стандартных библиотек среды), то перед переходом в main() втихую вызывается инициализация RAM. Она прочищает/устанавливает необходимые области/переменный RAM. Все, что пользовал загрузчик, будет бесследно "перезатерто". Цитата(aaarrr @ Apr 12 2016, 17:49)  Код __set_MSP(*((uint32_t *)IMAGE_START)); (*((void (**)(void))(IMAGE_START + 4)))(); Вставлю свои 5 копеек: в ассемблерном стартовом файле я дописал: Код VTOR EQU (0xE000E000 + 0x0D08) StartMainApp PROC ; R0 - parameter with the APP vector table address EXPORT StartMainApp [WEAK] LDR R1, =VTOR ; Load VTOR register address STR R0, [R1] ; Store to VTOR to relocate the vector table LDR SP, [R0] ; Reinit the user stack LDR R0, [R0, #4] ; Get the user entry point... BX R0 ; ... and jump to it ENDP В С-шном файле вызов выглядит как: Код extern StartMainApp(uint32_t vector); ... StartMainApp(0x08000000 + 0x4000); // для моего случая программы с 16К
|
|
|
|
|
Apr 13 2016, 09:47
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
QUOTE (scifi @ Apr 13 2016, 12:41)  А, ну да. Разъехались, панимаишь, по свету. Космополиты  К делу отношения не имеет, но я никуда не "разъехались", я сейчас на родине. А писать стараюсь всегда максимально четко и без привлечения лишних сущностей и заплаток. В данном случае вызов пишется на чистейшем как слеза Си. Посему и пятое колесо не нужно.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Apr 13 2016, 11:14
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
QUOTE (scifi @ Apr 13 2016, 11:50)  Все уже предъявили, а Вы скрываете Не все еще. CODE struct application { struct vectors { typedef void( *handler )( void ); uint32_t MSP_init; handler Reset_vector; handler Core_handler[14]; handler MCU_handler[MCU_VECTORS]; } Vectors; uint32_t Size; // application size, 4-byte words };
extern application const Application;
.... { // set vectors table to application vectors SCB->VTOR = (uintptr_t)&Application.Vectors; __set_MSP(Application.Vectors.MSP_init); Application.Vectors.Reset_vector(); }
// в скрипте линкера: .application (NOLOAD): { Application = .; } > APPLICATION
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|