|
Предисловие:
В начале года я узнал про pic’и,
мне стало интересно и я решил ими позаниматься. Заказал нахаляву sample пики,
перечитал pic16f84.narod.ru и микрочиповскую документацию о среднем семействе
(pic16) и о конкретных пиках (16f84a и 16f628), сваял макетку как в статье о
световых эффектах (решил собрать все на 1 плате) и стал искать туториалы, гайды,
книги, сайты, вобщем по чему можно учиться писать для пиков. Ничего особенного
я не нашел и начал учиться сам. И вот первые плоды моего творчества: я написал
две работающие программы на MASMе (хоть я и не работал на С, но мне кажется
с ограниченным объемом памяти ASM будет выгоднее, так как он компактнее и красивее
чем скомпилированный С), одна без управления (это проще, да и на момент её написания
у меня ещё не было кнопок), вторая попроще эффект но зато можно регулировать
скорость (в этой проге эффект не главное =)). Теперь я хочу поделиться своей
работой с теми кто ещё только начинает (начинающие начинающим =)), имхо кому-то
это будет интересно. Профам прошу не пинать особенно за неграмотность, а по
возможности исправлять.
Итак, поехали.
Начнём с того, что всё (или почти
всё), с чем мы будем работать в программе – это регистры. По архитектуре пиков,
память программ и память оперативная (данных) разделены. Память программы имеет
разрядность 14 бит и энергонезависимая, там хранится собственно исполняемая
программа, эта память недоступна для чтения из программы. Память данных (RAM)
имеет разрядность 8 бит (1 байт) и используется во время работы пика для хранения
регистров (1 регистр – 1 байт), специальных и общих (я говорю здесь конкретно
о 16f84). Специальные регистры контролируют работу микроконтроллера, общие предназначены
для хранения данных пользователем. Память данных разнесена по банкам, в каждом
банке может быть до 128 регистров (байтов), в 16f84 используется два банка,
причем не полностью (в первом доступны адреса 00h-4Fh, во втором 80h-CFh). В
каждом банке в начале хранятся специальные регистры – в первом по адресам 00h-0Bh
включительно, остальное в первом – 68 регистров общего назначения (0Ch-4Fh),
некоторые из специальных повторяются в обоих банках, их содержимое естественно
и там и там одинаковое (самые необходимые и часто используемые регистры). Про
регистры наверное всё.
Теперь команды. Есть несколько
типов команд: байт-ориентированные – команды, в которых операндами (то, над
чем совершается действие) являются любой регистр из памяти данных и в большинстве
случаев W – рабочий (work) регистр. В остальных просто только один операнд (регистр
из памяти) или его вообще нет (даже так). Байт-ориентированными они называются
потому-что действия происходят с целым байтом данных. Важно: регистр из памяти
может быть только одним операндом, второй обязательно W!
Бит-ориентированные команды: ну
тут вообще просто. Операнды – регистр из RAM и собственно нужный из 8 битов
из этого регистра (0h-7h).
Команды управления и операции с
константами.. тут либо действие происходит между W и константой k, либо команда
выполняет действие, слабо связанное с данными (CLRWDT – очистить WDT, предотвратить
сброс по переполнению WDT (watch-dog timer), SLEEP – переход в режим пониженного
энергопотребления, RETURN – возврат из подпрограммы (данные тут не зависят от
пользователя))
По пути я буду расписывать действие
команд и назначение специальных регистров.
Первая часть программы
(ещё не сама программа, а вводная часть, некоторые данные для
ассемблера)
STATUS EQU 03h
PORTA EQU 05h
PORTB EQU 06h
TIME1 EQU 0Ch ;
TIME2 EQU 0Dh ;
TIME3 EQU 0Eh ;Здесь я назначаю имена моих общих
VARS EQU 0Fh ;регистров и указываю ассемблеру
TEMP1 EQU 10h ;их адреса в памяти
TW EQU 11h ;
C EQU 0h
DC EQU 1h
Z EQU 2h ;Это названия битов регистра
PD EQU 3h ;STATUS
TO EQU 4h
RP EQU 5h
TRISA EQU 85h
TRISB EQU 86h
EQU (equal) – знак
для ассемблера что вместо какогото символа (то, что слева от EQU) подставлять
числовое значение (справа от EQU). Сколько бы мы в этой части не писали (можем
хоть все 68 общих расписать и все биты специальных регистров), это не отразится
на объеме программы в микроконтроллере. А теперь о каждом специальном регистре
по-подробнее.
STATUS – очень
важный регистр, содержит 6 битов, управляющих некоторыми аспектами работы мк,
в частности:
Бит 5: RP: выбор
активного банка памяти. 0 – банк 0 (00h-7Fh), 1 – банк 1 (80h-FFh)
Ничего сложного, просто надо помнить, что если нужный регистр в банке 1, то
перед операции с ним над переключиться в банк памяти 1
Бит 0: C: бит переноса,
если был перенос из старшего бита (7h), флаг устанавливается в 1
Остальные бита регистра STATUS в
программе не используются
PORTA – используются
только биты 4h-0h, отображают состояние pin’ов ввода/вывода порта А: RA4-RA0
PORTB – тоже самое,
только порт B: RB7-RB0
TRISA – отвечает
за состояние каждого pin’а (ввод или вывод), 0 – вывод, 1 – ввод
TRISB – тоже самое только порт B
Названия общих регистров произвольные,
лишь бы самому было понятно.
Часть вторая (короткая)
– самое начало программы
ORG 0 GOTO BEGIN
ORG – это тоже команда для
ассемблера, она обозначает адрес в памяти программ, где размещаются нижеследующие
команды.
Впринципе, этой части может
и не быть. Дело в том, что она должна быть в программе, использующей прерывания.
Если происходит разрешенное прерывание, адрес, на котором сейчас выполняется
программа аппаратно записывается в стек (без участия пользователя), мк переходит
на адрес 0004h (вектор прерывания) и выполняет команды, находящиеся там. Возврат
из прерывания производится пользователем (программой). При этом выполнение команд
возвращается на записанный в стеке адрес. Если бы мы использовали прерывание,
мы бы дополнили эту части следующими строками:
ORG 4h
Траляля
Траляля
Траляля
(программа обработки прерывания)
В последствии часто придется использовать
прерывания, поэтому этот пример полезен для запоминания.
Часть третья – инициализация.
ORG 100h ;1
BEGIN
CLRF PORTA ;2
CLRF PORTB ;3
BSF STATUS,RP ;4
CLRF TRISA ;5
CLRF TRISB ;6
BCF STATUS,RP ;7
BSF PORTA,3 ;8
Адрес в первой строчке у нас взят
от фонаря (естественно в пределах памяти программы мк), в оптимизированной программе
с использованием прерываний он у нас будет равен адресу, где уже можно размещать
основную программу (код обработки прерывания закончился, после него).
BEGIN – это метка, она не занимает
памяти, служит для как операнд в командах CALL и GOTO (пока мы пишем программу),
ассемблер заменяет её на адрес команды, находящейся после этой метки (в данном
случае CLRF PORTA)
Итак, первая команда:
CLRF f
– очистить регистр f, короче говоря затереть его нулями
Во второй и третьей строчках
мы обнуляем регистры PORTA и PORTB, потому что после включения питания их состояние
непредсказуемо.
BCF f,b
– сбросить бит b в регистре f
BSF f,b – установить бит b в регистре f
В четвертой строчке мы
выбираем банк1 (т.к. регистры TRISA и TRISB находятся в банке1)
Обнуляем их (ставим все pin’ы как выходы)
Выбираем банк0
Восьмой строчкой мы гасим
светодиод на выходе RA3 (он горит когда на RA3 – его катоде – низкий уровень
сигнала)
Теперь надо рассказать про сам эффект:
Всегда зажжены 2 светодиода,
двигаются они по такой схеме (4 фазы движения)
Начало
7 6 5 4 3 2 1 0
X X - - - - - -
Фаза 1
Светодиод 6 двигается вправо до
конца:
7 6 5 4 3 2 1 0
X - - - - - - X
Фаза2
Светодиод 7 перемещается в положение
1
7 6 5 4 3 2 1 0
- - - - - - X X
Фаза3
Светодиод 1 (тот же) двигается обратно
7 6 5 4 3 2 1 0
X - - - - - - X
Фаза4
Светодиод 0 перемещается в положение
6
7 6 5 4 3 2 1 0
X X - - - - - -
Потом все начинается сначала.
При «столкновении» светодиодов, когда зажжены два соседних (крайних) светодиода,
мигает сигнальный LED (RA3).
Часть четвертая - начало
программы. Эта часть выполняется
один раз.
MOVLW
– загрузить константу в регистр W
MOVWF – переслать содержимое W в регистр f
MOVLW 7h
MOVWF TIME1
BCF STATUS,C
CLRF TEMP1
BSF TEMP1,06h
BSF PORTB,07h
BSF PORTB,06h
В первых двух строках мы загружаем 7h в регистр TIME1. Зачем мы это делаем я
расскажу позже.
Состояние общих регистров после
включения питания непредсказуемо, поэтому мы обнуляем TEMP1
Устанавливаем бит 6 в TEMP1
Делаем начальное состояние светодиодов
(до первой фазы) – горят 7 и 6
Если бы нам надо было установить более 2 битов в PORTB, экономичнее было бы
использовать команды:
MOVLW B’11100000’
MOVWF PORTB
,так как BSF бы занимали
три строки.
Часть пятая – фаза 1
RRF f,d
- сдвиг вправо регистра f, сохранить результат в f, если d=1 и в W, если d=0.
Этот сдвиг осуществляется с использованием бита STATUS,C. Если бит 0 установлен
в 1, C при сдвиге установится в 1. Другими словами, бит 0 переносится в STATUS,C.
RLF f,d
- тоже самое, только сдвиг влево, соответственно в C переносится бит 7.
IORWF
f,d - логическое побитное ИЛИ между W и f. При выполнении ИЛИ (OR) между двумя
битами в результат записывается 1, если хоть 1 бит был равен 1. Если оба = 0,
результат 0.
CALL <метка>
- вызов подпрограммы. GOTO отличается от CALL тем, что из последнего можно вернутся
командой RETURN (RETLW, RETFIE).
BTFSS
f,b - проверить бит b в регистре f, пропустить следующую команду, если b=1
BTFSC
f,b - пропустить следующую команду, если b=0
LOOP1
MOVLW B'10000000' ;1
RRF TEMP1,1 ;2
IORWF TEMP1,0 ;3
MOVWF PORTB ;4
CALL PAUSE ;5
BTFSS TEMP1,0h ;6
GOTO LOOP1 ;7
RRF TEMP1,1 ;8
1. Загружаем в W постоянную состовляющую фазы 1.
2. Двигаем вправо TEMP1, оставляем результат в TEMP1
3. командой ИЛИ мы «накладываем» переменную состовляющую на постоянную, результат в W
4. Переносим наложение в PORTB
5. Вызываем паузу (расскажу скоро)
6. Проверяем, не достиг ли двигающийся светодиод места назначения (см. описание фаз)
7. Нет, повторяем движение первой фазы
8. Да, переносим в STATUS,C двигающийся сд (нужно для следующей фазы)
Когда выполнение дойдет
до строки 8, будут гореть два крайних светодиода (как на схеме).
Пауза – единственная подпрограмма
DECFSZ
f,d - уменьшить на 1 f, сохранить в d (W если 0 и f если 1), пропустить следующую
команду, если результат = 0
DECF f,d
- уменьшить f на 1
Сначала надо сказать, что
TIME1 определяет время задержки (1 = примерно 10мс при ~4MHz)
PAUSE
MOVWF TW
MOVF TIME1,W
MOVWF VARS
MOVLW 0FFh
MOVWF TIME2
MOVLW 0Dh
MOVWF TIME3
PAUSELOOP
DECFSZ TIME2,1 ;1
GOTO PAUSELOOP ;2
DECF TIME2 ;3
DECFSZ TIME3,1 ;4
GOTO PAUSELOOP ;5
DECF TIME2 ;6
MOVLW 0Dh ;8
MOVWF TIME3 ;9
DECFSZ TIME1,1 ;10
GOTO PAUSELOOP ;11
MOVF VARS,W ;12
MOVWF TIME1 ;13
MOVF TW,W ;14
RETURN
До метки PAUSELOOP мы сохраняем
значения W и TIME1, пишем некоторые константы в TIME2 и TIME3 (они обеспечивают
нужное время паузы)
В первых двух строчках
уменьшаем TIME2 до 0, потом восстанавливаем значение TIME2 (00h-1 = FFh)
Ходим через GOTO на 5 строке
пока TIME3 не станет 0
Восстанавливаем значения
TIME2 и TIME3.
Если TIME1 больше 1, проходим
все заново количество раз, равное TIME1 – 1 (один раз мы уже прошли)
Если TIME1 = 0, восстанавливаем
сохранённые W и TIME1, выходим из подпрограммы паузы.
TIME1 мы сохраняем и восстанавливаем
для того чтобы не писать его по много раз если он всегда один и тот же (процедура
паузы же универсальная и может использоваться в другой программе).
Подпрограммы удобно держать
в самом конце программы.
Часть шестая – фаза 2
LOOP2
MOVLW B'00000001'
RRF TEMP1,1
IORWF TEMP1,0
MOVWF PORTB
CALL PAUSE10
BTFSS TEMP1,1h
GOTO LOOP2
BCF PORTA,03h
CALL PAUSE5
BSF PORTA,03h
Здесь мы делаем очень похожие
действие, что в части пятой. Различие в том, что в конце этой фазы светодиоды
у нас «сталкиваются», поэтому надо зажечь RA3 на момент. Для этого используется
процедура PAUSE5 (см листинг в конце), имеющая фиксированное время паузы (около
5мс).
Часть шестая и седьмая – фазы 3
и 4
LOOP3
MOVLW B'00000001'
RLF TEMP1,1
IORWF TEMP1,0
MOVWF PORTB
CALL PAUSE10
BTFSS TEMP1,7h
GOTO LOOP3
RLF TEMP1,1
LOOP4
MOVLW B'10000000'
RLF TEMP1,1
IORWF TEMP1,0
MOVWF PORTB
CALL PAUSE10
BTFSS TEMP1,6h
GOTO LOOP4
BCF PORTA,03h
CALL PAUSE5
BSF PORTA,03h
GOTO LOOP1
Опять же, всё очень похоже,
отличие лишь в том, что TEMP1 мы двигаем влево. В конце фазы 4 мы переходим
в начало, имея позицию светодиодов, как в начале.
Сложив все части программы
и добавив кое-что ещё, можно получить следующий листинг:
STATUS EQU 03h
PORTA EQU 05h
PORTB EQU 06h
TIME1 EQU 0Ch
TIME2 EQU 0Dh
TIME3 EQU 0Eh
VARS EQU 0Fh
TEMP1 EQU 10h
TW EQU 11h
C EQU 0h
DC EQU 1h
Z EQU 2h
PD EQU 3h
TO EQU 4h
RP EQU 5h
TRISA EQU 85h
TRISB EQU 86h
ORG 0
GOTO BEGIN
ORG 100h
BEGIN
CLRF PORTA
CLRF PORTB
BSF STATUS,RP
CLRF TRISA
CLRF TRISB
BCF STATUS,RP
BSF PORTA,3
MOVLW 7h
MOVWF TIME1
BCF STATUS,C
CLRF TEMP1
BSF TEMP1,06h
BSF PORTB,07h
BSF PORTB,06h
LOOP1
MOVLW B'10000000'
RRF TEMP1,1
IORWF TEMP1,0
MOVWF PORTB
CALL PAUSE
BTFSS TEMP1,0h
GOTO LOOP1
RRF TEMP1,1
LOOP2
MOVLW B'00000001'
RRF TEMP1,1
IORWF TEMP1,0
MOVWF PORTB
CALL PAUSE
BTFSS TEMP1,1h
GOTO LOOP2
BCF PORTA,03h
CALL PAUSE5
BSF PORTA,03h
LOOP3
MOVLW B'00000001'
RLF TEMP1,1
IORWF TEMP1,0
MOVWF PORTB
CALL PAUSE
BTFSS TEMP1,7h
GOTO LOOP3
RLF TEMP1,1
LOOP4
MOVLW B'10000000'
RLF TEMP1,1
IORWF TEMP1,0
MOVWF PORTB
CALL PAUSE
BTFSS TEMP1,6h
GOTO LOOP4
BCF PORTA,03h
CALL PAUSE5
BSF PORTA,03h
GOTO LOOP1
PAUSE5
MOVWF TW
MOVF TIME1,W
MOVWF VARS
MOVLW 01h
MOVWF TIME1
MOVLW 0FFh
MOVWF TIME2
MOVLW 15h
MOVWF TIME3
GOTO PAUSELOOP
PAUSE
MOVWF TW
MOVF TIME1,W
MOVWF VARS
MOVLW 0FFh
MOVWF TIME2
MOVLW 0Dh
MOVWF TIME3
PAUSELOOP
DECFSZ TIME2,1
GOTO PAUSELOOP
DECF TIME2
DECFSZ TIME3,1
GOTO PAUSELOOP
DECF TIME2
MOVLW 0Dh
MOVWF TIME3
DECFSZ TIME1,1
GOTO PAUSELOOP
MOVF VARS,W
MOVWF TIME1
MOVF TW,W
RETURN
END
Остаётся сказать, что всё,
что находится после знака ; ассемблер игнорирует, а команда END нужна ассемблеру,
чтобы понять, что дальше кода (хода =)) нет.
Статью написал Froxy.
|