Администрация форума не несёт ответственности за достоверность информации и оставляет за собой право редактировать или в особых случаях даже удалять посты без предупреждения. Спасибо за понимание.

Программирование ATMEL в BASCOM.

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Программирование ATMEL в BASCOM. » ! Санькины ПЛЮШКИ ! » Кооперативная ОС реального времени AQUA RTOS для BASCOM AVR


Кооперативная ОС реального времени AQUA RTOS для BASCOM AVR

Сообщений 31 страница 43 из 43

31

Cirno_9 написал(а):

Ставите обработчик, код которого вызывает сервис ОС Сигнализировать.

имеется ввиду OS_WaitEvent? как это поможет если в сам обработчик диспетчер через раз пускает?

0

32

Новые грабли с этой ОС, теперь упёрся в приём данных через USART ((

0

33

Kasym написал(а):

Новые грабли с этой ОС, теперь упёрся в приём данных через USART ((

А в чем грабли?

В начале кода:

declare sub UART_GetChar()
dim evEVENT as byte

' *** объявляем событие; включаем прерывание, которое будет принимать символы из UART

evEVENT = OS_CreateEvent()   
on URXC UART_GetChar
enable URXC

' *** пишем обработчик прерывания UART:

sub UART_GetChar
   bUART_CHAR = inkey()                                                   ' get one symbol 
   ' *** some code *** какая-то обработка
   OS_SignalEvent evEVENT ' сигналим ОС, когда произошли заданные условия (совпадение строки и т.п.)
end sub

' *** какая-то задача ждет указанных условий, т.е. сигнала о событии

sub Task_TASK()
   do
      ' *** some code ***
      OS_WaitEvent evEVENT
      ' *** some code ***
   loop
end sub

Просто переключите голову на событийно-ориентированный подход.

Тут при старте ОС или где-то еще дальше по необходимости стартует задача Task_TASK, которая сразу же приостанавливает свое исполнение, ожидая события evEVENT (управление передается диспетчеру).
Прерывание порта принимает символы и ведет какую-то обработку и чаще всего представляет собой конечный автомат с несколькими состояниями и графом переходов.
Например,  он ожидает первый байт синхропоследовательности, затем переходит в состояние "ожидать второй байт", и если следующий принятый байт является вторым синхро-байтом, автомат чистит буфер и переходит в состояние "принимать строку".
Теперь он будет копить принятые символы в буфер до наступления каких-то условий (стоп-байт, достижение заданной длины буфера и т.п.). Затем могут быть и другие состояния типа проверки CRC.
И, наконец, если все в порядке, процедура обработки сигналит ОС о том, что строка принята, событием evEVENT.
После выхода из прерывания ОС ставит задачу Task_TASK в очередь на исполнение, и когда она подойдет, задача может продолжиться.
Очевидно, тут ей самое время разобрать принятую строку и выполнить какие-то действие (в кооперативной ОС, если это что-то довольно сложное -- выдать сообщение, просигналить о событии или непосредственно разрешить исполнение задаче, "привязанной" к данной строке).

Подобная схема покрывает большинство практических потребностей работы с UART.

Отредактировано Cirno_9 (2020-10-15 10:10:24)

+1

34

БАГФИКС

Обнаружила крайне неприятный логический баг в шедулере, из-за которого остановленная задача рестартовала не сначала, а продолжала исполнение как приостановленная.

Для исправления необходимо в процедуре sub OS_Sheduler() найти строку if task_state(task_current) <> OSTS_STOP or task_state(task_current) <> OSTS_RESTART then и заменить or на and

С наступающим! )

+1

35

Я решил попробовать Вашу ОС.  Скопировал текст ОС в отдельный файл. Взял первый пример из Вашей статьи.   Не компилирует - выдает ошибку:

Error : 323   Line :   184   Label too long [OSERR_CANT_FIND_TASK OR OSERR_CRITICAL]  , in File : OS_AQUA\OS_AQUA_RTOS.bas
..... ...

У меня Bascom AVR  2.0.7.1

0

36

Вы на какой версии Bascom обкатывали ОС  ??? 

Баг из прошлого поста обошел.
Добавил еще одну переменную :
dim OS_tmp1 as Byte 
Макрос исправил:   

Код:
macro MGetTaskIdByLabel
   for task_id = 1 to task_count                            ' по адресу задачи в task_label находит ее task_id в системе или возвращает 0
      if task_entry(task_id) = task_label then exit for
   next
   if task_id > task_count then
      OS_tmp1  = OSERR_CANT_FIND_TASK or OSERR_CRITICAL
      OS_RaiseErr OS_tmp1
   end if
end macro

Теперь вылез новый баг в OS_Sheduler(): 
Строка:    if task_state(task_queue(task_id)) <> OSTS_READY then continue 
Ошибка: Unknown statement [CONTINUE]   
т.е. Что происходит после  THEN  ???

И ещё из примера 1

Unknown statement [TOOGLE LED_1]
Unknown statement [TOOGLE LED_2]

Отредактировано SV12 (2021-01-12 10:33:28)

0

37

SV12 написал(а):

У меня Bascom AVR  2.0.7.1

Текущая врсия 2.0.8.3.

SV12 написал(а):

Ошибка: Unknown statement [CONTINUE]

Оператор continue добавлен в версии 2081.

0

38

Эх.. придется искать версию 2081.  А там посмотрим ... .

Отредактировано SV12 (2021-01-12 12:15:04)

0

39

SV12 написал(а):

Эх.. придется искать версию 2081.  А там посмотрим ... .

Отредактировано SV12 (Сегодня 09:15:04)

На цепочку сообщений о багах в BASCOM лучше не смотреть: после первого там лезет всякое невообразимое, типа "все пошло прахом".
Версию 2.8.0.3 раздают какие-то арабские товарищи.

0

40

В продолжение темы операционных систем
Очень хотел бы поблагодарить автора этой темы. С ее подачи понял как работать с симулятором в Баском и начал осваивать Ассемблер
Вот что из этого вышло) Псевдопараллельная многозадачность

Код:
$nocompile
Config Submode = Old

Const Всего_задач = 4                                       'указываем сколько всего задач (не больше и не меньше!!!)
Const Стек_под_задачу = 60                                  'сколько байт стека отдать под задачу (не меньше 40!!!)

Const Ожидает = 1
Const Готова = 0

Dim Указатель_стека(всего_задач) As Word                    'здесь хранится смещение стека задач
Dim Статус_задачи(всего_задач) As Byte                      '7 бит статус, 5-4-3 биты приоритет,2-1-0 биты счетчик пропущенных циклов
Dim Пауза_задачи(всего_задач) As Word                       'максимально 65 сек
Dim Указатель As Word                                       'буфер для вычислений указателя стека
Dim Указатель_hi As Byte At Указатель + 1 Overlay
Dim Указатель_lo As Byte At Указатель Overlay
Dim Адрес As Word
Dim Адрес_hi As Byte At Адрес + 1 Overlay
Dim Адрес_lo As Byte At Адрес Overlay
Dim Буфер As Word
Dim Буфер_hi As Byte At Буфер + 1 Overlay
Dim Буфер_lo As Byte At Буфер Overlay
Dim Номер_задачи As Byte

Config Timer0 = Counter , Prescale = 64 , Clear Timer = 1
On Compare0a Диспетчер Nosave
 'прерывание по сравненю таймера (1/16)*64= 4мксек на 1 значение
Ocr0a = 249
Enable Compare0a
Enable Interrupts
Stop Timer0

Declare Sub Добавить_задачу(bylabel Адрес_задачи As Word , Byval Номер As Byte , Byval Приоритет As Byte , Byval Статус As Byte)
Declare Sub Запуск_диспетчера(byval Запуск_задачи As Byte)
Declare Sub Изменить_статус_задачи(byval Номер As Byte , Byval Приоритет As Byte , Byval Статус As Byte)
Declare Sub Wait_ms(byval Пауза As Word)



Goto Rtos_m1

Sub Запуск_диспетчера(запуск_задачи)
    'забираем точку входа сюда,чтобы не наследить
    !pop R15
    !pop R15
    'подгружаем область стека с номера задачи, которую нужно запустить первой
    Номер_задачи = Запуск_задачи
    Указатель = Указатель_стека(номер_задачи)               'достаем указатель нужной задачи
    Указатель = Указатель + 33                              'прибавляемем резерв (т.к. стек растет с конца)
    !ldS R15,{Указатель_lo}                                 'под еще не сохраненные регистры
    !ldS R16,{Указатель_hi}
    !OUT SPL,R15
    !OUT SPH,R16
    Start Timer0                                            'с этого момента можно запустить таймер
    'и т.к. указатель стека перемещен на адрес нужной нам задачи
    'выход отсюда будет переходом в ее начало
End Sub

Sub Wait_ms(byval Пауза As Word)
    !CLI                                                    'запретили прерывания
    Пауза_задачи(номер_задачи) = Пауза
    Goto Диспетчер
End Sub

Sub Добавить_задачу(адрес_задачи , Номер , Приоритет , Статус)
    'сохраняем адрес возврата отсюда
    !POP R15
    !POP R16
    !STS {Буфер_hi},R15
    !STS {Буфер_lo},R16
    'размечаем память под задачу
    Номер_задачи = Номер                                    'задаем задаче номер
    Shift Статус , Left , 7                                 'делаем статус самым старшим 7 битом
    Shift Приоритет , Left , 3                              'а приоритет 3-4-5 битами
    Статус = Статус + Приоритет                             'получаем 7 бит статус, 6 бит свободный
    Статус_задачи(номер_задачи) = Статус                    '5-4-3 биты приоритет, 2-1-0 биты для диспетчера
    Адрес = Адрес_задачи                                    'получаем удвоеный адрес задачи (тут разработчиков нужно
    Shift Адрес , Right , 1                                 'брать за ноги и бить об стену,чтобы не делить вот так постоянно)
    !LDS R17,{Адрес_lo}                                     'помещаем его в область стека этой задачи
    !PUSH R17
    !LDS R17,{Адрес_hi}
    !PUSH R17
    !IN R17,SPL                                             'и сохраняем указатель стека для этой задачи
    !IN R18,SPH
    !STS {Указатель_lo},R17
    !STS {Указатель_hi},R18
    Указатель = Указатель - 33                              'манипуляции под 32 POH + SREG
    Указатель_стека(номер_задачи) = Указатель
    Указатель = Указатель + 33
    Указатель = Указатель - Стек_под_задачу                 'и сколько вообще необходимо оставить стека (минимум 33!!!)
    'готовим область стека под следующую задачу
    !ldS R17,{Указатель_lo}
    !ldS R18,{Указатель_hi}
    !OUT SPL,R17
    !OUT SPH,R18
    'загружаем адрес возврата и валим отсюда
    !lds R15,{Буфер_hi}
    !LDS R16,{Буфер_lo}
    !PUSH R16
    !PUSH R15
End Sub

Sub Изменить_статус_задачи(номер , Приоритет , Статус)
    !CLI
    Shift Статус , Left , 7                                 'делаем статус самым старшим 7 битом
    Shift Приоритет , Left , 3                              'а приоритет 5-4-3 битами
    Статус = Статус + Приоритет                             'получаем 7 бит статус, 6 бит свободный
    Статус_задачи(номер) = Статус                           '5-4-3 биты приоритет, 2-1-0 биты для диспетчера
    !SEI
End Sub

Диспетчер:

'============================================================================
'сохраняем все регистры (R0...R31,SREG) текущей задачи
'============================================================================
!CLI
!PUSH R0
!PUSH R1
!PUSH R2
!PUSH R3
!PUSH R4
!PUSH R5
!PUSH R6
!PUSH R7
!PUSH R8
!PUSH R9
!PUSH R10
!PUSH R11
!PUSH R12
!PUSH R13
!PUSH R14
!PUSH R15
!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
!in R16,sreg
!PUSH R16
!SEI
'============================================================================
'сохраняем указатель для последующего входа
'============================================================================
!in R30,spl
!in R31,SPH
!lds R16,{Номер_задачи}                                     'прямая загрузка из переменной в регистр
!ADD R16,R16                                                'суммирование
!LDI R17,0                                                  'загрузить константу
!clr R18                                                    'очистить регистр(скоро он пригодится)
'Получаем Адрес Первой Переменной В Массиве
Loadadr Указатель_стека(1) , X
'высчтываем смещение в массиве
!ADD R26,R16                                                'суммирование
!ADC R27,R17                                                'суммирование с переносом
!st -X,R31                                                  'косвенное сохранение с пре-декрементом
!st -X,R30                                                  'косвенное сохранение с пре-декрементом
'============================================================================
'проверяем нужно ли декрементировать таймеры (было ли совпадение TIMER0)
'============================================================================
 '!!! В А Ж Н О !!! регистр TIFR и биты разные в разных МК и способе прерывания по таймеру!!!
!SBIc TIFR0,1                                               'если совпадение было, пропускаем следующий шаг
!RJMP Следующая_задача                                      'пропускаем декрементацию,если здесь не по прерыванию
'============================================================================
'уменьшаем переменную времени для каждой задачи, если это необходимо
'========= декремент всех переменных WORD в массиве =========================
!CLR R18                                                    'очищаем R18 (из-за rjmp может быть не очищен)
!ldi R19,Всего_задач                                        'загрузить константу в регистр
!ADD R19,R19                                                'суммирование
Next_i:
 'получаем адрес первой переменной в массиве
Loadadr Пауза_задачи(1) , X
 'высчтываем смещение в массиве
!ADD R26,R19                                                'суммирование
!ADC R27,R17                                                'суммирование с переносом
 'забираем данные из массива
 'старший байт
!LD R31,-X                                                  'косвенная загрузка с пре-декрементом
 'младший байт
!LD R30,-X                                                  'косвенная загрузка с пре-декрементом
!cpi R30,0                                                  'сравнение с константой
!cpc R31,R18                                                'сравнение с переносом
!breq пропускаем                                            'перейти, если равно
Декрементация:
!sbiw R30,1                                                 'вычесть константу из слова
!st X+,R30                                                  'косвенное сохранение с пост-инкрементом
!st X,R31                                                   'косвенное сохранение
Пропускаем:
!SUBI R19,2                                                 'вычетание константы
!BRNE Next_i                                                'перейти если не равно
'============================================================================
'выбтраем следующую готовую к выполнению задачу
'============================================================================
Следующая_задача:
'========= если меньше константы инкрементируем, если больше то = 1 =======
!lds R16,{Номер_задачи}                                     'прямая загрузка из переменной в регистр
!cpi R16, всего_задач                                       'сравниваем регистр с константой
!Brcs счетчик_плюс                                          'перейти если меньше чем (Максимум_задач +1)
!clr R16                                                    'очищаем регистр
Счетчик_плюс:
!inc R16                                                    'инкрементируем
!sts {Номер_задачи}, R16                                    'сохраняем значение регистра в переменной
'============================================================================
'проверяем на паузу (равно ли время паузы нулю)
'================= если значение массива > 0 ================================
!ADD R16,R16                                                'суммирование
'получаем адрес первой переменной в массиве
Loadadr Пауза_задачи(1) , X
 'высчтываем смещение в массиве
!ADD R26,R16                                                'суммирование
!ADC R27,R17                                                'суммирование с переносом
 'забираем данные из массива
 'старший байт
!LD R31,-X                                                  'косвенная загрузка с пре-декрементом
 'младший байт
!LD R30,-X                                                  'косвенная загрузка с пре-декрементом
!cpi R31,0                                                  'сравнение с константой
!cpc R30,R18                                                'сравнение с переносом
!brne Следующая_задача                                      'перейти, если не равно
'============================================================================
'проверяем на статус (равно ли нулю)
'================= если значение массива > 0 ================================
 'получаем адрес первой переменной в массиве
Loadadr Статус_задачи(1) , X
 'высчтываем смещение в массиве
!lds R20,{Номер_задачи}                                     'прямая загрузка из переменной в регистр
!ADD R26,R20                                                'суммирование
!ADC R27,R17                                                'суммирование с переносом
 'забираем данные из массива
!LD R20,-X                                                  'косвенная загрузка с пре-декрементом
 'копируем регистр в нужные нам
!MOV R21,R20
!MOV R22,R20
!MOV R23,R20
 'накладываем маски на копии
!ANDI R21,&B0011_1000
!ANDI R22,&B0000_0111
!ANDI R23,&B1000_0000
'сравниваем статус
!CPI R23,0
!BRNE Следующая_задача
'сравниваем счетчик приоритета (младшие 3 бита)
!CPI R22,0
!BREQ OK
'если не ноль, то декрементируем и сохраняем
!DEC R22
!ADD R22,R21
!ST X+,R22
!RJMP Следующая_задача
'если статус и счетчик приоритета нули, задаем счетчику стартовое значение
Ok:
!ADD R22,R21
!LSR R22
!LSR R22
!LSR R22
!ADD R22,R21
!ST X+,R22
'============================================================================
'восстанавливаем указатель стека следующей задачи
'============================================================================
 'Получаем Адрес Первой Переменной В Массиве
Loadadr Указатель_стека(1) , X
 'высчтываем смещение в массиве
!ADD R26,R16                                                'суммирование
!ADC R27,R17                                                'суммирование с переносом
 'забираем данные из массива
 'старший байт
!LD R31,-X                                                  'косвенная загрузка с пре-декрементом
 'младший байт
!LD R30,-X                                                  'косвенная загрузка с пре-декрементом
!out spl,R30
!OUT SPH,R31
'============================================================================
'восстанавливаем SREG  и остальные регистры
'============================================================================
!CLI
!pop R16
!out sreg,R16
!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
!pop R15
!pop R14
!pop R13
!pop R12
!pop R11
!pop R10
!pop R9
!pop R8
!pop R7
!pop R6
!pop R5
!pop R4
!pop R3
!pop R2
!pop R1
!pop R0
!SEI
 'переходим к нужной задаче
Return

Rtos_m1:

Переключение каждую 1 мс (при 16Мгц) на следующую задачу, сохраняя все действия предыдущей, чтобы вернувшись продолжить начатое. На переключение уходит около 300-400 тактов МК. зависит от количества задач. То есть, при 16МГц, 97% времени уходит на выполнение задачи, 3% на сохранение - переключение - восстановление. Выше частота - меньше процент времени  на переключение.
Разный приоритет у каждой задачи. То есть задача может пропускаться до 7 раз, отдавая свой квант времени другой задаче с более высоким приоритетом
Плюсом, можно управлять статусом каждой задачи в любой момент. То есть остановить задачу, запустить или изменить приоритет

Ну и небольшой бессмысленный пример использования

Код:
$regfile = "attiny88.dat"
$crystal = 16000000
$hwstack = 32                                               'главное со стеком не промахнуться здесь и в файле ОС
$swstack = 300                                              'чтобы хватало задачам и переменные не пересекались с ним                                                                                       '
$framesize = 32

Led Alias Portd.0
Gnd Alias Portd.1
Gnd2 Alias Porta.3
Beep Alias Portb.1
Btn Alias Porta.2

Config Led = Output
Config Beep = Output
Config Gnd = Output : Reset Gnd
Config Gnd2 = Output : Reset Gnd2
Config Btn = Input : Set Btn

$include "RTOS.bas"                                         'добавим файл ОС

Open "comd.2:115200,8,n,1" As Output At #1

Dim X As Word
Dim A As Word

Declare Sub Мырг()
Declare Sub Кнопка()
Declare Sub Порт()
Declare Sub Писк()


Добавить_задачу Мырг , 1 , 3 , Готова                       'задача, ее порядковый номер, приоритет, статус
Добавить_задачу Кнопка , 2 , 1 , Готова                     'порядковый номер от 1 до константы "всего задач"
Добавить_задачу Порт , 3 , 7 , Готова                       'приоритет 0...7,где 0 - не пропускает циклы, 7 - пропускает 7 циклов
Добавить_задачу Писк , 4 , 0 , Ожидает                      '"готова" = 0 - запускается диспетчером
                                                            '"ожидает" = 1 - пропускается диспетчером
Запуск_диспетчера 1                                         'номер задачи, которая запустится первой
End

Sub Мырг
    Do
      Set Led                                               'моргаем диодом ,как будто бы все работает
      Wait_ms 200                                           'чтобы жизнь не казалась сладкой,
      Reset Led                                             'в цикле будут две паузы
      Wait_ms 800
    Loop
End Sub

Sub Кнопка()
    Do
      If Pina.2 = 0 Then                                    'когда кнопка нажата, пускаем в ход задачу "Писк"
         Изменить_статус_задачи 4 , 0 , Готова              'номер задачи, приоритет, статус
      End If
    Loop
End Sub

Sub Порт
    Do
      If X => 10 Then
         A = X Mod 10
         If A = 0 Then Изменить_статус_задачи 4 , 0 , Готова       'пищим каждые 10 сек
      End If
      Incr X
      !CLI                                                  'чтобы с терминалом чудеса не происходили, запрещаем прерывания
      Print#1 , X ; " секунд"                               'от 8 символов по 87мкс на символ при 115200 бод
                                                        'в 1 мс укладываемся, а значит проблем быть не должно
      !SEI                                                  ' не забываем включить прерывания
      Wait_ms 1000
    Loop
End Sub

Sub Писк
    Do
      Sound Beep , 500 , 1500                               'пищит какое то время
      Изменить_статус_задачи 4 , 0 , Ожидает                'и сама себя отключает
    Loop
End Sub

0

41

ОС это классная тема.
Руки чешутся на xmega A1 с внешней памятью запустить разумную вычислительную мощность.
Как оказалось действительно по большей части МК ничего не делает и тут в сложных проектах проще с операционной системой.

0

42

Сам проперся от ее использования))) Третий день балуюсь с ней, нашел пару багов.
1. Как бы мне не хотелось посреди подпрограммы прерывания (переключения задач) проверять регистр TIFR  на признак прерывания (чтобы узнать как мы в ней, подпрограмме, оказались - было ли это прерывание по таймеру или какая-то из задач досрочно отдала свой остаток времени, вызвав переключение), он всегда в нулях. Потому что сбрасывается сам, как только произошел переход по прерыванию. Обошел используя две точки входа в подпрограмму. Первая от прерывния, в ней в регистре SREG взводим пользовательский бит "Т" и следом вторая точка с основной подпрограммой переключения. И уже в подпрограмме проверяем не TIFR , а SREG. Естественно, задачи вызывают переключение обращаясь ко второй метке. Выглядит теперь так:

Доработанный код ОС

$nocompile
Config Submode = Old

Const Всего_задач = 3                                       'указываем сколько всего задач (не больше и не меньше!!!)
Const Стек_под_задачу = 50                                  'сколько байт стека отдать под задачу (не меньше 40!!!)

Const Ожидает = 1
Const Готова = 0

Dim Указатель_стека(всего_задач) As Word                    'здесь хранится смещение стека задач
Dim Статус_задачи(всего_задач) As Byte                      '7 бит статус, 5-4-3 биты приоритет,2-1-0 биты счетчик пропущенных циклов
Dim Пауза_задачи(всего_задач) As Word                       'максимально 65 сек
Dim Указатель As Word                                       'буфер для вычислений указателя стека
Dim Указатель_hi As Byte At Указатель + 1 Overlay
Dim Указатель_lo As Byte At Указатель Overlay
Dim Адрес As Word
Dim Адрес_hi As Byte At Адрес + 1 Overlay
Dim Адрес_lo As Byte At Адрес Overlay
Dim Буфер As Word
Dim Буфер_hi As Byte At Буфер + 1 Overlay
Dim Буфер_lo As Byte At Буфер Overlay
Dim Номер_задачи As Byte

'Config Timer0 = Timer , Prescale = 64 : On Timer0 Диспетчер Nosave
'Enable Timer0

'прерывание по сравненю таймера (1/16)*64= 4мксек на 1 значение
Config Timer0 = Counter , Prescale = 64 , Clear Timer = 1
On Compare0a Диспетчер Nosave
Ocr0a = 249
Enable Compare0a
Enable Interrupts
Stop Timer0

Declare Sub Добавить_задачу(bylabel Адрес_задачи As Word , Byval Номер As Byte , Byval Приоритет As Byte , Byval Статус As Byte)
Declare Sub Запуск_диспетчера(byval Запуск_задачи As Byte)
Declare Sub Изменить_статус_задачи(byval Номер As Byte , Byval Приоритет As Byte , Byval Статус As Byte)
Declare Sub Wait_ms(byval Пауза As Word)

Goto Rtos_m1

Sub Запуск_диспетчера(запуск_задачи)
    'забираем точку входа сюда,чтобы не наследить
    !pop R15
    !pop R15
    'подгружаем область стека с номера задачи, которую нужно запустить первой
    Номер_задачи = Запуск_задачи
    Указатель = Указатель_стека(номер_задачи)               'достаем указатель нужной задачи
    Указатель = Указатель + 33                              'прибавляемем резерв (т.к. стек растет с конца)
    !ldS R15,{Указатель_lo}                                 'под еще не сохраненные регистры
    !ldS R16,{Указатель_hi}
    !OUT SPL,R15
    !OUT SPH,R16
    Start Timer0                                            'с этого момента можно запустить таймер
    'и т.к. указатель стека перемещен на адрес нужной нам задачи
    'выход отсюда будет переходом в ее начало
End Sub

Sub Wait_ms(byval Пауза As Word)
    !CLI
    Пауза_задачи(номер_задачи) = Пауза
    Goto Вызов_диспетчера
End Sub

Sub Добавить_задачу(адрес_задачи , Номер , Приоритет , Статус)
    'сохраняем адрес возврата отсюда
    !POP R15
    !POP R16
    !STS {Буфер_hi},R15
    !STS {Буфер_lo},R16
    'размечаем память под задачу
    Номер_задачи = Номер                                    'задаем задаче номер
    Shift Статус , Left , 7                                 'делаем статус самым старшим 7 битом
    Shift Приоритет , Left , 3                              'а приоритет 3-4-5 битами
    Статус = Статус + Приоритет                             'получаем 7 бит статус, 6 бит свободный
    Статус_задачи(номер_задачи) = Статус                    '5-4-3 биты приоритет, 2-1-0 биты для диспетчера
    Адрес = Адрес_задачи                                    'получаем удвоеный адрес задачи (тут разработчиков нужно
    Shift Адрес , Right , 1                                 'брать за ноги и бить об стену,чтобы не делить вот так постоянно)
    !LDS R17,{Адрес_lo}                                     'помещаем его в область стека этой задачи
    !PUSH R17
    !LDS R17,{Адрес_hi}
    !PUSH R17
    !IN R17,SPL                                             'и сохраняем указатель стека для этой задачи
    !IN R18,SPH
    !STS {Указатель_lo},R17
    !STS {Указатель_hi},R18
    Указатель = Указатель - 33                              'манипуляции под 32 POH + SREG
    Указатель_стека(номер_задачи) = Указатель
    Указатель = Указатель + 33
    Указатель = Указатель - Стек_под_задачу                 'и сколько вообще необходимо оставить стека (минимум 33!!!)
    'готовим область стека под следующую задачу
    !ldS R17,{Указатель_lo}
    !ldS R18,{Указатель_hi}
    !OUT SPL,R17
    !OUT SPH,R18
    'загружаем адрес возврата и валим отсюда
    !lds R15,{Буфер_hi}
    !LDS R16,{Буфер_lo}
    !PUSH R16
    !PUSH R15
End Sub

Sub Изменить_статус_задачи(номер , Приоритет , Статус)
    !CLI
    Shift Статус , Left , 7                                 'делаем статус самым старшим 7 битом
    Shift Приоритет , Left , 3                              'а приоритет 5-4-3 битами
    Статус = Статус + Приоритет                             'получаем 7 бит статус, 6 бит свободный
    Статус_задачи(номер) = Статус                           '5-4-3 биты приоритет, 2-1-0 биты для диспетчера
    !SEI
End Sub

Диспетчер:
'помечаем, что вошли сюда по прерыванию
!set
Вызов_диспетчера:
'============================================================================
'сохраняем все регистры (R0...R31,SREG) текущей задачи
'============================================================================
'запрещаем прерывания
!CLI
'сохраняемся
!PUSH R0
!PUSH R1
!PUSH R2
!PUSH R3
!PUSH R4
!PUSH R5
!PUSH R6
!PUSH R7
!PUSH R8
!PUSH R9
!PUSH R10
!PUSH R11
!PUSH R12
!PUSH R13
!PUSH R14
!PUSH R15
!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
!in R16,sreg
!PUSH R16
'============================================================================
'сохраняем указатель для последующего входа
'============================================================================
!in R30,spl
!in R31,SPH
!lds R16,{Номер_задачи}                                     'прямая загрузка из переменной в регистр
!ADD R16,R16                                                'суммирование
!LDI R17,0                                                  'загрузить константу
!clr R18                                                    'очистить регистр(скоро он пригодится)
'Получаем Адрес Первой Переменной В Массиве
Loadadr Указатель_стека(1) , X
'высчитываем смещение в массиве
!ADD R26,R16                                                'суммирование
!ADC R27,R17                                                'суммирование с переносом
!st -X,R31                                                  'косвенное сохранение с пре-декрементом
!st -X,R30                                                  'косвенное сохранение с пре-декрементом
'============================================================================
'проверяем нужно ли декрементировать таймеры (было ли совпадение TIMER0)
'============================================================================
'так как по прерыванию мы взводим бит "Т" в SREG, его и проверяем
!BRTC Следующая_задача
'============================================================================
'уменьшаем переменную времени для каждой задачи, если это необходимо
'========= декремент всех переменных WORD в массиве =========================
!ldI R19,Всего_задач                                        'загрузить константу в регистр
!ADD R19,R19                                                'суммирование
Next_i:
'получаем адрес первой переменной в массиве
Loadadr Пауза_задачи(1) , X
'высчитываем смещение в массиве
!ADD R26,R19                                                'суммирование
!ADC R27,R17                                                'суммирование с переносом
'забираем данные из массива
'старший байт
!LD R31,-X                                                  'косвенная загрузка с пре-декрементом
'младший байт
!LD R30,-X                                                  'косвенная загрузка с пре-декрементом
!cpi R30,0                                                  'сравнение с константой
!cpc R31,R18                                                'сравнение с переносом
!breq пропускаем                                            'перейти, если равно
!sbiw R30,1                                                 'вычесть константу из слова
!st X+,R30                                                  'косвенное сохранение с пост-инкрементом
!st X,R31                                                   'косвенное сохранение
Пропускаем:
!SUBI R19,2                                                 'вычетание константы
!BRNE Next_i                                                'перейти если не равно
'============================================================================
'выбираем следующую готовую к выполнению задачу
'============================================================================
Следующая_задача:
'========= если меньше константы инкрементируем, если больше то = 1 =======
!lds R16,{Номер_задачи}                                     'прямая загрузка из переменной в регистр
!cpi R16, всего_задач                                       'сравниваем регистр с константой
!Brcs счетчик_плюс                                          'перейти если меньше чем (Максимум_задач +1)
!clr R16                                                    'очищаем регистр
Счетчик_плюс:
!inc R16                                                    'инкрементируем
!sts {Номер_задачи}, R16                                    'сохраняем значение регистра в переменной
'============================================================================
'проверяем на паузу (равно ли время паузы нулю)
'================= если значение массива > 0 ================================
!ADD R16,R16                                                'суммирование
'получаем адрес первой переменной в массиве
Loadadr Пауза_задачи(1) , X
'высчитываем смещение в массиве
!ADD R26,R16                                                'суммирование
!ADC R27,R17                                                'суммирование с переносом
'забираем данные из массива
'старший байт
!LD R31,-X                                                  'косвенная загрузка с пре-декрементом
'младший байт
!LD R30,-X                                                  'косвенная загрузка с пре-декрементом
!cpi R31,0                                                  'сравнение с константой
!cpc R30,R18                                                'сравнение с переносом
!brne Следующая_задача                                      'перейти, если не равно
'============================================================================
'проверяем на статус и приоритет (равно ли нулю)
'================= если значение массива > 0 ================================
'получаем адрес первой переменной в массиве
Loadadr Статус_задачи(1) , X
'высчитываем смещение в массиве
!lds R20,{Номер_задачи}                                     'прямая загрузка из переменной в регистр
!ADD R26,R20                                                'суммирование
!ADC R27,R17                                                'суммирование с переносом
'забираем данные из массива
!LD R20,-X                                                  'косвенная загрузка с пре-декрементом
'копируем регистр в нужные нам
!MOV R21,R20
!MOV R22,R20
!MOV R23,R20
'накладываем маски на копии
!ANDI R21,&B0011_1000                                       'заданный приоритет (3-4-5 биты)
!ANDI R22,&B0000_0111                                       'счетчик приоритета (0-1-2 биты)
!ANDI R23,&B1000_0000                                       'статус задачи (7 бит)
'сравниваем статус
!CPI R23,0
!BRNE Следующая_задача
'сравниваем счетчик приоритета (младшие 3 бита)
!CPI R22,0
!BREQ OK
'если не ноль, то декрементируем и сохраняем
!DEC R22
!ADD R22,R21
!ST X+,R22
!RJMP Следующая_задача
'если статус и счетчик приоритета нули, задаем счетчику стартовое значение
Ok:
!ADD R22,R21
!LSR R22
!LSR R22
!LSR R22
!ADD R22,R21
!ST X+,R22
'============================================================================
'восстанавливаем указатель стека следующей задачи
'============================================================================
'Получаем Адрес Первой Переменной В Массиве
Loadadr Указатель_стека(1) , X
'высчитываем смещение в массиве
!ADD R26,R16                                                'суммирование
!ADC R27,R17                                                'суммирование с переносом
'забираем данные из массива
'старший байт
!LD R31,-X                                                  'косвенная загрузка с пре-декрементом
'младший байт
!LD R30,-X                                                  'косвенная загрузка с пре-декрементом
!out spl,R30
!OUT SPH,R31
'============================================================================
'восстанавливаем SREG  и остальные регистры
'============================================================================
!pop R16
!out sreg,R16
!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
!pop R15
!pop R14
!pop R13
!pop R12
!pop R11
!pop R10
!pop R9
!pop R8
!pop R7
!pop R6
!pop R5
!pop R4
!pop R3
!pop R2
!pop R1
!pop R0
'снимаем метку о прерывании
!CLT
'разрешаем прерывания
!SEI
'переходим к нужной задаче
Return

Rtos_m1:

2. Если нет готовой задачи (все на паузе Wait_ms) то пауза не закончится никогда, так как прерывания выключены (таймер паузы не уменьшается), а диспетчер в бесконечном цикле ищет задачу готовую к выполнению, чтобы ее загрузить из стека и, включив прерывания, запустить. Но, почесав затылок, понял, что это не баг, а,  скорее, фича - можно запустить в цикле лишнюю задачу с "IDLE". Тогда, МК будет "дремать" во время простоев, просыпаясь каждую миллисекунду проверить не истекло ли время паузы задач.
Ради теста набросал код, где МК выключает светодиод, когда спит, и включает когда что-то делает. В задачу ему дал раз в 1 секунду читать сообщения от GPS модуля, обновлять координаты (выводятся 10 раз в секунду) и раз в 5 секунд выводить их в терминал. Светодиод лишь вспыхивал на короткое время. И то, софт UART, приходится на время приема или передачи отключать прерывания.
До этого тестировал на барометре BMP180  и 7-seg LED индикаторе (просто потому что все уже спаяно, пол года трудится как уличный термометр-барометр),  где раз в 50 мс опрашивается давление, из него высчитывается высота и выводится на индикатор (вывод на индикатор в бесконечном цикле), раз в 1 сек опрашивается температура для поправки коэффициентов и раз в 20 сек на индикатор выводится на 1 сек температура  и на 1 сек давление.  Изредко было замечено мерцание индикатора. Скорее всего, потому что отображение построено с waitms-ами (не стал переделывать код) , а они растягиваются во времени кратно количеству задач и приоритетности.

З.Ы Хоть и ресурсы ОСь расходует, на практике мне показалось, что за счет отсутствия многих условий и проверок, присущих линейному коду, ресурсы расходуются, наоборот, экономичнее, а написание алгоритмов ускоряется ввиду отсутствия необходимости прослеживать цепочку событий через весь код.

Отредактировано Nec (2023-02-26 11:04:10)

+2

43

Наверное есть смысл добавить сохранение и восстановление RAMPZ, для МК с памятью >64 kb

    !Push R16
    !In R16 , Sreg
    !Push R16

    #if _romsize > 128000
        !in R16 , RAMPZ
        !Push R16
        !clr R16
        !out RAMPZ , R16
    #endif

...............

    #if _romsize > 128000
        !pop R16
        !out RAMPZ  , R16
    #endif

    !pop R16
    !Out Sreg , R16

+2


Вы здесь » Программирование ATMEL в BASCOM. » ! Санькины ПЛЮШКИ ! » Кооперативная ОС реального времени AQUA RTOS для BASCOM AVR