Операционная система для микроконтроллеров с ядром AVR.


AvrOS v.1.0

  1. Назначение.

Операционная система AvrOS v.1.0 для микроконтроллеров с ядром AVR предназначена для выполнения следующих функций:

  1. Общая структура системы.

Операционная система условно подразделяется на несколько модулей (уровней):

Уровень RTL.

На данный момент содержит изменяемых ресурсов.

Уровень OSL.

На данный момент содержит изменяемых ресурсов.

Уровень UPL.

Уровень пользователя не может содержать ресурсов системы.


Каталог system.


  1. Добавление нового модуля в систему.

    Программист-пользователь 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. Имя файла обязательно должно иметь формат: res_<имя_модуля>.h, поскольку файл добавляется в систему автоматически. Все файлы в каталоге system, имеющие формат имени res_<имя_модуля>.h при компиляции системы воспринимаются, как заголовочные файлы модулей и будут включены в систему.

    Общая структура файла system/res_userpwm.h следующая:


#define USERPWM_ENABLED /* Закомментировать для отключения модуля */

    #ifdef USERPWM_ENABLED

    // Описание всех определений, которые часто изменяются (например разрядность ШИМ,

    // частота ШИМ в нашем случае и т.п.)

    #endif /* USERPWM_ENABLED */

    Шаг 4. Открываем Makefile. Там есть строчки примерно такого вида

    # Hardware level

    HL-SRC = timer0.c int0.c adc0.c uart0.c pwm.c

    HL-OBJ = hl.o

    # Real-Time Level

    RTL-SRC = rt_task.c

    RTL-OBJ = rtl.o

    # Operating-System Level

    OSL-SRC = syscall.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 uart0.c pwm.c

    на

    HL-SRC = timer0.c int0.c adc0.c uart0.c pwm.c userpwm.c

    То есть просто добавить имя файла с исходными текстами нашего нового модуля в список файлов с исходными текстами того уровня, где расположен наш модуль.

    Вуаля ! Можете компилировать вашу новую систему и наслаждаться.




  1. Вызов процедур пользователя по системному таймеру.

    После запуска системы прерывания всегда разрешены и периодически приходит прерывания от системного таймера. Пользователь может добавлять процедуры своих модулей для вызова их по прерыванию. Для этого надо открыть файл 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()

    //----------------------------------------------------------------------

    Можете компилировать вашу новую систему и наслаждаться тем, как красиво бегает огонек.

  1. Старт уровня пользователя.

    Программы уровня пользователя должны стартовать после инициализации всех модулей более нижних уровней. Для того, чтобы это было так, самой первой строчкой в процедуре int main(); файла upl/main.c должна быть строчка AUTOINIT_ALL_MODULES(); то есть вызов макроса инициализации всех системных уровней.

    Приведем пример процедуры int main();

    int main()

      {AUTOINIT_ALL_MODULES();

      // Код программы пользователя

      .....

      // конец программы пользователя

      return(0);

      }


    Для каждого файла *.c должен существовать заголовочный файл *.h, даже если он пустой. (Например для файла upl/main.c должен существовать файл upl/main.h.)