Программа, рассмотренная в этой статье, разработана для контроллера SPI-шлюза (шлюз у нас реализован на ATTiny2313). Эта программа позволяет из терминальной программы персонального компьютера общаться в качестве Master-а с различными SPI устройствами. В программе реализованы все 4 режима SPI, размер пакета может быть установлен от 1 до 64 бит, возможен выбор передачи пакета младшим или старшим битом вперёд, а также можно выбрать ручное или автоматическое управление линией SS. Написана программа полностью на ассемблере, в конце статьи выложены исходники (с комментариями) и прошивка.
Для реализации обмена данными по SPI между контроллером и подключаемым устройством были использованы стандартные, написанные нами ранее процедуры.
Протокол обмена с компьютером был придуман на ходу и состоит из сообщений, размером в 1 или несколько байт.
При передаче данных от компьютера к шлюзу, первый байт сообщения – это всегда команда. Далее, в зависимости от команды, сообщение может включать ещё несколько байт.
Команды используются такие:
01h – загрузка конфигурации в шлюз. После принятия такой команды шлюз ждёт два байта конфигурации, которые определяют режим SPI, размер пакета, режим управления линией SS, порядок передачи бит (младшим или старшим вперёд) и скорость передачи. В шлюзе конфигурация хранится в двух специально выделенных регистрах: C1 и C2. Здесь нужно пожалуй добавить, что максимальная скорость передачи по SPI получилась где-то в районе 75 КБит/с, что довольно медленно для SPI и позволяет работать с любым устройством. Поскольку в конфигурации по умолчанию как раз заложена эта максимальная скорость, то менять её в общем-то особого смысла нет.
02h – считывание конфигурации из шлюза. После принятия такой команды шлюз отсылает на компьютер два байта конфигурации (С1, С2).
03h – загрузить пакет в шлюз. После принятия такой команды шлюз ждёт N байт пакета. N зависит от размера пакета, установленного в конфигурации шлюза и определяется по формуле N=(K-1)/8+1, где К – размер пакета в битах.
04h – считать пакет из шлюза. После принятия этой команды шлюз отсылает на компьютер N используемых байт сдвигового регистра.
05h – переключить SS в единицу. Если в конфигурации установлено ручное управление линией SS, то после принятия этой команды шлюз переключает линию SS в единицу.
06h – переключить SS в ноль. Если в конфигурации установлено ручное управление линией SS, то после принятия этой команды шлюз переключает линию SS в ноль.
07h – старт обмена. После принятия этой команды шлюз выполняет обмен пакетами с подключенным к нему по SPI устройством, в соответствии с установленной конфигурацией (режим SPI, размер пакета и прочее).
При передаче данных от шлюза к компьютеру сообщение также может состоять из одного или нескольких байт. В данном случае служебным является последний байт сообщения, – он сообщает о результатах выполнения последней команды компьютера, а байты перед ним – это байты, запрошенные последней командой (конфигурация или пакет). Соответственно, если предыдущая команда не запрашивала байты от шлюза, то и сообщение шлюза будет состоять из одного только служебного байта.
Служебные байты могут быть такими:
01h – предыдущая команда выполнена успешно.
FFh – шлюзом была получена неизвестная команда.
FEh – попытка установить размер пакета равным нулю (то есть получены неправильные байты конфигурации).
FDh – попытка передачи, при высоком уровне на линии SS (такая ошибка возникает, если послать шлюзу команду 07h при ручном управлении линией SS, когда эта линия установлена в 1).
Вообще ручное и автоматическое управление линией SS работает следующим образом: при ручном управлении линия переключается только специальными командами от компа, при автоматическом – линия переключается в низкий уровень автоматически, сразу после приёма пакета от компьютера, после этого автоматически запускается обмен, а сразу после его завершения SS автоматически возвращается к высокому уровню (тут правильнее было бы проверять – не пришёл ли от компа ещё один пакет, пока мы передавали, и, в случае его обнаружения, – продолжать передачу, но пока переделывать прогу лень).
Прога написана таким образом, что при загрузке пакета из компьютера в шлюз, сначала надо отправлять младшие байты пакета, а потом старшие (т.е. если пакет имеет вид “2Ah 3Bh 4Eh”, то шлюзу надо сначала отправлять 4Eh, потом 3Bh, а потом 2Ah).
Из интересных фишек в реализации отмечу ещё вот какую. В зависимости от режима SPI и порядка передачи бит, чтение бита с шины и сдвиг могут происходить по разному (у нас есть 2 процедуры для чтения и 4 для сдвига) и в разном порядке. Чтобы в процессе обмена не выяснять каждый раз заново, какую именно процедуру нужно выполнять (это бы очень сильно снизило скорость работы) – мы делаем это только один раз, на этапе конфигурирования, а потом записываем в 4 специальных регистра (Proc1_Address_L, Proc1_Address_H, Proc2_Address_L, Proc2_Address_H) адреса процедур, нужных нам в данной конфигурации для чтения и сдвига, причём если сначала выполняется чтение, а потом сдвиг, то мы в первые два регистра пишем адрес процедуры чтения, а во вторые два – адрес процедуры сдвига, если сначала нам нужен сдвиг, потом чтение, то наоборот, – в первые два регистра пишем адрес процедуры сдвига, а во вторые два – адрес процедуры чтения. Всё, теперь непосредственно при обмене нам вообще не нужно задумываться о том, что и как делать, – нужно просто по чётным фронтам вызывать с помощью косвенной адресации процедуру по первому сохранённому адресу, а по нечётным – процедуру по второму сохранённому адресу.
Вот пожалуй и всё описание (если возникнут вопросы – добро пожаловать на форум), а теперь перейдём к алгоритму и программе.
Алгоритм:
Итак, в аппаратной части мы имеем:
PB0 – линия SCLK
PB1 – линия MISO
PB2 – линия MOSI
PD5 – линия SS
PD0 – линия Rx
PD1 – линия Tx
Programm:
;---------------------------------------------------------
.device ATtiny2313
.include "tn2313def.inc"
.list
;-- определяем свои переменные
.def Low_byte_Address = r0 ; здесь мы храним адрес младшего байта регистра
.def Hi_byte_Address = r1 ; здесь мы храним адрес старшего байта регистра
.def Byte_Number = r2 ; здесь храним размер сдвигового регистра
.def C1 = r3 ; первый байт конфигурации
;(старшие 2 бита - режим SPI, младшие 6 бит - размер пакета)
.def C2 = r4 ; второй байт конфигурации
;(старшие 2 бита - режим управления линией CS (0-man, 1-aut),
; порядок передачи битов (0-L, 1-H), младшие 6 бит - скорость)
.def SR_Offset = r5 ; смещение для выравнивания вправо/влево
.def Razmer_bit = r6 ; размер регистра в битах
.def Timer_Set = r7 ; значение для регистра таймера (скорость)
.def Proc1_Address_L = r8 ; младший байт адреса первой процедуры
.def Proc1_Address_H = r9 ; старший байт адреса первой процедуры
.def Proc2_Address_L = r10; младший байт адреса второй процедуры
.def Proc2_Address_H = r11; младший байт адреса второй процедуры
;--------------------------------------
.def REG_0=r16 ; начало сдвигового регистра (10h)
.def REG_1=r17
.def REG_2=r18
.def REG_3=r19
.def REG_4=r20
.def REG_5=r21
.def REG_6=r22
.def REG_7=r23 ; конец сдвигового регистра (17h)
;-------------------------------------
.def Front_Counter=r24 ; счётчик фронтов SCLK
.def Byte_Counter=r25 ; счётчик байт
;регистры YH=w, YL, ZH=temp1, ZL=temp2
;-------------------------------------
.def w=r29
.def temp1=r31
.def temp2=r30
;-- Используемые порты ---------------
.equ PORT_SPI=0x18 ; порт B - выходы
.equ PIN_SPI =0x16 ; порт B - входы
;-- определяем названия выводов ------
.equ SCLK_Line=0 ; PortB0/PinB0 - clock
.equ MISO_Line=1 ; PortB1/PinB1 - вход мастера
.equ MOSI_Line=2 ; PortB2/PinB2 - выход мастера
.equ CS_Line=5 ; PinD5 - выход Chip Select
; кроме того, мы используем линии Rx (PD0), Tx (PD1)
;-------------------------------------
;-- начало программного кода
.cseg
.org 0
rjmp Init ; переход на начало программы (вектор сброса)
;-- дальше идут вектора прерываний
;-- если не используем - reti, иначе - переход на начало обработчика
reti ; внешнее прерывание INT0
reti ; внешнее прерывание INT1
reti ; Input capture interrupt 1
reti ; Timer/Counter1 Compare Match A
reti ; Overflow1 Interrupt
reti ; Overflow0 Interrupt
rjmp RX_INT ; USART0 RX Complete Interrupt
reti ; USART0 Data Register Empty Interrupt
reti ; USART0 TX Complete Interrupt
reti ; Analog Comparator Interrupt
reti ; Pin Change Interrupt
reti ; Timer/Counter1 Compare Match B
rjmp T0_INT ; Timer/Counter0 Compare Match A
reti ; Timer/Counter0 Compare Match B
reti ; USI start interrupt
reti ; USI overflow interrupt
reti ; EEPROM write complete
reti ; Watchdog Timer Interrupt
;-- начало программы
Init: ldi w,RAMEND ; устанавливаем указатель вершины
out SPL,w ; стека на старший байт RAM
sbi ACSR,ACD ; выключаем компаратор
;-- инициализируем порты
ser w ; w=0xFF
out DDRA,w ; настраиваем порт A. все линии на выход
clr w ; w=0x00
out PORTA,w ; на всех линиях ноль
ldi w,0b11111101; настраиваем порт B. PB1 - вход
out DDRB,w
clr w ; определяем нач.состояние выходов и подт.на входах
out PORTB,w ; (выходы - нули, подтяжек нет)
ldi w,0b11111100; настраиваем порт D
out DDRD,w ; PD0, PD1 - входы
clr w ; определяем нач.состояние выходов и подт.на входах
out PORTD,w ; (выходы - нули, подтяжек нет)
;-- инициализируем UART
out UBRRH,w ; UBRRR (для кварца 20МГц и скорости 115200)
ldi w,10 ; равен 10, т.е. UBRRH=0, UBRRL=10
out UBRRL,w
ldi w,0b00001110; поднимаем биты USBS, UCSZ1:0
out UCSRC,w ; формат: 1 старт, 8 данные, 2 стоп
sbi UCSRB,TXEN ; включить передатчик
nop
sbi UCSRB,RXEN ; включить приёмник
;-- разрешаем прерывание от приёмника
sbi UCSRB,RXCIE ; включить прерывания от приёмника
;-- Загружаем начальную конфигурацию
ldi w,0b00001000; Mode0, пакет 8 бит
mov C1,w
ldi w,0b00010010; CS-man, младшим вперёд, скорость 18 (около 75 кГц)
mov C2,w
rcall Interface_config
;-- сообщаем компу, что загрузились и ждём команду
ldi w,0x01
out UDR,w
;-- разрешаем глобальные прерывания
sei
;-- ждём у моря погоды ---
Wait_data:
rjmp Wait_data
;---------------------------------------------------------
;--- Обработчик прерывания от таймера ---
T0_INT:
;-- генерим фронт (инвертируем clock)
in w,PORT_SPI
ldi temp1,(1<<SCLK_Line)
eor w,temp1 ; инвертируем clock
out PORT_SPI,w
;-- выполняем действия в зависимости от конфига
sbrc Front_Counter,0 ; если чётный фронт (считаем обратно), то
rjmp Proc2 ; выполняем функцию 1, иначе 2
Proc1:
mov ZH,Proc1_Address_H
mov ZL,Proc1_Address_L
icall
rjmp Next_T0_INT
Proc2:
mov ZH,Proc2_Address_H
mov ZL,Proc2_Address_L
icall
;-- уменьшить и проверить счётчик
Next_T0_INT:
dec Front_Counter
breq End_Transfer
reti
;-- Конец передачи
End_Transfer:
clr w
out TCCR0B,w ; выключаем предделитель
;-- выравнивание
sbrc C2,6 ; если передавали старшим битом вперёд - пропускаем
rcall Offset_Low
;---------------
in w,UDR ; читаем порт (чтоб сбросить флаг приёма, если что)
sbi UCSRB,RXCIE ; включаем прерывания от приёмника
sbrc C2,7 ; если управление линией CS автоматическое, то
rjmp Set_CS_H ; выполняем эту команду
ldi w,0x01 ; собщаем компу, что всё сделали
out UDR,w
reti ; и выходим
;------------------------------------------
;--- Обработчик прерывания от приёмника ---
RX_INT:
in w,UDR ; читаем байт из приёмника в w
cpi w,0x01 ; принятый байт = 1?
breq Config_Recieve ; принимаем конфигурацию с компа
cpi w,0x02 ; принятый байт = 2?
breq Config_Transmit ; отправляем конфигурацию на комп
cpi w,0x03 ; принятый байт = 3?
breq Paket_Upload ; загружаем пакет с компа
cpi w,0x04 ; принятый байт = 4?
breq Paket_Download ; выгружаем пакет на комп
cpi w,0x05 ; принятый байт = 5?
breq Set_CS_H ; CS=1
cpi w,0x06 ; принятый байт = 6?
breq Set_CS_L ; CS=0
cpi w,0x07 ; принятый байт = 7?
breq SPI_Start ; производим сеанс обмена
;-- Сообщаем, что приняли неизвестную команду и выходим
Error_Command:
ldi w,0xFF
out UDR,w
reti
;--- Говорим компу, что всё сделали и ждём ещё команд
Send_Ok:
ldi w,0x01
out UDR,w
ret
;--- ОБРАБОТЧИКИ КОМАНД --------------------------
;--------------------------------------------------
;-- принимаем конфигурацию (два байта от компа) ---
Config_Recieve:
Wait_First_Byte:
sbis UCSRA,RXC ; ждём первый байт от компа
rjmp Wait_First_Byte
in C1,UDR ; читаем принятый байт в C1
Wait_Second_Byte:
sbis UCSRA,RXC ; ждём второй байт от компа
rjmp Wait_Second_Byte
in C2,UDR ; читаем принятый байт в C2
;-- конфигурим интерфейс
rcall Interface_Config
;----------------
rcall Send_Ok ; собщаем компу, что всё сделали
;----------------
reti ; выходим
;--------------------------------------------------
;-- отсылаем компу конфиг
Config_Transmit:
out UDR,C1
Wait_Send_C1:
sbis UCSRA,UDRE
rjmp Wait_Send_C1
out UDR,C2
Wait_Send_C2:
sbis UCSRA,UDRE
rjmp Wait_Send_C2
;----------------
rcall Send_Ok ; собщаем компу, что всё сделали
;----------------
reti ; выходим
;--------------------------------------------------
;-- Загружаем пакет с компа ---
Paket_Upload:
clr w
mov XL,Low_byte_Address
Wait_Paket_Byte:
sbis UCSRA,RXC ; ждём первый байт от компа
rjmp Wait_Paket_Byte
in temp1,UDR ; читаем принятый байт в temp1
st X,temp1 ; сохраняем по адресу, записанному в X
dec XL ; переводим указатель на следующий байт
subi w,0xF8 ; вычитание F8 равносильно прибавлению восьми
cp w,Razmer_bit
brge End_of_Paket; если w>= кол-ва бит в пакете - приём пакета окончен
rjmp Wait_Paket_Byte
End_of_Paket:
;-- если нужно, то выравниваем
sbrc C2,6 ; если передаём младшим битом вперёд (6-й бит C2=0),
rcall Offset_Hi ; то пропускаем эту команду
;-- если нужно - сразу устанавливаем первый бит на выходе
sbrs C1,6 ; если CPHA=1 - пропускаем команду
rcall Set_MOSI
sbrc C2,7 ; если управление линией CS автоматическое, то
rjmp Set_CS_L ; выполняем эту команду
;-------------
rcall Send_Ok
;-------------
reti
;--------------------------------------------------
;-- Выгружаем пакет на комп ---
Paket_Download:
mov w,Byte_Number
mov XL,Low_byte_Address
Download_next_byte:
ld temp1,X
out UDR,temp1
dec w
breq Download_finished
Wait_Send_Byte:
sbis UCSRA,UDRE
rjmp Wait_Send_Byte
rjmp Download_next_byte
Download_finished:
;----------------
rcall Send_Ok ; собщаем компу, что всё сделали
;----------------
reti ; выходим
;--------------------------------------------------
;-- Поднимаем CS
Set_CS_H:
sbi PORTD, CS_Line
rcall Send_Ok ; собщаем компу, что всё сделали
reti
;--------------------------------------------------
;-- Роняем CS и если она в автоматическом режиме - начинаем передачу
Set_CS_L:
cbi PORTD, CS_Line
sbrc C2,7 ; если управление линией CS автоматическое, то
rjmp SPI_Start ; выполняем эту команду
rcall Send_Ok ; собщаем компу, что всё сделали
reti
;--------------------------------------------------
;-- Начинаем передавать содержимое регистра
SPI_Start:
;-- если CS=1, то ошибка
sbic PORTD, CS_Line
rjmp Error_CS
;-----------------------
cbi UCSRB,RXCIE ; выключаем прерывания от приёмника
mov Front_Counter, Razmer_bit ; инициализируем счётчик
lsl Front_Counter ; умножаем на 2 (количество фронтов)
;-- Сбрасываем таймер, его флаги и включаем прерывание от таймера
clr w
out TCNT0,w ; сбрасываем таймер
ser w
out TIFR,w ; сбрасываем флаги таймера
ldi w,0b00000010
out TCCR0B,w ; включаем предделитель
ldi w,0b00000001 ; разрешаем прерывание по сравнению
out TIMSK,w
reti
Error_CS:
ldi w,0xFD
out UDR,w
reti
;---------------------------------------------------------
;--- ПРОЦЕДУРЫ SPI ---------------------------------------
;-- Конфигурирование интерфейса
Interface_Config:
;-- определяем размер регистра
mov w,C1
andi w,0b00111111 ; отрезаем два старших бита
breq Error_Size ; если размер=0 - это ошибка
mov Razmer_bit,w ; записываем размер регистра в битах
;-- определяем кол-во используемых байт
dec w ; вычитаем 1
lsr w ; 3 раза делим на 2
lsr w ; (получается деление на 8)
lsr w
inc w ; и прибавляем 1
mov Byte_Number,w ; сохраняем кол-во используемых байт
;-- определяем величину сдвига для выравнивания
mov w,Razmer_bit
subi w,64 ; вычитаем 64 (получается N-64)
mov SR_Offset,w ; сохраняем вычисленное значение в регистре
;-- записываем адреса младшего и старшего байтов
ldi w,0x17 ; адрес REG_7
mov Low_byte_Address,w
ldi w,0x10 ; адрес REG_0
mov Hi_byte_Address,w
;-- настраиваем линию SCLK
sbrc C1,7 ; если CPOL=0 - пропускаем следующую команду
sbi PORT_SPI, SCLK_Line ; устанавливаем линию clock в единицу
sbrs C1,7 ; если CPOL=1 - пропускаем следующую команду
cbi PORT_SPI, SCLK_Line ; устанавливаем линию clock в ноль
;-- устанавливаем скорость и настраиваем таймер
mov w,C2
andi w,0b00111111 ; отрезаем два старших бита
inc w
out OCR0A,w ; загружаем значение для сравнения
ldi w,0b00000010 ; таймер отключен от выводов OC0A(B),
out TCCR0A,w ; сброс при совпадении
;-- определяем смещение адресов процедур, которые будут использоваться
;-- относительно метки Get_Address
;-- CPHA ? --
sbrc C1,6 ; если CPHA=0 - пропускаем команду
rjmp CPHA1
CPHA0:
;-- старшим или младшим вперёд
sbrc C2,6 ; если С2[6]=0, то младшим вперёд
rjmp CPHA0_H
CPHA0_L: ; CPHA=0, младшим вперёд
ldi temp1,11 ; ссылка на Read_MISO_L0H1
ldi temp2,25 ; ссылка на Shift_i_Set_MOSI_L0
rjmp Next_Int_Config
CPHA0_H: ; CPHA=0, старшим вперёд
ldi temp1,18 ; ссылка на Read_MISO_H0L1
ldi temp2,28 ; ссылка на Shift_i_Set_MOSI_H0
rjmp Next_Int_Config
CPHA1:
;-- старшим или младшим вперёд
sbrc C2,6 ; если С2[6]=0, то младшим вперёд
rjmp CPHA1_H
CPHA1_L: ; CPHA=1, младшим вперёд
ldi temp1,31 ; ссылка на Shift_i_Set_MOSI_L1
ldi temp2,18 ; ссылка на Read_MISO_H0L1
rjmp Next_Int_Config
CPHA1_H: ; CPHA=1, старшим вперёд
ldi temp1,34 ; ссылка на Shift_i_Set_MOSI_H1
ldi temp2,11 ; ссылка на Read_MISO_L0H1
Next_Int_Config:
rcall Save_Proc_Address
ret
Error_Size:
ldi w,0xFE ; сообщаем компу об ошибке
out UDR,w
ret
;-----------------------------------------------
;-- Выравнивание к старшему биту старшего байта
Offset_Hi:
mov w, SR_Offset
tst w
Next_Offset_Hi:
breq End_Offset_Hi
rcall Shift_Left
inc w
rjmp Next_Offset_Hi ; команда флаги не меняет, tst не нужно
End_Offset_Hi:
ret
;-- Выравнивание к младшему биту младшего байта
Offset_Low:
mov w, SR_Offset
tst w
Next_Offset_Low:
breq End_Offset_Low
rcall Shift_Right
inc w
rjmp Next_Offset_Low
End_Offset_Low:
ret
;-----------------------------------------------
;/////////////////////////////////////////////////////////
;-- Если в этом блоке что-то изменить, то адреса сдвинутся
;/////////////////////////////////////////////////////////
;-- Вычисляем адреса команд, которые будем вызывать
Save_Proc_Address:
rcall Get_Address
Get_Address:
pop Proc1_Address_H
pop Proc1_Address_L ;адрес предыдущей команды
mov Proc2_Address_H,Proc1_Address_H
mov Proc2_Address_L,Proc1_Address_L ; копируем его
;------------------------
add Proc1_Address_L,temp1
brcc No_inc_H1
inc Proc1_Address_H
No_inc_H1:
;------------------------
add Proc2_Address_L,temp2
brcc No_inc_H2
inc Proc2_Address_H
No_inc_H2:
ret
;-- Для передачи младшим битом вперёд при CPHA=0 и старшим битом вперёд
;-- при CPHA=1
Read_MISO_L0H1: ; +11
mov XL,Low_byte_Address
ld temp1, X
cbr temp1, 0b00000001
sbic PIN_SPI, MISO_Line
sbr temp1, 0b00000001
st X, temp1
ret
;-- Для передачи старшим битом вперёд при CPHA=0 и младшим битом вперёд
;-- при CPHA=1
Read_MISO_H0L1: ; +18
mov XL,Hi_byte_Address
ld temp1, X
cbr temp1, 0b10000000
sbic PIN_SPI, MISO_Line
sbr temp1, 0b10000000
st X, temp1
ret
;-----------------------------------------------
;-- Для передачи младшим битом вперёд при CPHA=0
Shift_i_Set_MOSI_L0: ; +25
rcall Shift_Right
rcall Set_MOSI
ret
;-- Для передачи при старшим битом вперёд CPHA=0
Shift_i_Set_MOSI_H0: ; +28
rcall Shift_Left
rcall Set_MOSI
ret
;-- Для передачи младшим битом вперёд при CPHA=1
Shift_i_Set_MOSI_L1: ; +31
rcall Set_MOSI
rcall Shift_Right
ret
;-- Для передачи старшим битом вперёд при CPHA=1
Shift_i_Set_MOSI_H1: ; +34
rcall Set_MOSI
rcall Shift_Left
ret
;//////////////////////////////////////////////////////////
;------------------------------------------------
;-- Установка MOSI
Set_MOSI:
in temp1, PORT_SPI
cbr temp1, (1 << MOSI_Line)
sbrc C2,6 ; если передаём младшим битом вперёд - пропускаем команду
rjmp First_HI
First_Low: ; при передаче младшим битом вперёд
mov XL, Low_byte_Address
ld temp2, X
sbrc temp2, 0
sbr temp1, (1 << MOSI_Line)
rjmp End_Set_MOSI
First_Hi: ; при передаче старшим битом вперёд
mov XL, Hi_byte_Address
ld temp2, X
sbrc temp2, 7
sbr temp1, (1 << MOSI_Line)
End_Set_MOSI:
out PORT_SPI, temp1
ret
;-----------------------------
;-- сдвиг регистра вправо ----
Shift_Right:
mov XL, Hi_byte_Address
ldi Byte_Counter, 8
clc
Next_Shift_Right:
ld temp1, X
ror temp1
st X, temp1
inc XL
dec Byte_Counter
brne Next_Shift_Right
brcc Shift_Right_Exit
mov XL, Hi_byte_Address
ld temp1, X
sbr temp1, 0b10000000
st X, temp1
Shift_Right_Exit:
ret
;-- сдвиг регистра влево ----
Shift_Left:
mov XL, Low_byte_Address
ldi Byte_Counter, 8
clc
Next_Shift_Left:
ld temp1, X
rol temp1
st X, temp1
dec XL
dec Byte_Counter
brne Next_Shift_Left
brcc Shift_Left_Exit
mov XL, Low_byte_Address
ld temp1, X
sbr temp1, 0b00000001
st X, temp1
Shift_Left_Exit:
ret
;--- КОНЕЦ ПРОЦЕДУР SPI -------------------------------
Для правильной работы шлюза в контроллере должны быть “запрограммированы” следующие фьюзы: SPIEN, SUT0
Скачать готовую прошивку и asm-файл
Приведу небольшой пример работы со шлюзом.
Пусть у нас есть драйвер семисегментных индикаторов max7219. Из даташита мы узнаём, что драйвером этим можно рулить по SPI в режиме Mode0, при этом размер пакета должен быть 16 бит и биты при обмене передаются старшим вперёд.
Итак, заходим в терминалку, выбираем порт, скорость обмена (скорость у нас 115200), подключаемся и делаем следующее:
– отправляем шлюзу 05h | // (установить CS=1) |
– получаем от шлюза 01h | // он сообщает, что установил CS=1 |
– отправляем шлюзу 01h | // говорим шлюзу, что будем слать конфигурацию |
– отправляем шлюзу 10h D2h | // Mode0, 16 бит, автоматическое управление SS, передача старшим вперёд, скорость “18” |
– получаем от шлюза 01h | // шлюз сообщает, что загрузил конфигурацию |
– отправляем шлюзу 03h | // говорим шлюзу, что будем слать пакет (поскольку выбрано автоматическое управление SS, то принятый пакет шлюз сразу же и отправит) |
– отправляем шлюзу 01h 0Сh | // пакет для max-а: “0Ch 01h” (выход из режима shutdown) |
– получаем от шлюза 01h | // шлюз сообщает, что всё успешно отправил |
– отправляем шлюзу 03h | // говорим шлюзу, что будем слать пакет |
– отправляем шлюзу 07h 0Bh | // пакет для max-а: “0Bh 07h” (scan limit=7, отображаем 7 символов) |
– получаем от шлюза 01h | // шлюз сообщает, что всё успешно отправил |
– отправляем шлюзу 03h | // говорим шлюзу, что будем слать пакет |
– отправляем шлюзу FFh 09h | // пакет для max-а: “09h FFh” (для всех символов режим декодирования B) |
– получаем от шлюза 01h | // шлюз сообщает, что всё успешно отправил |
– отправляем шлюзу 03h | // говорим шлюзу, что будем слать пакет |
– отправляем шлюзу 02h 0Bh | // пакет для max-а: “0Bh 02h” (отобразить на втором индикаторе символ “E”) |
– получаем от шлюза 01h | // шлюз сообщает, что всё успешно отправил |
В результате этих действий на втором семисегментном индикаторе, подключенном к нашему max7219, будет отображаться символ “E”.
Источник: http://radiohlam.ru