Основы работы отечественных МК 1986ВЕ1Т с болгарскими отладчиками OLIMEX ARM-USB-OCD-H. Часть 2 — RAM & Interruptions

—

от автора

Преамбула

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

Стек:

  • Отладочная плата LDM-HELPER-K1986BE1QI-FULL;

  • МК K1986BE1QI(но все паки и тд. юзаю 1986ВЕ1Т ибо те же яйца, только с боку даже даташит 1 на целую линейку);

  • Программатор ARM-USB-OCD-H;

  • IDE Keil uVison 5;

  • Open OCD.

Оффтоп

«RAM & Interruptions» не потому что «Я умею писать по буржуйски», а потому что «Размер заголовка не может превышать 120 символов» и «Отладка прерываний в опреативной памяти» не лезет. Ну, как говорил препод по информатике: «Это по молодости кажется, что не влазит»

Подготовка к работе

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

Настройка Keil для линковки проекта в оперативную память

Для того, чтобы собрать проект под 1986BE1T (и не только) для отладки с помощью OLIMEX ARM-USB-OCD-H (и не только) из оперативной памяти средствами среды Keil uVision 5 необходимо для начала создать новый проект keil (смотри все тот же предыдущий пост).

После того, как проект будет создан, необходимо как-то объяснить линкеру, что именно мы хотим. Посмотрим, где и как это делать. Если жамкнуть правой кнопкой по «target» в дереве проекта можно увидеть опции – заходим туда.

С «Output» все просто – минимально необходимое условие выполнения наших целей – это установленный флаг «Create Executable».

«Target» и «Linker» же немного более интересны. На выходе конфигурации этих вкладок получим готовые скрипты для линкера с помощью так называемых «Scatter files». Что это за скатерть такая объяснять не будем, есть документация.

На вкладке «Linker» нужно включить флаг «Use Memory Layout from Target Dialog» — это позволит автогенерить скрипты в зависимости от того, как мы настроим вкладку «Target».

Собственно, к «Target».

Что видим тут полезного для поставленной цели, так это 2 блока с описанием памяти (причем предзаполненных! Спасибо пакам keil-а.).

Посмотрим, какая скатерть получается на выходе. В папке проекта есть ./RTE/Device/MDR1986BE1T, а там файл MDR1986VE1T.sct. Это дефолтный скрипт. Так как никаких изменений не проводилось будет юзаться именно он.

MDR1986VE1T.sct
; ****************************************************************************** ; ************* Scatter-Loading Description File for MDR1986VE1T *************** ; ******************************************************************************  LR_IROM1 0x00000000 0x00020000  {      ; load region size_region     ER_IROM1 0x00000000 0x00020000  {  ; load address = execution address         *.o (RESET, +First)         *(InRoot$$Sections)         .ANY (+RO)         .ANY (+XO)     }     RW_IRAM1 0x20000000 0x00008000  {  ; RW data         .ANY (+RW +ZI)     }     RW_IRAM2 0x20100000 0x00004000  {         .ANY (+RW +ZI)     } }

Небольшой анализ показывает, что код будет записываться в ER_IROM1. Правда сейчас там все сконфигурировано так, чтобы грузить программу во флешу. Будем менять.

Можно, кончено, править scatter руками, но проще воспользоваться рафическими инструментами. Инструкции линкуются в первый раздел IROM памяти. Т.е. изменив поля «Start» и «Size» IROM1 на вкладке «Target» можно получить нужную линковку. Видим, что разделов IRAM у нас 2. Но. Читая даташит натыкаемся на фразу: «AHB-Lite – шина выборки инструкций и данных из внешнего адресного пространства» и, немного покурив остальное описание понимаем, что эта шина – единственный способ дотянуться до инструкций в оперативной памяти. Смотрим адреса и понимаем, что программу для выполнения в оперативной памяти дозволено грузить с 0x20100000.

Итого: копируем поля из IRAM2 в IROM1 и ребилдим.

Результирующий scatter файл. Будет находиться ./Objects/projectName.sct
; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; *************************************************************  LR_IROM1 0x20100000 0x00004000  {    ; load region size_region   ER_IROM1 0x20100000 0x00004000  {  ; load address = execution address    *.o (RESET, +First)    *(InRoot$$Sections)    .ANY (+RO)    .ANY (+XO)   }   RW_IRAM1 0x20000000 0x00008000  {  ; RW data    .ANY (+RW +ZI)   } }

Теперь видно, что инструкции будут располагаться в оперативной памяти, причем именно в той, которая для них и предназначена. Отлично! Пора проверить.

Запуск в оперативной памяти

Загрузим проект по образу и подобию 1 части статьи. И посмотрим адреса функций, например:

(gdb) x main 0x20100aa8 <main>:      0x25d95598

Т.е. адрес входа в main — 0x20100aa8, что вполне себе похоже на то, ради чего все это было затеяно.

Попробуем простенькую программу. Пусть по прерыванию таймера зажжем диод на плате.

Вначале было слово тактирование. С этого и начнем. Еще с первых дней знакомства с Миландровскими вычислителями был написан небольшой блочок, для того, чтобы в будущих простых проектах не париться по поводу настройки частоты проца. Суть: работает от внутреннего HSI, разрешает задавать частоты от 8 до 128 МГц. Пока что этого хватало.

Рассмотрим программу, которую будем грузить в RAM. Код туп, прост и, надеюсь, понятен.

main.c
static void ledInit( void ); inline static void ledOn( void ) { MDR_PORTB->RXTX = 0; } inline static void ledOff( void ) { MDR_PORTB->RXTX = 1; } static void timerInit( void ); inline static void timerStart( void ) { MDR_TIMER1->CNTRL = 0x01; }  void TIMER1_IRQHandler( void );  int main( void ) {   // Основное тактирование   freqHsiCpuSet( 8 );      // Инициализация диода   ledInit();      // Инициализация таймера   timerInit();      // Стартуем! Я сказала стар-ту-ем.   timerStart();      while(1) __NOP(); }  static void ledInit( void ) { MDR_RST_CLK->PER_CLOCK |= 1 << 22;  PORT_InitTypeDef portLEDInitStruct; PORT_StructInit( &portLEDInitStruct ); portLEDInitStruct.PORT_OE = PORT_OE_OUT; portLEDInitStruct.PORT_MODE = PORT_MODE_DIGITAL; portLEDInitStruct.PORT_SPEED = PORT_SPEED_SLOW; portLEDInitStruct.PORT_Pin = PORT_Pin_0; PORT_Init( MDR_PORTB, &portLEDInitStruct );  ledOff(); }  static void timerInit( void ) { NVIC_EnableIRQ( TIMER1_IRQn );  // Разрешение тактирования TIMER1 MDR_RST_CLK->PER_CLOCK |= 1 << 14;  // Разрешение тактовой частоты на TIM1 MDR_RST_CLK->TIM_CLOCK |= 1 < 24;   MDR_TIMER1->CNTRL = 0;   // Настраиваем работу основного счетчика  // Начальное значение MDR_TIMER1->CNT = 0;   //Предделитель частоты  MDR_TIMER1->PSG = 0;  // Основание счета MDR_TIMER1->ARR = 0x0F;   // Разрешение генерировать прерывание при CNT = ARR  MDR_TIMER1->IE = 1 << 1; }  void TIMER1_IRQHandler( void ) { ledOn();  MDR_TIMER1->IE = 0; NVIC_DisableIRQ( TIMER1_IRQn ); }

Что ожидается? А того, что после запуска практически сразу будет сработано прерывание от таймера. Окей. Грузим в оперативку. Запускаем.

Аккуратный вывод «Continuing.» говорит о том, что программа запущена, но диод не горит(

В чем же, собственно, дело?

Приостановим проргамму и увидим,

(gdb) <Cntrl + c> Program received signal SIGINT, Interrupt. main () at main.c:29 29              while(1) __NOP();

… что мы в бесконечном цикле ожидания.

Первая мысль (конечно же, после того, как выругал компилятор/отладчик/среду и тд. и тп. ибо виноват ТОЧНО не ты) — возможно не так проинициализировл LED. Ну, энивей, прерывание сработать должно, попробуем в него попасть, поставив точку останова в обработчике прерывания таймера:

(gdb) b TIMER1_IRQHandler Breakpoint 2 at 0x20100192: TIMER1_IRQHandler. (2 locations)

Перезапустим:

(gdb) monitor reset halt (gdb) load (gdb) c

Ничего не изменилось(

Глянем состояние регистров таймера. Счетчик (Регистр CNT) лежит по адресу 0x40070000, а Основание (Регистр ARR) по 0x40070000.

(gdb) x 0x40070000 0x40070000:     0x0000  (gdb) x 0x40070000 0x40070000:     0x0000

Таким же образом можно проверить флаги разрешения прерываний и прочее. И, судя по, что счетчик досчитал до основания и прерывания разрешены (trust me i am an engineer) все должно было сработать. Но не сработало.

А теперь попробуем провернуть все тоже самое, но во флеше. Проводим нехитрые манипуляции с вкладкой «Target», ребилдим, запускаем.

Загрузились, запустились – все работает. И диод горит в прерывании остановились.
(gdb) c Continuing.  Breakpoint 1, main () at main.c:18 18              freqHsiCpuSet( 8 );  (gdb) c Continuing.  Breakpoint 2, TIMER1_IRQHandler () at main.c:77 77              ledOn(); (gdb) c Continuing.

Суть в том, что таблица векторов прерываний в данных МК непереносима в принципе и располагается по нулевому адресу. Убедимся, рассмотрев файл startup_MDR1986VE1T.S. Там происходит начальная инициализация: стек поинтер, размеры кучи, стека… Но сейчас будет интересоватьтаблица векторов прерываний.

startup_MDR1986VE1T (кусочек)
; Vector Table Mapped to Address 0 at Reset                  AREA    RESET, DATA, READONLY                 EXPORT  __Vectors  __Vectors       DCD     __initial_sp                ; Top of Stack                 DCD     Reset_Handler               ; Reset Handler                 DCD     NMI_Handler                 ; NMI Handler                 DCD     HardFault_Handler           ; Hard Fault Handler                 DCD     0                           ; Reserved                 DCD     0                           ; Reserved                 DCD     0                           ; Reserved                 DCD     0                           ; Reserved                 DCD     0                           ; Reserved                 DCD     0                           ; Reserved                 DCD     0                           ; Reserved                 DCD     SVC_Handler                 ; SVCall Handler                 DCD     0                           ; Reserved                 DCD     0                           ; Reserved                 DCD     PendSV_Handler              ; PendSV Handler                 DCD     SysTick_Handler             ; SysTick Handler                  ; External Interrupts                                  DCD     MIL_STD_1553B2_IRQHandler   ;IRQ0                 DCD     MIL_STD_1553B1_IRQHandler   ;IRQ1                 DCD     USB_IRQHandler              ;IRQ2                 DCD     CAN1_IRQHandler             ;IRQ3                 DCD     CAN2_IRQHandler             ;IRQ4                 DCD     DMA_IRQHandler              ;IRQ5                 DCD     UART1_IRQHandler            ;IRQ6                 DCD     UART2_IRQHandler            ;IRQ7                 DCD     SSP1_IRQHandler             ;IRQ8                 DCD     BUSY_IRQHandler             ;IRQ9                 DCD     ARINC429R_IRQHandler        ;IRQ10                 DCD     POWER_IRQHandler            ;IRQ11                 DCD     WWDG_IRQHandler             ;IRQ12                 DCD     TIMER4_IRQHandler           ;IRQ13                 DCD     TIMER1_IRQHandler           ;IRQ14                 DCD     TIMER2_IRQHandler           ;IRQ15                 DCD     TIMER3_IRQHandler           ;IRQ16                 DCD     ADC_IRQHandler              ;IRQ17                 DCD     ETHERNET_IRQHandler         ;IRQ18                 DCD     SSP3_IRQHandler             ;IRQ19                 DCD     SSP2_IRQHandler             ;IRQ20                 DCD     ARINC429T1_IRQHandler       ;IRQ21                 DCD     ARINC429T2_IRQHandler       ;IRQ22                 DCD     ARINC429T3_IRQHandler       ;IRQ23                 DCD     ARINC429T4_IRQHandler       ;IRQ24                 DCD     0                           ;IRQ25                 DCD     0                           ;IRQ26                 DCD     BKP_IRQHandler              ;IRQ27                 DCD     EXT_INT1_IRQHandler         ;IRQ28                 DCD     EXT_INT2_IRQHandler         ;IRQ29                 DCD     EXT_INT3_IRQHandler         ;IRQ30                 DCD     EXT_INT4_IRQHandler         ;IRQ31

Эта же таблица (только External Interrupts) описана в разделе 31.10 «Lock-up» даташита.

А вот в пункте 31.1 «Типы исключений» написано: «В таблице представлены типы исключений, их номера и приоритет. Номер показывает словное смещение исключений относительно стартового адреса таблицы векторов, которая всегда расположена в 0x0.»

После небольшого когнитивного напряжения замечаем, что таблица «железно» вбивается в нулевой адрес. Заметить это нужно очень и очень, ибо с этим пришлось повозиться. Сразу было неочевидно. (Ну, мб я дурень, а остальные до этого сразу догадываются).

Ок, посмотрим на таблицу изнутри.
(gdb) x/48 0x0 0x0 <RST_CLK_GetClocksFreq>:    0x20002068      0x00000165      0x0000016d      0x0000016f 0x10 <RST_CLK_GetClocksFreq+16>:        0x00000000      0x00000000      0x00000000      0x00000000 0x20 <RST_CLK_GetClocksFreq+32>:        0x00000000      0x00000000      0x00000000      0x00000171 0x30 <RST_CLK_GetClocksFreq+48>:        0x00000000      0x00000000      0x00000173      0x00000175 0x40 <RST_CLK_GetClocksFreq+64>:        0x00000177      0x00000179      0x0000017b      0x0000017d 0x50 <RST_CLK_GetClocksFreq+80>:        0x0000017f      0x00000181      0x00000183      0x00000185 0x60 <RST_CLK_GetClocksFreq+96>:        0x00000187      0x00000189      0x0000018b      0x0000018d 0x70 <RST_CLK_GetClocksFreq+112>:       0x0000018f      0x00000191      0x0000083d      0x00000195 0x80 <RST_CLK_GetClocksFreq+128>:       0x00000197      0x00000199      0x0000019b      0x0000019d 0x90 <RST_CLK_GetClocksFreq+144>:       0x0000019f      0x000001a1      0x000001a3      0x000001a5 0xa0 <RST_CLK_GetClocksFreq+160>:       0x000001a7      0x00000000      0x00000000      0x000001a9 0xb0 <RST_CLK_GetClocksFreq+176>:       0x000001ab      0x000001ad      0x000001af      0x000001b1

Так как таблица — это вполне себе символ можно указать

(gdb) x/48 __Vectors

Выведет тоже самое.

Почему 48? 32 внешних прерывания + адрес вершины стека + 6 системных прерываний + 9 зарезервированных полей.

Заметим, что наше прерывание от таймера (TIMER1_IRQHandler) – это IRQ14. Т.е. (16 + 14) * 4 = 120(в дес) ил 0x78. Что лежит по этому адресу?

(gdb) x/ 0x78 0x78 <RST_CLK_GetClocksFreq+120>:       0x0000083d

А куда указывает?

(gdb) x 0x0000083d 0x83d <TIMER1_IRQHandler>:      0x8ff000b5

Есть! То самое прерывание, которое искали.

Подобное можно выщучить из таблицы. Полчуачется строка 0x70 <RST_CLK_GetClocksFreq+112> столбец 3, т.к. 1 слово = 4 байта. Кстати, выглядит это все приемрно так:

Вернемся немного назад и провернем все тоже самое, но в оперативке. Глянем таблицу.

(gdb) x/48 0

Осталась неизменной, что, наверное и следовало ожидать, ведь флеш не прошивалась.

А что лежит по

(gdb) x 0x0000083d 0x83d:  0x8ff000b5

Собственно, то, что и было, но без символа.

Заметил только при написании статьи

Возможно теперь это отработает правильно (ибо флеш прошита такой же программой, что и в оперативке, но мы «зайдем» не в тот обработчик, что написали, а в тот, что прошит). Для чистоты эксперимента лучше очистить флешку.

А глянем теперь таблицу через символ
(gdb) x/48 __Vectors 0x20100000 <__Vectors>: 0x20002068      0x20100165      0x2010016d      0x2010016f 0x20100010:     0x00000000      0x00000000      0x00000000      0x00000000 0x20100020:     0x00000000      0x00000000      0x00000000      0x20100171 0x20100030:     0x00000000      0x00000000      0x20100173      0x20100175 0x20100040:     0x20100177      0x20100179      0x2010017b      0x2010017d 0x20100050:     0x2010017f      0x20100181      0x20100183      0x20100185 0x20100060:     0x20100187      0x20100189      0x2010018b      0x2010018d 0x20100070:     0x2010018f      0x20100191      0x2010083d      0x20100195 0x20100080:     0x20100197      0x20100199      0x2010019b      0x2010019d 0x20100090:     0x2010019f      0x201001a1      0x201001a3      0x201001a5 0x201000a0:     0x201001a7      0x00000000      0x00000000      0x201001a9 0x201000b0:     0x201001ab      0x201001ad      0x201001af      0x201001b1

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

Последний рывочек

Есть несколько способов добиться желаемого результата, но вот этот мне нравится больше всех: прошить во флешку обработчик прерывания, который будет переходить по нужному адресу в оперативной памяти.

Создаем новый проект, который будет располагаться во флеш
#define READWD(x)  (*((volatile uint32_t *)(x))) typedef void ( *fUserIrptHandler )( void ); int main() {    while(1){} }   void TIMER1_IRQHandler() {   volatile uint32_t addr = READWD( 0x20100078 );  fUserIrptHandler funcInRAM = ( fUserIrptHandler )( addr ); funcInRAM();  } 

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

Смещение постоянное, так что для расчета не тяжко: Стартовый адрес памяти (0x20100000) + сдвиг для конкретного прерывания (0x78).

Прошиваем данную флеш вспомогательной программой. Запускаем ту программу, что с таймером из оперативной. Baldezh.

Итого

Да, совсем обойтись без флехи не удалось, но всегда нужно искать плюсы:

  • Если такой способ есть гуру обязательно подскажет о нем в комментарии — буду рад;

  • Погрузились во внутрянку работы проца, это полезно;

  • Все-таки прошить можно 1 раз флешку нужными обработчиками и уйму раз отлаживаться в оперативке, не коцая постоянку. Есть конечно, трабл: если во флешке будет прошит обработчик, а в оперативке мы его не сделаем — будет доступ не туда, куда надо — а это беда.

Спасибо, надеюсь кому-то статья поможет 🙂


ссылка на оригинал статьи https://habr.com/ru/post/657533/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *