Код: Выделить всё
.include "m88PAdef.inc" ; Используем ATmega88PA
.include "macrobaselib.inc" ; Макроопределения
;======== RAM ============
.DSEG ; Сегмент ОЗУ
StrPtr: .byte 2
UCSR0B_temp: .byte 1
.equ MAXBUFF_IN = 10 ; Размер в байтах
.equ MAXBUFF_OUT = 10
IN_buff: .byte MAXBUFF_IN ; Буфер приема
IN_PTR_S: .byte 1 ; Указатель начала
IN_PTR_E: .byte 1 ; Указатель конца
IN_FULL: .byte 1 ; Флаг переполнения
OUT_buff: .byte MAXBUFF_OUT ; Буфер передачи
OUT_PTR_S: .byte 1 ; Указатель начала
OUT_PTR_E: .byte 1 ; Указатель конца
OUT_FULL: .byte 1 ; Флаг переполнения.
ADC_state: .byte 1 ;Состояние АЦП (вкл/выкл)
RX_sel: .byte 1 ;Переменная состояния отправки данных
ADCH_sel: .byte 1 ;Переменная текущего канала АЦП
ADCCH: .byte 8 ;Восемь байт под хранение результатов АЦП. По байту на канал.
;======= FLASH ===========
.CSEG ; Кодовый сегмент
.ORG 0x0000 ; (RESET)
RJMP Reset
.include "ivectors.inc" // Подключим файл с таблицой прерываний
;----------
; Это обработчик прерывания. Тут, на просторе, можно наворотить сколько
; угодно кода.
RX_OK:
PUSHF ; Макрос, пихающий в стек SREG и R16
PUSH R17
PUSH R18
PUSH XL
PUSH XH
LDI XL,low(IN_buff) ; Берем адрес начала буффера
LDI XH,high(IN_buff)
LDS R16,IN_PTR_E ; Берем смещение точки записи
LDS R18,IN_PTR_S ; Берем смещение точки чтения
ADD XL,R16 ; Сложением адреса со смещением
CLR R17 ; получаем адрес точки записи
ADC XH,R17
LDS R17,UDR0 ; Забираем данные
ST X,R17 ; сохраняем их в кольцо
INC R16 ; Увеличиваем смещение
CPI R16,MAXBUFF_IN ; Если достигли конца
BRNE NoEnd
CLR R16 ; переставляем на начало
NoEnd:
CP R16,R18 ; Дошли до непрочитанных данных?
BRNE RX_OUT ; Если нет, то просто выходим
RX_FULL:
LDI R18,1 ; Если да, то буффер переполнен.
STS IN_FULL,R18 ; Записываем флаг наполненности
RX_OUT:
STS IN_PTR_E,R16 ; Сохраняем смещение. Выходим
POP XH
POP XL
POP R18
POP R17
POPF ; Достаем SREG и R16
RETI
;***********************************
TX_OK:
RETI
;***********************************
UD_OK:
PUSHF ; Пихаем в стек SPEG, R16, R17, R18, R19
PUSH R17 ; и пару XL:XH
PUSH R18
PUSH R19
PUSH XL
PUSH XH
LDI XL,low(OUT_buff) ; Берем адрес начала буффера
LDI XH,high(OUT_buff)
LDS R16,OUT_PTR_E ; Берем смещение точки записи
LDS R18,OUT_PTR_S ; Берем смещение точки чтения
LDS R19,OUT_FULL ; Берем флаг переполнения
CPI R19,1 ; Если буффер переполнен, то указатель начала
BREQ NeedSend ; Равен указателю конца. Это надо учесть.
CP R18,R16 ; Указатель чтения достиг указателя записи?
BRNE NeedSend ; Нет! Буффер не пуст. Надо слать дальше
LDI R16,(1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0)|(1<<TXCIE0)|(0<<UDRIE0)
STS UCSR0B, R16 ; Запрет прерывания По пустому UDR
RJMP TX_OUT ; Выходим
NeedSend:
CLR R17 ; Получаем ноль
STS OUT_FULL,R17 ; Сбрасываем флаг переполнения
ADD XL,R18 ; Сложением адреса со смещением
ADC XH,R17 ; получаем адрес точки чтения
LD R17,X ; Берем байт из буффера
STS UDR0,R17 ; Отправляем его в USART
INC R18 ; Увеличиваем смещение указателя чтения
CPI R18,MAXBUFF_OUT ; Достигли конца кольца?
BRNE TX_OUT ; Нет?
CLR R18 ; Да? Сбрасываем, переставляя на 0
TX_OUT:
STS OUT_PTR_S,R18 ; Сохраняем указатель
POP XH
POP XL
POP R19
POP R18
POP R17
POPF ; Выходим, достав все из стека
RETI
;**************************************
ADC_OK:
PUSHF
PUSH ZL
PUSH ZH
PUSH R17
LDS R16,ADMUX ;Берем ADMUX
ANDI R16,0b0001111 ;Маской отрезаем лишние биты. Получаем номер канала
;С которого было снято измерение.
;MOV R17,R16 ; Cохранили копию номера канала
LDI ZL,low(ADCCH) ; Берем адрес начала массива с будущими данными.
LDI ZH,High(ADCCH)
ADD ZL,R16 ; Прибавляем к адресу наш номер канала.
; Если было переполнение, то будет флаг С
CLR R16 ; Флаг важен, а значение в R16 уже нет. Но нам нужен ноль
; Возьмем и сделаем его из R16.
ADC ZH,R16 ; Сложим флаг возможного переполнения с ZH
; Т.о. у нас получается в Z = адрес (ADCCH+номер канала)
; И значения из разных каналов ложатся в разные переменные массива
; с адресом базы ADCCH
LDS R16,ADCL ; Младшее значение нам не надо. Но считать его нужно.
LDS R16,ADCH ; Берем в R16 значение из АЦП
ST Z,R16 ; Сохраняем его в массив по нужному адресу
/*
LDS R16,ADMUX ; Опять взяли ADMUX
ANDI R16,0xF8 ; На этот раз обнулили номер канала. Оставив остальные биты нетронутыми
INC R17 ; Увеличили на 1 заныченный ранее номер канала
ANDI R17,0x07 ; Обрезали лишние биты, чтобы не было переполнения.
; Число по маске 0х07 в принципе не может быть больше 7.
; А каналов у нас от 0 до 7. То что надо.
OR R16,R17 ; Слепили старое значение из ADMUX c новым значением номера
OUT ADMUX,R16 ; Канала. Т.е. по факту сделали MUX = MUX+1 выбрав следующий канал
; Спихнули его в регистр ADMUX. Все, следующий канал выбран. Можно запускать
; Следующее преобразование. */
OUTI ADCSRA,(1<<ADEN)|(1<<ADIE)|(1<<ADSC)|(0<<ADATE)|(3<<ADPS0) ; Запустили
POP R17 ; Корректный выход из прерывания.
POP ZH
POP ZL
POPF
RETI
;----------
// Теперь инициализируем всё, что нам нужно.
Reset:
STACKINIT // Инициализация стека
RAMFLUSH // Очистка ОЗУ
GPRFLUSH // Очистка РОН (Регистров Общего Назначения)
.equ XTAL = 16000000
.equ baudrate = 9600
.equ bauddivider = XTAL/(16*baudrate)-1
//Инициализация USART
LDI R16, low(bauddivider) ;Записываем в UBRR0H:UBRR0L
STS UBRR0L,R16 ;делитель для настройки скорости передачи
LDI R16, high(bauddivider)
STS UBRR0H,R16
LDI R16, 0
STS UCSR0A, R16
; Прерывания прием-передачи разрешены, прием-передача разрешена
LDI R16, (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0)|(1<<TXCIE0)|(0<<UDRIE0)
STS UCSR0B, R16
; Формат кадра - 8 бит
LDI R16, (1<<UCSZ00)|(1<<UCSZ01)
STS UCSR0C, R16
//Инициализация АЦП
OUTI ADMUX,(1<<REFS0)|(1<<ADLAR)|(0<<MUX0)
;Опорное напряжение с AVCC, выравнивание влево, канал 0.
//Мигание светодиодом для зрительного контроля рестарта микроконтроллера
OUTI DDRB, 0b11111101 //Инициализация порта B, PB0 - на выход
RCALL LED_flashing
SEI // Глобально разрешаем прерывания
Main:
rcall Delay
rcall Delay
RCALL Buff_Pop ; Вызываем подпрограмму чтения из буфера приема USART
; В R19 - код ошибки (1 - пустой буфер), R17 - данные.
CPI R19, 1 ; Проверяем, пустой ли буфер.
BREQ M1 ; Если буфер пуст, то идем на М1.
RCALL Processing_of_external_commands
; Процедура обработки команд, принимаемых по USART
M1:
LDS R16, ADC_state
SBRS R16, 0 ; Если АЦП запущен, то с него нужно отправить данные по USART
rjmp M2 ; А если не запущен, то идем дальше
LDS R19, ADCCH ; Берем данные с 0-ого канала АЦП
RCALL Buff_Push ; Кладем их в буфер
LDI R16,(1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0)|(1<<TXCIE0)|(1<<UDRIE0)
STS UCSR0B, R16 ; Разрешаем прерывания По пустому UDR
M2:
rjmp Main
/*----------
| Начало сектора подпрограмм и функций |
----------*/
;---------- обработки команд внешнего управления-------*
Processing_of_external_commands:
cpi R17, '0' ;Сравниваем R17 с нулем
BREQ I1 ;Если пришел нуль, то гасим светодиод
cpi R17, '1' ;Сравниваем R17 с единицей
BREQ I2 ;Если пришла единица - зажигаем
cpi R17, 'G' ;Сравниваем R17 c 'G'
BREQ I3 ;Запускаем АЦП - Go
cpi R17, 'S' ;Сравниваем R17 c 'S'
BREQ I4 ;Выключаем АЦП - Stop
rjmp Processing_of_external_commands_EXIT
I1:
cbi portb,0 ;Гасим светодиод
rjmp Processing_of_external_commands_EXIT
I2:
sbi portb,0 ;Зажигаем светодиод
rjmp Processing_of_external_commands_EXIT
I3:
cli
LDI R16, 0b00000001
STS ADC_state, R16
sei
OUTI ADCSRA,(1<<ADEN)|(1<<ADIE)|(1<<ADSC)|(0<<ADATE)|(3<<ADPS0)
rjmp Processing_of_external_commands_EXIT
I4:
cli
LDI R16, 0b00000000
STS ADC_state, R16
sei
OUTI ADCSRA,(0<<ADEN)|(0<<ADIE)|(0<<ADSC)|(0<<ADATE)|(3<<ADPS0)
Processing_of_external_commands_EXIT:
RET
;---------- подпрограммы-----------------------------------
;---------- задержки [Delay]----------*
Delay:
LDI R20,0
Loop:
dec R20
BRNE Loop
dec R21
BRNE Loop
ret
;---------- подпрограммы-----------------------------------
;--------Подпрограмма чтения из буфера USART---------------------------*
; OUT: R17 - Data,
; R19 - ERROR CODE
Buff_Pop:
LDS R16, UCSR0B ; Берем в R16 значение UCSR0B
STS UCSR0B_temp, R16 ; Сажаем предыдущее значение UCSR0B в ОЗУ
LDI R16, (0<<RXCIE0)|(0<<TXCIE0)|(0<<UDRIE0)
STS UCSR0B, R16 ; Запрещаем прерывания от UART
; для соблюдения атомарности.
LDI XL,low(IN_buff) ; Берем адрес начала буффера
LDI XH,high(IN_buff)
LDS R16,IN_PTR_E ; Берем смещение точки записи
LDS R18,IN_PTR_S ; Берем смещение точки чтения
LDS R19,IN_FULL ; Берм флаг переполнения
CPI R19,1 ; Если буффер переполнен, то указатель начала
BREQ NeedPop ; Равен указателю конца. Это надо учесть.
CP R18,R16 ; Указатель чтения достиг указателя записи?
BRNE NeedPop ; Нет! Буффер не пуст. Работаем дальше
LDI R19,1 ; Код ошибки - пустой буффер!
RJMP _TX_OUT ; Выходим
NeedPop:
CLR R17 ; Получаем ноль
STS IN_FULL,R17 ; Сбрасываем флаг переполнения
ADD XL,R18 ; Сложением адреса со смещением
ADC XH,R17 ; получаем адрес точки чтения
LD R17,X ; Берем байт из буффера
CLR R19 ; Сброс кода ошибки
INC R18 ; Увеличиваем смещение указателя чтения
CPI R18,MAXBUFF_OUT ; Достигли конца кольца?
BRNE _TX_OUT ; Нет?
CLR R18 ; Да? Сбрасываем, переставляя на 0
_TX_OUT:
STS IN_PTR_S,R18 ; Сохраняем указатель
LDS R16, UCSR0B_temp ;Загружаем в R16 предыдущее состояние UCSR0B
STS UCSR0B, R16 ;Возвращаем обратно прерывания по USART
RET
;---------- подпрограммы-----------------------------------------
;--------Подпрограмма записи в буфер USART-----------------------------*
; IN R19 - DATA
; OUT R19 - ERROR CODE
Buff_Push:
PUSH ZL
PUSH ZH
LDS R16, UCSR0B ; Берем в R16 значение UCSR0B
STS UCSR0B_temp, R16 ; Сажаем предыдущее значение UCSR0B в ОЗУ
LDI R16, (0<<RXCIE0)|(0<<TXCIE0)|(0<<UDRIE0)
STS UCSR0B, R16 ; Запрещаем прерывания от UART
; для соблюдения атомарности.
LDI XL,low(OUT_buff) ; Берем адрес начала буффера
LDI XH,high(OUT_buff)
LDS R16,OUT_PTR_E ; Берем смещение точки записи
LDS R18,OUT_PTR_S ; Берем смещение точки чтения
ADD XL,R16 ; Сложением адреса со смещением
CLR R17 ; получаем адрес точки записи
ADC XH,R17
ST X,R19 ; сохраняем их в кольцо
CLR R19 ; Очищаем R19, теперь там код ошибки
; Который вернет подпрограмма
INC R16 ; Увеличиваем смещение
CPI R16,MAXBUFF_OUT ; Если достигли конца
BRNE _NoEnd
CLR R16 ; переставляем на начало
_NoEnd:
CP R16,R18 ; Дошли до непрочитанных данных?
BRNE _RX_OUT ; Если нет, то просто выходим
_RX_FULL:
LDI R19,1 ; Если да, то буффер переполнен.
STS OUT_FULL,R19 ; Записываем флаг наполненности
; В R19 остается 1 - код ошибки переполнения
_RX_OUT:
STS OUT_PTR_E,R16 ; Сохраняем смещение. Выходим
LDS R16, UCSR0B_temp ;Загружаем в R16 предыдущее состояние UCSR0B
STS UCSR0B, R16 ;Возвращаем обратно прерывания по USART
POP ZH
POP ZL
RET
;---------- подпрограммы-----------------------------------------
;--------Подпрограмма стартового мигания светодиодом-------------------*
LED_flashing:
OUTI PORTB, 0b00000011 //Подтяжка на PB0 и PB1
rcall Delay
rcall Delay
OUTI PORTB, 0b00000010
rcall Delay
rcall Delay
OUTI PORTB, 0b00000011
rcall Delay
rcall Delay
OUTI PORTB, 0b00000010
rcall Delay
rcall Delay
OUTI PORTB, 0b00000011 //Подтяжка на PB0 и PB1
rcall Delay
rcall Delay
OUTI PORTB, 0b00000010
rcall Delay
rcall Delay
OUTI PORTB, 0b00000011
rcall Delay
rcall Delay
OUTI PORTB, 0b00000010
rcall Delay
rcall Delay
OUTI PORTB, 0b00000011 //Подтяжка на PB0 и PB1
rcall Delay
rcall Delay
OUTI PORTB, 0b00000010
rcall Delay
rcall Delay
OUTI PORTB, 0b00000011
rcall Delay
rcall Delay
OUTI PORTB, 0b00000010
rcall Delay
rcall Delay
RET
;---------- подпрограммы-----------------------------------
String: .db "23C ",0
;===== EEPROM ============
.ESEG ; Сегмент EEPROM (ППЗУ)