Коротенький диспетчер

Понадобился мне небольшой диспетчер. Вот, собственно, результат. Тут, конечно, фиг чего нового придумаешь, но все таки пусть лежит. Нуждается в легком допиливании, ибо добавление задачи сверх положенного сорвет контроллеру крышу, ну и стек в прерывании некисло прогружается. Под катом здоровая простыня кода, можно сразу копировать и запускать. Есть фрагменты кода DI HALT`a, они со мной давно ))


.include "m8def.inc"   /// 	Чuп Mega8 

////////////////// Макросы /////////////////////
.MACRO add_task ///// @0 - метка задачи, @1 - отсрочка в миллисекундах
	PUSH R16
		LDI R16,HIGH(@0)
		STS task_high,R16
		LDI R16,LOW(@0)
		STS task_low,R16
		LDI R16,HIGH(@1)
		STS timer_high,R16
		LDI R16,LOW(@1)
		STS timer_low,R16		
	POP R16
	RCALL add_task_to_queue
.ENDM
.MACRO IncPointer /// @0 - метка указателя, @1 - максимум указателя
	PUSH R16
	PUSH R17
	PUSH R18
		LDS R16,@0 // указатель
		LDI R17,@1 // максимум указателя
		LDI R18,0
		INC R16 // 
		CPSE R16,R17
			MOV R18,R16 	// если не максимум, то в r18 копируем текущее значение указателя. 
					// если максимум, то эта строка будет пропущена, а в r18 останется 0
		STS @0,R18
	POP R18
	POP R17
	POP R16
.ENDM
.macro    OUTI
    ldi    R16,@1
   .if @0 < 0x40
      out    @0,R16        
   .else
      sts      @0,R16
   .endif
.endm

.MACRO PUSHF // это макрос, пихающий в стек все важное
	PUSH	R16
	IN	R16,SREG
	PUSH	R16
	PUSH R17
	PUSH R18
	PUSH R19
	PUSH R20	
	PUSH ZH
	PUSH ZL
	PUSH XH
	PUSH XL
	PUSH YH
	PUSH YL
.ENDM

.MACRO POPF // это макрос, достающий из стека все важное
	POP YL
	POP YH
	POP XL
	POP XH
	POP ZL
	POP ZH	
	POP R20
	POP R19
	POP R18
	POP R17
	POP	R16
	OUT	SREG,R16
	POP	R16
.ENDM

.DSEG ; Сегмент данных

.equ tim1_prescale=1
.IFNDEF F_CPU
	.equ F_CPU=8000000
.ENDIF
.equ tim1_ocr_value=(F_CPU/(tim1_prescale*1000))-1 // это значение регистров сравнения для таймера, таймер в режиме "Сброс по сравнению" (CTC)

///// важный момент. Попытка добавления 33 задачи все угробит.
.equ queue_len=64 // длина очереди, удвоенная - 2 байта на задачу
queue_timers: .byte queue_len // 32 задачи по 2 байта на задачу - значения таймеров
queue_tasks:  .byte queue_len // 32 задачи по 2 байта на задачу - адреса задач
queue_first_free: .byte 1 // первое свободное место, записывается из функции

.equ stack_len=64
queue_stack: .byte stack_len*2 // очередь исполнения - задачи с истекшим таймером, первый зашел, первый вышел. По сути своей, кольцевой буфер. Два байта на задачу.
queue_stack_push_pointer: .byte 1 // указатель записи очереди
queue_stack_pop_pointer: .byte 1 // указатель чтения очереди
task_high: .byte 1 // это переменные для макроса добавления задачи
task_low: .byte 1
timer_high: .byte 1
timer_low: .byte 1

.equ	PULL_UP=1 // типовые значения
.equ	PIN_IN=0
.equ	PIN_OUT=1
.equ	ON=1
.equ	OFF=0

 .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		
		RETI
		.ORG OVF2addr ; Timer/Counter2 Overflow
		RETI
		.ORG ICP1addr ; Timer/Counter1 Capture Event
		RETI
		.ORG OC1Aaddr ; Timer/Counter1 Compare Match A
		rjmp tim1_cmpA
		.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
		RETI
		.ORG UDREaddr ; USART Data Register Empty
		RETI
		.ORG UTXCaddr ; USART, Tx Complete
		RETI
		.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 ; Конец таблицы прерываний

/////////////////////  Прерывания
tim1_cmpA:  // таймер миллисекунды
	pushf // все в стек
	rcall decrement_timers 
	popf // все из стека
reti


////////////////// Стартовая инициализация (с) DiHalt
Reset:   	LDI 	R16,Low(RAMEND)	; Инициализация стека
		    OUT 	SPL,R16			; Обязательно!!!

		 	LDI 	R16,High(RAMEND)
		 	OUT 	SPH,R16
	 
RAM_Flush:	LDI		ZL,Low(SRAM_START)	; Адрес начала ОЗУ в индекс
			LDI		ZH,High(SRAM_START)
			CLR		R16					; Очищаем R16
Flush:		ST 		Z+,R16				; Сохраняем 0 в ячейку памяти
			CPI		ZH,High(RAMEND)		; Достигли конца оперативки?
			BRNE	Flush				; Нет? Крутимся дальше!
 
			CPI		ZL,Low(RAMEND)		; А младший байт достиг конца?
			BRNE	Flush
 
			CLR		ZL					; Очищаем индекс
			CLR		ZH
			CLR		R0
			CLR		R1
			CLR		R2
			CLR		R3
			CLR		R4
			CLR		R5
			CLR		R6
			CLR		R7
			CLR		R8
			CLR		R9
			CLR		R10
			CLR		R11
			CLR		R12
			CLR		R13
			CLR		R14
			CLR		R15
			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

////////////////// Инициализация таймера /////////////////////

OUTI TIMSK,(ON<<OCIE1A) // разрешаем прерывание по сравнению 
OUTI TCCR1B,(1<<WGM12)|(OFF<<CS12)|(OFF<<CS11)|(ON<<CS10) // таймера1 в режим CTC - сброс по сравнению, без предделителя
OUTI OCR1AH,high(tim1_ocr_value) // значение сравнения, обеспечение 1мс
OUTI OCR1AL,low(tim1_ocr_value)

add_task task1,10 // добавляем задачи
add_task task2,20
add_task task3,30


SEI 
////////////////// Основной цикл /////////////////////
Main:	
	LDS R16,queue_stack_push_pointer
	LDS R17,queue_stack_pop_pointer
	CP R16,R17 // если указатели не равны, значит, в очереди есть что исполнять 
	BREQ Main0
		RCALL activate_task	// исполняем первую в очереди задачу с истекшим таймером
	Main0:
RJMP Main

////////////////// Функции /////////////////////

task1:
	add_task task1,10
ret
task2:
	add_task task2,20
ret
task3:
	add_task task3,30
ret



find_place: // функция поиска места для новой задачи
LDI R16,0 // задаем начальное смещение 
find_place_loop:
	LDI ZH,HIGH(queue_timers) // начало - метка queue_timers
	LDI ZL,LOW(queue_timers)
	ADD ZL,R16 // добавляем смещение по таблице
	ADC ZH,R0 // с учетом займа
	LD R17,Z+ // читаем старший бит зачения таймера
	LD R18,Z  // читаем младший бит значения таймера
		CP R17,R0 // сравнение значения таймера с 0
		CPC R18,R0 // с учетом займа
		BRNE find_place_next // если не 0 - уходим на следующую итерацию
			// если таймер нулевой
			LSR R16 // делим смещение на два (два байта на задачу)
			STS queue_first_free,R16 // и сохраняем как пустое место
			RET // все, нашли. Выход обратно.
	find_place_next: // на этой метке окажемся, если таймер нулевой
	INC R16 // следующее значение
	INC R16 // 
	RJMP find_place_loop // зацикливаемся
RET

activate_task:
/// активация задачи из стека
	LDI XH,HIGH(queue_stack) // указатель X на стек задач с истекшим таймером
	LDI XL,LOW(queue_stack) 
	LDS R16,queue_stack_pop_pointer // текуший указатель чтения читаем
	IncPointer queue_stack_pop_pointer,stack_len // инкремент указателя чтения 
	LSL R16 // умножаем на 2 (2 байта на задачу)
	ADD XL,R16 // задаем смещение
	ADC XH,R0
	LD ZH,X+ // читаем адрес задачи на выполнение, обязательно в Z!
	LD ZL,X
	ICALL // ну и запуск того, что лежит по адресу Z
RET

decrement_timers:
// декремент ненулевых таймеров
LDI R16,0
decrement_timers_loop:
	LDI ZH,HIGH(queue_timers)
	LDI ZL,LOW(queue_timers)
	ADD ZL,R16
	ADC ZH,R0
	LD R17,Z+ // старший бит зачения таймера
	LD R18,Z  // младший бит значения таймера
		CP R17,R0
		CPC R18,R0
		BREQ DECREMENT_timers_next // если таймер нулевой, то ничего делать не надо
			// если таймер не нулевой
			SUBI R18,LOW(1) // уменьшаем на 1
			SBCI R17,HIGH(1)
			CP R17,R0
			CPC R18,R0
			BRNE decrement_timers_next_0 // не ноль - ничего не делать
				// таймер обнулился
				// читаем задачу
					LDI XH,HIGH(queue_tasks)
					LDI XL,LOW(queue_tasks)
					ADD XL,R16
					ADC XH,R0
					PUSH R16
					PUSH R17
					PUSH R18
						LDI YH,HIGH(queue_stack)
						LDI YL,LOW(queue_stack)
						LDS R16,queue_stack_push_pointer	
						LSL R16 // *2
						ADD YL,R16
						ADC YH,R0						
						LD R17,X+ // HIGH
						LD R18,X  // LOW
						ST Y+,R17
						ST Y,R18
						IncPointer queue_stack_push_pointer,stack_len
					POP R18
					POP R17
					POP R16
				// переносим в очередь
			decrement_timers_next_0:
			ST Z,R18
			ST -Z, R17
	decrement_timers_next:
	INC R16
	INC R16
	CPI R16,queue_len
	BRNE decrement_timers_loop
RET

add_task_to_queue: // добавление задачи в очередь
	RCALL find_place
	LDI ZH,HIGH(queue_timers)
	LDI ZL,LOW(queue_timers)
	LDS R16,queue_first_free
	LSL R16
	ADD ZL,R16
	ADC ZH,R0
	LDS R19,timer_high
	ST Z+,R19 // запись значения таймера
	LDS R19,timer_low
	ST Z,R19 

	LDI ZH,HIGH(queue_tasks)
	LDI ZL,LOW(queue_tasks)
	ADD ZL,R16
	ADC ZH,R0
	LDS R19,task_high
	ST Z+,R19 // запись значения таймера
	LDS R19,task_low
	ST Z,R19 
RET
/////////////////// EEPROM
			.ESEG		

Комментарии (10)

RSS свернуть / развернуть
Немного не понял роль рег. R0. Используется много, но не сохраняется в обработчике. Почему?
0
Это просто ноль. Вспомогательный. Значение R0 нигде не меняется.
Если конечно я правильно понял
0
верно. По уму еще надо все массивы в инициализации залить нулями. А R0 просто нигде не используется, он и командно только умножением заполняется. В данном случае использован как всегда нулевой регистр.
0
Гм. Тогда я не понимаю как писать пользовательские задачи. Вдруг в них используется/портится R0. Необязательно явно, как результат умножения MUL… и т.п.
0
Или LPM…
0
Не использовать R0 )). Вообще, я младшие 16 регистров уж не помню когда пользовал. Обычно хватает R16:R19. Простенький диспетчер, аха.
0
Но LPM!
0
хм. Я обычно LPM куда-нибудь делаю. Даже и забыл, что можно без указания регистра. Ну да, если так, то есть смысл тогда либо R0 заменить на R2, либо в стек его пихать.
0
Было время, когда все «пиписками» мерялись, потом тактами на вывод пикселя на экран Спектрума, а теперь кто лучше диспетчер на асме для АВРок напишет. Извините ничего личного. Может кому-то навеял воспоминания.
0
ну, не все же сразу за диспетчеры хватаются. Кому-то может пригодиться.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.