24188

Hello World - светодиод

Дата последнего изменения: 16.07.2020 16:54:04

Создадим пример простейшего мигания светодиодом. Конечный вариант проекта доступен для скачивания в конце статьи.

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

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

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

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

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

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

В этом окне необходимо отключить выбор Startup_… для процессора 1986ВЕ1Т  и выбрать Startup_… для 1986ВЕ9х. В разделе Drivers по-прежнему нужно выбрать блоки PORTS и RST_CLK. Нажимаем OK и видим, что в дереве проекта ошибка исчезла.

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

Аналогичные действия по переходу на другой процессор описаны в статье Переход на другой микроконтроллер в среде Keil uVision.

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

#include <MDR32F9Qx_port.h>
#include <MDR32F9Qx_rst_clk.h>

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

Здесь есть один нюанс - файл MDR32F9Qx_port.h должен быть подключен первым, затем MDR32F9Qx_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 будем придерживаться "старых" правил.

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

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

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

Посмотрим, как реализована функция включения тактирования. Кликнем правой клавишей на интересующей функции или переменной и в выпадающем меню выбираем Go To Definition Of. Откроется файл MDR32F9Qx_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. Откроется файл MDR32F9Qx_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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Программное обеспечение

Standard Peripherals Library + software pack для Keil MDK 5
Standard Peripherals Library + software pack для Keil MDK 5
ОФИЦИАЛЬНАЯ СБОРКА
Standard Peripherals Library – библиотека для микроконтроллеров.

Теги

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