
- •1 Задание на курсовую работу
- •2 Разработка принципиальной схемы устройства
- •2.1 Аналоговая часть
- •2.2 Цифровая часть
- •3 Протокол обмена данными с компьютером
- •4 Разработка программного обеспечения контроллера
- •5 Тестирование программы
- •6 Алгоритм программного продукта
- •7 Листинг программы
- •8 Принципиальная схема устройства
5 Тестирование программы
Тестирование системы заключается в проверке работы внешнего ОЗУ как наиболее сложной части системы. Тестирование осуществляется заполнением ОЗУ данными с определенным значением, затем проверкой и изменением данных на инверсное значение и повторной проверкой. Это позволяет проверить наличие «битых» ячеек, имеющих всегда нулевое или единичное значение, а также шину данных ОЗУ. Проверка шины адреса ОЗУ осуществляется при повторной проверке после изменения значения – если на одну ячейку ссылаются два адреса, то после изменения значения по первому адресу изменится и значение по второму адресу, и вторая проверка даст ошибку в этом месте. В качестве данных записывается некоторое значение, получаемое путем прибавления к начальному значению (нулю) числа 3, т.е. записывается 0,3,6,9,… и т.д.
Так как во внешнем ОЗУ используется только 14400 значений, проверка проводится только по 16384 первым значениям. Это позволяет использовать ОЗУ объемом 16К вместо установленных 32К.
Прием пакета осуществляется в прерывании, состояние которого задается байтом rsrdpos. Возможно несколько состояний – ожидание начала приема (прием байта 0xАА), продолжение – прием байта 00, далее прием двух байтов и их сравнение. Если принят байт, не соответствующий ожидаемому, происходит возврат в состояние ожидания байта 0xАА. В противном случае если оба принятых байта совпали – сбрасываются соответствующие биты порта B.
Проводилось частичное тестирование программы на эмуляторе. На эмуляторе проверялось время теста ОЗУ при командах перехода, замененных на nop – 58.5 мс.
Проведенный тест говорит о том, что время тестирования ОЗУ не превышает заданного значения – 61 мс, и что частота опроса АЦП совпадает с расчетной. Также проверена логика переключения каналов АЦП и записи в ОЗУ. Работа с портом RS232 не тестировалась в связи со сложностью его эмуляции, а также невозможностью эмуляции внешнего ОЗУ и как следствие – отсутствия передаваемых данных.
6 Алгоритм программного продукта
Алгоритм основной программы:
Рисунок 4
Алгоритм обработчика прерывания от модуля сравнения таймера
Рисунок 5
Алгоритм обработчика прерывания от АЦП
4 8
4
4
F0
4
4
4
4
4
4
3600
3489
Рисунок 6
Алгоритм обработчика прерывания от приемника UART
Рисунок 7
Алгоритм обработчика прерывания от освобождения буфера передатчика UART
Рисунок 8
7 Листинг программы
.include "m128def.inc"
.DSEG
;Делитель для выравнивания частоты отсчетов АЦП
timerdiv: .BYTE 1
; Переменные получения данных с АЦП
adcsigma: .BYTE 8 ; 4 двухбайтовых сумм для усреднения результата
adcnum: .BYTE 1 ; Счетчик полученных измерений
arrpos: .BYTE 2 ; Позиция в буфере результата
; Буфер для хранения четных данных
rezbuf: .BYTE 4
; Переменные отправки данных по RS232
rsstate: .BYTE 1 ; Состояние алгоритма передачи
rspos: .BYTE 2 ; Положение передаваемого байта в буфере
rscrc: .BYTE 1 ; Контрольная сумма
; Переменные получения блока по RS232
rsrdpos: .BYTE 1 ; Состояние алгоритма приема пакета
rsbyte: .BYTE 1 ; Принятый байт
; Переменные процедуры тестирования внешнего ОЗУ
chkcnt: .BYTE 1 ; Счетчик блоков до повторного теста
work: .BYTE 1 ; устанавливается в не ноль при необходимости тестирования
; Сегмент кода векторов прерываний - 35 векторов. Используются только
; команды jmp, так как они занимают ровно столько места, сколько выделено
; под каждый вектор, и нет нужды задавать в явном виде адреса векторов.
.CSEG
.org 0x0
jmp main ; 1
jmp toreti ; 2
jmp toreti ; 3
jmp toreti ; 4
jmp toreti ; 5
jmp toreti ; 6
jmp toreti ; 7
jmp toreti ; 8
jmp toreti ; 9
jmp toreti ; 10
jmp toreti ; 11
jmp toreti ; 12
jmp toreti ; 13
jmp toreti ; 14
jmp toreti ; 15
jmp TimerOcr ; 16
jmp toreti ; 17
jmp toreti ; 18
jmp GetNextByte ; 19
jmp SendNextByte; 20
jmp toreti ; 21
jmp ADCgetrez ; 22
jmp toreti ; 23
jmp toreti ; 24
jmp toreti ; 25
jmp toreti ; 26
jmp toreti ; 27
jmp toreti ; 28
jmp toreti ; 29
jmp toreti ; 30
jmp toreti ; 31
jmp toreti ; 32
jmp toreti ; 33
jmp toreti ; 34
jmp toreti ; 35
; Мой сегмент кода
.CSEG
; "Пустая" процедура прерывания для неинициализированных векторов
toreti:
reti
;================================================
; Процедура, вызываемая при получении очередного байта
; Состояния приемника - rsrdpos
; 0 - получаем очередной байт, проверяем на 0xAA
; 1 - получаем очередной байт, проверяем на 00
; 2 - получаем и сохраняем
; 3 - получаем, проверяем с сохраненным. Если совпадает - меняем состояния флагов
; При сбое в любом состоянии (кроме 2 - в нём сбиваться нечему) возвращаемся к нулевому состоянию
GetNextByte:
push r16
push r17
push r18
lds r16,UDR0 ; Получаем принятый байт
lds r17,rsrdpos
; Выбираем куда переходить
ldi r18,1
sub r18,r17
breq rdst1
ldi r18,2
sub r18,r17
breq rdst2
ldi r18,3
sub r18,r17
breq rdst3
; Сюда - при нулевом либо ошибочном состоянии
ldi r17,0xAA
sub r16,r17
breq setst1
resetreader:
; Сбрасываем состояние
ldi r17,0
sts rsrdpos,r17
rjmp exitgnb
setst1:
ldi r17,1
sts rsrdpos,r17
rjmp exitgnb
rdst1:
ldi r17,0x00
sub r16,r17
brne resetreader
ldi r17,2
sts rsrdpos,r17
rjmp exitgnb
rdst2:
sts rsbyte,r17
ldi r17,3
sts rsrdpos,r17
rjmp exitgnb
rdst3:
lds r17,rsbyte
sub r17,r16
brne resetreader
; Применяем байт
in r17,PORTB
com r16
ori r16,0xC0
and r17,r16
out PORTB,r17
rjmp resetreader ; Возвращаемся к ожиданию байта
exitgnb:
pop r18
pop r17
pop r16
reti
;================================================
; Процедура, вызываемая при опустошении буфера передачи
; Вход - SendNextByte
; Внутренности процедуры - чтобы хватало длины отноительного перехода
snbis6:
lds r16,rscrc
out UDR0,r16
cbi UCSR0A,5 ; Запрещаем прерывание от освобождения буфера передачи
ldi r16,0
sts rsstate,r16 ; Сбрасываем в ноль
rjmp outsnb
snbis20:
snbis40:
ldi r16,0
out UDR0,r16
rjmp snbincstate
snbis21:
ldi r16,0xAA
out UDR0,r16
rjmp snbincstate
snbis22:
ldi r16,0xAA
out UDR0,r16
cbi UCSR0A,5 ; Запрещаем прерывание от освобождения буфера передачи
ldi r16,0
sts rsstate,r16 ; Сбрасываем в ноль
rjmp outsnb
snbis41:
ldi r16,0xFF
out UDR0,r16
rjmp snbincstate
snbis42:
ldi r16,0xFF
out UDR0,r16
cbi UCSR0A,5 ; Запрещаем прерывание от освобождения буфера передачи
ldi r16,0
sts rsstate,r16 ; Сбрасываем в ноль
rjmp outsnb
SendNextByte:
push r0
push r16
push r17
push r28
push r29
; Обработка - в зависимости от состояния
; Состояния могут быть:
; любой байт, не состоящий в списке - ничего не делаем, прерывания запрещаются. В это состояние процедура даже попадать не должна
; 1 - Передается первый байт, надо передать 0x00 и перейти к следующему состоянию
; 2 - Передается второй байт, надо передать 0x41, прибавить к CRC и перейти к следующему состоянию
; 3 - Передается третий байт, надо передать 0x38, прибавить к CRC и перейти к следующему состоянию
; 4 - Передается четвертый байт, надо передать байт флагов переполнения, прибавить к CRC и перейти к следующему состоянию
; 5 - Передается основной пакет. Надо передавать очередной байт, прибавлять к CRC пока не будет передано 14400 байтов. Затем перейти к следующему состоянию и разрешить проверку ОЗУ.
; 6 - Надо передать CRC и запретить прерывания - больше передавать нечего
; Передача пакета "тест успешен"
; 20 - Передаем 00, переходим к следующему
; 21 - Передаем AA
; 22 - Передаем AA и останавливаем передачу
; Передача пакета "тест не пройден"
; 40 - Передаем 00, переходим к следующему
; 41 - Передаем FF
; 42 - Передаем FF и останавливаем передачу
lds r0,rsstate
ldi r16,1
sub r16,r0
breq snbis1
ldi r16,2
sub r16,r0
breq snbis2
ldi r16,3
sub r16,r0
breq snbis3
ldi r16,4
sub r16,r0
breq snbis4
ldi r16,5
sub r16,r0
breq snbis5
ldi r16,6
sub r16,r0
breq snbis6
ldi r16,20
sub r16,r0
breq snbis20
ldi r16,21
sub r16,r0
breq snbis21
ldi r16,22
sub r16,r0
breq snbis22
ldi r16,40
sub r16,r0
breq snbis40
ldi r16,41
sub r16,r0
breq snbis41
ldi r16,42
sub r16,r0
breq snbis42
; Данного варианта нет в списке - запрещаем прерывания
cbi UCSR0A,5 ; Запрещаем прерывание от освобождения буфера передачи
ldi r16,0
sts rsstate,r16 ; На всякий случай сбрасываем в ноль
rjmp outsnb
snbis1:
ldi r16,0
out UDR0,r16
rjmp snbincstate
snbis2:
ldi r16,0x41
rjmp addcrcincstate
snbis3:
ldi r16,0x38
rjmp addcrcincstate
snbis4:
in r16,PORTB
andi r16,0x3F
rjmp addcrcincstate
snbis5:
; Тут получаем очередной байт из буфера
lds r0,rspos
lds r1,rspos+1
ldi r28,0 ; Прибавляем начальный адрес в памяти
ldi r29,0x80
add r28,r0
adc r29,r1
ld r16,Y ; Считываем
; Запускаем на передачу, добавляем к CRC
out UDR0,r16
lds r0,rscrc
add r0,r16
sts rscrc,r0
; Прибавляем 1 к счетчику байтов
ldi r16,1
ldi r17,0
add r16,r0
adc r17,r1
sts rspos,r0
sts rspos+1,r1
; Проверяем, не завершена ли передача пакета?
ldi r16,0x38
sub r1,r16
brne outsnb
ldi r16,0x40
sub r0,r16
brne outsnb
; Всё передано - проверяем, не запустить ли тест системы
lds r16,chkcnt
inc r16
sts chkcnt,r16
subi r16,0x0A
brne snbincstate ; Переходим к передаче CRC
; Выставляем флаг разрешения теста ОЗУ
ldi r16,0xFF
sts work,r16
; переходим к передаче CRC
rjmp snbincstate
; Выдаем в порт r16 и добавляем его к CRC
addcrcincstate:
out UDR0,r16
lds r0,rscrc
add r0,r16
sts rscrc,r0
; Переходим к следующему состоянию
snbincstate:
lds r0,rsstate
inc r0
sts rsstate,r0
outsnb:
pop r29
pop r28
pop r17
pop r16
pop r0
reti
;================================================
; Процедура, вызываемая каждые 1/480 секунды
TimerOcr:
push r16
push r17
; Корректируем значение регистра сравнения
in r16,OCR0
ldi r17,0xF0
add r16,r17
out OCR0,r16
; Запускаем АЦП
sbi ADCSR,6
;
pop r17
pop r16
reti
;================================================
; Процедура, вызываемая при завершении измерения АЦП
ADCgetrez:
push r0
push r1
push r2
push r3
push r16
push r17
push r20
push r28
push r29
push r30
push r31
; Получаем результат и прибавляем его к соответствующей сумме
;
; Рассчитываем адрес в массиве сумм
in r16,ADMUX
andi r16,0x07
add r16,r16
ldi r30,Low(adcsigma)
ldi r31,High(adcsigma)
ldi r17,0
add r30,r16
adc r31,r17
; Считываем предыдущую сумму
ld r0,Z
ldd r1,Z+1
; Прибавляем результат АЦП
in r16,ADCL
in r17,ADCH
add r0,r16
adc r1,r17
; Сравниваем с порогами - 0 и 1023.
mov r2,r16
or r2,r17
breq toalarm ; Если ноль - переполнение
; Прибавляем единицу
clr r2
clr r3
inc r2
add r2,r16
adc r3,r17
ldi r16,0x04 ; Если прибавили единицу и получилось 1024 (0x400) - тоже переполнение
sub r16,r17
brne tonoalarm
toalarm:
; Фиксируем переполнение данного канала
in r16,ADMUX
andi r16,0x07
ldi r17,1
; Рассчитываем, какой бит установить, для этого сдвигаем единицу
; влево столько раз, каков номер канала
calcbitcycle:
or r16,r16
breq tosetled
rol r17
dec r16
rjmp calcbitcycle
tosetled:
; Устанавливаем соответствующий бит регистра PORTB
in r16,PORTB
or r16,r17
out PORTB,r16
tonoalarm:
; Записываем новую сумму
st Z,r0
std Z+1,r1
; Переходим к следующему каналу
in r16,ADMUX
inc r16
out ADMUX,r16
mov r17,r16
andi r17,0x07
subi r17,0x04 ; Количество каналов
breq tocheck8
jmp nilabel
tocheck8:
; Все четыре канала получены - возвращаемся к нулевому каналу
andi r16,0xF0
out ADMUX,r16
; Прибавляем счетчик измерений
lds r16,adcnum
inc r16
andi r16,3 ; От этого зависит число усреднений
sts adcnum,r16
; Проверяем - если 0, то нужно усреднять и сохранять. Все 4 получены.
breq tousedata
jmp nilabel
tousedata:
; Да - обрабатываем результаты
;
; Рассчитываем адрес в массиве данных
lds r16,arrpos
lds r17,arrpos+1
; Выбираем, записываем во внешнее ОЗУ или в промежуточный буфер
sbrc r16,0
rjmp realwrite
; Сброшен - т.е. четный номер. Записываем в промежуточный буфер
ldi r30,Low(rezbuf)
ldi r31,High(rezbuf)
rjmp tocalcandwrite
realwrite:
andi r16,0xFE
; Умножаем номер элемента на 4 при помощи сдвигов влево
rol r16
rol r17
andi r16,0xfe ; Сбрасываем младший бит. r16/r17 теперь умножены на два
rol r16
rol r17
andi r16,0xfe ; Сбрасываем младший бит. r16/r17 теперь умножены на четыре
mov r0,r16 ; получаем r*4
mov r1,r17
;
lds r30,0 ; Массив находится во внешнем ОЗУ
lds r31,0x80 ; Всегда по фиксированному адресу
add r30,r0 ; Прибавляем
adc r31,r1
; Теперь Z ссылается на первую ячейку, в которую надо записать результат
; Записываем промежуточный буфер
ldi r16,0x04
ldi r28,Low(rezbuf)
ldi r29,High(rezbuf)
cyclecopy1:
ld r2,Y+
st Z+,r2
; Повторяем r16 раз
dec r16
brne cyclecopy1
; На выходе - адрес в Z уже указывает на нужное место в ОЗУ
tocalcandwrite:
; Для каждой двухбайтовой ячейки массива adcsigma производим следующие действия:
; - Считываем оба байта
; - Делим на 4 - находим среднее значение
; - Делим на 4 - чтобы результат был от 0 до 255
; - Сохраняем в массив результатов только один байт
; Деление на 4*4=16 - это сдвиг на 4 бита вправо.
ldi r28,Low(adcsigma)
ldi r29,High(adcsigma)
ldi r16,0x80 ; Для перевода в дополнительный код
ldi r20,4 ; Повторяем 4 раза
cycle1:
ld r2,Y+
ld r3,Y+
ror r3
ror r2
ror r3
ror r2
ror r3
ror r2
ror r3
ror r2
; Теперь в r2 получились данные, в которых "0" соответствует числу 0x80, а "+U" - 255.
; Нужно перевести результат в дополнительный код вычитанием из числа 0x80
sub r2,r16
st Z+,r2
; Повторяем r4 раз
dec r20
brne cycle1
; Скопировали. Стираем массив adcsigma
ldi r28,Low(adcsigma)
ldi r29,High(adcsigma)
ldi r20,4 ; Повторяем 8 раз
clr r2
cycle2:
st Y+,r2
dec r20
brne cycle2
; === Переходим к следующему отсчету
lds r16,arrpos
lds r17,arrpos+1
ldi r28,1
ldi r29,0
add r16,r28
adc r17,r29
; Сравниваем с 3600 - если уже равно, надо сбрасывать счетчик в ноль
; 3600 = 0xE10
ldi r29,0x0E
sub r29,r16
brne notclearpos
ldi r29,0x10
sub r29,r17
brne notclearpos
; Сбрасываем указатель в ноль
ldi r16,0
ldi r17,0
notclearpos:
sts arrpos,r16
sts arrpos+1,r17
; Отсчет увеличили
; === Тут проверяем, не запустить ли передачу данных?
; 3489 = 0xDA1
ldi r29,0x0D
sub r29,r16
brne nilabel
ldi r29,0xA1
sub r29,r17
brne nilabel
; Да. Запускаем передачу - получен 3489-й отсчет
ldi r16,0x00
sts rscrc,r16 ; Сбрасываем контрольную сумму
; Заносим нулевую позицию в буфере
sts rspos,r16
sts rspos+1,r16
ldi r16,0x01
sts rsstate,r16 ; Переходим в состояние "1" процедуры отправки пакета в порт
sbi UCSR0A,5 ; Разрешаем прерывание от освобождения буфера передачи
ldi r16,0x55
out UDR0,r16 ; Начинаем передавать первый байт пакета. После того как байт начнет передаваться, произойдет прерывание от опустошения буфера
; Передача начата. Дальнейшее сделает процедура обработки буфера передачи
nilabel:
pop r31
pop r30
pop r29
pop r28
pop r20
pop r17
pop r16
pop r3
pop r2
pop r1
pop r0
reti
;================================================
; Основная программа
main:
; Конфигурируем стек
ldi r16,0
out SPL,r16
ldi r16,0x10
out SPH,r16
; Конфигурируем порты
ldi r16,0
out DDRA,r16
out DDRC,r16
out DDRE,r16
sts DDRF+0x20,r16
ldi r16,0x0F
out DDRB,r16
ldi r16,0x40
out DDRD,r16
ldi r16,0
out PORTB,r16
out PORTD,r16
; Конфигурируем ОЗУ - будем использовать диапазон 0x8000-0xFFFF
in r16,MCUCR
ori r16,0x80
out MCUCR,r16
ldi r16,0x00 ; ОЗУ работает с минимальными задержками по всем адресам
sts XMCRA+0x20,r16
ldi r16,0x80 ; Полный диапазон адресов, Bus keeper включен
sts XMCRB+0x20,r16
; Конфигурируем UART, скорость 38400, прерывания на прием сразу разрешены
ldi r16,0x20
out UCSR0A,r16 ; Удвоения скорости нет
ldi r16,0x98
out UCSR0B,r16 ; Разрешены прерывания от приема байта, включен приемник и передатчик, 8 бит данных
ldi r16,0x06
sts UCSR0C+0x20,r16 ; Асинхронный режим, 1 стопбит, 8 бит данных
ldi r16,0x00
sts UBRR0H+0x20,r16
ldi r16,11
out UBRR0L,r16 ; Скорость - 38400. Константа взята из datasheet на контроллер
; Конфигурируем таймер0 на генерацию 1/480 секундных интервалов
ldi r16,0x04 ; Входы и выходы таймера не используются, делитель на 64
out TCCR0,r16
ldi r16,0x00 ; Синхронизация от внутренней частоты
out ASSR,r16
ldi r16,0xF0 ; 240 - делитель для получения нужного периода
out OCR0,r16
in r16,TIMSK
ori r16,0x02
out TIMSK,r16
; Конфигурируем АЦП
ldi r16,0xC0 ; Внутренний 2.56В источник опорного напряжения, правое выравниваение результата, канал 0
out ADMUX,r16
ldi r16,0x8E ; АЦП разрешен, делитель на 64, прерывания от АЦП разрешены, измерение выключено
out ADCSR,r16
; Устанавливаем начальные значения переменных
ldi r16,0xFF
sts work,r16 ; Начинаем с теста ОЗУ
ldi r16,0
sts adcsigma,r16
sts adcsigma+1,r16
sts adcsigma+2,r16
sts adcsigma+3,r16
sts adcsigma+4,r16
sts adcsigma+5,r16
sts adcsigma+6,r16
sts adcsigma+7,r16
sts adcsigma+8,r16
sts adcsigma+9,r16
sts adcsigma+10,r16
sts adcsigma+11,r16
sts adcsigma+12,r16
sts adcsigma+13,r16 ; Сумм нету
sts adcnum,r16 ;
sts arrpos,r16 ; Позиция - нулевая
sts arrpos+1,r16 ;
sts rsstate,r16 ; Ничего не передается
sts timerdiv,r16
; Основной цикл программы
sei ; Разрешаем прерывания
maincycle:
ldi r16,work
or r16,r16
breq maincycle
; Сбрасываем флаг
ldi r16,0
sts work,r16
; Здесь - процедура теста ОЗУ
ldi r30,0
ldi r31,0x80
ldi r16,0
ldi r17,3
; Заполнение
wr1clk:
st Z+,r16
add r16,r17
sbrs r31,6
rjmp wr1clk
; Проверка прямого значения и заполнение инверсным
ldi r30,0
ldi r31,0x80
ldi r16,0
ldi r17,3
wr2clk:
ld r18,Z
com r18
st Z+,r18
sub r18,r16
nop ; brne chkerr
add r16,r17
sbrs r31,6
rjmp wr2clk
; Проверка инверсного значения
ldi r30,0
ldi r31,0x80
ldi r16,0
ldi r17,3
wr3clk:
ld r18,Z+
com r18
sub r18,r16
nop ; brne chkerr
add r16,r17
sbrs r31,6
rjmp wr3clk
; Проверка прошла успешно
; Отключаем светодиод "ошибка"
in r16,PORTD
andi r16,0xBF
out PORTD,r16
; Переходим к состоянию "передача блока удачной проверки"
ldi r16,20
jmp startpkg
chkerr:
; Проверка завершилась ошибкой
; Включаем светодиод "ошибка"
in r16,PORTD
ori r16,0x40
out PORTD,r16
; Переходим к состоянию "передача блока ошибки проверки"
ldi r16,40
startpkg: ; Запуск передачи блока
cli ; Запрещаем прерывания - дальше идет блок, работающий с теми же данными, что и процедуры прерывания
sts rsstate,r16 ; Переходим в состояние "1" процедуры отправки пакета в порт
sbi UCSR0A,5 ; Разрешаем прерывание от освобождения буфера передачи
ldi r16,0xA5
out UDR0,r16 ; Начинаем передавать первый байт пакета. После того как байт начнет передаваться, произойдет прерывание от опустошения буфера
sei ; Разрешаем прерывания
ldi r16,0 ;
sts chkcnt,r16 ; Блоки считаем с нуля - повторный тест через 10 блоков
jmp maincycle