Код программы для микроконтроллера

Материалы приведенные далее рассчитаны на пользователей знакомых с программированием в целом и языком C (не обязательно) в частности.
В основном излагаемый материал описывает использование микроконтроллеров AVR, особое внимание на ATmega328. Для изучения материалов понадобится Arduino Uno или Arduino Nano 3. Да простят меня ардуино-ненавистники, но в данном случае Arduino буд е т использоваться в качестве макетных плат по доступной цене и с возможность использования без программатора .

1. Подготовка к изучению

  • Среда разработки;
  • Стандартные библиотеки C для микроконтроллеров AVR ;
  • Программа для загрузки микропрограмм в микроконтроллер;
  • Микроконтроллер.

1.1. Среда разработки

При выборе среды разработки можно натолкнутся на «не пробиваемую стену» множества программных оболочек для программирования на разных языках программирования. Но учитывая направление на микроконтроллеры круг поиска сужается до сред разработки адаптированных под конкретный вид микроконтроллеров семейства AVR фирмы Atmel. Кроме того среда разработки язык программирования C.

Пишем первый код для ATMEGA32 в Атмел студио и мигаем светодиодом

Из множества вариантов рассмотрим среду разработки С odeBlocks. Будем использовать оригинальную версию С odeBlocks с сайта разработчиков www.codeblocks.org последней версии, на момент написания это версия 16.01. Данная среда разработки интересна наличием версий под популярные операционные системы Linux, Windows и Mac OS.

Вкратце рассмотрим установку под Windows. Скачав файл codeblocks-16.01-setup.exe запускаем его.

Инсталятор

Ознакомимся с лицензией и принимаем ее.

Лицензионное соглашение

Устанавливаем все компоненты без изменений

Компоненты CodeBlocks

Путь установки оставляем без изменений.

Путь установки

Пробегают строки установки и появляется предложение запустить программу, соглашаемся. И конечно нажимаем Next в установщике и завершаем установку.

Завершение установки CodeBlocks

Получаем установленную среду разработки Code::Blocks.

Среда разработки Code::Blocks

1.2. Стандартные библиотеки C/ C++ для микроконтроллеров AVR

Среда разработки установлена, но для работы необходимо подключить библиотеки для компиляции программ для микроконтроллера. Один из способов это поставить WinAVR. Только зачем, если среда разработки уже выбрана. Возьмем все необходимое с сайта производителя микроконтроллеров семейства AVR.

Понадобится Atmel AVR 8-bit Toolchain так как использовать собираемся ATmega328 а он 8- bit. После скачивания запускаем полученный само распаковываемый архив и получаем папку (вида avr8-gnu-toolchain) со всем необходимым. Куда ее положить?

Как просто научиться программировать микроконтроллеры PIC и AVR / Бегущие огни за 8 минут!

Запускаем ранее установленный Code::Blocks идем в меню Settings >> Compiler переходим во вкладку Toolchain executables выбираем интересующий нас компилятор из списка Selected compiler это будет GNU GCC Compiler for AVR. Далее смотрим путь по умолчанию для размещения ранее скачанной и распакованной папки.

Путь к библиотекам C/C AVR

Переименовываем нашу папку как в настройках Code::Blocks и перемещаем по указанному пути.

1.3. Программа для загрузки микропрограмм в микроконтроллер

Теперь все готово для программирования, но не хватает программы для облегчения прошивки микроконтроллера. Для изучения микроконтроллера ATmega328 будем использовать платформу Arduino UNO или Arduino Nano v3. Это дает возможность изучать микроконтроллер без паяльника и программатора. Для Arduino есть хорошая программа ArduinoBuilder и оболочка из проекта CodeBlocks Arduino IDE ( среда разработки Code::Blocks с добавлением библиотек ардуино ). Использовать микроконтроллер без ардуино гораздо интересней поэтому скачиваем только ArduinoBuilder. Его будем использовать для экспериментов с микроконтроллером на плате Arduino. Распаковываем архив, например в корень диска c: в папку ну скажем ArduinoBuilder, из нее делаем ссылку на рабочий стол и получаем два ярлыка:

Ярлыки программ

Все программное обеспечение готово. Приступим к «железным» вопросам

1.4. Микроконтроллер

В своих изысканиях будем рассматривать микроконтроллер ATmega328 программы будем писать именно для него. «Знатоки» сразу нас пошлют к DataSheet но это не для нас. Мы пойдем своим путем и будем изучать его анатомию практически — методом «Тыка» : ).

Первое что необходимо, это приобрести минимальное оборудование. Ограничимся для начала покупкой ардуины или аналога. Главное, чтобы на ней был установлен микроконтроллер ATmega328.

  • Arduino Uno;
  • Arduino Ethernet;
  • Arduino Nano v3 ;
  • Arduino Fio;
  • Arduino Pro (на ATmega328 );
  • и конечно клоны от китайских товарищей.

Любой из перечисленных вариантов подойдет с теми или иными ограничениями или изменениями.

Рассмотрим подробнее вариант Arduino Nano v3 . Здесь установлен микроконтроллер ATmega328P, есть возможность подключать через USB, а также существует несколько клонов по приемлемой цене. Описания в интернете можно найти массу, поэтому рассмотрим только схематичное описание найденное на просторах интернет.

Arduino Nano

На схеме темно-серым выделены соответствия физическим выводам микроконтроллера, на них и будем опираться при программировании.

Теги:

vavaav Опубликована: 02.08.2016 0 5

Вознаградить Я собрал 0 0

Оценить статью

  • Техническая грамотность

Источник: cxem.net

Низкоуровневое программирование STM32: от включения питания до «Hello, World»

В этом материале я хочу рассказать о том, как писать программы для микроконтроллеров (Microcontroller Unit, MCU) Cortex-M, вроде STM32, используя лишь набор инструментов ARM и документацию, подготовленную STMicroelectronics. У некоторых читателей может появиться вопрос о том, почему кому-то это может понадобиться. Если вам эта идея, на первый взгляд, не показалась очень уж страшной, то, возможно, вам будет интересно то, о чём пойдёт речь в этом материале. И, кстати, подумаем о том, кому и зачем это может пригодиться.

Конечно, разрабатывать программы для MCU STM32 можно с помощью существующих фреймворков. Это может быть ST HAL, обычный CMSIS, или даже что-то, более близкое к Arduino. Но… что тут увлекательного? Ведь, в итоге, тот, кто пользуется каким-то фреймворком, полностью зависим от документации к нему и от его разработчиков. И, с другой стороны, если документация к STM32 кажется кому-то, работающему с этой платформой, так сказать, бредом сивой кобылы, то можно ли говорить о том, что этот человек по-настоящему понимает данную платформу?

Поэтому давайте поговорим о низкоуровневом программировании STM32 и доберёмся от включения питания STM32 до «Hello, World».

STM32 очень похож на компьютер

На низком уровне микроконтроллер не особенно сильно отличается от полнофункционального компьютера, основанного на процессоре от Intel или AMD. Тут имеется как минимум одно процессорное ядро, инициализирующееся после подачи и стабилизации внешнего питания. В этот момент производится считывание загрузчика, код которого находится по адресу, заранее известному микроконтроллеру.

А в обычных компьютерах подобную роль играет BIOS. В случае с MCU это — код, находящийся по определённому смещению в (обычно) интегрированной памяти, предназначенной только для чтения. То, что происходит потом, полностью зависит от этого кода.

В целом, этот код решает основные задачи по подготовке системы к работе. Например, задаёт таблицу векторов прерываний и записывает определённые данные в некие регистры. Очень важной задачей, кроме того, является инициализация указателя стека (Stack Pointer, SP). В начале работы системы некоторые данные из ROM копируются в RAM. В итоге вызывается функция main() , что похоже на запуск операционной системы обычного компьютера, выполняемый после завершения подготовки системы к работе средствами BIOS.

Пример Pushy

Возможно, аналогом «Hello, World» для STM32 можно назвать пример из моего фреймворка для STM32 Nodate, который я ласково называю Pushy. Он ещё проще, чем традиционный пример Blinky, так как он использует лишь регистры управления тактированием и сбросом (Reset //const uint8_t led_pin = 3; // Nucleo-f042k6: Port B, pin 3. //const GPIO_ports led_port = GPIO_PORT_B; //const uint8_t led_pin = 13; // STM32F4-Discovery: Port D, pin 13 (оранжевый) //const GPIO_ports led_port = GPIO_PORT_D; //const uint8_t led_pin = 7; // Nucleo-F746ZG: Port B, pin 7 (синий) //const GPIO_ports led_port = GPIO_PORT_B; const uint8_t led_pin = 13; // Blue Pill: Port C, pin 13. const GPIO_ports led_port = GPIO_PORT_C; //const uint8_t button_pin = 1; // Nucleo-f042k6 (PB1) //const GPIO_ports button_port = GPIO_PORT_B; //const uint8_t button_pin = 0; // STM32F4-Discovery (PA0) //const GPIO_ports button_port = GPIO_PORT_A; //const uint8_t button_pin = 13; // Nucleo-F746ZG (PC13) //const GPIO_ports button_port = GPIO_PORT_C; const uint8_t button_pin = 10; // Blue Pill const GPIO_ports button_port = GPIO_PORT_B; // Установить режим вывода на пине, к которому подключён светодиод GPIO::set_output(led_port, led_pin, GPIO_PULL_UP); GPIO::write(led_port, led_pin, GPIO_LEVEL_LOW); // Установить режим ввода на пине, к которому подключена кнопка GPIO::set_input(button_port, button_pin, GPIO_FLOATING); // Если кнопка нажата (переход от высокого состояния к низкому), то ‘button_down’ будет в низком состоянии в том случае, если кнопка будет нажата. // Если кнопка не нажата (переход от низкого состояния к высокому, к Vdd), то ‘button_down’ будет в высоком состоянии в том случае, если кнопка будет нажата. uint8_t button_down; while (1) < button_down = GPIO::read(button_port, button_pin); if (button_down == 1) < GPIO::write(led_port, led_pin, GPIO_LEVEL_HIGH); >else < GPIO::write(led_port, led_pin, GPIO_LEVEL_LOW); >> return 0; >

Читайте также:
Как запретить программе доступ к камере

Тут можно сразу обратить внимание на два самых заметных элемента.

Первый — это функция main() , которую вызывает система. Второй — это подключения модуля GPIO. Этот модуль содержит статический C++-класс, возможности которого используются для записи данных в GPIO-выход, к которому подключён светодиод. Его же возможности применяются и при чтении данных с входа, к которому подключена кнопка. Тут можно видеть ещё и упоминание имён пинов платы Blue Pill (STM32F103C8), но в примере имеются предустановки и для других плат, которые можно активировать, раскомментировав соответствующие строки.

Где именно в этом примере используются регистры группы RCC? В названии этих регистров содержится намёк на то, что они позволяют управлять тактовой частотой MCU. Их можно сравнить с переключателями, которые могут пребывать в двух состояниях — «включено» или «выключено», включая и отключая соответствующие возможности MCU. Если посмотреть, например, на описание регистра RCC_AHBENR в разделе 6.4 руководства по STM32F0xx, то мы увидим бит, маркированный как IOPAEN (Input/Output Port A ENable, включение порта ввода/вывода A), который управляет частотой для периферии, подключённой к GPIO A. То же касается и других портов.

Раздел 6.4.6 руководства по STM32F0xx, описание регистра RCC_AHBENR

Как можно видеть на вышеприведённой иллюстрации, RCC_AHBENR — это регистр, отвечающий за включение AHB. Это — одна из шин внутри MCU, к которой подключены процессорное ядро, SRAM, ROM и периферийные устройства.

Шины AHB (Advanced High-performance Bus) и APB (Advanced Peripheral Bus) описаны в спецификации AMBA фирмы Arm.

Раздел 2.1 руководства по STM32F0xx, архитектура STM32F0xx

В целом можно отметить, что AHB — это более быстрая шина, соединяющая процессорное ядро со SRAM, ROM и с высокоскоростной периферией. Более медленная периферия подключается к более медленной шине APB. Между AHB и APB имеется мост, позволяющий устройствам, подключённым к ним, взаимодействовать друг с другом.

Низкоуровневое программирование

Как уже было сказано, первым при включении STM32 запускается код загрузчика. В случае с MCU STM32F042x6 универсальный код загрузчика, написанный на ассемблере Thumb, можно найти здесь. Это — обычный код, предоставляемый STMicroelectronics (например, для STM32F0xx) вместе с CMSIS-пакетом. Он инициализирует MCU и вызывает функцию SystemInit() , объявленную в низкоуровневом C-коде CMSIS (вот — пример для STM32F0xx).

Функция SystemInit() сбрасывает системные регистры, отвечающие за частоту, что приводит к использованию стандартной частоты HSI (High Speed Internal oscillator, высокоскоростной внутренний генератор). После выполнения процедур настройки libc (в данном случае используется Newlib — вспомогательная C/C++-библиотека) она, наконец, вызывает функцию main() следующей командой:

bl main

Эта инструкция, название которой расшифровывается как Branch with Link (переход с сохранением адреса возврата), приводит к переходу к заданной метке. В этот момент мы оказываемся в функции main() нашего примера Pushy. После этого в дело вступают возможности класса GPIO.

Класс GPIO

Первый вызываемый нами метод класса — это GPIO::set_output() . Он позволяет сделать указанный пин (с подключённым к нему повышающим резистором) выходным. Именно здесь мы встречаемся с первым различием между семействами MCU STM32. Дело в том, что более старые MCU, основанные на Cortex-M3 F1, имеют GPIO-периферию, очень сильно отличающуюся от той, которая используется в их более новых собратьях семейств F0, F4 и F7. Это выражается в том, что при работе с пинами STM32F1xx нужно записывать в единственный регистр множество опций:

// Управление вводом/выводом распределено между двумя комбинированными регистрами (CRL, CRH). if (pin < 8) < // Установим регистр CRL (CNF uint8_t pincnf = pinmode + 2; if (speed == GPIO_LOW) < instance.regs->CRL |= (0x2 else if (speed == GPIO_MID) < instance.regs->CRL |= (0x1 else if (speed == GPIO_HIGH) < instance.regs->CRL |= (0x3 if (type == GPIO_PUSH_PULL) < instance.regs->CRL instance.regs->CRL |= (0x1 > else < // Установим регистр CRH. uint8_t pinmode = (pin — 8) * 4; uint8_t pincnf = pinmode + 2; if (speed == GPIO_LOW) < instance.regs->CRH |= (0x2 else if (speed == GPIO_MID) < instance.regs->CRH |= (0x1 else if (speed == GPIO_HIGH) < instance.regs->CRH |= (0x3 if (type == GPIO_PUSH_PULL) < instance.regs->CRH instance.regs->CRH |= (0x1 >

А в других упомянутых семействах MCU имеются отдельные регистры для каждой опции (режим, скорость, повышающий или понижающий резистор, тип):

uint8_t pin2 = pin * 2; instance.regs->MODER = ~(0x3 PUPDR |= (0x1 else if (pupd == GPIO_PULL_DOWN) < instance.regs->PUPDR |= (0x2 if (type == GPIO_PUSH_PULL) < instance.regs->OTYPER instance.regs->OTYPER |= (0x1 if (speed == GPIO_LOW) < instance.regs->OSPEEDR instance.regs->OSPEEDR instance.regs->OSPEEDR instance.regs->MODER = ~(0x3 PUPDR |= (0x1 else < instance.regs->PUPDR |= (0x2

Для чтения данных с входного пина мы пользуемся IDR (Input Data Register, регистр входных данных) для банка GPIO, с которым работаем:

uint32_t idr = instance.regs->IDR; out = (idr >> pin) // Прочитать нужный бит.

Аналогично выглядит и использование ODR (Output Data Register, регистр выходных данных), с помощью которого осуществляется вывод данных на пин:

if (level == GPIO_LEVEL_LOW) < instance.regs->ODR instance.regs->ODR |= (0x1

И, наконец, в вышеприведённом коде имеется сущность instance , которая представляет собой ссылку на запись в структуре std::vector . Она была статически создана в ходе запуска MCU. В ней зарегистрированы свойства периферии:

std::vector* GPIO_instances() < GPIO_instance instance; static std::vector* instancesStatic = new std::vector(12, instance); #if defined RCC_AHBENR_GPIOAEN || defined RCC_AHB1ENR_GPIOAEN || defined RCC_APB2ENR_IOPAEN ((*instancesStatic))[GPIO_PORT_A].regs = GPIOA; #endif #if defined RCC_AHBENR_GPIOBEN || defined RCC_AHB1ENR_GPIOBEN || defined RCC_APB2ENR_IOPBEN ((*instancesStatic))[GPIO_PORT_B].regs = GPIOB; #endif [..] return instancesStatic; > static std::vector* instancesStatic = GPIO_instances();

Если периферийное устройство существует (то есть — имеется в CMSIS-заголовке для конкретного MCU, например, для STM32F042), то в структуре GPIO_instance создаётся запись, указывающая на память, соответствующую регистрам этого устройства ( regs ). К этим записям, равно как и к мета-информации, содержащейся в них, потом можно обращаться. Например, можно узнать о состоянии устройства:

GPIO_instance // Проверяем, является ли порт активным. Если это не так — активируем его. if (!instance.active) < if (Rcc::enablePort((RccPort) port)) < instance.active = true; >else < return false; >>

Преимущество такого подхода, как мы уже видели, заключается в том, что для работы с разными периферийными устройствами, вне зависимости от того, что это за устройства, можно пользоваться одним и тем же кодом. Дело в том, что эти устройства идентичны в плане используемых ими регистров.

Класс RCC

Класс RCC тоже интересуется тем, существует ли то или иное периферийное устройство. Делается это для того чтобы избежать разного рода сюрпризов. При этом используются те же определения препроцессора CMSIS. После проверки существования устройства включить его довольно просто:

Читайте также:
Программа для защиты компьютера или мобильного устройства от вредоносных программ

bool Rcc::enable(RccPeripheral peripheral) < uint8_t perNum = (uint8_t) peripheral; RccPeripheralHandle if (ph.exists == false) < return false; >// Проверка состояния текущего периферийного устройства. if (ph.count > 0) < if (ph.count >= handle_max) < return false; >// Увеличение количества обработчиков на 1. ph.count++; > else < // Активация периферийного устройства. ph.count = 1; *(ph.enr) |= (1 return true; >

В дополнение к изменению значения соответствующего бита ( ph.enable ) мы подсчитываем ссылки. Это делается для того чтобы случайно не выключить периферийное устройство, которое используется в другом месте кода.

Запуск примера

После того, как мы разобрались с вышеприведённым материалом, у нас должно появиться некоторое понимание того, как пример Pushy работает на низком уровне. Теперь мы можем его собрать и запустить. Для этого нам понадобится, как уже было сказано, набор инструментов ARM и фреймворк Nodate. Первый можно установить с помощью используемого вами менеджера пакетов (речь идёт о пакете arm-none-eabi-gcc ) или — загрузив его с сайта Arm. Фреймворк Nodate можно установить с GitHub. После этого путь к корневой папке фреймворка нужно записать в глобальную системную переменную NODATE_HOME .

После того, как эти задачи решены, нужно перейти в папку Nodate , а потом — в подпапку examples/stm32/pushy . В ней надо открыть файл Makefile и указать предустановки, рассчитанные на используемую плату (там сейчас есть предустановки для Blue Pill, Nucleo-F042K6, STM32F4-Discovery, Nucleo-746ZG). Далее, надо открыть файл src/pushy.cpp и раскомментировать строки, имеющие отношение к целевой плате.

Далее, в папке, в которой находится Makefile , нужно собрать проект командой make . Целевая плата должна быть подключена к компьютеру с использованием ST-Link, на компьютере должна быть установлена программа OpenOCD. Если это так — MCU можно прошить командой make flash . После этого соответствующий образ будет записан в память устройства.

Когда кнопка подключена к используемому в коде пину и к Vdd, нажатие на эту кнопку должно зажигать соответствующим образом подключенный к плате светодиод.

Здесь продемонстрирован простой пример низкоуровневого программирования STM32. Освоив его, вы, фактически, находитесь лишь немного «ниже» уровня примера Blinky.

Надеюсь, я смогла показать то, что низкоуровневое программирование STM32 — это совсем несложно.

Какие инструменты вы используете при написании программ для STM32?

Источник: habr.com

Пишем код на СИ. Зажигаем светодиод

Сегодня мы научимся писать код на C в среде Atmel Studio, для примера мы возьмём тот же проект, который мы создали на прошлом занятии.

Прежде чем мы начнём непосредственно заниматься написанием кода, мы изучим те строки кода, которые нам уже сгенерировала студия в нашем файле Test01.c.

В самом начале кода мы видим строку в виде следующей директивы

#include

Посмотрим определение данной директивы

Директива #include просит препроцессор (компилятор) включить файл, объявленный после нее в исходный код. Имя файла может заключаться либо в треугольные скобки <> либо в кавычки ””. Треугольные скобки используются в случае включения в код файла из самой среды разработки, а кавычки – пользовательского файла.

#include имя файла>

В нашем случае в текст кода включается файл io.h. Если мы откроем данный файл, то мы увидим, что в зависимости от каких-то условий в наш код включаются ещё определённые заголовочные файлы. В нашем частном случае условием является использование нами определённого микроконтроллера. Так как мы используем Atmega8a, то соответственно включится следующий файл:

В данном файле находятся макросы для векторов прерываний, для определённых шин. Что такое макросы и как их использовать, мы разберём немного позднее. Движемся дальше по файлу io.h и видим, что в него также включены ещё 3 файла, но нам будет интересен следующий файл:

В данном файле находятся также макроподстановки для наших лапок портов и прочей периферии, чтобы нам было удобнее писать и читать наш код. Откроем данный файл и изучим в нём данные строки

/* Port Data Register (generic) */
#define PORT7 7
#define PORT6 6
#define PORT5 5
#define PORT4 4
#define PORT3 3
#define PORT2 2
#define PORT1 1
#define PORT0 0

Данные строки нам говорят о том, что, например, если при компиляции проекта препроцессор (интерпретатор команд) встретит в коде слово PORT4, то он его сразу заменит на цифру 4.

Тем самым мы постепенно с вами подошли к изучению ещё одной директивы

Директива #define

Просит препроцессор (компилятор) в случае появления в тексте кода буквосочетания 1 заменить его на буквосочетание 2.

Данная директива используется для удобства чтения кода.

#define буквосочетание 1> буквосочетание 2>

Вернёмся в наш код. Далее мы видим следующее

int main(void)

То, что мы с вами наблюдаем в языке C и C++ называется функция. Функциями мы будем пользоваться постоянно. Функция — это такая подпрограмма, которая вызывается из какого-нибудь участка кода. Самое важное у функции — это её имя (в нашем случае main). По имени мы будем вызывать функцию из другого участка кода.

Также у функции существуют входные аргументы, возвращаемые аргументы, а также тело. Входные аргументы находятся сразу после имени в скобках и перечисляются один за другим, а разделяются запятыми. В нашем случае стоит один тип «void», обозначающий, что у нашей функции вообще нет входных аргументов.

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

Изучение переменных вообще не входит в рамки наших уроков, как правило это объясняется непосредственно в уроках и литературе по языкам программирования. Тип возвращаемого аргумента указывается перед именем функции. В нашем случае — это int (целочисленная знаковая переменная). Также у функции существует тело — это участок кода, находящийся между открывающей и закрывающей фигурными скобками. Вот этот участок кода и будет выполняться в случае вызова функции.

Функцию main мы явно нигде не вызываем. Это главная функция нашего приложения, недаром она и называется main, что по английски значит главный. Встретив данное имя, компилятор и начинает выполнение программы с данного места. То есть это своего рода входная точка нашей программы. Отсюда всё и начинается.

Сюда мы и начинаем писать свой код.

Давайте же что-нибудь сюда и напишем. Мы пока не будем обращать внимание на строки, уже содержащиеся в теле данной функции.

У программистов, которые пишут программы под ПК, начинать занятия принято с вывода строки «Hello World!», а у тех программистов, которые пишут под мелкие чипы, принято начинать работу с подключения и управления свечением светодиодами. Затем они учат их мигать просто, мигать по очереди, а уже после этого приступать к программированию каких-то более серьёзных вещей. Мы также не будем отступать от данного правила.

Давайте сначала подключим светодиод к какой-нибудь ножке контроллера, например к ножке 0 порта D

image001

У порта D, как мы видим из данной распиновки, существует как раз 8 ножек, что соответствует байту (8 бит). Также как биты в байты, ножки портов отсчитываются от 0.

Напишем мы сначала следующую строку

DDRD = 0xFF;

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

Читайте также:
Установка программ через active directory

DDRD — это команда, которая устанавливает состояние лапок порта D. Состояние лапки порта — это то, в каком направлении данная лапка будет работать — на выход или на вход, что соответствует установке определённого бита в 0 или в 1. Но так как мы будем зажигать светодиод, мы должны на определённой ножке порта выставить высокий логический уровень (в нашем случае 5 вольт).

А чтобы управлять уровнями ножки, она должна быть определена, как работающая на выход или на вывод. То есть состоянием лапки мы будем управлять из контроллера, а не из внешнего источника уровня.

Так как у нас лапка нулевая, то и бит мы и должны выставить в ноль нулевой бит нашего байта, соответствующего нашему порту D. Так как мы не пользуемся сегодня остальными лапками порта, то нам их состояние будет не важно и мы выставляем все лапки портов на вывод. Поэтому мы присваиваем переменной DDRD значение 0xFF. Данное значение мы написали в шестнадцатиричном виде.

Этот вид очень удобен для программистов, так как визуально о многом говорит. FF — это в десятичной системе 255, а в двоичной — 11111111, что говорит о том, что все биты в данном числе выставлены в единицу. Также мы видим, что наша строка заканчивается точкой с запятой (;).

Данный оператор — это разделитель команд, так как в одной строке мы можем писать не обязательно одну только команду, а можем и несколько, если они небольшие. Также в данной строке мы видим оператор «=» (знак равенства). Данный оператор заставляет наш препроцессор присвоить значение, находящееся справа возле него переменной, находящейся слева.

Ну, вообщем, переключили мы весь наш порт в состояние вывода данных. Теперь осталось включить на лапке PD0 высокий логический уровень. Сделать это мы можем следующей командой:

PORTD = 0b00000001;

Данная команда или переменная PORTD управляет записью или считыванием значений в порт или из порта в зависимости от состояния. То есть данной командой мы включили нулевую лапку в высокое логическое состояние (в единицу). Здесь мы с вами уже попробуем использовать написание значения в двоичном виде. Чтобы писать значения в данном виде, мы используем префикс 0b.

Данный вид удобен тем, что мы здесь видим полностью, как выглядит наш байт побитно. Лапки портов в байте, также как и биты считаются справа налево. То есть данной командой мы выставили в высокое состояние нулевую лапку порта D, а остальные мы выставили в низкое.

Вообщем, арифметическо-логическое устройство микроконтроллера сначала включит все ножки порта на выход, а затем установит на нулевой ножке высокое логическое состояние, и после этого у нас должен будет зажечься светодиод, так как через токоограничивающий резистор мы его анодом подключим к данной ножке, а катодом к общему проводу. Тем самым на контактах светодиода появится разность потенциалов, которая заставит его светиться. Кроме написанных нами двух строк далее в коде присутствует команда while. Данная команда является командой условного цикла.

PORTD = 0b00000001;
while(1)

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

Как только условие перестанет выполняться, а проверяется это тогда, когда код выполнится до конца (до закрывающей фигурной скобки), то мы выходим из цикла и код продолжает выполняться уже дальше тот, который находится уже не в теле цикла, а после закрывающей фигурной скобки. А истина в информатике — это TRUE или 1. Поэтому в данном случае цикл будет бесконечным, так как там стоит единице, а единица всегда равна единице. Бесконечный цикл организован для того, чтобы код, написанный для контроллера, выполнялся постоянно, то есть чтобы наш контроллер постоянно работал и не останавливался. В нашем случае тело пустое, и наш контроллер, вернее его АЛУ, будет всё время висеть в данном цикле и ничего не делать до тех пор, пока мы не отключим питание, либо нам его не отключат в розетке, либо, не дай Бог, сгорит контроллер. То есть светодиод наш будет светиться постоянно.

Сегодня мы не будем пробовать нашу программу прошивать в микроконтроллер, и даже не будем пробовать ещё в виртуальном контроллере, то есть в программе симуляции, а попробуем симуляцию запустить в самой студии, так как на прошлом занятии мы в качестве отладчика и выбрали симулятор. Двойным щелчком мыши либо клавишей F9 мы установим точку останова на команде PORTD = 0b00000001; и, когда мы запустим отладку, то отладчик, как только увидит данную точку, должен будет в этом месте остановить выполнение программы, и мы сможем посмотреть, какие уровни и где у нас установились.

002

Чтобы запустить отладку, мы нажмём кнопку в виде зелёного треугольника и дождёмся, когда отладчик остановится на нашей красной точке

003

Здесь мы наблюдаем, что ещё у нас открылась карта нашей памяти, в которой нам пока ещё ничего не понятно. Если бы мы писали на ассемблере либо на машинном коде, то нам бы это было понятнее. Поэтому нас интересует другая. Карта. Для этого мы нажмём вот эту кнопочку

004

Данная кнопочка (I/O View) откроет нам окно с данными наших портов ввода-вывода и прочей периферии

005

Нажмем в данном окне на строку PORTD и увидим в нижней половине окна, что весь наш регистр DDRD, отвечающий за направление отдельных ножек порта выставился весь в единички, то есть на выход

006

А дальше уже проблема. Чтобы нам посмотреть, как сработает следующая команда, которая включит нам нулевую ножку, отладчику необходимо остановиться на следующей строке кода, а у нас её нет, у нас только бесконечный цикл, который для отладчика — не строка. Поэтому мы пока остановим отладчик следующей кнопкой

007

И мы напишем какую-нибудь ещё строку, например, мы захотим, чтобы данный светодиод у нас погас, а зажегся следующий, который присоединен к следующей лапке, чтобы создать эффект бегущего огня

PORTD = 0b00000010;

Конечно всё это на практике у нас не сработает, так как мы не успеем заметить свечение предыдущего светодиода. Чтобы задумка заработала практически, мы должны ещё с вами включить между данными командами задержку, а это тема уже других более поздних занятий. Но тем не менее мы данную команду включим, чтобы отладчику было где остановиться. Затем мы запустим заново отладку.

Точка останова у нас также находится пока на той строке, на какой и была до этого. Запустим опять отладчик. Собирать проект перед отладкой необязательно, так как отладчик сам его пересоберет. Дожидаемся остановке отладчика на точке. В окошке с вводом-выводом опять нажмём на строке с нашим портом.

Дальше начинаем шагать по программе. Для этого нажимаем следующую кнопку или функциональную клавишу F10, о чем нам подсказывает студия, как только мы подносим указатель мыши к данной кнопке

008

Теперь отладчик остановится на следующей строке

009

И теперь в окне ввода-вывода мы видим уже следующую картину

010

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru