Существует и еще две важных причины, по которым ОСРВ, приведенная в листинге предыдущей главы, не сможет найти широкого применения. Во-первых, разные задачи могут использовать одни и те же регистры. А при переключении задач РОНы, как и SREG, нужно где-то сохранить. В принципе, этот вопрос можно решить: отвести для каждой задачи свою область памяти и переписывать туда их содержимое. Но такой обработчик потеряет “гибкость”, будет занимать много памяти и медленно выполнятся. Второй, и уже не разрешимой проблемой, окажется то, что в ходе выполнения задачи будет невозможно вызывать подпрограммы. Это легко можно увидеть на следующем примере. Допустим, в задаче 1 встретилась инструкция rcall, после чего адрес возврата из подпрограммы был сохранен в стеке. Через некоторое время возникает прерывание, в работу вмешивается ОСРВ и запускает задачу 2. Если теперь в задаче 2 встреться команда ret, то произойдет переход по последнему адресу, записанному в стеке, т.е. переход в некоторую точку задачи 1. Из всего вышесказанного можно сделать вывод, что для полноценного функционирования ОСРВ, помимо прочих ресурсов, каждая задача должна иметь еще и свой собственный стек для размещения своего контекста (регистров, адресов возврата и т.д.).
ОСРВ, приведенная в листинге ниже, подобно примеру в листинге предыдущей главы также управляет работой двух задач, но с некоторыми изменениями. В ней цикл задержки времени вынесен в отдельную подпрограмму pause, которая теперь может быть доступна для совместного использования. Кроме этого все переменные системы расположены в SRAM. Каждая задача имеет свое адресное пространство памяти данных размером MAMSPACE = 64 б. Задача 1 занимает диапазон адресов 0x060-0x09F, начиная с метки mem1; задача 2 - 0x0A0-0x09F-0x0DF, начиная с mem2.
Рис.1 Состояние памяти двух задач в ОСРВ
а - во время выполнения задачи 1
б - при переключении контекстов задач
в - во время выполнения задачи 2
Состоянием памяти сразу после инициализации программы показано на рис.1а. В данный момент времени активна задача 1. Дно стека совпадает с самым верхним адресом ее адресного пространства(SP = 0x09F). На рис.1б показано, какие действия производит ОС для переключения контекста задачи 1 на контекст задачи 2, в случае если прерывание произошло, когда PC = 0x0280 и SP = 0x009D, т.е. в ходе выполнения подпрограммы задержки времени. Самое первое, что сделает ОС - это сохранит в стеке текущей задачи все РОНы и SREG. Указатель стека при этом переместится до уровня SP = 0x007A. На самом нижнем уровне будет находиться адрес возврата 0x0106, сохраненный при вызове подпрограммы pause. Следующие 2 байта – адрес возврата 0x0281 после выхода из прерывания. Далее 32 РОНа и SREG. Итого общий размер стека составит 37 б. На следующем шаге происходит сохранение SP задачи 1 в ячейках sp1h:sp1l и загрузка в SP из sp2h:sp2l нового значения указателя стека задачи 2. В ходе первого прерывания это значение после инициализации 0x0281. Теперь из стека восстанавливается контекст второй задачи (32 РОН и SREG) и после команды reti программа продолжает выполняться с метки task2 (PC = 0x0200), что является начальным адресом возврата в задачу 2(рис.2в). После того, как истечет время отведенное задаче 2, ОСРВ таким-же образом передаст управление задаче 1 (сохранит контекст задачи 1, сохранит SP задачи 1, загрузит SP задачи 2, восстановит контекст задачи 2) и т.д.
.include "m8def.inc" .equ DELAY = 100 ;задержка времени .equ MEMSPACE = 64 ;размер адресного пространства задачи .def temp = R16 ;регистр для промежуточных операций .def mask = R17 ;временный регистр .def cnt = R18 ;счетчик циклов .def templ = R19 ;регистры для промежуточных операций при .def temph = R20;сохранении и восстановлении адреса возврата .dseg .org 0x060 mem1: .byte MEMSPACE ;адресное пространство задачи 1 mem2: .byte MEMSPACE ;адресное пространство задачи 2 sp1h: .byte 1 ;старший байт указателя стека задачи 1 sp1l: .byte 1 ;младший байт указателя стека задачи 1 sp2h: .byte 1 ;старший байт указателя стека задачи 2 sp2l: .byte 1 ;младший байт указателя стека задачи 1 tsknum: .byte 1 ;ячейка хранения номера текущей задачи .cseg .org 0 rjmp initial .org 0x0009 rjmp service_T0OVER .org 0x0020 initial: clr temp ;чистка SRAM перед началом работы ldi ZH,high(SRAM_START) ldi ZL,low(SRAM_START) ldi YH,high(SRAM_SIZE) ldi YL,low(SRAM_SIZE) mcl: st Z+,temp sbiw YH:YL,1 brne mcl sbi DDRB,DDB0 ;установка линии 0 порта B на вывод sbi DDRB,DDB1 ;установка линии 1 порта B на вывод ldi temp,(1 << CS02)|(1 << CS00) ;включение таймера 0 с out TCCR0,temp ;предделителем F/1024 ldi temp,1 << TOIE0 ;разрешение прерывания по переполнению out TIMSK,temp ;таймера 0 (период T = F/1024/256) ldi temp,low(task2) ;загрузка начального адреса sts mem2+MEMSPACE-1,temp ;возврата task2 = 0x0200 на ldi temp,high(task2) ;дно стека 0x0DF задачи 2 sts mem2+MEMSPACE-2,temp ldi temp,high(mem2+MEMSPACE-36) ;инициализация стека sts sp2h,temp ;задачи 2 на SP = 0x0BD ldi temp,low(mem2+MEMSPACE-35) sts sp2l,temp ldi temp,high(mem1+MEMSPACE-1) ;инициализация стека out SPH,temp ;задачи 1 на SP = 0x09F ldi temp,low(mem1+MEMSPACE-1) out SPL,temp ldi temp,1 ;заносим номер текущей задачи sts tsknum,temp sei ;глобальное разрешение прерываний rjmp task1 ;переходим к выполнению задачи 1 .org 0x0100 task1: ; PC SP in temp,PORTB ldi mask,1 << PB0 eor mask,temp out PORTB,mask ldi cnt,DELAY rcall pause rjmp task1 .org 0x0200 task2: in temp,PORTB ;0x0200 0x00DF <- Выход из прерывания 1 ldi mask,1 << PB1 eor mask,temp out PORTB,mask ldi cnt,DELAY/2 rcall pause rjmp task2 .org 0x0280 pause: dec cnt ;0x0280 0x009D <- Прерывание 1 brne pause ret .org 0x0300 service_T0OVER: push R0 ;сохранение контекста текущей задачи push R1 push R2 push R3 push R4 push R5 . push R26 push R27 push R28 push R29 push R30 push R31 in temp,SREG push temp in temph,SPH in templ,SPL lds temp,tsknum cpi temp,1 brne sr1 sts sp1h,temph ;если прерывание возникло при выполнении sts sp1l,templ ;задачи 1, то сохраняем указатель стека lds temph,sp2h ;задачи 1 в sp1h:sp1l и заносим во lds templ,sp2l ;временные регистры temph:templ указатель ldi temp,2 ;стека задачи 2 rjmp sr2 sr1: sts sp2h,temph ;если прерывание возникло при выполнении sts sp2l,templ ;задачи 2, то сохраняем указатель стека lds temph,sp1h ;задачи 2 в sp2h:sp2l и заносим во lds templ,sp1l ;временные регистры temph:templ указатель ldi temp,1 ;стека задачи 1 sr2: sts tsknum,temp out SPH,temph ;загрузка в стек нового значения out SPL,templ ;из временных регистров temph:templ pop temp out SREG,temp pop R31 pop R30 pop R29 pop R28 pop R27 pop R26 . pop R5 pop R4 pop R3 pop R2 pop R1 pop R0 ;восстановление контекста следующей задачи reti
Выше показан наиболее общий случай, когда контекст каждой задачи сохраняется целиком. В нашем примере, конечно, нет необходимости заботится о сохранении всех 32 регистров. Для работы каждой задачи требуется только 3. Кроме того размер адресного пространства у каждой из задач тоже избыточен поскольку максимальная глубина стека не превысит 8 б.
Перейти к следующей части: Особенности работы ОСРВ
Комментарии (0) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация