====== Первая программа на языке программирования Оберон ====== В [[ob:o7:recordino-flash-stm32|предыдущей заметке]] было описано, как создать новый проект в среде разработки Рекордино, компилировать модуль-заготовку и затем — сформировать файл прошивки и записать его в память микроконтроллера. Теперь разберём детально сам код этого небольшого модуля, чтобы лучше понять идеологию модульного подхода и особенности [[https://online.oberon.org/oberon|языка программирования Оберон]]. После запуска проекта открывается следующий код: MODULE Main; IMPORT SYSTEM, ARMv7M := MicroARMv7M, SysTick0 := MobxARMv7MSTM32SysTick0, MCU := MicroSTM32F4, Pins := MicroSTM32F4Pins, Sys := MicroSTM32F4System; VAR msec: INTEGER; PROCEDURE Setup; BEGIN msec := 0; SysTick0.Init(Sys.HCLK, 1000); Pins.Configure(Pins.C, 13, Pins.output, Pins.pushPull, Pins.medium, Pins.noPull, Pins.AF0); END Setup; PROCEDURE Loop; BEGIN REPEAT IF SysTick0.OnTimer() THEN IF msec = 999 THEN msec := 0 ELSE INC(msec) END; IF msec = 0 THEN SYSTEM.PUT(MCU.GPIOCBSRR, {13}) ELSIF msec = 500 THEN SYSTEM.PUT(MCU.GPIOCBSRR, {13 + 16}) END; END; ARMv7M.WFI UNTIL FALSE END Loop; BEGIN Setup; Loop END Main. Программа прошивки состоит из модулей, как из кирпичиков Лего. **Модуль** всегда начинается с слова ''MODULE'', после которого следует его название. И заканчивается словом ''END'', после которого повторяется название, и завершается точкой. MODULE Main; END Main. В общем случае название модуля может быть любым, название главного модуля редактируется в настройках проекта. Именно для главного модуля будет создаваться прошивка при нажатии на кнопку сборки на панели меню. Секция импорта начинается ключевым словом ''IMPORT'', после которого через запятую идут названия других модулей — строительных блоков программы. При этом с помощью оператора присваивания '':='' для модулей допускается указывать короткие обозначения. MODULE Main; IMPORT SYSTEM, ARMv7M := MicroARMv7M, (* общее для всех контроллеров архитектуры ARMv7 *) SysTick0 := MobxARMv7MSTM32SysTick0, (* пример таймера на прерываниях *) MCU := MicroSTM32F4, (* ардеса регистров для микроконтроллеров STM32F4* *) Pins := MicroSTM32F4Pins, (* модуль для настройки портов ввода/вывода *) Sys := MicroSTM32F4System; (* модуль для базовой настройки STM32F4* *) После секции импорта возможно указывать константы и описывать новые типы данных. Однако в этой простой программе это не используются, поэтому за секцией импорта следует секция глобальных переменных ''VAR''. После идентификатора (имени) переменной через двоеточие указывается тип данных, который будет хранить переменная. VAR msec: INTEGER; В Обероне существует шесть базовых типов для переменных: MODULE Main; IMPORT SYSTEM, ARMv7M := MicroARMv7M, (* общее для всех контроллеров архитектуры ARMv7 *) SysTick0 := MobxARMv7MSTM32SysTick0, (* пример таймера на прерываниях *) MCU := MicroSTM32F4, (* ардеса регистров для микроконтроллеров STM32F4* *) Pins := MicroSTM32F4Pins, (* модуль для настройки портов ввода/вывода *) Sys := MicroSTM32F4System; (* модуль для базовой настройки STM32F4* *) После описания переменных идёт описание процедур и программный код модуля. В этом примере две процедуры: ''Setup'' — отвечает за настройку микроконтроллера, ''Loop'' — содержит главную петлю программы, которая повторяется во время работы микроконтроллера после настройки. MODULE Main; ... PROCEDURE Setup; BEGIN ... END Setup; PROCEDURE Loop; BEGIN ... END Loop; BEGIN Setup; Loop END Main. Процедура настройки ''Setup'' имеет три выражения: - установка начального значения глобальной переменной; - настройка частоты таймера делается вызовом процедуры ''Init'' из модуля ''SysTick0''. Процедура имеет два аргумента, мы передаём ей частоту работы главного кварца микроконтроллера и желаемую частоту вызова таймера в Герцах (так таймер будет вызываться 1000 раз в секунду); - настройка вывода **PC13** осуществляется с помощью специального модуля ''Pins'' через процедуру ''Configure'', 6 параметров задают порт, номер вывода, тип вывода (вход/выход), наличие/отсутствие подтяжки, скорость работы, тип подтяжки, и вариант альтернативной функции для вывода. (если ножка будет работать в режиме **GPIO**, то нужно использовать значение ''Pins.AF0''). PROCEDURE Setup; BEGIN msec := 0; SysTick0.Init(Sys.HCLK, 1000); Pins.Configure(Pins.C,13,Pins.output,Pins.pushPull,Pins.medium,Pins.noPull,Pins.AF0); END Setup; Процедура ''Loop'' содержит бесконечный цикл ''REPEAT UNTIL FALSE'', в котором находится два выражения: - условие проверки срабатывание таймера ''SysTick0.OnTimer()'' с кодом, который выполняется, если таймер сработал; - команда которая отправляет микроконтроллер в спящий режим до срабатывания следующего вызова таймера или иного прерывания ''ARMv7M.WFI'' PROCEDURE Loop; BEGIN REPEAT IF SysTick0.OnTimer() THEN IF msec = 999 THEN msec := 0 ELSE INC(msec) END; IF msec = 0 THEN SYSTEM.PUT(MCU.GPIOCBSRR, {13}) ELSIF msec = 500 THEN SYSTEM.PUT(MCU.GPIOCBSRR, {13 + 16}) END; END; ARMv7M.WFI UNTIL FALSE END Loop; Внутри условия, есть выражение для добавления единицы к счётчику милисекунд ''INC(msec)'', а также условия сброса счётчика в ''0'', когда его значение достигнет константы ''999'': IF msec = 999 THEN msec := 0 ELSE INC(msec) END; Второе условное ветвление, в зависимости от значения глобальной переменной ''msec'' с помощью команды ''SYSTEM.PUT'' устанавливает значение битов регистра ''GPIOC_BSRR'' , так что сначала на вывод **PC13** подаётся напряжение 3,3 Вольта, а затем через половину секунды напряжение устанавливается в 0 Вольт. IF msec = 0 THEN SYSTEM.PUT(MCU.GPIOCBSRR, {13}) ELSIF msec = 500 THEN SYSTEM.PUT(MCU.GPIOCBSRR, {13 + 16}) END; {13} ­— это константа типа SET, которая соответствует двоичному числу с единицей в положении 13. 0000 0000 0000 0000 0001 0000 0000 0000 Запись такого машинного слова по адресу **MCU.GPIOCBSRR** вызывает документированные изменения напряжений на выводе **PC13**, как описано выше. {13+16} ­— это также константа типа SET, которая соответствует двоичному числу с единицей в положении 29. 0001 0000 0000 0000 0000 0000 0000 0000 Мы специально записали эту константу в виде суммы 13+16, чтобы было легче читать код. Ведь запись такого машинного слова по адресу **MCU.GPIOCBSRR** вызывает установку нуля на ножке **PC13**. «Почему один регистр управляет и включением и выключением напряжения?» — вероятно спросите вы… Это очень удобно, так как позволяет за одну операцию сразу включить и выключить группы выводов. К примеру вот такая команда за один такт работы микроконтроллера включит напряжение на выводах **PC1** и **PC3**, при этом выключит напряжения начиная с **PC6** и до **PC12**. SYSTEM.PUT(MCU.GPIOCBSRR, {1,3,6+16..12+16}) BSSR — расшифровывается как Bit Set/Reset Register (регистр для установки и сброса бит). Подробнее про управление регистрами читайте [[https://wiki.oberon.org/ob/o7/example/put|в заметке в википедии по Оберону]]. Управление микроконтроллером — это запись и чтение данных из регистров. Про чтение регистров мы поговорим в следующей заметке, где разберём подключение кнопки к микроконтроллеру. Задавайте вопросы в группе [[https://vk.com/mcuoberon|ВК]] или в [[https://away.vk.com/away.php?rh=c2146088-7486-481c-8f59-c5efc5856fac|телеграм-чате]].