Fundamentals of work of domestic MK 1986BE1T with Bulgarian debuggers OLIMEX ARM-USB-OCD-H. Part 2 – RAM & Interruptions


Initially, the second chapter was conceived only as a cheat sheet for working from RAM, but doing and understanding this is not very difficult. The main “zapara” can overtake the ignorant precisely when working with interrupts. Sobsna, it was decided to merge.



“RAM & Interruptions” is not because “I can write in bourgeois”, but because “Header size cannot exceed 120 characters” and “Debugging interrupts in RAM” does not fit. Well, as the teacher in computer science said: “It seems that in youth it doesn’t fit”

Preparation for work

In principle, the preparation is no different from the previous post, so we will not repeat ourselves once again.

Setting up Keil to link a project into RAM

In order to build a project under 1986BE1T (and not only) for debugging using OLIMEX ARM-USB-OCD-H (and not only) from RAM using the Keil uVision 5 environment, you must first create a new keil project (see all the same previous post).

After the project is created, it is necessary to somehow explain to the linker what exactly we want. Let’s see where and how to do it. If you right-click on “target” in the project tree, you can see the options – go there.

With “Output”, everything is simple – the minimum necessary condition for fulfilling our goals is the “Create Executable” flag set.

“Target” and “Linker” are a bit more interesting. As a result of the configuration of these tabs, we will get ready-made scripts for the linker using the so-called “Scatter files”. We won’t explain what kind of tablecloth this is, there is documentation.

On the “Linker” tab, you need to enable the “Use Memory Layout from Target Dialog” flag – this will allow auto-generating scripts depending on how we configure the “Target” tab.

Actually, to “Target”.

What we see here that is useful for our goal is 2 blocks with a description of the memory (moreover, pre-filled! Thanks to the keil packs.).

Let’s see what kind of tablecloth comes out. The project folder contains ./RTE/Device/MDR1986BE1T, and there is the MDR1986VE1T.sct file. This is a default script. Since no changes have been made, it will be used by him.

; ******************************************************************************
; ************* 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)
        .ANY (+RO)
        .ANY (+XO)
    RW_IRAM1 0x20000000 0x00008000  {  ; RW data
        .ANY (+RW +ZI)
    RW_IRAM2 0x20100000 0x00004000  {
        .ANY (+RW +ZI)

A little analysis shows that the code will be written to ER_IROM1. True, now everything is configured there in such a way as to load the program into a flash. We will change.

You can, of course, edit the scatter by hand, but it’s easier to use raffic tools. The instructions are linked to the first section of the IROM. Those. by changing the “Start” and “Size” fields of IROM1 on the “Target” tab, you can get the desired link. We see that we have 2 IRAM sections. But. Reading the datasheet, we come across the phrase: “AHB-Lite – a bus for fetching instructions and data from an external address space” and, having smoked the rest of the description a bit, we understand that this bus is the only way to reach the instructions in RAM. We look at the addresses and understand that the program for execution in RAM can be loaded from 0x20100000.

Total: copy the fields from IRAM2 to IROM1 and rebuild.

The resulting scatter file. Will be ./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)
   .ANY (+RO)
   .ANY (+XO)
  RW_IRAM1 0x20000000 0x00008000  {  ; RW data
   .ANY (+RW +ZI)

Now it is clear that the instructions will be located in the RAM, and in the one that is intended for them. Fine! Time to check.

Running in RAM

Let’s download the project in the image and likeness of part 1 of the article. And let’s see the addresses of the functions, for example:

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

Those. the main entry address is 0x20100aa8, which is quite similar to what all this was started for.

Let’s try a simple program. Let’s turn on the diode on the board by interrupting the timer.

In the beginning it was word clocking. Let’s start with this. From the very first days of acquaintance with the Milandro calculators, it was written small block, so that in future simple projects you don’t have to worry about setting the processor’s frequency. Essence: it works from the internal HSI, it allows you to set frequencies from 8 to 128 MHz. So far this has been enough.

Consider a program that we will load into RAM. The code is dumb, simple, and hopefully understandable.

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 );
  // Инициализация диода
  // Инициализация таймера
  // Стартуем! Я сказала стар-ту-ем.
  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_Pin = PORT_Pin_0;	
	PORT_Init( MDR_PORTB, &portLEDInitStruct );

static void timerInit( void )
	// Разрешение тактирования TIMER1
	MDR_RST_CLK->PER_CLOCK |= 1 << 14;
	// Разрешение тактовой частоты на TIM1
	MDR_RST_CLK->TIM_CLOCK |= 1 < 24; 
	// Настраиваем работу основного счетчика 
	// Начальное значение
	MDR_TIMER1->CNT = 0; 
	//Предделитель частоты 
	// Основание счета
	MDR_TIMER1->ARR = 0x0F;
	// Разрешение генерировать прерывание при CNT = ARR 
	MDR_TIMER1->IE = 1 << 1;

void TIMER1_IRQHandler( void )
	MDR_TIMER1->IE = 0;
	NVIC_DisableIRQ( TIMER1_IRQn );

What is expected? And the fact that after the launch, an interrupt from the timer will be triggered almost immediately. OK. We load into the operating room. We launch.

Neat output “Continuing.” indicates that the program is running, but the diode is off (

What, exactly, is the point?

Let’s pause the program and see

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

…that we’re in an endless waiting loop.

The first thought (of course, after scolding the compiler/debugger/environment, etc., etc. because it’s EXACTLY not your fault) – perhaps the LED was not initialized that way. Well, enivey, the interrupt should work, let’s try to hit it by setting a breakpoint in the timer interrupt handler:

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


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

Nothing changed(

Let’s look at the state of the timer registers. The Counter (CNT Register) is at 0x40070000 and the Base (ARR Register) is at 0x40070000.

(gdb) x 0x40070000
0x40070000:     0x0000

(gdb) x 0x40070000
0x40070000:     0x0000

In the same way, you can check the interrupt enable flags and so on. And, judging by the fact that the counter counted to the base and interrupts are allowed (trust me i am an engineer), everything should have worked. But it didn’t work.

And now let’s try to do the same thing, but in a flash. We carry out simple manipulations with the “Target” tab, rebuild, launch.

Loaded, launched – everything works. And the diode is lit in interruption stopped.
(gdb) c

Breakpoint 1, main () at main.c:18
18              freqHsiCpuSet( 8 );

(gdb) c

Breakpoint 2, TIMER1_IRQHandler () at main.c:77
77              ledOn();
(gdb) c

The bottom line is that the table of interrupt vectors in the MK data basically intolerable. and is located on zero address. Let’s make sure by examining the file startup_MDR1986VE1T.S. The initial initialization takes place there: the pointer stack, the sizes of the heap, the stack … But now the table of interrupt vectors will be of interest.

startup_MDR1986VE1T (piece)
; 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

The same table (only External Interrupts) is described in section 31.10 “Lock-up” of the datasheet.

But in paragraph 31.1 “Exception Types” written: “The table shows the types of exceptions, their numbers and priority. The number indicates the offset of the exceptions relative to the starting address of the vector table, which is always located at 0x0.”

After a little cognitive stress, we notice that the table is “hard-wired” into the zero address. You need to notice this very, very much, because you had to tinker with it. It wasn’t obvious right away. (Well, I’m a fool, and the rest immediately guess before that).

Ok, let’s look at the table from the inside.
(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

Since the table is quite a symbol, you can specify

(gdb) x/48 __Vectors

It will output the same.

Why 48? 32 external interrupts + stack top address + 6 system interrupts + 9 reserved fields.

Note that our timer interrupt (TIMER1_IRQHandler) is IRQ14. Those. (16 + 14) * 4 = 120(in decimal) or 0x78. What is at this address?

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

And where does it point?

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

There is! The interrupt you were looking for.

This can be found in the table. Row 0x70 column 3 is retrieved because 1 word = 4 bytes. By the way, it all looks like this:

Let’s go back a little and check everything the same, but in the RAM. Let’s look at the table.

(gdb) x/48 0

It remained unchanged, which, probably, was to be expected, because the flash was not flashed.

And what lies on

(gdb) x 0x0000083d
0x83d:  0x8ff000b5

Actually, what it was, but without a symbol.

I only noticed while writing the article.

Perhaps now it will work correctly (because the flash is flashed with the same program as in the RAM, but we will “go” not into the handler that we wrote, but into the one that is flashed). For the purity of the experiment, it is better to clean the flash drive.

And now let’s look at the table through the symbol
(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

There is a native one, and our interruption of Tuts is described correctly. It remains to move it to the flash. Unfortunately, I don’t know any clean ways, I’ll have to touch the flash drive with enivey.

Last jerk

There are several ways to achieve the desired result, but this one I like the most: flash an interrupt handler into the flash drive that will jump to the desired address in RAM.

Create a new project that will be located in flash
#define READWD(x)  (*((volatile uint32_t *)(x)))
typedef void ( *fUserIrptHandler )( void );
int main()

void TIMER1_IRQHandler()
  volatile uint32_t addr = READWD( 0x20100078 );
 	fUserIrptHandler funcInRAM = ( fUserIrptHandler )( addr );

Everything is simple. When the interrupt is triggered, according to the table, we will get into the handler that is in the flash. In it, we go to the user handler, located already in RAM. This turns out to be a matryoshka.

The offset is constant, so it’s not hard to calculate: Memory start address (0x20100000) + interrupt specific offset (0x78).

We flash this flash with an auxiliary program. We launch the program that with the timer from the operational. Baldezh.


Yes, it was not possible to completely do without a flash, but you always need to look for the pros:

  • If there is such a way, the guru will definitely tell you about it in the comments – I will be glad;

  • We plunged into the inner workings of the processor, this is useful;

  • Still, you can flash the flash drive 1 time with the necessary handlers and debug a lot of times in the RAM without breaking the constant. Of course, there is a problem: if a handler is flashed in the flash drive, but we don’t make it in the RAM, there will be access to the wrong place – and this is a disaster.

Thank you, I hope this article helps someone 🙂

Similar Posts

Leave a Reply Cancel reply