Операционная система для микроконтроллеров с ядром AVR.
AvrOS v.1.0
Назначение.
Операционная система AvrOS v.1.0 для микроконтроллеров с ядром AVR предназначена для выполнения следующих функций:
Обслуживание задействованных встроенных и внешних устройств микроконтроллера.
Запуск задач на выполнение по некоторому событию. (Внешнее прерывание, прерывание от таймера и т.д.).
Поддержка протоколов связи различных уровней (MicroLAN, RS232 и т.п.).
Обслуживание задач реального времени.
Общая структура системы.
Операционная система условно подразделяется на несколько модулей (уровней):
Уровень аппаратного обеспечения (hardware level, HL). Файлы, относящиеся к этому уровню хранятся в каталоге ../source/hl. Данный уровень обеспечивает инициализацию и взаимодействие ПО со встроенными и внешними устройствами микроконтроллера. Все остальные уровни общаются с аппаратным обеспечением только через уровень HL (за исключением встроенных портов микроконтроллера, с ними все уровни могут общаться непосредственно).
Уровень задач реального времени (real-time level, RTL). Файлы, относящиеся к этому уровню хранятся в каталоге ../source/rtl. Данный уровень обеспечивает запуск задач реального времени (по событию от таймера, внешнего прерывания и т.п.). Задачи, запущенные в данном уровне наиболее приоритетные. Они не могут быть прерваны другими задачами. Исключение – когда задача уровня RTL запускает задачи более высоких уровней как процессы. Процессы (в отличии от задач RTL могут быть прерваны другими процессами и задачами, но имеют защиту от повторного вхождения). Программист так же может разрешить прерывания в задаче уровня RTL, но в этом случае он должен предусмотреть сам защиту от повторного вхождения в эту задачу. (см. описание макроса settask(name)).
Уровень операционной системы (operating-system level, OSL). Файлы, относящиеся к этому уровню хранятся в каталоге ../source/osl. Данный уровень обеспечивает запуск процессов не-реального времени. Но при этом запуск может быть осуществлен по событию с уровня RTL , например по таймеру. Например, уровень OSL обеспечивает поддержку высокоуровневых протоколов (например, уровень RTL обеспечивает прием-передачу пакетов по линии связи RS232, а уровень OSL обеспечивает разбор принятых пакетов и формирование ответа). Так же этот уровень содержит различные вызовы, не имеющие отношения к реального времени, например – получение кода нажатой клавиши, библиотеки вывода чисел и символов на индикатор, преобразования форматов.
Уровень программ пользователя (users program level, UPL). Файлы, относящиеся к этому уровню хранятся в каталоге ../source/upl. Этот уровень по приоритетам выполнения процессов абсолютно равноправен с уровнем OSL , но отделен от него, поскольку в каталоге ../source/upl находится программа пользователя. Пользователь так же может запускать свои задачи по тем же событиям и с теми же приоритетами, как и на уровне OSL. На уровне программ пользователя находится точка входа в программу void main().
Каталог ../source/system стоит особняком. В нем хранятся общесистемные настройки и макросы. (Например тактовая частота кварцевого генератора контроллера, параметры использования АЦП, таймеров и т.п.). Для каждого ресурса системы, за исключением памяти, должен быть определен разрешающий символ, иначе ресурс и все с ним связанное (макросы, процедуры и т.п.) просто не включаются в состав программы. Например, чтобы использовать таймер 0, достаточно в файле res_timers.h написать строчку: #define TIMER0_ENABLED. Этого достаточно, чтобы таймер 0 инициализировал ся и запустился. В этом же файле задаются остальные параметры таймера 0 (частота прерываний, делитель и т.п.). Для каждого ресурса (или нескольких сходных ресурсов) выделен свой файл с именем вида res_<resname>.h, где resname – название ресурса. Ресурсом может быть не только аппаратная, но и программная часть системы. Например, файл res_mlan.h содержит все настройки протокола MicroLAN, в том числе и разрешающий символ.
Приведем назначение файлов описания ресурсов:
res_led.h - описание настроек модуля поддержки светодиодных индикаторов, соединенного с контроллером однопроводным интерфейсом (аналогичному MicroLAN).
res_pwm.h - описание настроек модуля поддержки ШИМ (на таймере1)
res_uart.h - описание настроек модуля поддержки UART.
res_key.h - описание настроек модуля поддержки клавиатуры.
res_adc.h - описание настроек модуля поддержки АЦП.
res_extio.h - описание настроек модуля поддержки внешних портов ввода- вывода (1554АП5, 1554ИР23).
res_ints.h - описание настроек модуля поддержки внешних прерываний.
res_mlan.h - описание настроек модуля поддержки протокола обмена по однопроводному интерфейсу.
res_timers.h - описание настроек модуля поддержки встроенных таймеров (реализована пока для таймера 0, используемого в качестве системного таймера).
sysdef.h - описание всех модулей, а так же некоторых параметров системы (тактовой частоты кварца, например).
Добавление нового модуля в систему.
Программист-пользователь AVROS может добавлять в нее новые модули. Рассмотрим, как это делается.
Предположим, нам надо ввести поддержку ШИМ. Это несложно.
Шаг1. Принимаем решение на каком уровне будет размещен модуль. Если модуль напрямую взаимодействует с аппаратурой – то он должен располагаться в каталоге hl (уровень аппаратного обеспечения). В нашем случае это так и есть.
Шаг2. Пишем драйвер, который сохраним в файле hl/userpwm.c. Общая структура файла hl/userpwm.c следующая:
#ifdef USERPWM_ENABLED
// код драйвера
void userpwm_init(){/*код инициализации*/}
....
#endif /* USERPWM_ENABLED */
Шаг2. Пишем заголовочный файл hl/userpwm.h. Этот файл – обязателен. В нем должен быть определен макрос userpwm_init_mac(). Это лучше делать следующим образом.
#ifdef USERPWM_ENABLED
#define userpwm_init_mac() userpwm_init()
// Все определения для драйвера, которые постоянны
...
#else
#define userpwm_init_mac()
#endif /* USERPWM_ENABLED */
Макрос userpwm_init_mac() испоьлзуется для автоматического вызова процедуры инициализации драйвера. Если даже драйвер не используется или процедура инициализации не нужна, то этот макрос все равно должен быть определен, хоть он и пустой. Общее правило определения имени макроса начальной инициализации
#define <имя_модуля>_init_mac() <Имя процедуры инициализации>(), где
<имя_модуля> - имя *.h файла, который описывает модуль. (В нашем случае userpwm, т.к. файл называется userpwm.h).
<Имя процедуры инициализации> - имя процедуры инициализации, описанной в файле с кодом драйвера (В нашем случае это файл userpwm.c, процедура void userpwm_init()).
Шаг 3. Создаем файл system/res_userpwm.h. В этом файле обязательно в начале должна быть строчка #define USERPWM_ENABLED. Если эту строчку закомментировать, то все, связанное с нашим драйвером не будет включено в код программы. Таким образом, чтобы добавить или удалить модуль из программы, достаточно раскомментировать или закомментировать всего одну строчку в файле res_<имя модуля>.h в каталоге system.
Общая структура файла system/res_userpwm.h следующая:
#define USERPWM_ENABLED /* Закомментировать для отключения модуля */
#ifdef USERPWM_ENABLED
// Описание всех определений, которые часто изменяются (например разрядность ШИМ,
// частота ШИМ в нашем случае и т.п.)
#endif /* USERPWM_ENABLED */
Шаг 4. Добавляем в файл system/sysdef.h строчку #include “res_userpwm.h”. Этим действием мы включаем в систему все определения из файла system/res_userpwm.h.
Шаг 5. Открываем Makefile. Там есть строчки примерно такого вида
# Hardware level
HL-SRC = timer0.c int0.c adc0.c extports.c uart0.c pwm.c
HL-OBJ = hl.o
# Real-Time Level
RTL-SRC = rt_task.c mlans.c mlan.c
RTL-OBJ = rtl.o
# Operating-System Level
OSL-SRC = syscall.c led_mlan.c key.c
OSL-OBJ = osl.o
# User Program Level
UPL-SRC = main.c
UPL-OBJ = upl.o
Поскольку наш новый модуль находится в каталоге hl, то нетрудно догадаться что для
его добавления надо изменить строчку
HL-SRC = timer0.c int0.c adc0.c extports.c uart0.c pwm.c
на
HL-SRC = timer0.c int0.c adc0.c extports.c uart0.c pwm.c userpwm.c
То есть просто добавить имя файла с исходными текстами нашего нового модуля в список файлов с исходными текстами того уровня, где расположен наш модуль.
Вуаля ! Можете компилировать вашу новую систему и наслаждаться.
Вызов процедур пользователя по системному таймеру.
После запуска системы прерывания всегда разрешены и периодически приходит прерывания от системного таймера. Пользователь может добавлять процедуры своих модулей для вызова их по прерыванию. Для этого надо открыть файл rtl_mac.h (для модулей уровней upl и osl) или hl_mac.h (для модулей уровня rtl), раскомментировать в нем одну из строчек и добавить в нее имя своей процедуры, которая должна вызываться по таймеру.
Для модулей уровней upl и osl доступны следующие периоды вызова процедур:
- 0.1 сек (вид #define UPL_TASK_100MS_xx или #define OSL_TASK_100MS_xx)
- 1сек (вид #define UPL_TASK_1S_xx или #define OSL_TASK_1S_xx)
- 1минута (вид #define UPL_TASK_1M_xx или #define OSL_TASK_1M_xx)
- 1час. (вид #define UPL_TASK_1H_xx или #define OSL_TASK_1H_xx)
Для модулей уровня rtl доступны следующие периоды вызова процедур:
- FMAX (вид #define T_INT_MAX_xx )
- 0.1 сек (вид #define T_INT_100MS_xx)
- 1сек (вид #define T_INT_1S_xx)
- 1минута (вид #define T_INT_1M_xx)
- 1час. (вид #define T_INT_1H_xx)
Частота FMAX – максимально возможная частота прерываний таймера. (определяется как F_CLK (частота кварца) деленная на количество тактов, через которые таймер посылает прерывания. Или FMAX= F_CLK/TIMER0_DEVIDER. Параметр F_CLK должен быть корректно указан в файле system/sysdef.h, а параметр TIMER0_DEVIDER – в файле system/res_timers.h.
Процедуры защищены от повторного вхождения, но для корректной работы системы все же лучше соразмерять время выполнения процедуры, вызываемой по таймеру с периодом ее вызова.
Приведем пример как создать эффект “бегущий огонь” на плате NedoPC-90.8535.
Заходим в каталог upl.
Пишем файл main.c (или изменяем его):
//----------------------------------------------------------------------
#define LEDPORT PORTB /*светодиодный порт*/
#define LEDDDR DDRB /*регистр управления этим портом*/
//----------------------------------------------------------------------
char sled = 0x01; // 1 в том бите, где зажжен светодиод
// Процедура, которая будет вызываемой по таймеру с периодом 1сек
void ttask_leds_shift()
{sled = sled << 0x01; // Сдвигаем 1 влево на 1 разряд
if(sled > 0x0F){sled=0x01;} // Если 1 вышла за пределы младших 4х бит, возвращаем ее на место
output(LEDPORT,(input(LEDPORT) | 0x0F) & (~sled));_NOP();_NOP(); // Изменяем состояния порта
}
//----------------------------------------------------------------------
// Основная программа
int main() {AUTOINIT_ALL_MODULES();
// Инициализируем светодиодный порт
output(LEDPORT,input(LEDPORT) | 0x0F);_NOP();_NOP();
output(LEDDDR,input(LEDDDR) | 0x0F);_NOP();_NOP();
//
while(1){}
//
return(0);
}
//----------------------------------------------------------------------
Пишем файл main.h (или изменяем его):
//----------------------------------------------------------------------------
// File: main.h
// Contain headers for main.c
//----------------------------------------------------------------------------
void ttask_leds_shift();
//----------------------------------------------------------------------------
Заметьте, что имя процедуры, вызываемой по прерыванию, ОБЯЗАТЕЛЬНО должно быть указано в заголовочном файле. Иначе – ошибка компиляции.
Пишем файл rtl_mac.h (или изменяем его):
//----------------------------------------------------------------------
// Макросы вызова процедур по таймеру уровня UPL
//----------------------------------------------------------------------
// 100ms
//#define UPL_TASK_100MS_0()
//#define UPL_TASK_100MS_1()
//#define UPL_TASK_100MS_2()
//#define UPL_TASK_100MS_3()
//----------------------------------------------------------------------
// 1s
#define UPL_TASK_1S_0() ttask_leds_shift()
//#define UPL_TASK_1S_1()
//#define UPL_TASK_1S_2()
//#define UPL_TASK_1S_3()
//----------------------------------------------------------------------
// 1minute
//#define UPL_TASK_1M_0()
//#define UPL_TASK_1M_1()
//#define UPL_TASK_1M_2()
//#define UPL_TASK_1M_3()
//----------------------------------------------------------------------
// 1hour
//#define UPL_TASK_1H_0()
//#define UPL_TASK_1H_1()
//#define UPL_TASK_1H_2()
//#define UPL_TASK_1H_3()
//----------------------------------------------------------------------
Можете компилировать вашу новую систему и наслаждаться тем, как красиво бегает огонек.
Описание отдельных модулей пока не готово, но продолжение следует.