АИС «Диспетчер» — универсальная российская система мониторинга промышленного оборудования и персонала, которая позволяет контролировать работу любого оборудования и рабочих мест, обеспечивая объективными данные все системы управления предприятием.
Корпорация «Галактика» и компания «Цифра» 25 марта 2019 года объявили о старте стратегического партнерства в области продвижения решений Индустрии 4.0 в отечественной промышленности. В рамках партнерства компании планируют осуществить бесшовную интеграцию цифровых решений для производственных предприятий и осуществлять их совместное продвижение в рамках единого стека. Подробнее здесь.
Интеграция систем «Галактика ЕАМ» и «Диспетчер» позволит повысить надежность работы оборудования: автоматизировать контроль наработки, получаемой в режиме реального времени, планировать ТОиР на основе оперативной информации о техническом состоянии станочного парка, объективно оценивать остаточный ресурс, выявлять причину выхода оборудования из строя и предупреждать поломки оборудования, — считает Александр Бургардт, вице-президент корпорации «Галактика», руководитель дирекции ЕАМ. |
(Данные актуальны на ноябрь 2017 года)
Мониторинг производства
Оптимизация производства
Управление простоями
Цифровизация производства
www.tadviser.ru
ИАС «Диспетчер ЖКХ» — программа предназначена для автоматизации работы диспетчерской службы ЖКХ в области приема заявок от населения на любые виды работ и контроля их выполнения. Автоматизация диспетчерского пункта ЖКХ, созданного при УК, ТСЖ, ЖЭУ, РЭУ и других компаний, работающих в этой сфере. ИАС Диспетчер ЖКХ, позволяет вести журналы заявок в электронном виде, производить звукозапись телефонных разговоров, печатать наряды, отмечать выполнение заявок, а также формировать различные отчеты по поступившим заявкам. Если Вас заинтересовал наш программный продукт, напишите или позвоните нам, чтобы мы могли договорится о проведении удаленной презентации системы. Мы ответим на любые Ваши вопросы и покажем как наш программный продукт решает все задачи, связанные с данной сферой деятельности. телефон офиса +7 (4722) 78-14-07 [email protected] или [email protected] http://ciritas.ru Сейчас заканчивается разработка новой версии системы Диспетчер ЖКХ ОНЛАЙН Система полностью расположена в интернете, доступ к ней осуществляется через браузер. Теперь не требуется покупка оборудования или приложения, работать в системе возможно из любого браузера, где бы вы не находились. Система имеет гибкую структуру и позволяет организовать работу как собственного диспетчерского пункта в вашей УК, так и объединенного диспетчерского пункта, с приемом заявок в одном или в нескольких местах, с распределением заявок по УК, по исполнителям, по жилому фонду. Также предусматриватеся интеграция данной системы с ГИС ЖКХ. SaaS система Диспетчер ЖКХ имеет большой ряд преимуществ по сравнению с обычной десктопной версией. Если вам инетерсна данная система, вы можете оставить ваш емеил на сайте системы, и мы обязательно вас уведомим о ее запуске. Сайт системы расположен по адресу http://gkh.city Всем подписавшимся на уведомление о запуске системы на нашем сайте, будет БЕСПЛАТНО предоставлен доступ на срок 6 месяцев. Преимущества нашего программного продукта: — Программа работает более 15 лет. — САМАЯ низкая цена на рынке. — Полностью соответствует законодательству РФ. — Встроенный редактор отчетных форм. — Более 50 готовых базовых отчетных форм. — Постоянно поддерживается и развивается. — Проста и недорога в обслуживании и поддержке. Функционал системы позволяет производить: — Массовый ввод данных — Импорт данных — Прием заявок — Импорт заявок — Автоматизация обратной связи — Звукозапись разговора — Печать нарядов — Обработка нарядов — Просмотр долгов — Отметка материалов — Отображение на карте — Формирование отчетов ТЭГИ: ИАС Диспетчер, ИАС Диспетчер ЖКХ, Диспетчер ЖКХ, купить Диспетчер ЖКХ, диспетчерская ЖКХ, прием заявок, печать нарядов, плановые работы, аварийные работы, услуги ЖКХ, жилищно-коммунальное хозяйство
rutube.ru
www.chaynikam.info
В прошлой части возник вопрос организации программы по задачам. Чтобы можно было разбить программу на кучу независимых частей и не заморачиваться на тот счет, что где то у нас будет затык.
Общая диаграмма работы ОС
Что из себя представляет задача
Это практически то же самое, что и процедура, вызываемая командой RCALL с тремя отличиями:
Сама задача представляет собой обычную процедуру, записанную без каких либо замудреностей.
Распологается там, где обычно прописаны вызываемые процедуры. В этом отношении ничего не поменялось. В принципе, вызывать можно код и из внешнего файла, главное правильно прописать все это в таблице переходов.
В тестовом примере это выглядит так:
Расположение: Trash-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 | ;========================================================= ;Tasks ;========================================================= Idle: RET ; Задача пустого цикла, но ничего ; не мешает сунуть сюда любой код. ; Он будет выполнен. В последнюю очередь. ;------------------------------------------------------- Fire: LDS OSRG,U_B ; Это код задачи "Fire" OUTI UDR,'A' ; Выдрано из реального проекта ; Суть данного кода не важна CBI PORTD,7 ; Поэтому можешь не вникать. Тут NOP ; Может быть абсолютно любой NOP ; код -- код твоей задачи! SBI PORTD,7 ; Если любопытно, то тут обычное LDS Counter,PovCT ; заполнение сдвигового регистра LDPA Lines ; из трех микросхем 74HC164 ; средствами SPI передатчика CLR OSRG ; Оставил его лишь для примера. ; Чтобы наглядно показать, что ADD ZL,Counter ; Из себя представляет задача. ADC ZH,OSRG LPM OSRG,Z+ OUT SPDR,OSRG Wait0: SBIS SPSR,SPIF RJMP Wait0 INC Counter CPI Counter,150 BRSH Clear STS PovCT,Counter RET Clear: CLR Counter STS PovCT,Counter RET ; Выход из задачи только по RET!!! ;------------------------------------------------------- Task2: RET ; Это пустые заглушки. На этом месте ; могла бы быть ваша задача! :) ;------------------------------------------------------- Task3: RET ; Аналогично, надо будет задействую. ;------------------------------------------------------- Task4: RET ; Названия в стиле Task4 тоже живут ; недолго. Обычно переименовываю ;------------------------------------------------------- Task5: RET ; Как с задачей "Fire" ;------------------------------------------------------- Task6: RET ;------------------------------------------------------- Task7: RET ;------------------------------------------------------- Task8: RET ;------------------------------------------------------- Task9: RET |
;========================================================= ;Tasks ;========================================================= Idle: RET ; Задача пустого цикла, но ничего ; не мешает сунуть сюда любой код. ; Он будет выполнен. В последнюю очередь. ;——————————————————- Fire: LDS OSRG,U_B ; Это код задачи «Fire» OUTI UDR,’A’ ; Выдрано из реального проекта ; Суть данного кода не важна CBI PORTD,7 ; Поэтому можешь не вникать. Тут NOP ; Может быть абсолютно любой NOP ; код — код твоей задачи! SBI PORTD,7 ; Если любопытно, то тут обычное LDS Counter,PovCT ; заполнение сдвигового регистра LDPA Lines ; из трех микросхем 74HC164 ; средствами SPI передатчика CLR OSRG ; Оставил его лишь для примера. ; Чтобы наглядно показать, что ADD ZL,Counter ; Из себя представляет задача. ADC ZH,OSRG LPM OSRG,Z+ OUT SPDR,OSRG Wait0: SBIS SPSR,SPIF RJMP Wait0 INC Counter CPI Counter,150 BRSH Clear STS PovCT,Counter RET Clear: CLR Counter STS PovCT,Counter RET ; Выход из задачи только по RET!!! ;——————————————————- Task2: RET ; Это пустые заглушки. На этом месте ; могла бы быть ваша задача! :) ;——————————————————- Task3: RET ; Аналогично, надо будет задействую. ;——————————————————- Task4: RET ; Названия в стиле Task4 тоже живут ; недолго. Обычно переименовываю ;——————————————————- Task5: RET ; Как с задачей «Fire» ;——————————————————- Task6: RET ;——————————————————- Task7: RET ;——————————————————- Task8: RET ;——————————————————- Task9: RET
Таблица переходов
После всего кода задач распологается таблица переходов и код самой ОС:
Расположение: Trash-rtos.asm — главный файл программы, в самом низу, в конце ПЗУ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ;======================================================================== ; RTOS Here ;======================================================================== .include "kerneldef.asm" ; Настройки ядра - переменные и ряд макросов. .include "kernel.asm" ; Подклчюаем ядро ОС. ;Это таблица переходов. TaskProcs: .dw Idle ; [00] Она содержит в себе реальные адреса задач .dw Fire ; [01] Как видишь, 0 тут у задачи пустого цикла, .dw Task2 ; [02] 01 у "Fire", ну и дальше .dw Task3 ; [03] По порядку. .dw Task4 ; [04] .dw Task5 ; [05] .dw Task6 ; [06] .dw Task7 ; [07] .dw Task8 ; [08] .dw Task9 ; [09] |
;======================================================================== ; RTOS Here ;======================================================================== .include «kerneldef.asm» ; Настройки ядра — переменные и ряд макросов. .include «kernel.asm» ; Подклчюаем ядро ОС. ;Это таблица переходов. TaskProcs: .dw Idle ; [00] Она содержит в себе реальные адреса задач .dw Fire ; [01] Как видишь, 0 тут у задачи пустого цикла, .dw Task2 ; [02] 01 у «Fire», ну и дальше .dw Task3 ; [03] По порядку. .dw Task4 ; [04] .dw Task5 ; [05] .dw Task6 ; [06] .dw Task7 ; [07] .dw Task8 ; [08] .dw Task9 ; [09]
Причем, в таблице задач не обязательно должны быть разные задачи. Можно делать одну, но на разные ячейки, например так:
1 2 3 4 5 6 7 8 9 10 | TaskProcs: .dw Idle ; [00] .dw Fire ; [01] .dw Task2 ; [02] .dw Task3 ; [03] .dw Task4 ; [04] .dw Fire ; [05] .dw Task6 ; [06] .dw Idle ; [07] .dw Idle ; [08] .dw Task9 ; [09] |
TaskProcs: .dw Idle ; [00] .dw Fire ; [01] .dw Task2 ; [02] .dw Task3 ; [03] .dw Task4 ; [04] .dw Fire ; [05] .dw Task6 ; [06] .dw Idle ; [07] .dw Idle ; [08] .dw Task9 ; [09]
Это иногда бывает удобно.
Таблица переходов нужна для того, чтобы можно было любому адресу в программе дать адрес-смещение относительно начала таблицы перехода. То есть, теперь, чтобы перейти на Task4 нам не нужно знать точный адрес этой задачи, достаточно лишь знать, что ее адрес записан в таблице переходов в четвертой ячейке. Адрес начала таблицы переходов у нас фиксированный, поэтому просто прибавляем к нему смещение (равное номеру задачи*2) и берем оттуда искомый адрес. Благодаря этому, мы можем в очереди задач держать не двубайтные адреса переходов, а однобайтные номера под которыми эти адреса размещены в таблице.
А чтобы не путаться под каким номером какая задача спрятана, то введем для них символическое обозначение:
Расположение: kerneldef.asm — файл макроопределений ядра
1 2 3 4 5 6 7 8 9 10 | .equ TS_Idle = 0 ; Просто нумерация. Не более того .equ TS_Fire = 1 ; Зато теперь можно смело отправлять в очередь .equ TS_Task2 = 2 ; задачу с именем TS_Fire и не париться на счет того, .equ TS_Task3 = 3 ; что запишется что то не то. .equ TS_Task4 = 4 ; Тут все по порядку, жестко привязано к ячейкам таблицы! .equ TS_Task5 = 5 ; Так что если в таблице и можно делать одинаковые задачи, .equ TS_Task6 = 6 ; то тут у них идентификаторы должны быть разные!!! .equ TS_Task7 = 7 ; А имена можно придумывать любые, они не привязаны ни к чему, .equ TS_Task8 = 8 ; Главное самому не забыть что где. .equ TS_Task9 = 9 |
.equ TS_Idle = 0 ; Просто нумерация. Не более того .equ TS_Fire = 1 ; Зато теперь можно смело отправлять в очередь .equ TS_Task2 = 2 ; задачу с именем TS_Fire и не париться на счет того, .equ TS_Task3 = 3 ; что запишется что то не то. .equ TS_Task4 = 4 ; Тут все по порядку, жестко привязано к ячейкам таблицы! .equ TS_Task5 = 5 ; Так что если в таблице и можно делать одинаковые задачи, .equ TS_Task6 = 6 ; то тут у них идентификаторы должны быть разные!!! .equ TS_Task7 = 7 ; А имена можно придумывать любые, они не привязаны ни к чему, .equ TS_Task8 = 8 ; Главное самому не забыть что где. .equ TS_Task9 = 9
Очередь задач.
Логически выглядит как строка в памяти, где каждый байт это номер задачи. Два числа 0 и FF зарезервированы системой. 0 — это Idle, холостой цикл диспетчера. FF — нет задачи, конец очереди.
В коде это выглядит так:
Расположение: Trash-rtos.asm — главный файл программы, самое начало. Где идет разметка ОЗУ
1 2 3 | .DSEG .equ TaskQueueSize = 11 ; Длина очереди задач TaskQueue: .byte TaskQueueSize ; Адрес очереди сотытий в SRAM |
.DSEG .equ TaskQueueSize = 11 ; Длина очереди задач TaskQueue: .byte TaskQueueSize ; Адрес очереди сотытий в SRAM
Длина очереди задается с запасом, чтобы не произошло ее срыва. Располагать ее лучше после остальных данных, чтобы она тянулась навстречу стеку.
Диспетчер задач
Это небольшая процедурка, которая берет из очереди байт, сравнивает его с FF. Если равно, значит очередь пуста и происходит выход в главный цикл. Если же там есть какое либо число отличное от FF, то оно загружается в регистр, происходит вычисление адреса по таблице переходов и прыжок на адрес задачи и выполнение кода. Перед прыжком текущий номер задачи удаляется из очереди, а вся очередь сдвигается на один байт вперед. При следующем заходе в диспетчер все повторяется заново до полного опустошения очереди. При этом в очередь можно добавлять новые задачи.
Главный цикл программы при этом выглядит следующим образом:
Расположение: Trash-rtos.asm — главный файл программы
1 2 3 4 5 | Main: SEI ; Разрешаем прерывания. WDR ; Reset Watch DOG RCALL ProcessTaskQueue ; Обработка очереди процессов (Диспетчер) RCALL Idle ; Простой Ядра RJMP Main |
Main: SEI ; Разрешаем прерывания. WDR ; Reset Watch DOG RCALL ProcessTaskQueue ; Обработка очереди процессов (Диспетчер) RCALL Idle ; Простой Ядра RJMP Main
Сам обработчик очереди несложен. Распологается в файле kernel.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 | ProcessTaskQueue: ldi ZL, low(TaskQueue) ; Берем адрес начала очереди задач ldi ZH, high(TaskQueue) ; Напомню, что это в ОЗУ. ld OSRG, Z ; Берем первый байт (OSRG = R17 рабочий cpi OSRG, $FF ; регистр OS) Сравниваем с FF breq PTQL02 ; Равно? Значит очередь пуста - выход. clr ZH ; Сбрасываем старший байт lsl OSRG ; А взятый номер задачи умножаем на 2 mov ZL, OSRG ; Так как адреса у нас двубайтные, а значит ; И ячейки в таблице перехода двубайтные ; Получается смещение по таблице subi ZL, low(-TaskProcs*2) ; Прибавляем получившееся смещение к адресу sbci ZH, high(-TaskProcs*2) ; начала таблицы переходов. Ну и что, что AVR ; не умеет ; складывать регистр с числом + перенос. ; Зато умеет вычитать, а минус на минус дают плюс! :) ; Математика царица всех наук! ; Теперь в Z у нас адрес где лежит адрес перехода. lpm ; Берем этот адрес! Сначала в R0 mov OSRG, r0 ; Потом в OSRG ld r0, Z+ ; Эта команда ничего ценного на грузит, мы ее применили ; Ради "Z+" чтобы увеличить адрес в Z и взять второй байт ; Целевого адреса по которому мы перейдем. lpm ; Берем в R0 второй байт адреса. mov ZL, OSRG ; А из OSRG перекладываем в ZL mov ZH, r0 ; И из R0 в ZH. Теперь у нас в Z полный адрес перехода. ; Можно драпать, в смысле IJMP - индексный переход по Z ; Но пока рано! Надо же еще очередь в порядок привести! push ZL ; Спрячем наш адрес, наше сокровище, в стек... push ZH ; Глубоко зароем нашу прелесссть.... ; Займемся грязной работой. Продвинем очередь. ldi Counter, TaskQueueSize-1; Загрузим длинну очереди. Иначе мы всю память ldi ZL, low(TaskQueue) ; подвинем. И хапнем в Z начало очереди. ldi ZH, high(TaskQueue) cli ; Запретим прерывания. А то если очередь сорвет получим ; армагедец. PTQL01: ldd OSRG, Z+1 ; Грузим из следующего Z+1 байта и перекладываем st Z+, OSRG ; все в Z, а Z после этого увеличиваем на 1 dec Counter ; Уменьшаем счетчик (там длинна очереди!) brne PTQL01 ; Если не конец, то в цикле. ldi OSRG, $FF ; А если конец, то по последнему адресу записываем FF st Z+, OSRG ; Который является признаком конца очереди. sei ; Разрешаем прерывания. Можно расслабиться pop ZH ; Достаем из стека нашу прелессть... наш адрес перехода pop ZL ; Оба байта, старший и младший. ijmp ; Переходим в задачу!!! ; Обрати внимание - сюда мы пришли по RCALL из главного цикла ; Значит в стеке у нас лежит адрес возврата. А ушли мы в задачу по IJMP ; который стек не меняет. Но это не страшно! Ведь из задачи мы ; выходим по RET! PTQL02: ret |
ProcessTaskQueue: ldi ZL, low(TaskQueue) ; Берем адрес начала очереди задач ldi ZH, high(TaskQueue) ; Напомню, что это в ОЗУ. ld OSRG, Z ; Берем первый байт (OSRG = R17 рабочий cpi OSRG, $FF ; регистр OS) Сравниваем с FF breq PTQL02 ; Равно? Значит очередь пуста — выход. clr ZH ; Сбрасываем старший байт lsl OSRG ; А взятый номер задачи умножаем на 2 mov ZL, OSRG ; Так как адреса у нас двубайтные, а значит ; И ячейки в таблице перехода двубайтные ; Получается смещение по таблице subi ZL, low(-TaskProcs*2) ; Прибавляем получившееся смещение к адресу sbci ZH, high(-TaskProcs*2) ; начала таблицы переходов. Ну и что, что AVR ; не умеет ; складывать регистр с числом + перенос. ; Зато умеет вычитать, а минус на минус дают плюс! 🙂 ; Математика царица всех наук! ; Теперь в Z у нас адрес где лежит адрес перехода. lpm ; Берем этот адрес! Сначала в R0 mov OSRG, r0 ; Потом в OSRG ld r0, Z+ ; Эта команда ничего ценного на грузит, мы ее применили ; Ради «Z+» чтобы увеличить адрес в Z и взять второй байт ; Целевого адреса по которому мы перейдем. lpm ; Берем в R0 второй байт адреса. mov ZL, OSRG ; А из OSRG перекладываем в ZL mov ZH, r0 ; И из R0 в ZH. Теперь у нас в Z полный адрес перехода. ; Можно драпать, в смысле IJMP — индексный переход по Z ; Но пока рано! Надо же еще очередь в порядок привести! push ZL ; Спрячем наш адрес, наше сокровище, в стек… push ZH ; Глубоко зароем нашу прелесссть…. ; Займемся грязной работой. Продвинем очередь. ldi Counter, TaskQueueSize-1; Загрузим длинну очереди. Иначе мы всю память ldi ZL, low(TaskQueue) ; подвинем. И хапнем в Z начало очереди. ldi ZH, high(TaskQueue) cli ; Запретим прерывания. А то если очередь сорвет получим ; армагедец. PTQL01: ldd OSRG, Z+1 ; Грузим из следующего Z+1 байта и перекладываем st Z+, OSRG ; все в Z, а Z после этого увеличиваем на 1 dec Counter ; Уменьшаем счетчик (там длинна очереди!) brne PTQL01 ; Если не конец, то в цикле. ldi OSRG, $FF ; А если конец, то по последнему адресу записываем FF st Z+, OSRG ; Который является признаком конца очереди. sei ; Разрешаем прерывания. Можно расслабиться pop ZH ; Достаем из стека нашу прелессть… наш адрес перехода pop ZL ; Оба байта, старший и младший. ijmp ; Переходим в задачу!!! ; Обрати внимание — сюда мы пришли по RCALL из главного цикла ; Значит в стеке у нас лежит адрес возврата. А ушли мы в задачу по IJMP ; который стек не меняет. Но это не страшно! Ведь из задачи мы ; выходим по RET! PTQL02: ret
Для большей понятности нарисовал диаграммку со стрелочками разными, про то как формируется адрес в таблице переходов:
Задачи кладутся в очередь другой процедурой:
1 2 | ldi OSRG, TS_Task4 ; Запускаем в очередь задачу Task4 rcall SendTask |
ldi OSRG, TS_Task4 ; Запускаем в очередь задачу Task4 rcall SendTask
Делать это можно где угодно, хоть в прерывании, хоть в другой задаче.
Для удобства был написан макрос:
Сама процедура SendTask работает тоже несложно, она всего лишь ставит задачу в очередь.
Расположение: kernel.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 | ; OSRG - Event ; В рабочем регистре ОС - номер задачи SendTask: push ZL ; Сохраняем все что используется push ZH ; в стеке push Tmp2 push Counter in Tmp2,SREG ; Сохраняем значение флагов push Tmp2 ldi ZL, low(TaskQueue) ; Грузим в Z адрес очереди задач. ldi ZH, high(TaskQueue) ldi Counter, TaskQueueSize ; А в счетчик длинну очереди, чтобы ; не начать всю память засаживать. cli ; запрещаем прерывания. SEQL01: ld Tmp2, Z+ ; Грузим в темп байт из очереди cpi Tmp2, $FF ; и ищем ближайшее пустое место = FF breq SEQL02 ; Если нашли, то переходим на сохранение dec Counter ; Либо конец очереди по счетчику. breq SEQL03 rjmp SEQL01 SEQL02: st -Z, OSRG ; Нашли? Сохраняем в очереди номер задачи. SEQL03: pop Tmp2 ; Возвращаем флаги. Если там прерывание было out SREG,Tmp2 ; разрешено, то оно вернется в это значение. pop Counter ; Выходим, достав все заныченное. pop Tmp2 pop ZH pop ZL ret |
; OSRG — Event ; В рабочем регистре ОС — номер задачи SendTask: push ZL ; Сохраняем все что используется push ZH ; в стеке push Tmp2 push Counter in Tmp2,SREG ; Сохраняем значение флагов push Tmp2 ldi ZL, low(TaskQueue) ; Грузим в Z адрес очереди задач. ldi ZH, high(TaskQueue) ldi Counter, TaskQueueSize ; А в счетчик длинну очереди, чтобы ; не начать всю память засаживать. cli ; запрещаем прерывания. SEQL01: ld Tmp2, Z+ ; Грузим в темп байт из очереди cpi Tmp2, $FF ; и ищем ближайшее пустое место = FF breq SEQL02 ; Если нашли, то переходим на сохранение dec Counter ; Либо конец очереди по счетчику. breq SEQL03 rjmp SEQL01 SEQL02: st -Z, OSRG ; Нашли? Сохраняем в очереди номер задачи. SEQL03: pop Tmp2 ; Возвращаем флаги. Если там прерывание было out SREG,Tmp2 ; разрешено, то оно вернется в это значение. pop Counter ; Выходим, достав все заныченное. pop Tmp2 pop ZH pop ZL ret
Архив с исходниками и работающим проектом для ATMega8
З.Ы.
Ухх, ну накатал телегу. Аж самому страшно. А там еще столько же, про таймерную службу, да пошаговые руководства по инсталляции и использованию… Но это после. Пока переварите это.
Продолжение следует…
easyelectronics.ru