[an error occurred while processing this directive]
|
Итак, здесь пример переключателя 2^n задач. Ниже жестко прописано для 2 задач.
Микроконтроллер - AT90S2313. Кварц вроде бы я спользовал 8 МГц-вый, частота влияет
на нулевой таймер, который по совместительству и отсчитывает кванты времени для
любой задачи (см. настройки таймера0). Поскольку 2313 имеет уж очень мало ресурсов,
(хотя это не помешало забацать на нём софтUSB) то память ОЗУ жестко разбита вручную
(мы же на асме пишем). Поскольку дело было очень давно, то точные значения в hex я уже
вспомнить не могу, поэтому обрисую картинку в общем.
1) Задачи 2, т.к. ОЗУ всего 128 байт - каши не сваришь. При использовании 8535
у меня было 8 задач.
2) Для каждой задачи определяется контекст, это просто означает, что для задачи
выделяется область ОЗУ определённого размера. По-моему, я использовал размер 32 байта.
Контекст - это всё то, что необходимо иметь в задаче (когда на неё передано управление),
чтобы она "думала", что монопольно владеет МК. Полностью конечно так сделать нельзя.
А частично в примере ниже в контексте сохраняются:
- текущее значение PC (нужно иметь в виду, что прямых команд для изменения этого
регистра нет, поэтому см. в таблицу команд в спецификации на МК и выбираем те,
которые модифицируют PC - для меня подошло использование ret и reti);
- текущее значние статус регистра;
- все верхние регистры от R16 до R31 - нижние сохранять не только по-моему не имеет
смысла, но и как это сделать я тогда не знал. Кроме того, как известно, нижние и
верхние регистры не равноценны, а те кто программил на чистом асме под avr, несомненно
в курсе, что иметь для каждой задачи все верхние регистры - это великое счастье.
Оставшиеся от 32 байт выделенной памяти место - стек текущей задачи. Ясный пень,
вложенность ограничена. 32 - 2 (PC) - 1 (SREG) - 16 (Rxx) = 13 делим попалам и получаем
вложенность около 6, либо использовать 13 байт для PUSH/POP'а регистров.
Вот значит, перед началом работы переключателя сначала нужно инициализирвать области
под контексты и кроме того в начало RAM есть 2 ячейки для 2-х адресов - места прерывания
задач Task0 и Task1. Данный переключаель называется неприоритетным. Прерывания у меня
использовали конец RAM при операции со стеком, либо вообще стек не использовали -
нельзя портить в ISR то, что находится в ОЗУ контекстов.
Наверное надо ещё че-нить написать... но не знаю, никогда описанием этой проги не занимался.
Про то, что это подходит под описание "карусельного" переключателя узнал недавно от
Энди Таненбаума - из книжки его по проектированию ОС Minix.
Вот код. Он может быть скомпилен в AVR Studio 4. Если Вы используете последнюю версию,
то поставьте галочку - использовать ASM версии 1, т.к. прога эта писалась в 2003 году и
тогда я пользовался этим убогим Ассемблером. Вторая версия удобнее несомненно.
// Main.asm
; CP-1251 (WIN)
; Таблица векторов прерываний
.cseg
.org 0000
rjmp Reset ; Reset Handle
reti ; IRQ0 Handler
reti ; IRQ1 Handler
reti ; Timer1 Capture Handler
reti ; Timer1 Compare Handler
reti ; Timer1 Overflow Handler
mTaskMngr:
rjmp TIM0_OVF ; Timer0 Overflow Handler
reti ; UART RX Complete Handler
reti ; UDR Empty Handler
reti ; UART TX Complete Handler
.include "inc\2313def.inc"
.include "inc\def.inc"
.include "task0.asm"
.include "task1.asm"
; Действия при перезагрузке
Reset:
ldi _tmp, Low(RAMEND)
mov RAMEndLo, _tmp
out SPL, _tmp
; Начальные установки регистров направления и портов
rcall InitExtInts
rcall InitPorts
rcall InitTimers
; Инициализация задач
rcall InitTask0
rcall InitTask1
clr ZeroReg
clr tSPlo
clr DFlags
clr MsgFlags
clr jTskCnt
; Установка флагов по умолчанию
; ...
; Переход на нулевую задачу
ldi _tmp, 0x8F
out SPL, _tmp
; Начальное значение PC для нулевой задачи
ldi _tmp, Low(Task0)
push _tmp
ldi _tmp, High(Task0)
push _tmp
ClearUpRegs
; Обработчик прерывания от нулевого таймера
TIM0_OVF:
; в стеке находятся два байта PC
; необходимо ещё сохранить значение статус регистра
; и регистров общего назначения
; значение счётчика текущей задачи хранится в jTskCnt
in stmp, SReg
push stmp
push r16
push r17
push r18
push r19
push r20
push r21
push r22
push r23
push r24
push r25
push r26
push r27
push r28
push r29
push r30
push r31
; сохраняем указатель стека текущей задачи
mov TaskCnt, jTskCnt
ldi XH, High(BegCntxAddr)
ldi XL, Low(BegCntxAddr)
andi TaskCnt, 0x01 ; две задачи
mov CntxOfst, TaskCnt
add XL, CntxOfst
adc XH, ZeroReg
in _tmp, SPL
st X, _tmp
inc TaskCnt
; теперь переключаемся на стек следующей задачи
ldi XH, High(BegCntxAddr)
ldi XL, Low(BegCntxAddr)
andi TaskCnt, 0x01 ; две задачи
mov CntxOfst, TaskCnt
add XL, CntxOfst
adc XH, ZeroReg
ld _tmp, X
out SPL, _tmp
mov jTskCnt, TaskCnt
; восстанавливаем все регистры
pop r31
pop r30
pop r29
pop r28
pop r27
pop r26
pop r25
pop r24
pop r23
pop r22
pop r21
pop r20
pop r19
pop r18
pop r17
pop r16
; начальное значение счётчика нулевого таймера
out TCNT0, ZeroReg
pop stmp
out SReg, stmp
reti; TIM0_OVF
InitExtInts:
; ...
ret; InitExtInts
InitPorts:
; ...
ret; InitPorts
InitTimers: =============================================================== // task0.asm ; нулевая задача InitTask0: =============================================================== // task1.asm ; первая задача InitTask1: ================================================================ // def.inc .equ AddrCnt0 = 0x0062 .equ BegCntxAddr = 0x0060 .equ PCntx0Addr = 0x0060 .equ DataAddr = 0x006F ; Старшие регистры .macro sjmp .macro SetFlag .macro ClrFlag .macro ClearUpRegs .macro ConvLow .macro ConvHigh .macro SClkLow .macro SClkHigh ============================================ // task0.inc и task1.inc одинаковы Это проект просто шаблон для исследования переключателя или для новых простеньких проектов. 2 NAUT: Про то, что неактуально я сказал, т.к. 2 года я уже не занимался вообще с тех пор контроллерами и радиотехникой вообще. А вот тянет обратно :) вот посмотрел, что нынче делается, так думаю Вам подскажут что нить более простое в использовании или общеупотребительное. Я по незнанию ваял своё и мало разумел в Си.
E-mail:
info@telesys.ru
ldi _tmp, ((1<
ldi _tmp, ((1<
; начальное значение счётчика нулевого таймера
clr _tmp
out TCNT0, _tmp
; начальное значение счётчика Таймера 1 (~100 мс)
ldi _tmp, 0xFE
out TCNT1H, _tmp
ldi _tmp, 0x79
out TCNT1L, _tmp
; установка битов прерываний от таймеров
ldi _tmp, ((1<
; инициализация программных таймеров
ldi XH, High(BegCntrAddr)
ldi XL, Low(BegCntrAddr)
; начальное значение счётчика 0
ser _tmp
st X+, _tmp
st X+, _tmp
clr _tmp
st X+, _tmp
st X+, _tmp
; начальное значение счётчика 1
ser _tmp
st X+, _tmp
st X+, _tmp
clr _tmp
st X+, _tmp
st X+, _tmp
ret; InitTimers
.include "inc\task0.inc"
.cseg
TASK0:
rjmp Task0
; sjmp Task0 ; прерывание задачи и возврат к менеджеру
; --------------------- End Task0 -----------------------
in tSPlo, SPL
; Указатель на контекст нулевой задачи находится в SRAM
; по адресу 0x0060, размером 2 байта, по меньшему адресу
; младший байт
ldi XH, High(PCntx0Addr)
ldi XL, Low(PCntx0Addr)
; Сохраняем указатель стека нулевой задачи
; Инициализируем указатель контекста нулевой задачи
ldi _tmp, 0x7C
st X, _tmp
ldi _tmp, 0x8F
out SPL, _tmp
; Начальное значение PC для нулевой задачи
ldi _tmp, Low(Task0)
push _tmp
ldi _tmp, High(Task0)
push _tmp
; Сохраняем текущее значение статус регистра
in _tmp, SReg
push _tmp
; Инициализируем контекст нулями
ldi _tmp, 29
mov ZeroReg, _tmp
clr _tmp
t0Loop0:
push _tmp
dec ZeroReg
brne t0Loop0
out SPL, tSPlo
ret; InitTask0
.include "inc\task1.inc"
.cseg
TASK1:
nop
rjmp Task1
; sjmp Task1 ; прерывание задачи и возврат к менеджеру
; --------------------- End Task1 -----------------------
in tSPlo, SPL
; Указатель на контекст первой задачи находится в SRAM
; по адресу 0x0061, размером 1 байт
ldi XH, High(PCntx1Addr)
ldi XL, Low(PCntx1Addr)
; Сохраняем указатель стека первой задачи
; Инициализируем указатель контекста первой задачи
ldi _tmp, 0x9C
st X, _tmp
ldi _tmp, 0xAF
out SPL, _tmp
; Начальное значение PC для первой задачи
ldi _tmp, Low(Task1)
push _tmp
ldi _tmp, High(Task1)
push _tmp
; Сохраняем текущее значение статус регистра
in _tmp, SReg
push _tmp
; Инициализируем контекст нулями
ldi _tmp, 29
mov ZeroReg, _tmp
clr _tmp
t1Loop0:
push _tmp
dec ZeroReg
brne t1Loop0
out SPL, tSPlo
ret; InitTask1
.equ DelBit0 = 0
.equ DelBit1 = 1
.equ DelBit2 = 2
.equ DelBit3 = 3
.equ DelBit4 = 4
.equ DelBit5 = 5
.equ DelBit6 = 6
.equ DelBit7 = 7
.equ AddrCnt1 = 0x0066
.equ BegCntrAddr = 0x0062
.equ PCntx1Addr = 0x0061
; Младшие регистры
;.def = r0
;.def = r1
;.def = r2
;.def = r3
;.def = r4
;.def = r5
;.def = r6
;.def = r7
.def RAMEndLo = r8
.def tSPlo = r9
.def StatReg = r10
.def ZeroReg = r11
.def CntxOfst = r12
.def stmp = r12
.def DFlags = r13
.def MsgFlags = r14
.def jTskCnt = r15
.def _tmp = r16
.def TaskCnt = r17
;.def = r18
;.def = r19
;.def = r20
;.def = r21
;.def = r22
;.def = r23
;.def = r24
;.def = r25
;.def = r26
;.def = r27
;.def = r28
;.def = r29
;.def = r30
;.def = r31
cli
ldi ZL, Low(@0)
push ZL
ldi ZH, High(@0)
push ZH
; Jump to mTaskMngr
ldi ZL, Low(mTaskMngr)
ldi ZH, High(mTaskMngr)
ijmp
.endm
set
bld @0, @1
.endm
clt
bld @0, @1
.endm
clr r16
clr r17
clr r18
clr r19
clr r20
clr r21
clr r22
clr r23
clr r24
clr r25
clr r26
clr r27
clr r28
clr r29
clr r30
clr r31
.endm
cbi PortB, ConvPin
.endm
sbi PortB, ConvPin
.endm
cbi PortB, SClkPin
.endm
sbi PortB, SClkPin
.endm
;.def = r16
;.def = r17
;.def = r18
;.def = r19
;.def = r19
;.def = r20
;.def = r21
;.def = r22
;.def = r23
;.def = r24
;.def = r25
;.def = r26
;.def = r27
;.def = r28
;.def = r29
;.def = r30
;.def = r31
К этому переключателю идёт ещё целая библиотека подпрограмм, а написание задач - отдельная
песня. Так вот - тут можно вполне считать, что есть 2 более менее устойчивых состояния - задача 0
и задача 1. Прерываться они могу и по Таймеру0 и могу самостоятельно покидать свой цикл,
который в примере не показан. Я вообще дальше я "построил" очери сообщений и завязал их
с интерфейсом и получил нечто вроде Windows'а :) А основа - клавиатурный "автомат" как тут SM
описал. Только это уже на 8535 делано было.
Ответы
Перейти к списку ответов
|||
Конференция
|||
Архив
|||
Главная страница
|||
Содержание