Свежие обсуждения
Микроконтроллеры

Ликбез по программированию PIC

1 30 99

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

movlw '11111111'
movwf leds
repeat ; метка для цикла
movf leds,W
movwf PORTB ; физический вывод в порт
.........
опрос и обработка клавиатуры
.........
goto repeat

PS. Какие конкретно использовать при этом команды можно уточнить по ходу написания программы.

 

SAK: movwf TRISB^80h
почему не просто
movwf TRISB

Дело темное. Хоть регистр TRISB и находится в первом банке, т. е. для работы с ним нужно переходить в первый банк, адрес, по которому он вызывается, такой же, как и у PORTB. В файле P16F628A.INC этому регистру присвоен адрес из первого банка, поэтому и приходится так изголяться TRISB^80h. Если написать просто TRISB, то при ассемблировании MPLAB будет немножко ругаться, хотя вроде все работает. Но лучше наверное, чтоб не ругался? Тайный смысл этого явления я не знаю, просто знаю, что надо писать так.
SAK: Еще конструкцию вида
movlw B'00000000'
movwf XXX
можно заменить на
clrf XXX

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

 

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

 

Про файлообменник см. тут: http://www.pro-radio.ru/misc/2767/

Итак, отвечаю подробнее на вопросы, которые задал Allex:
- для чего используется команда SWAP, т.е. что она делает я понимаю, не понимаю для чего это можно применить.
- биты переноса. Как, например, организовать двухбайтный счетчик?
- как сравнить два числа (больше - меньше)?

1. Команда swapf меняет местами полубайты указанного регистра - это понятно. Любитель верно сказал - с её помощью можно быстро выполнить сдвиг на четыре бита в любую сторону. Полезна она и при табличной конвертации. Вот о табличной конвертации в PIC16 расскажу подробнее.

Таблица позволяет сопоставить одному 8-битному числу другое произвольное 8-битное число. Это по сути одномерный массив с 8-битным индексом и с 8-битными элементами. Обычно таблицы начинаются с нулевого индекса, т.е. нулю сопоставляется первое желаемое значение, единице - второе, двойке - третье и т.д.
0 --> xxxxxxxx
1 --> yyyyyyyy
2 --> zzzzzzzz
Такая таблица в PIC16 обычно содержит не более 256 ячеек, да и то с оговорками. Если ячеек более 256 - требуются некоторые хитрые приёмы, о которых я здесь рассказывать не буду.
Для организации таблиц служит команда retlw - возврат из подпрограммы с записанной в регистр W константой. Сама таблица оформляется в виде подпрограммы, содержащей последовательность команд retlw с требуемыми константами. Для выбора требуемой по счёту команды retlw из последовательности служит регистр PCL (младшие биты программного счётчика).
Программный счётчик (PC) у PIC16 13-разрядный (от 0 до 8191 в десятичном представлении). Находящееся в PC число указывает адрес выполняемой в данный момент инструкции в памяти программ. Например, при переходе на обработку прерывания в PC загружается число 4, или 0000000000100 в двоичном представлении. Младшие 8 бит программного счётчика доступны программисту для чтения и записи через регистр PCL. Старшие биты PC напрямую для чтения и записи недоступны. Посредником здесь выступает регистр PCLATH. Биты из PCLATH переписываются в PC в двух случаях (см. стр.25 даташита):
- при выполнении команд goto и call биты 3 и 4 из PCLATH переписываются в 11 и 12 биты PC. Остальные 10 бит указываются в коде операции (при написании программы они обычно указываются не числом, а условным именем метки);
- при выполнении любой команды, изменяющей данные в регистре PCL. В этом случае из кода операции в PC переписывается лишь 8 бит (из PCL), а остальные 5 берутся из PCLATH.
Первый случай при работе с PIC16F628(A) интереса не представляет, у него всего 2048 ячеек памяти программ, и десятью битами они однозначно адресуются. Проблем с "длинными переходами" не возникает.
Второй случай как раз наш. Записывая в PCL свои числа, мы можем посылать контроллер в любую точку в пределах 256-байтного блока. Какой именно блок это будет - определяется числом в PCLATH. Обычно таблицы стараются размещать в самом начале памяти программ, чтоб не возиться с записью в PCLATH.
Перед вызовом подпрограммы табличной конвертации следует записать индекс в регистр W. По команде call контроллер перейдет к началу таблицы. А там уже поджидает инструкция
addwf PCL,F
Эта инструкция предписывает контроллеру сложить содержимое регистров PCL и W, а затем записать результат в PCL. Такая операция называется "вычисляемым переходом". В результате контроллер "перепрыгнет" такое число команд retlw, какое и было записано предварительно в W.
Если в W был ноль - "перепрыгивания" не будет, и выполнится самая первая по порядку команда retlw. В результате произойдёт выход из подпрограммы с записанной в W константой, которую мы сопоставили индексу 0. Какое это число (константа) - однозначно решает программист при написании программы.
Если в W была единичка - будет "перепрыгнута" одна команда retlw, и выполнится следующая, вторая по порядку команда retlw. В результате произойдёт выход из подпрограммы с записанной в W константой, которую мы сопоставили индексу 1. Какое это число (константа) - также однозначно решает программист при написании программы.
И так далее. Главных засад на пути вычисляемого перехода две - это "вылет за таблицу" и переполнение регистра PCL. Допустим, наша таблица содержит всего пять элементов, и, соответственно, команд retlw тоже пять. А мы записали в W число 9 и вызвали подпрограмму. Последствия будут поистине ужасны, не шучу - будет выполнена находящаяся за пределами подпрограммы команда. Это повлечёт за собой непредсказуемые результаты работы программы в целом. Это и есть "вылет". Если же сумма значений в W и PCL окажется больше 255 - произойдет переполнение регистра PCL, и опять же будет выполнена не та команда. Проконтролировать выполнение условия (W+PCL)<=255 можно по листингу программы после трансляции (см. файл .lst). Так вот, перед выполнением вычисляемого перехода следует обязательно убедиться, что записанное в W число не превышает количества команд retlw минус 1 (т.к. индекс у нас начинается с нуля). </b> Вычисляемый переход может применяться не только для организации таблиц. Заменив команды retlw на goto, получим аналог команды case - выбор действий по индексу. Такую хитрость я применил в своё время в программе "поющей ёлки".

Вернёмся теперь к команде . Допустим, у нас 4 кнопки подключены к RB4-RB7. Всего возможно 16 комбинаций. Таблица из 16 ячеек. Но напрямую считанное с PORTB состояние кнопок невыгодно передавать в подпрограмму табличной конвертации. Значения будут от 0 до 255, потребуется таблица на 255 ячеек, из которых большинство окажутся просто невостребованными. Переставив полубайты и забив старший нулями (наложив маску), мы получим число от 0 до 15, и таблицы из 16 ячеек окажется вполне достаточно. Именно так и сделано в программе Zandy на стр.25.

2. Биты переноса. Их можно называть также флагами переноса/заёма. Расположены они в регистре STATUS.
Бит С изменяется при выполнении команд сложения (addlw, addwf), вычитания (sublw, subwf) и сдвига (rrf, rlf). При выполнении команд сложения бит С устанавливается в 1 в том случае, если сумма слагаемых превышает 255. Если сумма не превышает 255, то С=0. Например: 150+180=330. Но регистры-то 8-битные, поэтому получим 0x96+0xB4=0x4A и установленный бит С.
При вычитании С является битом заёма, поэтому всё происходит с точностью до наоборот. Если уменьшаемое не меньше вычитаемого и результат операции вычитания, соответственно, неотрицательный - тогда бит С устанавливается в 1. Если же арифметический результат вычитания отрицательный - тогда С=0.
При операциях сдвига из бита С берется "задвигаемый" в регистр бит, а "выталкиваемый" бит затем сохраняется там же, в бите С.
Очень хорошо об этих битах написано в "самоучителе":
http://ikarab.narod.ru/Kea_33.html
Там же есть и пример суммирования трёхбайтных чисел с пояснениями. Почитайте, а если останутся ещё вопросы - обсудим здесь.

3. Сравнение чисел (больше/меньше) производится командами сложения/вычитания. Результатом является состояние бита С. После прочтения указанной главы "самоучителя", думаю, всё станет ясно. Останутся вопросы - обсудим здесь.

Теперь посмотрю результаты творчества Zandy.

 

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

Дык а я и не применяю rlf к порту, с чего вы взяли?

L bsf PORTA,OutL ; Установить RА0 в 1
bcf PORTA,OutR ; Установить RА1 в 0
movlw B'11111111' ; Запись в Temp1
movwf Temp ; 11111111
bcf STATUS,C ; Сброс флага переноса
movlw .6 ; Закладка в Sec
movwf Sec ; количества проходов
Loop1 rlf Temp,F ; Циклич. сдвиг влево
movf Temp,W
movwf PORTB ; Запись в порт В
call Pause ; Уход в пп задержки
decfsz Sec,F ; Вычитаем 1 и проверяем на 0
goto Loop1 ; Закольцовка
goto M2 ; В начало

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

 

Zandy: Если написать просто TRISB, то при ассемблировании MPLAB будет немножко ругаться
Нет он не ругается, он предупреждает, что бы не забыли перейти в нужный банк.

Кстати, у команды
addwf PCL, F
есть одна особенность. При её использовании надо учитывать в каких адресах она находится и текущее состояние PCLATH. В данной программе проблемы нет, но если эту команду унести за границу 256 байт, то программа поведёт себя не так как хочется потому, что переход пойдёт не на ожидаемую команду goto, а на команду в первых 256 байтах.
В общем случае приходится делать так:
movlw HIGH table
movwf PCLATH
movf xxx,W
table ; метка
addwf PCL,F
......

PS. Пока писал, picmaniac, опередил.

 

Zandy: Дык а я и не применяю rlf к порту, с чего вы взяли?
Прошу прощения, виновен в невнимательности, готов понести суровое наказание . Я уже об этом написал. Просто я пытался решать более сложную задачу, чем задано.

 

Кстати, в программе не хватает проверки времени удержания кнопки, по заданию, если не ошибаюсь, должно быть 100 мс.
Вместо задания 6-и циклов можно проверять на "0" конечный бит в Temp, получится на пару команд короче.

 

Посмотрел. Zandy , читайте ТЗ внимательно!
Во-первых, как верно заметил SAK , не должно происходить никаких действий, если кнопка нажата менее 100 мс. Этим реализуется подавление дребезга. Во-вторых, лог.1 на выходе должен появляться не сразу же после нажатия на кнопку (т.к. опять же возможен дребезг!), а только на то время, пока горит хотя бы один светодиод.
Не стоит так спешить с написанием кода. Кодинг - это ремесло. А вот построение оптимального алгоритма - искусство! Для начала я предлагаю описать работу автомата на словах. Вот один из возможных вариантов такого описания, навскидку.

1. Подано питание. Инициализация, настройка портов.
2. Выходы обнулить как можно раньше.
3. Светодиоды погасить.

4. Проверить кнопки.
5. Если ни одна кнопка не нажата - погасить светодиоды, обнулить выходы и вернуться к п.4.
6. Если нажаты обе кнопки - зажечь 2 средних светодиода и переход к п.4.

7. Если нажата только одна кнопка - запомнить состояние кнопок.
8. Выдержать паузу 100 мс.
9. Проверить кнопки повторно.

10. Если состояние кнопок не соответствует запомненному - переход к п.4
11. Если нажата только кнопка L - установить 1 на OutL и вызвать подпрограмму бегущего огня влево (во время выполнения которой кнопки не опрашиваются).
12. Если нажата только кнопка R - установить 1 на OutR и вызвать подпрограмму бегущего огня вправо (во время выполнения которой кнопки не опрашиваются).
13. Возврат из подпрограммы бегущего огня.
14. Переход к п.10. (чтобы лог.0 не появился на выходе при нажатой кнопке)

Это не окончательный вариант. Прошу покритиковать. Всё-таки хочу граф сделать. Тогда дальнейшая работа значительно упростится.

 

Спасибо, буду соображать. Но вот с графом как-то не того.