Зеркало сайта: vivacious-stockings-frog.cyclic.app
pull-up c английского
переводится как подтягивание
Когда речь шла об управлении дисплеем HD44780, то там все было относительно ясно. Напрямую соединялось два устройства, одно работало только на прием, другое только на передачу. Поэтому достаточно было выставить нужный логический уровень на выводах GPIO микроконтроллера, чтобы все работало.
Несколько сложнее обстоят дела, когда нужно организовать обмен данными между несколькими устройствами. Выводы устройств имеют два состояния: логичская единица т.е. +5 или +3 Вольта и логический ноль, когда контакт соединен с "землей". Если начать соединять их напрямую, то произойдет короткое замыкание. Bit-banging это та тема, где нужно четко представлять, что происходит.
На основе этих трех состояний можно постоить последовательную шину вида:
Небольшая демо-программа, таймер для ATmega8 с дисплеем HD44780. В качестве отправной точки использовал исходник из предыдущего поста. Идея была почерпнута здесь: LCD "Bigfont" Numbers over 2 or 4 lines".
Исходник:
Управление ЖК-дисплеем HD44780 довольно простое. Он имеет параллельную шину на 8 пин и три управляющих линии.
UART для передачи использует два
провода, один на прием, другой
на передачу. Поэтому протокол
является полнодуплексным. Однако,
регистр данных UDR один.
Работа с UART через прерывания мне показалась более эффективной нежели через циклы ожидания. Потому что, программа кроме того что принимает или передает что-либо через UART, она, как правило, занята еще какими-то задачами, будь то опрос датчиков или вывод информации на дисплей. И в таком случае возникает совсем не простая проблема эффективного переключения между задачами. И если, например, ожидание приема корректных данных затянется то микроконтроллер по сути подвиснет. Делать прерывание ожидания по таймауту, это лишний код которого можно избежать, если реализовать рабту с UART через прерывания. Ниже приведен исходник эхо-программы которая читает строку через UART и посылает ее обратно. Строка должна оканчиваться символом двоеточия. В программе используется USART_RXC вектор, который срабатывает когда на порт что-то поступило. Обработчик прерывания это "что-то" переносит в рабочий буфер. Если принятый байт является символом двоеточия, то устанавливается флаг done, по которому главная программа понимает что были приняты какие-то данные. Конечно, следовало бы принятую строку заносить в сетк, но в данном случае решил исходник не усложнять.
Работа с UART на ATmega несколько проще чем с таймерами, но на мой взгляд, в целом эта тема более мутная, хотя и более интересная. Зависит от того, с какой стороны посмотреть.
Так же как и с таймерами, для работы c USART имеется набор прерываний:
и регистров:
Нулевой таймер на ATmega8 не имеет апаратного ШИМ'а, поэтому ему трудно придумать применение кроме как простого счетчика. На 16MГц кварце и делителем на 64, прерывание по переполнению счетчика будет вызаваться с частотой 1КГц, т.е. каждый вызов будет равняться одной миллисекунде. Разрядности unsigned long для счетчика должно хватить на 49 дней. В целом, такой счетчик мало годится для построения часов, но в качестве таймера для расчета разных задержек подойдет вполне.
/* timer.c LED Blink whitout delay via 8-bit Timer0 for AVR ATmega8 */ #define F_CPU 16000000UL #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> volatile unsigned long millis; ISR(TIMER0_OVF_vect) { millis++; } int main(void) { DDRB |= (1<<PB5); // pinMode(13,OUTPUT); в Wiring TIMSK =(1<<TOIE0); // timer0 enable TCCR0 = (1<<CS01) | (1<<CS00); // prescaler 1/64 volatile unsigned long curTime, lastTime, period; millis=0; lastTime=0; period=1000; // 1 sec sei(); for (;;) { curTime=millis; if ((curTime -lastTime)> period) { PORTB ^= (1<<PB5); lastTime=curTime; } } return 0; }
Второй 8-битный таймер ATmega8 - TIMER2. Кроме перывания по переполнению счетчика, расмотренное в предыдущем посте, TIMER2 имеет второе прерывание по значению компаратора. Т.е. не надо будет ждать переполнения счетчика, прерывание сработает когда счетчик достигнет определенного значения. Это позволяет более точно устанавливать частоту срабатывания и по сути является производным генератором частоты. Штука архиполезная, позволяет выставлять значения портов по какой либо-функции или наоборот снимать показания с портов с некой периодичностью. Используется для PWM, но PWM оставим на потом, сейчас будет полезно разобраться с CTC режимом работы компаратора, когда его можно использовать как счетчик.
CTC режим (Clear Timer on Compare Match Mode), это режим при котором зачение таймера сбрасывается при достижении занчения определенного в регистре OCR2. Для TIMER2 данный режим устанавливается в TCCR2 регистре флагом WGM21:
Если посмотреть таблицу прерываний для ATmega8 из предыдущего поста, то видно, что почти половина из них, это прерывания для работы с таймерами/cчетчиками. Также для их конфигурации отведена значительная часть регистров:
Также для их конфигурации отведена значительная часть регистров:
В отсутствии многозадачного режима,
вектора прервываний это единнственный
способ реализовать многозадачность
В одном из предыдущих постов я дизассемлировал blink написаный на Си, и затем переписывал программу на ассемблере. Помнится тогда я выбросил таблицу прерываний под предлогом, что прерывания здесь не используются. Однако, в дальнейшем они будут активно использоваться и поэтому с ней следует разобраться.
Делать все буду на примере ATmega8, мне пока этот микроконтроллер кажется наиболее привлекательным.
В начале каждой программы на ассемблере должна идти таблица векторов прерываний, т.е. ссылок на продпрограммы, которые будут обрабатывать то или иное прерывание. Прерывание, это в свою очередь какое-то событие, при котором ход программы прерывается, и процесор бросается обработать это событите. Как не трудно догадаться, обрабатывать он это событие будет, с помощью той подпрограммы, ссылку на которую, мы запишем в таблице векторов прерывание.
Для каждой модели микроконтроллера набор прерываний свой, порядок их чередования задан на аппаратном уровне. Поэтому перед написанием первой программы для микроконтроллера, следует ознакомиться с этой таблицой в оффициальной документации. Дока на ATmega8: ATmega8/L datasheet. На странице 46 находим такую табличку:
Если разобраться в предыдущем посте: дизассемблирование blink.hex, где подробно разобрана реализация blink на ассемблере AVR, то проблем с написанием собственного варианта Blink на ассемблере не будет. Вообще-то программа там уже выложена. Осталось только разобраться с преобразованием исходников в hex файлы.
Мой вариант blink.asm:
.equ DDRB, 0x17 .equ PB0, 0x00 .equ PORTB, 0x18 .org 0x00 ; начало sbi DDRB, PB0; порт PB0 на передачу ldi r25, 0x01; r25=1 ; главный цикл loop: in r24, PORTB; r24=PORTB eor r24, r25; r24 = r24 xor r25 out PORTB, r24; PORTB=r25 ldi r18, 0x3F; r18=0x3F ldi r19, 0x0D; r19=0x0D ldi r24, 0x03; r24=0x03 sleep: subi r18, 0x01; (r18r19r24)-1 вычитание трехбайтного целого числа sbci r19, 0x00 sbci r24, 0x00 brne sleep; если значение в r24 не равно нулю, то переход на начало операции вычитания rjmp loop; возврат на начало главного цикла
Компиляция:
avr-as -mmcu=attiny13 -o blink.o blink.asm
avr-ld -o blink.elf blink.o
avr-objcopy --output-target=ihex blink.elf blink.hex
Смотрим, все ли в порядке:
$ avr-objdump -m avr -D blink.hex blink.hex: file format ihex Disassembly of section .sec1: 00000000 <.sec1>: 0: b8 9a sbi 0x17, 0 ; 23 2: 91 e0 ldi r25, 0x01 ; 1 4: 88 b3 in r24, 0x18 ; 24 6: 89 27 eor r24, r25 8: 88 bb out 0x18, r24 ; 24 a: 2f e3 ldi r18, 0x3F ; 63 c: 3d e0 ldi r19, 0x0D ; 13 e: 81 e0 ldi r24, 0x03 ; 3 10: 21 50 subi r18, 0x01 ; 1 12: 30 40 sbci r19, 0x00 ; 0 14: 80 40 sbci r24, 0x00 ; 0 16: e1 f7 brne .-8 ; 0x10 18: f5 cf rjmp .-22 ; 0x4