Наш магазин на eBay Наш магазин на AliExpress Наш канал в telegram

Программирование микроконтроллеров PIC. Часть 4. Разработка рабочей части программы. Алгоритмы

Итак, наконец-то мы добрались до самого главного — разработки рабочей части программы, то есть до той части, которая, собственно, и будет решать поставленную задачу.

С чего начинается разработка программы? Естественно, разработка любой программы начинается с постановки задачи. Иначе что делать-то, вообще? Задачу нужно формулировать как можно более детально, потому что правильно и полно поставленная задача — это уже половина решения. Здесь работа программиста сводится к тому, чтобы увидеть и сформулировать (или потребовать от заказчика, если программа пишется не для себя) все необходимые для решения данные. Естественно, в большинстве случаев всё сразу увидеть невозможно, поэтому в процессе разработки задача обычно обрастает дополнительными деталями и условиями, это нормально, но лучше сразу получить исходных данных по максимуму.

После того, как задача сформулирована, нужно в деталях проработать и описать её решение. Прямо по шагам расписать — что, зачем и при каких условиях нужно делать. Такая последовательность необходимых для решения задачи действий называется алгоритмом. Этот очень важный этап (написание алгоритма) зачастую упускается, хотя на самом деле, достаточно детально прописанный алгоритм довольно легко преобразуется в программу на любом языке программирования. Даже если вы можете написать программу сходу, без всякого нарисованного на бумаге алгоритма, это вовсе не значит, что алгоритма нет. Он всё равно есть, но только он существует не на бумаге, а у вас в голове. Без алгоритма программу нельзя написать по определению.

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

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

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

Последовательность выполнения шагов (направление обхода алгоритма) принято считать сверху — вниз и слева — направо. Если эта последовательность нарушается, то направление принято указывать стрелками.

Для того, чтобы всё вышесказанное стало более понятно, давайте разберём описанные этапы разработки программы на конкретном примере:

Пусть мы хотим организовать с помощью микроконтроллера PIC16F628A мигание обычным маломощным светодиодом. Это, собственно, и есть задача. Казалось бы всё понятно.

На самом деле, такая формулировка задачи достаточно размыта, и в таком виде задача не решаема, поскольку не ясно — с какой частотой мы хотим мигать, какова скважность импульсов, как светодиод подключен. Можно, например, подключить анод светодиода к плюсу (через ограничительный резистор, естественно), а катод — к контроллеру, тогда светодиод будет загораться при подаче низкого уровня сигнала на выход контроллера, а можно подключить катод светодиода к минусу, а анод — к контроллеру, тогда светодиод будет загораться при подаче высокого уровня на выход контроллера.

Итак, сначала нам надо максимально детализировать задачу (естественно, это касается того, что нужно для решения, например, то, каким лаком потом будет покрыта плата, нас на этапе программирования совершенно не волнует).

Пусть мы решили, что мигать мы будем с частотой 1 Гц (1 импульс в секунду), скважность импульсов будет равна 2 (время импульса равно времени паузы) и анод светодиода будет подключен к выходу контроллера RB3. Всё, теперь наша задача сформулирована достаточно детально для того, чтобы начать разрабатывать алгоритм её решения.

Сначала пишем просто крупные этапы:

  1. Включить светодиод
  2. Подождать 0,5 сек
  3. Выключить светодиод
  4. Подождать 0,5 сек
  5. Перейти к первому пункту

Теперь давайте каждый из этих этапов детализируем.

  1. Мы решили, что анод светодиода подключен к контроллеру, а катод к минусу питания, следовательно светодиод будет загораться при подаче на выход контроллера высокого уровня. В нашем случае светодиод подключен к выходу RB3, то есть первый пункт можно записать так: подать высокий уровень на выход RB3.
  2. Паузу нужной длительности можно организовать по разному — с помощью таймеров, с помощью пустых циклов и т.д. Как конкретно — решать вам. Пусть в данном случае мы решили сделать паузу с помощью пустых циклов. То есть нужно просто организовать цикл, который заданное количество раз выполняет какую-нибудь не влияющую на дальнейшую работу программы команду (в принципе можно выполнять любую команду, но в пиках есть специальная команда, которая ничем кроме траты времени не занимается — nop). Общее время, затраченное на выполнение этого цикла должно быть равно требуемому времени задержки. Зная время выполнения отдельных команд, общее время цикла нетрудно посчитать. Для увеличения времени задержки, можно вкладывать один цикл в другой. Ну ладно, об этом мы подробнее поговорим позднее, а пока вернёмся к нашему алгоритму.
    1. заносим в переменную A значение, равное количеству циклов, которые мы хотим отсчитать
    2. выполняем пустую команду
    3. уменьшаем значение переменной А
    4. проверяем, если А=0, значит мы отсчитали заданное время, выходим из цикла и продолжаем дальнейшее исполнение программы. Если А не равно 0, то переходим ко второму пункту.
  3. Светодиод будет гаснуть при подаче на выход контроллера низкого уровня, т.е. на этом этапе нужно подать низкий уровень на выход RB3.
  4. Замечаем, что в четвёртом пункте нашего алгоритма необходимо выполнить те же действия, что и во втором, поэтому для экономии кода, можно оформить эти действия в виде подпрограммы и потом в основной программе вызывать два раза эту подпрограмму (во втором пункте алгоритма и в четвёртом). По сути, подпрограмма — это небольшая вспомогательная программа, которая вызывается в основной программе, соответственно, принципы её разработки такие же, как и для основной программы. Здесь также можно сформулировать задачу, написать алгоритм и перевести его в программный код на ассемблере.
  5. Переход к первому пункту.

Итак, окончательно алгоритм нашей основной программы выглядит так:

  1. Подать высокий уровень на выход RB3
  2. Вызвать подпрограмму паузы
  3. Подать низкий уровень на выход RB3
  4. Вызвать подпрограмму паузы
  5. Перейти к первому пункту

Ну вот, теперь наш алгоритм готов к тому, чтобы заменить обычный человеческий язык на язык ассемблера. Открываем доку на контроллер и смотрим. Сначала смотрим, как работает порт B. Видим, что для того, чтобы подать на выход RB3 высокий уровень, нужно установить третий бит в регистре PORTB в 1, чтобы подать на выход низкий уровень, нужно установить этот бит в 0. Вызов подпрограммы осуществляется командой Call, безусловный переход — командой goto.

Открываем список команд и видим, что бит в регистре устанавливается командой bsf. То есть, команда установки в 1 будет выглядеть так: bsf PORTB,3. Сбрасывается бит командой bcf.

После перевода на язык ассемблера наша основная программа будет выглядеть так:

nachalo: это просто метка, чтобы было понятно, куда возвращаться командой goto. Компилятор вычислит адрес команды, на которую она указывает (адрес первой команды после метки) и подставит этот адрес в команду goto
bsf PORTB,3
Call pause pause — это метка, которая стоит перед подпрограммой (она указывает на первую команду подпрограммы)
bcf PORTB,3
Call pause
goto nachalo

Кроме того, раз мы используем порт B, то нужно посмотреть в доке, как происходит инициализация порта, настройка на выход канала RB3 и добавить соответствующий код в начало программы, плюс если вы используете имена регистров, а не сразу адреса, то в шапке программы нужно привести соответствие имён адресам.

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

Вот и всё! Именно таким способом обычно и пишутся программы, причём не только на ассемблере, но и на любых других языках программирования.

На этом закончим с вводной общей частью и далее рассмотрим очень важную задачу любых алгоритмов и программ — организацию циклов и ветвлений.

  1. Часть 1. Необходимые инструменты и программы. Основы MPLAB
  2. Часть 2. Что такое микроконтроллер и как с ним работать
  3. Часть 3. Структура программы на ассемблере
  4. Часть 4. Разработка рабочей части программы. Алгоритмы
  5. Часть 5. Ассемблер. Организация циклов и ветвлений
  6. Часть 6. Как перевести контроллер в режим программирования и залить в него прошивку

Добавить комментарий