AVR. Учебный курс. Операционная система. Установка

Распечатать

Ядро у нас есть, теперь осталось это все хозяйство запихать на МК. Для этого всего лишь надо рассовать нужные части кода в исходник. Показывать буду на примере ATmega8. Для других МК разница минимальная. Может быть с таймером что нибудь помудрить придется, но не более того.
Например, недавно, вкорячивал ту же схему на ATmega168, так пришлось подправить иницилизацию таймера — регистры там зовутся по другому. Пришлось изменить макрос OUTI — так как многие привычные уже регистры перестали загружаться через комадну OUT — выпали из диапазона, только через LDS/STS ну и, собственно, все хлопоты. Потратил минут 20 на переименование регистров и заработало.

Итак. Есть у нас совершенно новый пустой файл NewMega8-rtos.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
; Добавляем в него первым же делом инклюдник восьмой меги:
 
		.include "m8def.inc"	; Используем ATMega8
 
; Следом я добавляю файл с моими макроопределениями в котором записаны все символические имена 
; для ресурсов, вроде регистров, портов, отдельных пинов. ИМХО удобней все держать по разным 
; файлам, но тут уже дело за вашими привычками.
 
		.include "define.asm"
 
;Потом файл с макросами, он тоже отдельный и кочует из программы в программу. Именно там всякие 
; левые самодельные команды вроде OUTI прописаны. 
 
		.include "macro.asm"	; Все макросы у нас тут		
 
; Следом идет файл макросов ядра ОС. Он должен распологаться в начале программы, иначе компилятор 
; не поймет. Именно там прописаны макросы таймерной службы, добавления задачи и таймера, там же 
; заныкан стандартный макрос инциализации UART и многое другое. 
 
		.include "kernel_macro.asm"
 
;Дальше прописывается сегмент оперативной памяти в котором заранее определены 
; все очереди задач и таймеров. 
		.DSEG
		.equ 	TaskQueueSize = 11	; Размер очереди событий
TaskQueue: 	.byte 	TaskQueueSize 		; Адрес очереди сотытий в SRAM
		.equ 	TimersPoolSize = 5	; Количество таймеров
TimersPool:	.byte 	TimersPoolSize*3	; Адреса информации о таймерах
 
; Следом уже идет код, начинается кодовый сегмент. Надо заметить, что у меня вся таблица 
; Прерываний спрятана в vectors.asm и вместо таблицы в коде .include "vectors.asm" это удобно
 
		.CSEG
		.ORG 	0x0000		; Проц стартует с нуля, но дальше идут вектора 
		RJMP 	Reset	
 
		.ORG	INT0addr	; External Interrupt Request 0
		RETI
		.ORG	INT1addr	; External Interrupt Request 1
		RETI
 
	.ORG	OC2addr			; Timer/Counter2 Compare Match
	RJMP	OutComp2Int		;<<<<<<<< Прерывание ОС!!!
 
		.ORG	OVF2addr	; Timer/Counter2 Overflow
		RETI
		.ORG	ICP1addr	; Timer/Counter1 Capture Event
		RETI
		.ORG	OC1Aaddr	; Timer/Counter1 Compare Match A
		RETI
		.ORG	OC1Baddr	; Timer/Counter1 Compare Match B
		RETI
		.ORG	OVF1addr	; Timer/Counter1 Overflow
		RETI
		.ORG	OVF0addr	; Timer/Counter0 Overflow
		RETI
		.ORG	SPIaddr		; Serial Transfer Complete
		RETI
		.ORG	URXCaddr	; USART, Rx Complete
		RJMP	Uart_RCV
		.ORG	UDREaddr	; USART Data Register Empty
		RETI
		.ORG	UTXCaddr	; USART, Tx Complete
		RJMP	Uart_TMT
		.ORG	ADCCaddr	; ADC Conversion Complete
		RETI
		.ORG	ERDYaddr	; EEPROM Ready
		RETI
		.ORG	ACIaddr		; Analog Comparator
		RETI
		.ORG	TWIaddr		; 2-wire Serial Interface
		RETI
		.ORG	SPMRaddr	; Store Program Memory Ready
		RETI
		.ORG	INT_VECTORS_SIZE	; Конец таблицы прерываний

После таблицы векторов идут обработчики прерываний. Они короткие, поэтому их размещаю в начале. Первым же обработчиком идет обработчик прерывания от таймера на котором висит таймерная служба ОС. Под таймерную службу желательно отдать самый стремный таймер, на который не завязана ШИМ или еще какая полезная служба. В идеале бы под это дело пустить Timer0, как самый лоховский. Но он не умеет cчитать от 0 до регистра сравнения, только от нуля до 255, впрочем, в обработчик прерывания можно добавить предварительную загрузку таймера0 нужным значением и не расходовать более навороченный таймер. Мне было лень, я повесил все на Таймер2, а в регистр сравнения прописал такое значение, чтобы прерывание было ровно один раз в 1мс. Разумеется, выставив предварительно нужный делитель.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
; Interrupts procs
; Output Compare 2 interrupt  - прерывание по совпадению TCNT2 и OCR2
; Main Timer Service - Служба Таймеров Ядра - Обработчик прерывания
 
OutComp2Int:	TimerService	; Служба таймера OS 
				; Весь код обработчика в виде одного макроса
				; Просто вставил и все. Куда угодно. Можно извратиться
				; Подать импульсы с нужной частотой на какой-нибудь
				; INT0 и службу таймеров повесить на его прерывание
				; Разумеется, в таблице векторов из вектора  прописан 
				;переход сюда	
		RETI		; выходим из прерывания
 
Uart_RCV:	RETI		; Другие прерывания если нужны
Uart_TMT:	RETI

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

1
2
Reset:		OUTI 	SPL,low(RAMEND) 		; Первым делом инициализируем стек
		OUTI	SPH,High(RAMEND)

Все инициализации у меня спрятана в .include «init.asm», но тут я распишу ее полностью.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
; init.asm 
; Очистка памяти
RAM_Flush:	LDI	ZL,Low(SRAM_START)
		LDI	ZH,High(SRAM_START)
		CLR	R16
Flush:		ST 	Z+,R16
		CPI	ZH,High(RAMEND)
		BRNE	Flush
 
		CPI	ZL,Low(RAMEND)
		BRNE	Flush
 
		CLR	ZL
		CLR	ZH
 
; Init RTOS			; В исходнике все сделано 
;		INIT_RTOS	; вот так вот, одним макросом. Макрос описан 
;				; в файле kernel_macro.asm
;				; Но я распишу тут подробно. Там идет настройка
;				; Таймера в работу
 
; Содержимое макроса INIT_RTOS
 
		OUTI 	SREG, 0		; Сброс всех флагов 
 
		rcall ClearTimers	; Очистить список таймеров РТОС
		rcall ClearTaskQueue	; Очистить очередь событий РТОС
		sei			; Разрешить обработку прерываний
 
; Настройка таймера 2 - Основной таймер для ядра
 
		.equ 	MainClock 	= 8000000		; CPU Clock
		.equ 	TimerDivider 	= MainClock/64/1000 	; 1 mS
 
		OUTI 	TCCR2,1<<CTC2|4<<CS20	; Установить режим CTC и предделитель =64
		OUTI 	TCNT2,0			; Установить начальное значение счётчиков
 
 
		ldi OSRG,low(TimerDivider)
		out OCR2,OSRG			; Установить значение в регистр сравнения
; Конец макроса INIT_RTOS
 
 
		OUTI	TIMSK,1<<OCF2 		; Разрешить прерывание по сравнению
 
; Инициализация остальной периферии
		USART_INIT
; Конец init.asm

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

1
Background:	NOP 		; Пока тут ничего нет

Главный цикл. Его надо скопировать без изменений, как есть.

1
2
3
4
5
6
7
8
Main:	SEI				; Разрешаем прерывания.
	wdr				; Reset Watch DOG 
	rcall 	ProcessTaskQueue	; Обработка очереди процессов
	rcall 	Idle			; Простой Ядра
	rjmp 	Main			; Основной цикл микроядра РТОС
 
; В Idle можно сунуть что нибудь простое, быстрое и некритичное.
; Но я обычно оставляю его пустым.

После главного цикла вставляется шаблон под секцию задач. Именно сюда вписывается наш исполняемый код. Тут творится самое интересное

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Idle:		RET
;-----------------------------------------------------------------------------
Task1:		RET
;-----------------------------------------------------------------------------
Task2:		RET
;-----------------------------------------------------------------------------
Task3:		RET
;-----------------------------------------------------------------------------
Task4:		RET
;-----------------------------------------------------------------------------
Task5:		RET
;-----------------------------------------------------------------------------
Task6:		RET
;-----------------------------------------------------------------------------
Task7:		RET
;-----------------------------------------------------------------------------
Task8:		RET
;-----------------------------------------------------------------------------
Task9:		RET
 
; А после секции задач вставляем шаблонную таблицу переходов и код ядра
		.include "kerneldef.asm"	; Подключаем настройки ядра
		.include "kernel.asm"		; Подклчюаем ядро ОС
 
TaskProcs: 	.dw Idle		; [00] 
		.dw Task1		; [01] 
		.dw Task2		; [02] 
		.dw Task3		; [03] 
		.dw Task4 		; [04] 
		.dw Task5		; [05] 
		.dw Task6		; [06] 
		.dw Task7		; [07] 
		.dw Task8		; [08]
		.dw Task9		; [09]

Готово! Можно компилировать, пока это пустой проект. Но ненадолго.

Итак, теперь то же самое, но по пунктам.
Установка AVR OS

  • Создаем пустой проект
  • Вставляем файлы макроопределений
  • Вставляем разметку памяти под очереди задач/таймеров
  • Вставляем таблицу векторов прерываний
  • Прописываем в таблице векторов прерываний переход на обработчик таймера по переполнению
  • Добавляем обработчик прерываний
  • Прописываем стартовую метку и инициализацию стека
  • Инициализация всего что только можно — портов, периферии, обнуление ОЗУ, обнуление очередей, запуск таймера ОС.
  • Добавляем секцию фоновых задач
  • Добавляем код главного цикла
  • Добавляем шаблонную сетку задач
  • Добавляем код ядра и таблицу переходов.
  • Пишем наш код
  • . . .
  • PROFIT

В следующий раз я покажу практический пример работы с этой ОС. В котором будет красочно показано ради чего, собственно, этот геморрой и почему мне он так нравится :)

Запись опубликована в рубрике AVR. Учебный курс с метками , , . Добавьте в закладки постоянную ссылку.

9 комментариев: AVR. Учебный курс. Операционная система. Установка

  1. Medved говорит:

    Вот бы поглядеть на этот геморой в деле!!! Очень хочу увидеть!! ))))

  2. nestandart говорит:

    чем таким красивым так красиво исходники в веб оформляешь ?

  3. goodic говорит:

    для 8-ки код выложить можешь?

  4. Aspiring говорит:

    Я извиняюсь, но по моему здесь выставляется предделитель не 64, а 256
    CSn2 CSn1 CSn0 Description
    0 1 1 clk /64 (From prescaler)
    1 0 0 clk /256 (From prescaler)

    ; Настройка таймера 2 — Основной таймер для ядра
    .equ MainClock = 8000000 ; CPU Clock
    .equ TimerDivider = MainClock/64/1000 ; 1 mS
    OUTI TCCR2,1<<CTC2|4<<CS20 ; Установить режим CTC и предделитель =64 OUTI TCNT2,0 ; Установить начальное значение счётчиков

  5. rumatavz говорит:

    Тут ошибка
    >.equ TimerDivider = MainClock/64/1000 ; 1 mS

    Пусть TimerDivider = 2
    Тогда будет так
    TimerDivider = 0
    задержка
    TimerDivider = 1
    задержка
    TimerDivider = 2
    задержка
    TimerDivider = 0 и прерывание

    Те 3 задержки а не 2, как ожидается. Т.о. при малых величинах TimerDivider частота срабатывания таймера будет отличатся от ожидаемого.

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