47553

[i] Проект "Hello, World" для МК К1986ВЕ92QI (К1986ВЕ92FI, К1986ВЕ92F1I, К1986ВЕ94GI) и К1986ВЕ1QI (К1986ВЕ1FI, К1986ВЕ1GI)

Дата последнего изменения: 30.11.2023 09:06:22
Материал из настоящей статьи, относящийся к микросхемам К1986ВЕ92QI и К1986ВЕ1QI, распространяется в том числе на микроконтроллеры К1986ВЕ92FI, К1986ВЕ92F1I, К1986ВЕ94GI и К1986ВЕ1FI, К1986ВЕ1GI

Создадим пример простейшего мигания светодиодом. Конечные примеры проектов (как для К1986ВЕ1QI, так и для К1986ВЕ92Q) доступны для скачивания в конце статьи.

За основу возьмем пустой проект, созданный в статье Создаем новый проект. В этом проекте была реализована пустая функция Main. В настройках проекта выбран процессор К1986ВЕ92QI. В Manage Run-Time Environment выбраны необходимые для работы модули из библиотеки SPL - Startup, PORT, RST_CLK.

1. Переход на другой процессор

При работе с демоплатой для процессора К1986ВЕ1QI проект необходимо модифицировать под новый процессор. Для этого необходимо открыть опции проекта и в закладке Device выбирать Cortex-M1 - MDR32F1QI, кликнуть ОК.

Рисунок 1 - Выбор микроконтроллера во вкладке 'Device' (Options for Target)

При этом в дереве проектов папка Device обновится. Это связано с тем, что файлы, подключенные в проект ранее (были для К1986ВЕ92QI), не подходят для нового процессора. Убедиться, что модули библиотеки SPL подключились верно можно при переходе в Manage Run-Time Environment (рисунок 2). 

Рисунок 2 - Окно 'Manage Run-Time Environment'

В разделе Drivers по-прежнему нужно убедиться, что выбраны блоки PORT и RST_CLK. Нажимаем OK.

Большой плюс SPL в том, что, если бы у пользователя уже была реализована программа для старого процессора, то при смене Target изменения в коде были бы минимальны. Функции и определения от процессора к процессору изменяются не значительно. Возникшие несоответствия устраняются на этапе компиляции. В случае со светодиодом изменения не потребовались бы вовсе.

Вместо модификации старого пустого проекта, можно было создать новый. Описанный выше алгоритм был приведен для того, чтобы показать такую возможность. Опции проекта, распределение памяти и алгоритмы, в данном случае, поменялись автоматически. Но их можно проверить по статье  Настройки проекта на примере К1986ВЕ1QI.

2. Программа мигания светодиодом 'Hello World'

Приведем листинг программы мигания светодиодом. Затем разберем его по шагам.

//Содержимое файла main.c
 #include <MDR32FxQI_port.h>
 #include <MDR32FxQI_rst_clk.h>

 // Прототип функции задержки, реализованной ниже
 void Delay(int waitTicks);

 // Точка входа, отсюда начинается исполнение программы
 int main()
 {
   // Заводим структуру конфигурации вывода(-ов) порта GPIO
   PORT_InitTypeDef GPIOInitStruct;

   // Включаем тактирование порта D
   RST_CLK_PCLKcmd (RST_CLK_PCLK_PORTD, ENABLE);

   // Инициализируем структуру конфигурации вывода(-ов) порта значениями по умолчанию
   PORT_StructInit(&GPIOInitStruct);

   // Изменяем значения по умолчанию на необходимые нам настройки
   GPIOInitStruct.PORT_Pin = PORT_Pin_7;
   GPIOInitStruct.PORT_OE = PORT_OE_OUT;
   GPIOInitStruct.PORT_SPEED = PORT_SPEED_SLOW;
   GPIOInitStruct.PORT_MODE = PORT_MODE_DIGITAL;

   // Применяем заполненную нами структуру для PORTD.
   PORT_Init(MDR_PORTD, &GPIOInitStruct);

   // Запускаем бесконечный цикл обработки - Основной цикл
   while (1)
   {
    // Считываем состояние вывода PD7
    // Если на выводе логический "0", то выставляем вывод в логическую "1"
    if (PORT_ReadInputDataBit (MDR_PORTD, PORT_Pin_7) == 0)
    {
        PORT_SetBits(MDR_PORTD, PORT_Pin_7); // LED
    }
    // Задержка
    Delay(1000000);

    // Считываем состояние вывода PD7
    // Если на выводе = "1", то выставляем "0"
    if (PORT_ReadInputDataBit (MDR_PORTD, PORT_Pin_7) == 1)
    {
        PORT_ResetBits(MDR_PORTD, PORT_Pin_7);
    };


    // Задержка
    Delay(1000000);
    }
 }

 // Простейшая функция задержки, позднее мы заменим ее на реализацию через таймер
 void Delay(int waitTicks)
 {
   int i;
   for (i = 0; i < waitTicks; i++)
  {
   __NOP();
  }
 }

2.1 Разбор программы

#include <MDR32FxQI_port.h>
#include <MDR32FxQI_rst_clk.h>

В директивах Include подключаются заголовочные файлы, в которых определены функции, необходимые для работы. В MDR32FxQI_port.h определены функции для работы с портами, а в MDR32FxQI_rst_clk.h - функции задания тактовой частоты. Необходимые с-файлы среда Keil подключила сама после выбора в "Manage Run-Time Environment".

Здесь есть один нюанс - файл MDR32FxQI_port.h должен быть подключен первым, затем MDR32FxQI_rst_clk.h. В иной последовательности при компиляции возникает множество ошибок.

// Прототип функции задержки, реализованной ниже
 void Delay(int waitTicks);

// Простейшая функция задержки, позднее будет заменена на реализацию через таймер
 void Delay(int waitTicks)
 {
   int i;
   for (i = 0; i < waitTicks; i++)
   {
    __NOP();
   }
 }

Delay() - простейшая реализация функции задержки на цикле. Значение задержки здесь необходимо подобрать экспериментально. В зависимости от оптимизаций компилятора один цикл for может занимать разное количество команд. Уточнить это можно, посмотрев ассемблерный код в отладчике. При этом необходимо учесть, что внешнее тактирование включено не было, а внутренний генератор задает тактирование 8 МГц.

Язык Си не содержит операции для пустой команды, поэтому определения, начинающиеся с подчеркиваний, например __NOP - это расширенные инструкции для компилятора ядра Cortex.

// Заводим структуру конфигурации вывода(-ов) порта GPIO
 PORT_InitTypeDef GPIOInitStruct;

Функция main() - точка входа в программу. По правилам ANSI_C все переменные должны быть объявлены в начале функции. Сейчас компилятор Keil 5 поддерживает стандарт C99, о чем говорит наличие галочки С99 Mode в Options - C/C++. Но для совместимости с Keil 4 будем придерживаться "старых" правил.

2.2 Тактирование

// Включаем тактирование порта D
 RST_CLK_PCLKcmd (RST_CLK_PCLK_PORTD, ENABLE);

ВАЖНОЕ правило, при работе с периферией - первым действием должно быть включение тактирования.

Посмотрим, как реализована функция включения тактирования. Кликнем правой клавишей на интересующей функции или переменной и в выпадающем меню выбираем Go To Definition Of. Откроется файл MDR32FxQI_rst_clk.c библиотеки SPL с реализацией этой функции.

Рисунок 3 - Вызов Go To Definition of

Если файл не открылся, необходимо скомпилировать проект.

Вот что делает эта функция:

void RST_CLK_PCLKcmd(uint32_t RST_CLK_PCLK, FunctionalState NewState)
 {
   // Проверка входных параметров на допустимость значений
   assert_param(IS_FUNCTIONAL_STATE(NewState));
   assert_param(IS_RST_CLK_PCLK(RST_CLK_PCLK));

   // Модификация регистра
   if (NewState != DISABLE)
   {
     MDR_RST_CLK->PER_CLOCK |= RST_CLK_PCLK;
   } 
   else
   {
     MDR_RST_CLK->PER_CLOCK &= ~RST_CLK_PCLK;
   }

Она просто выставляет или сбрасывает биты в определенном регистре процессора. Посмотрим, что выполняет DISABLE. Для этого кликнем на него мышкой и снова выберем Go To Definition Of.

typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState;

Как можно видеть, определен перечислимый тип FunctionalState с двумя значениями - Enabled и Disable. Передавая в функцию RST_CLK_PCLKcmd() нужное значение, можно включать и выключать тактирование. Какое тактирование будет включено, задает первый параметр. Для того чтобы узнать возможные варианты, надо вызвать Go Definition Of на макросе условной компиляции IS_RST_CLK_PCLK. Откроется файл MDR32FxQI_rst_clk.h библиотеки SPL. Все определения с RST_CLK_PCLK_… - это значения, которые можно подать на вход функции RST_CLK_PCLKcmd, то есть та периферия, для которой нужно включать тактирование при работе с ней.

RST_CLK_PCLKcmd (RST_CLK_PCLK_PORTC | RST_CLK_PCLK_PORTB, ENABLE);

Поскольку найденные значения представляют собой всего лишь биты в регистре MDR_RST_CLK→PER_CLOCK, то тактирование можно включать одной командой на несколько источников сразу.

Для того чтобы вернуться назад после прыжка Go Definition Of, удобно воспользоваться кнопками в тулбаре (рисунок 4). Нажав на стрелочку влево, возвращаемся в функцию, из которой был осуществлен прыжок.

Рисунок 4 - ToolBar. Navigation

Для перемещения по коду также удобно пользоваться закладками. Для того чтобы поставить закладку на линии курсора, надо нажать Ctrl+F2, эта же комбинация стирает закладку, если та уже установлена. Для перемещения между закладками надо просто нажать F2 (рисунок 5).


Рисунок 5

2.3 Инициализация порта

// Инициализируем структуру конфигурации вывода(-ов) порта значениями по умолчанию
   PORT_StructInit(&GPIOInitStruct);

// Изменяем значения по умолчанию на необходимые нам настройки
   GPIOInitStruct.PORT_Pin = PORT_Pin_7;
   GPIOInitStruct.PORT_OE = PORT_OE_OUT;
   GPIOInitStruct.PORT_SPEED = PORT_SPEED_SLOW;
   GPIOInitStruct.PORT_MODE = PORT_MODE_DIGITAL;
 // Применяем заполненную нами структуру для PORTD.
 PORT_Init(MDR_PORTD, &GPIOInitStruct);

Функция PORT_StructInit заполняет структуру конфигурации порта значениями по умолчанию. Диод, который будет мигать, расположен на PortD[7]. Выберем его в поле PORT_Pin. Этот пин будет работать как выход, скорость переключения ставим маленькую, поскольку в данном случае она не важна. Режим работы - цифровой. Подробнее о настройках порта рассказано в статьях Схемотехника портов GPIO и Настройка портов ввода-вывода.

После заполнения конфигурационной структуры вызываем функцию PORT_Init, чтобы применить настройки к необходимому порту - MDR_PORTD.

2.4 Цикл обработки

Запускаем бесконечный цикл обработки:

while (1)
{
   // выключаем светодиод
   // пауза
   // включаем светодиод
   // пауза
}

Для мигания светодиодом можно просто попеременно писать в порт "0" и "1", но чтобы показать использование функции чтения состояния вывода, будем проверять состояние этого вывода и менять его на противоположное. Для чтения используется функция PORT_ReadInputDataBit(), на вход которой необходимо подать интересующий порт и пин. В рассматриваемом случае это MDR_PORTD и PORT_Pin_7. Функция возвращает состояние вывода - "0" или "1". "1" - соответствует состоянию, когда диод светится.

// Если на выводе логический "0", то выставляем вывод в логическую "1"
 if (PORT_ReadInputDataBit (MDR_PORTD, PORT_Pin_7) == 0)
 { 
    PORT_SetBits(MDR_PORTD, PORT_Pin_7);  // LED
 }

 // Задержка Delay(1000000);

Если на выходе "0", то выводим туда "1". Это делает функция PORT_SetBits(). Смена состояния светодиода заканчивается паузой. Это сделано для того, чтобы можно было успеть увидеть это состояние (светодиод горит).

После этого изменим состояние на противоположное. Отличие только в том, что здесь вызывается функцию PORT_ResetBits(), которая выводит логический "0" на светодиод. И такое поведение повторяется, пока не будет выключено питание.

// Считываем состояние вода PD7
// Если на выводе = "1", то выставляем "0"
if (PORT_ReadInputDataBit (MDR_PORTD, PORT_Pin_7) == 1)
{
    PORT_ResetBits(MDR_PORTD, PORT_Pin_7);
};

// Задержка
Delay(1000000);

Сохранить статью в PDF

Файлы для скачивания

Теги

Была ли статья полезной?