Микропроцесорски системи/К1 2022

Извор: SI Wiki
Пређи на навигацију Пређи на претрагу

Први колоквијум 2022. године одржан је 5. новембра, била је доступна документација микроконтролера, процесора и Intel HEX формата као и презентације са предавања током теоријског и практичног дела испита. Поставка рока није доступна са странице предмета.

Теорија

1. задатак

Која вредност се налази у регистру R1 након извршавања дате секвенце асемблерских инструкција? Одговор унети према формату неозначених хексадецималних литерала у C програмском језику ширине 32 бита.

mov r1, 0xFF00
ldr r0, [r1]
ldr r0, [r1, 1]
ldr r0, [r1, 1]!
ldr r0, [r1], 1

Приказ очекиваног формата одговора за произвољно одабрану вредност 1 јесте:

0x00000001

Одговор: 0x0000FF02

Објашњење: Инструкције ldr r0, [r1, 1]! као и ldr r0, [r1], 1 мењају садржај R1 повећавањем за 1. Релевантна страница у Cortex-M3 Programming Manual је 62 (3.4.2 LDR and STR, immediate offset).

2. задатак

Посматра се Cortex-M3 процесор са подразумеваном конфигурацијом након ресета. У наставку је дато тренутно стање система описано вредностима које би биле добијене читањем одговарајућих меморијски мапираних регистара:

NVIC_IABR0: 0x00000000
NVIC_ISER0: 0x0000003C
NVIC_ISPR0: 0x0000002E

Колико пута ће бити активиран tail-chaining механизам за претходно наведено стање система под претпоставком да неће пристизати нови захтеви за прекидом?

Одговор: 2

Објашњење: Регистар NVIC_IABR0 (interrupt active bit register) нам каже да нема активних прекида тренутно. Регистар NVIC_ISER0 (interrupt set enable register) нам каже који прекиди су омогућени. Регистар NVIC_ISPR0 (interrupt set pending register) нам каже који прекиди чекају на опслуживање тренутно. Прекиди који могу да се опслуже тренутно су они који су и омогућени и чекају на опслуживање. Уколико урадимо битско И над вредностима NVIC_ISER0 и NVIC_ISPR0 добијамо 0x0000002C, односно 00000000000000000000000000101100 у бинарном, што нам говори да три прекида могу тренутно да се опслуже. Tail-chaining се дешава при прелазу између два прекида, а пошто ће се међу ова три прекида два пута прећи између прекида то је одговор на ово питање.

3. задатак

У наставку је приказан садржај две датотеке: (1) линкерска скрипта и (2) изворни асемблерски код програма. Посматра се извршавање датог програма који је резултат линковања помоћу приказане линкерске скрипте.

Која вредност се налази у регистру R4 у тренутку када ток контроле стигне до адресе указане лабелом leave_sv_call_handler? Одговор унети према формату неозначених хексадецималних литерала у C програмском језику ширине 32 бита.

Линкерска скрипта:

MEMORY
{
    FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 32K
    SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 10K
}
SECTIONS
{
    .vector_table : { *(.vector_table) } > FLASH
    .text : { *(.text*) } > FLASH
}

Изворни асемблерски код програма:

.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

Приказ очекиваног формата одговора за произвољно одабрану вредност 1 јесте:

0x00000001

Одговор: 0x00000001

Објашњење: Последњи регистар који се баца на стек при уласку у прекидну рутину је R0, а пре њега су R1, R2, R3, R12... Пошто дохватамо податак који је 4 бајта изнад стек показивача, а стек показивач показује на последњу заузету локацију, односно R0, садржај који ће бити уписан у R4 ће бити R1.

4. задатак

Исти као трећи задатак са августовског рока 2022. године.

Задатак

Поставка

Користећи алате arm-none-eabi-*, build-tools и Eclipse CDT и Proteus симулатор потребно је испунити ставке које се налазе у наставку.

  1. [1 поен] Направити нови пројекат у Proteus симулатору у оквиру којег на структурној шеми треба инстанцирати микроконтролер STM32F103R6, неопходне компоненте и извршити одговарајућа повезивања за успешно покретање симулације.
  2. [3 поена] Написати Makefile којим је могуће превести, асемблирати и повезати пројекат са изворним кодом написаним на C програмском језику у циљу добијања извршног кода у ELF формату (подразумевани формат излаза GNU асемблера и линкера) и Intel Hex формату. Makefile мора бити написан тако да испуњава следећа два услова: (1) додавање нових датотека са изворним кодом у пројекат не захтева експлицитно писање нових правила (измене другог типа у Makefile су дозвољене) и (2) све резултате и међурезултате процеса превођења, асемблирања и повезивања пројекта треба сместити у засебан поддиректоријум.
    Студенти којима претходно наведена два услова представљају препреку могу прећи на израду наредних ставки тако што ће написати рудиментаран Makefile чији рецепти садрже команде за превођење односно асемблирање сваке појединачне датотеке са изворним кодом и остале пратеће команде за повезивање читавог пројекта. Број поена за ову ставку у случају писања рудиментарног Makefile износи један поен од максималних три поена предвиђених за ову ставку.
  3. [3 поена] Написати линкерску скрипту која се може искористити приликом повезивања објектних датотека, које садрже секције са кодом, подацима без иницијалних вредности и подацима са иницијалним вредностима, у циљу добијања извршног кода односно меморијске мапе. Извршни код добијен на основу написане линкерске скрипте мора се исправно извршавати на микроконтролеру STM32F103R6 и након губитка напајања. Приликом писања линкерске скрипте могуће је усвојити претпоставку о постојању одговарајућих симбола (секција са садржајем IVT, улазна тачка програма итд.) у оквиру улазних објектних датотека.
  4. [3 поена] Написати изворни код којим се постиже у потпуности исправна иницијализација микроконтролера STM32F103R6 у општем случају (програм који се извршава садржи код, податке без иницијалних вредности и податке са иницијалним вредностима) при чему је потребно обезбедити исправно извршавање и након губитка напајања.
    Функцију test4 дефинисану у test.c датотеци позвати на самом почетку main функције.
  5. [2 поена] Конфигурисати систем и SCB (System control block) периферију језгра на описани начин. Обезбедити да битови који представљају приоритете изузетака/прекида имају следеће значење: највиша два бита одређују групни приоритет, преостали нижи битови одређују подприоритет. Поставити вредност 0xF0 за приоритет PendSV изузетка. Активирати PendSV изузетак постављањем његовог pending бита на активну вредност.
    Функцију test5 дефинисану у test.c датотеци позвати из PendSV руковаоца изузетком (exception handler) односно прекидне рутине.
  6. [2 поена] Конфигурисати систем и NVIC (Nested vectored interrupt controller) периферију језгра тако да су истовремено остварена следећа три услова: (1) захтеви за обрадом прекида IRQ0, IRQ1 и IRQ2 јесу омогућени, (2) у случају пристизања претходна три захтева у истом тренутку редослед њихове обраде јесте прво IRQ2, затим IRQ0 и на крају IRQ1 и (3) групни приоритет претходна три прекида је идентичан. Активирати IRQ0, IRQ1 и IRQ2 прекиде одједном истовременим постављањем њихових pending битова на активну вредност. Функције test6_irq0, test6_irq1 и test6_irq2 дефинисане у test.c датотеци појединачно позвати из прекидних рутина IRQ0, IRQ1 и IRQ2, респективно (сваку тест функцију позвати само у њој одговарајућој прекидној рутини).
  7. [2 поена] Конфигурисати систем и STK (SysTick timer) периферију језгра тако да буде могуће мерење времена протеклог између позива start и stop функција. Позивом start функције означава се почетни тренутак мерења времена. Позивом stop функције као повратна вредност добија се број милисекунди протеклих од последњег позива start функције. Имплементирати start и stop функције.
    extern void start();
    extern uint32_t stop();
    
    Након исправне конфигурације STK (SysTick timer) периферије језгра, односно када је систем спреман за позив start и stop функција, из main функције позвати функцију test7 дефинисану у test.c датотеци.

Решење

Додаци

Биле су дате датотеке nvic.h, scb.h, stk.h, test.c, test.h и utility.h као из примера прве предиспитне обавезе.

У scb.h је потребно додати следеће:

#define SCB ((SCB_RegisterMapType*) 0xE000ED00)

У nvic.h је потребно додати следеће:

#define NVIC ((NVIC_RegisterMapType*) 0xE000E100)

У stk.h је потребно додати следеће:

#define STK ((STK_RegisterMapType*) 0xE000E010)

#define STK_CTRL_CLKSOURCE (1 << 2)
#define STK_CTRL_TICKINT   (1 << 1)
#define STK_CTRL_ENABLE    (1 << 0)

Тестови

Датотека test.c је изгледала овако:

/*
 *  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();
}

док је датотека test.h изгледала овако:

/*
 *  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_ */

Линкерска скрипта

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);
}

Тестирање

Функционалности предвиђене задатком се тестирају на следећи начин (приказано референтним снимком на првом колоквијуму):

  1. Када се уђе у функцију test4, проверити садржај RAM како би видели да ли се податак VMA:RAM, LMA:FLASH исправно копирао.
  2. Када се уђе у функцију test5, отворити Registers прозор и проверити да ли Pri ред приказује C0.30 као вредност.
  3. Проверити да ли се у test6_irqX функције улази редом IRQ2, IRQ0, IRQ1.
  4. Забележити време које Proteus пријављује када се дође до линије са позивом start, затим наставити до линије са позивом stop и проверити да ли је у time_elapsed променљиву уписана разлика тренутног и забележеног времена у милисекундама.