Mikroprocesorski sistemi/K1 2022

Izvor: SI Wiki
< Микропроцесорски системи
Datum izmene: 18. septembar 2024. u 21:13; autor: Etfsibg (razgovor | doprinosi) (→‎1. задатак)
(razl) ← Starija izmena | Trenutna verzija (razl) | Novija izmena → (razl)
Pređi na navigaciju Pređi na pretragu

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. [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.
  2. [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. [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.
  4. [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četku main funkcije.
  5. [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.
  6. [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 i test6_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).
  7. [2 poena] Konfigurisati sistem i STK (SysTick timer) periferiju jezgra tako da bude moguće merenje vremena proteklog između poziva start i stop funkcija. Pozivom start funkcije označava se početni trenutak merenja vremena. Pozivom stop funkcije kao povratna vrednost dobija se broj milisekundi proteklih od poslednjeg poziva start funkcije. Implementirati start i stop funkcije.
    extern void start();
    extern uint32_t stop();
    
    Nakon ispravne konfiguracije STK (SysTick timer) periferije jezgra, odnosno kada je sistem spreman za poziv start i stop funkcija, iz main funkcije pozvati funkciju test7 definisanu u test.c datoteci.

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):

  1. Kada se uđe u funkciju test4, proveriti sadržaj RAM kako bi videli da li se podatak VMA:RAM, LMA:FLASH ispravno kopirao.
  2. Kada se uđe u funkciju test5, otvoriti Registers prozor i proveriti da li Pri red prikazuje C0.30 kao vrednost.
  3. Proveriti da li se u test6_irqX funkcije ulazi redom IRQ2, IRQ0, IRQ1.
  4. Zabeležiti vreme koje Proteus prijavljuje kada se dođe do linije sa pozivom start, zatim nastaviti do linije sa pozivom stop i proveriti da li je u time_elapsed promenljivu upisana razlika trenutnog i zabeleženog vremena u milisekundama.