Mikroprocesorski sistemi/K1 2022
Prvi kolokvijum 2022. godine održan je 5. novembra, bila je dostupna dokumentacija mikrokontrolera, procesora i Intel HEX formata kao i prezentacije sa predavanja tokom teorijskog i praktičnog dela ispita. Postavka roka nije dostupna sa stranice predmeta.
Teorija
1. zadatak
Koja vrednost se nalazi u registru R1 nakon izvršavanja date sekvence asemblerskih instrukcija? Odgovor uneti prema formatu neoznačenih heksadecimalnih literala u C programskom jeziku širine 32 bita.
mov r1, 0xFF00
ldr r0, [r1]
ldr r0, [r1, 1]
ldr r0, [r1, 1]!
ldr r0, [r1], 1
Prikaz očekivanog formata odgovora za proizvoljno odabranu vrednost 1 jeste:
0x00000001
Odgovor: 0x0000FF02
Objašnjenje: Instrukcije ldr r0, [r1, 1]!
kao i ldr r0, [r1], 1
menjaju sadržaj R1 povećavanjem za 1. Relevantna stranica u Cortex-M3 Programming Manual je 62 (3.4.2 LDR and STR, immediate offset).
2. zadatak
Posmatra se Cortex-M3 procesor sa podrazumevanom konfiguracijom nakon reseta. U nastavku je dato trenutno stanje sistema opisano vrednostima koje bi bile dobijene čitanjem odgovarajućih memorijski mapiranih registara:
NVIC_IABR0: 0x00000000 NVIC_ISER0: 0x0000003C NVIC_ISPR0: 0x0000002E
Koliko puta će biti aktiviran tail-chaining mehanizam za prethodno navedeno stanje sistema pod pretpostavkom da neće pristizati novi zahtevi za prekidom?
Odgovor: 2
Objašnjenje: Registar NVIC_IABR0
(interrupt active bit register) nam kaže da nema aktivnih prekida trenutno. Registar NVIC_ISER0
(interrupt set enable register) nam kaže koji prekidi su omogućeni. Registar NVIC_ISPR0
(interrupt set pending register) nam kaže koji prekidi čekaju na opsluživanje trenutno. Prekidi koji mogu da se opsluže trenutno su oni koji su i omogućeni i čekaju na opsluživanje. Ukoliko uradimo bitsko I nad vrednostima NVIC_ISER0
i NVIC_ISPR0
dobijamo 0x0000002C, odnosno 00000000000000000000000000101100 u binarnom, što nam govori da tri prekida mogu trenutno da se opsluže. Tail-chaining se dešava pri prelazu između dva prekida, a pošto će se među ova tri prekida dva puta preći između prekida to je odgovor na ovo pitanje.
3. zadatak
U nastavku je prikazan sadržaj dve datoteke: (1) linkerska skripta i (2) izvorni asemblerski kod programa. Posmatra se izvršavanje datog programa koji je rezultat linkovanja pomoću prikazane linkerske skripte.
Koja vrednost se nalazi u registru R4 u trenutku kada tok kontrole stigne do adrese ukazane labelom leave_sv_call_handler
? Odgovor uneti prema formatu neoznačenih heksadecimalnih literala u C programskom jeziku širine 32 bita.
Linkerska skripta:
MEMORY { FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 32K SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 10K } SECTIONS { .vector_table : { *(.vector_table) } > FLASH .text : { *(.text*) } > FLASH }
Izvorni asemblerski kod programa:
.cpu cortex-m3
.fpu softvfp
.syntax unified
.thumb
.section .vector_table, "a"
.word 0x20000064
.word reset_handler
.rept 9
.word default_handler
.endr
.word sv_call_handler
.section .text.reset_handler
.type reset_handler, %function
reset_handler:
mov r0, 0
mov r1, 1
mov r2, 2
mov r3, 3
mov r12, 12
mov r14, 14
svc 0
nop
infinite_loop:
b infinite_loop
.section .text.default_handler
.type default_handler, %function
default_handler:
b default_handler
.section .text.sv_call_handler
.type sv_call_handler, %function
sv_call_handler:
ldr r4, [sp, 1 * 4]
leave_sv_call_handler:
bx lr
.end
Prikaz očekivanog formata odgovora za proizvoljno odabranu vrednost 1 jeste:
0x00000001
Odgovor: 0x00000001
Objašnjenje: Poslednji registar koji se baca na stek pri ulasku u prekidnu rutinu je R0, a pre njega su R1, R2, R3, R12... Pošto dohvatamo podatak koji je 4 bajta iznad stek pokazivača, a stek pokazivač pokazuje na poslednju zauzetu lokaciju, odnosno R0, sadržaj koji će biti upisan u R4 će biti R1.
4. zadatak
Isti kao treći zadatak sa avgustovskog roka 2022. godine.
Zadatak
Postavka
Koristeći alate arm-none-eabi-*, build-tools i Eclipse CDT i Proteus simulator potrebno je ispuniti stavke koje se nalaze u nastavku.
- [1 poen] Napraviti novi projekat u Proteus simulatoru u okviru kojeg na strukturnoj šemi treba instancirati mikrokontroler STM32F103R6, neophodne komponente i izvršiti odgovarajuća povezivanja za uspešno pokretanje simulacije.
- [3 poena] Napisati Makefile kojim je moguće prevesti, asemblirati i povezati projekat sa izvornim kodom napisanim na C programskom jeziku u cilju dobijanja izvršnog koda u ELF formatu (podrazumevani format izlaza GNU asemblera i linkera) i Intel Hex formatu. Makefile mora biti napisan tako da ispunjava sledeća dva uslova: (1) dodavanje novih datoteka sa izvornim kodom u projekat ne zahteva eksplicitno pisanje novih pravila (izmene drugog tipa u Makefile su dozvoljene) i (2) sve rezultate i međurezultate procesa prevođenja, asembliranja i povezivanja projekta treba smestiti u zaseban poddirektorijum.
- Studenti kojima prethodno navedena dva uslova predstavljaju prepreku mogu preći na izradu narednih stavki tako što će napisati rudimentaran Makefile čiji recepti sadrže komande za prevođenje odnosno asembliranje svake pojedinačne datoteke sa izvornim kodom i ostale prateće komande za povezivanje čitavog projekta. Broj poena za ovu stavku u slučaju pisanja rudimentarnog Makefile iznosi jedan poen od maksimalnih tri poena predviđenih za ovu stavku.
- [3 poena] Napisati linkersku skriptu koja se može iskoristiti prilikom povezivanja objektnih datoteka, koje sadrže sekcije sa kodom, podacima bez inicijalnih vrednosti i podacima sa inicijalnim vrednostima, u cilju dobijanja izvršnog koda odnosno memorijske mape. Izvršni kod dobijen na osnovu napisane linkerske skripte mora se ispravno izvršavati na mikrokontroleru STM32F103R6 i nakon gubitka napajanja. Prilikom pisanja linkerske skripte moguće je usvojiti pretpostavku o postojanju odgovarajućih simbola (sekcija sa sadržajem IVT, ulazna tačka programa itd.) u okviru ulaznih objektnih datoteka.
- [3 poena] Napisati izvorni kod kojim se postiže u potpunosti ispravna inicijalizacija mikrokontrolera STM32F103R6 u opštem slučaju (program koji se izvršava sadrži kod, podatke bez inicijalnih vrednosti i podatke sa inicijalnim vrednostima) pri čemu je potrebno obezbediti ispravno izvršavanje i nakon gubitka napajanja.
- Funkciju
test4
definisanu u test.c datoteci pozvati na samom početkumain
funkcije.
- Funkciju
- [2 poena] Konfigurisati sistem i SCB (System control block) periferiju jezgra na opisani način. Obezbediti da bitovi koji predstavljaju prioritete izuzetaka/prekida imaju sledeće značenje: najviša dva bita određuju grupni prioritet, preostali niži bitovi određuju podprioritet. Postaviti vrednost
0xF0
za prioritet PendSV izuzetka. Aktivirati PendSV izuzetak postavljanjem njegovog pending bita na aktivnu vrednost.- Funkciju
test5
definisanu u test.c datoteci pozvati iz PendSV rukovaoca izuzetkom (exception handler) odnosno prekidne rutine.
- Funkciju
- [2 poena] Konfigurisati sistem i NVIC (Nested vectored interrupt controller) periferiju jezgra tako da su istovremeno ostvarena sledeća tri uslova: (1) zahtevi za obradom prekida IRQ0, IRQ1 i IRQ2 jesu omogućeni, (2) u slučaju pristizanja prethodna tri zahteva u istom trenutku redosled njihove obrade jeste prvo IRQ2, zatim IRQ0 i na kraju IRQ1 i (3) grupni prioritet prethodna tri prekida je identičan. Aktivirati IRQ0, IRQ1 i IRQ2 prekide odjednom istovremenim postavljanjem njihovih pending bitova na aktivnu vrednost. Funkcije
test6_irq0
,test6_irq1
itest6_irq2
definisane u test.c datoteci pojedinačno pozvati iz prekidnih rutina IRQ0, IRQ1 i IRQ2, respektivno (svaku test funkciju pozvati samo u njoj odgovarajućoj prekidnoj rutini). - [2 poena] Konfigurisati sistem i STK (SysTick timer) periferiju jezgra tako da bude moguće merenje vremena proteklog između poziva
start
istop
funkcija. Pozivomstart
funkcije označava se početni trenutak merenja vremena. Pozivomstop
funkcije kao povratna vrednost dobija se broj milisekundi proteklih od poslednjeg pozivastart
funkcije. Implementiratistart
istop
funkcije.extern void start(); extern uint32_t stop();
- Nakon ispravne konfiguracije STK (SysTick timer) periferije jezgra, odnosno kada je sistem spreman za poziv
start
istop
funkcija, izmain
funkcije pozvati funkcijutest7
definisanu u test.c datoteci.
- Nakon ispravne konfiguracije STK (SysTick timer) periferije jezgra, odnosno kada je sistem spreman za poziv
Rešenje
Dodaci
Bile su date datoteke nvic.h
, scb.h
, stk.h
, test.c
, test.h
i utility.h
kao iz primera prve predispitne obaveze.
U scb.h
je potrebno dodati sledeće:
#define SCB ((SCB_RegisterMapType*) 0xE000ED00)
U nvic.h
je potrebno dodati sledeće:
#define NVIC ((NVIC_RegisterMapType*) 0xE000E100)
U stk.h
je potrebno dodati sledeće:
#define STK ((STK_RegisterMapType*) 0xE000E010)
#define STK_CTRL_CLKSOURCE (1 << 2)
#define STK_CTRL_TICKINT (1 << 1)
#define STK_CTRL_ENABLE (1 << 0)
Testovi
Datoteka test.c
je izgledala ovako:
/*
* SADRZAJ OVE DATOTEKE NE MENJATI
*/
#include <stdint.h>
uint8_t const rodata[] = "VMA:FLASH, LMA:FLASH";
uint8_t data[] = "VMA:RAM, LMA:FLASH";
void test4()
{
// proveriti da li je sadrzaj iz FLASH memorije
// prekopiran u inicijalno praznu RAM memoriju
}
void test5()
{
// proveriti da li je postavljen prioritet PendSV izuzetka na dobar nacin
}
void test6_irq0()
{
// proveriti redosled izvrsavanja prekidnih rutina
}
void test6_irq1()
{
// proveriti redosled izvrsavanja prekidnih rutina
}
void test6_irq2()
{
// proveriti redosled izvrsavanja prekidnih rutina
}
extern void start();
extern uint32_t stop();
uint32_t time_elapsed = 0;
void test7()
{
start();
uint32_t consumer = 0;
for (uint32_t i = 0; i < 250000; i++)
{
consumer++;
}
time_elapsed = stop();
}
dok je datoteka test.h
izgledala ovako:
/*
* SADRZAJ OVE DATOTEKE NE MENJATI
*/
#ifndef _TEST_H_
#define _TEST_H_
extern void test4();
extern void test5();
extern void test6_irq0();
extern void test6_irq1();
extern void test6_irq2();
extern void test7();
#endif /* _TEST_H_ */
Linkerska skripta
MEMORY { FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 32K SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 10K } SECTIONS { .vector_table : { *(.vector_table) } > FLASH .text : { *(.text) } > FLASH .rodata : { *(.rodata) } > FLASH .data : { _vma_data_start = .; *(.data) _vma_data_end = .; } > SRAM AT> FLASH _lma_data_start = LOADADDR(.data); .bss : { *(.bss) } > SRAM }
Makefile
PROGRAM = program
BUILD_DIR = build
DEBUG_ENABLED = 1
SOURCE_C_LIST = \
src/main.c \
src/test.c
SOURCE_S_LIST = \
src/startup.s
INCLUDE_LIST = -Iinc
CC = arm-none-eabi-gcc -c
AS = arm-none-eabi-gcc -c -x assembler
LD = arm-none-eabi-ld
HX = arm-none-eabi-objcopy -O ihex
LINKER_SCRIPT = linker_script.ld
OBJECTS_LIST =
OBJECTS_LIST += $(addprefix $(BUILD_DIR)/, $(notdir $(SOURCE_C_LIST:.c=.o)))
OBJECTS_LIST += $(addprefix $(BUILD_DIR)/, $(notdir $(SOURCE_S_LIST:.s=.o)))
vpath %.c $(sort $(dir $(SOURCE_C_LIST)))
vpath %.s $(sort $(dir $(SOURCE_S_LIST)))
MCU = -mcpu=cortex-m3 -mthumb
FLAGS_DEBUG = -g -gdwarf-2 -fdebug-prefix-map==../
WARNINGS = -Wall -Wextra
FLAGS_AS =
FLAGS_AS += $(MCU)
FLAGS_AS += $(WARNINGS)
ifeq ($(DEBUG_ENABLED), 1)
FLAGS_AS += $(FLAGS_DEBUG)
endif
FLAGS_AS += -mlong-calls
FLAGS_CC =
FLAGS_CC += $(MCU)
FLAGS_CC += $(WARNINGS)
FLAGS_CC += $(INCLUDE_LIST)
ifeq ($(DEBUG_ENABLED), 1)
FLAGS_CC += $(FLAGS_DEBUG)
endif
FLAGS_CC += -MMD -MP
FLAGS_CC += -mlong-calls
all: $(BUILD_DIR)/$(PROGRAM).elf $(BUILD_DIR)/$(PROGRAM).hex
$(BUILD_DIR)/$(PROGRAM).hex: $(BUILD_DIR)/$(PROGRAM).elf
$(HX) $(<) $(@)
$(BUILD_DIR)/$(PROGRAM).elf: $(OBJECTS_LIST) $(LINKER_SCRIPT)
$(LD) -T $(LINKER_SCRIPT) -o $(@) $(OBJECTS_LIST)
$(BUILD_DIR)/%.o: %.s makefile | $(BUILD_DIR)
$(AS) $(FLAGS_AS) -o $(@) $(<)
$(BUILD_DIR)/%.o: %.c makefile | $(BUILD_DIR)
$(CC) $(FLAGS_CC) -o $(@) $(<)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
clean:
rm -rf $(BUILD_DIR)
-include $(wildcard $(BUILD_DIR)/*.d)
startup.s
.cpu cortex-m3
.fpu softvfp
.syntax unified
.thumb
.section .vector_table, "a"
.extern main
.extern systick_handler
.extern test5
.extern test6_irq0
.extern test6_irq1
.extern test6_irq2
.word 0x20002800
.word main
.rept 12
.word infinite_loop
.endr
.word test5
.word systick_handler
.word test6_irq0
.word test6_irq1
.word test6_irq2
.rept 65
.word infinite_loop
.endr
.text
infinite_loop:
b infinite_loop
main.c
#include "nvic.h"
#include "scb.h"
#include "stk.h"
#include "test.h"
uint32_t millis;
void start() {
millis = 0;
STK->VAL = 0;
STK->LOAD = 8000 - 1;
STK->CTRL |= STK_CTRL_CLKSOURCE | STK_CTRL_ENABLE | STK_CTRL_TICKINT;
}
uint32_t stop() {
STK->CTRL &= ~(STK_CTRL_CLKSOURCE | STK_CTRL_ENABLE | STK_CTRL_TICKINT);
return millis;
}
void systick_handler() {
++millis;
}
extern char _vma_data_start;
extern char _vma_data_end;
extern char _lma_data_start;
int main() {
// Kopiranje podataka
for (char *data_src = &_lma_data_start, *data_dst = &_vma_data_start; data_dst != &_vma_data_end; *(data_dst++) = *(data_src++));
// Poziv test funkcije kako je rečeno
test4();
// Podešavanje prioriteta: xx.yy (x - grupa, y - podgrupa)
SCB->AIRCR = (0x5FA << 16) | (5 << 8);
// Postavljanje prioriteta za PendSV
SCB->SHPR3 = (0xF0 << 16);
// Postavljanje pending bita za PendSV
SCB->ICSR |= 0x10000000;
// Omogućavanje IRQ2..0
NVIC->ISER[0] |= 0x7;
// Postavljanje prioriteta za IRQ2..0
NVIC->IPR[0] = (0b10000000 << 16) | (0b10110000 << 8) | (0b10010000 << 0);
// Postavljanje pending bita za IRQ2..0
NVIC->ISPR[0] |= 0x7;
// STK testiranje
test7();
// Vrtimo se do kraja izvršavanja
while (1);
}
Testiranje
Funkcionalnosti predviđene zadatkom se testiraju na sledeći način (prikazano referentnim snimkom na prvom kolokvijumu):
- Kada se uđe u funkciju
test4
, proveriti sadržaj RAM kako bi videli da li se podatakVMA:RAM, LMA:FLASH
ispravno kopirao. - Kada se uđe u funkciju
test5
, otvoriti Registers prozor i proveriti da liPri
red prikazujeC0.30
kao vrednost. - Proveriti da li se u
test6_irqX
funkcije ulazi redom IRQ2, IRQ0, IRQ1. - Zabeležiti vreme koje Proteus prijavljuje kada se dođe do linije sa pozivom
start
, zatim nastaviti do linije sa pozivomstop
i proveriti da li je utime_elapsed
promenljivu upisana razlika trenutnog i zabeleženog vremena u milisekundama.