Инструменты пользователя

Инструменты сайта


prog:start:helloword

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

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

За основу мы возьмем пустой проект созданный нами в статье Создаем новый проект.

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

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

Сейчас у меня в наличии только демо-плата для процессора 1986ВЕ92У, и учиться мигать светодиодом мы будем на ней. Давайте модифицируем проект под новый процессор.

Открываем опции проекта и в закладке Device выбираем Cortex-M3 - MDR1986BE92 и жмем ОК.

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

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

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

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

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

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

//Содержимое файла 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 с реализацией этой функции.

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

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, удобно воспользоваться кнопками в тулбаре. Нажав на стрелочку влево возвращаемся в функцию откуда был прыжок.

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

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

  //  Инициализируем структуру конфигурации вывода(-ов) порта значениями по умолчанию
  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 заполняет структуру конфигурации порта значениями по умолчанию. Мы будем мигать светодиодом, который управляется с пина 0 порта С, выбираем его в поле PORT_Pin. Этот пин будет работать как выход, скорость переключения ставим маленькую, поскольку скорость нам не важна. Режим работы - цифровой. Подробнее о настройках порта рассказано - "Настройка портов ввода-вывода" и "Схемотехника портов GPIO".

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

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

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

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

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

    // Если на выводе логический "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);
prog/start/helloword.txt · Последнее изменение: 2022/04/03 23:09 (внешнее изменение)