Завдання:
Написати та відлагодити програму для схеми, яка б здійснювала дискретизацію вхідного аналогового сигналу з діапазоном Uвх = 0-5 B з частотою FS, Гц аналого-цифровим перетворювачем MAX1241 (опорна напруга UREF = 5 В) та відображала значення напруги на рідкокристалічному дисплеї LM020L (1 рядок з 16 символами) з контролером HD44780, та передавала її зна-чення через послідовний порт в ПК зі швидкістю передачі R Бод. Тактова частота Ft МК AT90S2313 становить 7.3728 МГц. Якщо напруга виходить за межі 2.5 B ± 0.xx В (хх – останні дві цифри номера залікової книжки, якщо хх = 00 прийняти хх = 99) на дисплей виводиться повідомлення Alarm.
Перевірити правильність роботи програми шляхом симуляції в середовищі Proteus 7.
Варіант
Частота дискретизації
Fs, Гц
Швидкість передачі по УАПП
R, Бод

13
0.40
2400


Розрахункова частина
Оскільки номер залікової книжки – 0909049, то межі напруги становлять:
нижній поріг спрацювання: 2,01 В
верхній поріг спрацювання: 2,99 В
Якщо напруга не належить цьому відрізку необхідно вивести повідомлення Alarm.
Знайдемо значення констант спрацювання за формулою: , де - напруги порогів спрацювання, - опорна напруга на АЦП (5 Вольт), - шукана константа.


Знайдемо подільник та поріг спрацювання таймера Т1, для того, щоб проводити опитування напруги з частотою дискретизації Fs = 0.04 Гц.
Часовий інтервал генерації таймером переривання обчислюється за формулою:

Враховуючи, що при максимальному значені N = 65535, значення подільника становить DIV = 281, потрібно взяти подільник на порядок вище (DIV = 1024), тоді значення N становить:

Отже, значення порогу порівняння для таймера Т1 становить TimerVal = 18000
Знайдемо подільник частоти для UART, щоб забезпечити швидкість передачі R = 2400 бод.
Швидкість визначається наступним виразом:

де – швидкість передачі (в бодах); – тактова частота МК, Гц; UBRR – вміст регістру контролера швидкості передачі (0…255)
Звідси

Лістинг програми в середовищі AVR
.nolist
.include <2313def.inc>
.list
.def lcd = r16 ; регістр, який містить команди або дані для РКД
.def count = r17
.def temp = r18
.def Delay1 = r19 ; регістри часової затримки
.def Delay2 = r20
.def Delay3 = r21
.def cmp1 = r22
.def cmp2 = r23
.def ACP_res1 = r24 ; регістри містять результат перетворення АЦП
.def ACP_res2 = r25
.def ACP_res3 = r26
.def ACP_res4 = r27
.def B1 = r19 ; регістри вокористовуються при множенні
.def B2 = r20
.def B3 = r21
.def B4 = r22
.def num = r28
.def end_num = r29
.equ N_2mks = 3
.equ N_50mks = 71
.equ N_2ms = 2946
.equ N_20ms = 29489
.equ CMP_low = 1647 ; нижній рівень спрацювання 2.5 - 0.49 = 2.01
.equ CMP_high = 2449 ; верхній рівень спрацювання 2.5 + 0.49 = 2.99
; при подільнику 1024 і частоті 7,3728 частота спрацювання таймера 0.4 Гц
.equ TimerVal = 18000
.equ RW = PD4 ; керування рідкокристалічним дисплеєм
.equ E = PD5
.equ RS = PD6
.equ nCS = PB0 ; виводи для керування АЦП
.equ DOUT = PB1
.equ SCLK = PB2
.cseg
.org 0
rjmp RESET ; перехід після скидання
.org 0x04
rjmp TIMER_EXT ; Timer1 overlow
.org 0x0B
RESET:
; ініціалізація стеку
ldi temp, low(RAMEND)
out SPL, temp
ldi temp, (1<<PD1)+(1<<PD4)+(1<<PD5)+(1<<PD6)
out DDRD, temp ; налаштувати виводи порта D
ldi temp, ~((1<<PB1)+(1<<PB3))
out DDRB, temp ; налаштувати виводи порта B
; налаштування таймера
ldi temp, high(TimerVal)
out OCR1AH, temp ; занести старший байт
ldi temp, low(TimerVal)
out OCR1AL, temp ; занести молодший байт
ldi temp, (1<<OCIE1A) ; дозволити переривання при співпадінні
out TIMSK, temp
ldi temp, (1<<CTC1) + (1<<CS12) + (1<<CS10) ; div = 1024
out TCCR1B, temp
ldi temp, (1<<SE) ; дозволити енергозберігаючий режим
out MCUCR, temp

; налаштування UART
ldi temp, 192
out UBRR, temp; задати швидкість передачі 2400 бод при тактовій 7,3728

sbi UCR, TXEN ; дозволити передачу
rcall LCD_Setup ; підпрограма ініціалізації РКД
rcall TIMER_EXT ; вивести напругу і виставити флажок переривань
Wait_Loop:
sleep ; Idle режим
rjmp Wait_Loop

;///////////////////////////////////////////////
TIMER_EXT:
; отримати і вивести дані про напругу
ldi lcd, 0b00000001 ; очистити LCD встановити курсор на початок
rcall LCD_Com ; послати команду в LCD
rcall wait_2ms ; почекати поки LCD обробить команду
rcall GetACP_Res ; отримати дані з АЦП

; порівняти отримані дані з нижнім порогом спрацювання
ldi cmp1, low(CMP_low)
ldi cmp2, high(CMP_low)
cp ACP_Res1, cmp1 ; порівняти молодші байти
cpc ACP_Res2, cmp2 ; порівняти старші байти і перенос
brcs Alarm ; значення менше за нижній поріг
; Порівняти з верхнім порогом спрацювання
ldi cmp1, low(CMP_high)
ldi cmp2, high(CMP_high)
cp cmp1, ACP_Res1 ; порівняти молодші байти
cpc cmp2, ACP_Res2 ; порівняти старші байти і перенос
brcs Alarm ; значення менше за нижній поріг
; значення є в допустимих межах - необхідно вивести число на LCD
ldi ZL, low(szVBegin*2)
ldi ZH, high(szVBegin*2)
rcall LCD_Out ; вивести початок повідомлення (U = )

rcall Calc_Mul_Div ; множимо результат з АЦП на 5000 і ділимо на 4096
rcall GetDigits ; отримуємо значення напруги

mov lcd, ACP_Res1 ; виводимо числове значення
rcall LCD_Dat
ldi lcd, '.'
rcall LCD_Dat
mov lcd, ACP_Res2
rcall LCD_Dat
mov lcd, ACP_Res3
rcall LCD_Dat
mov lcd, ACP_Res4
rcall LCD_Dat

ldi ZL, low(szVEnd*2)
ldi ZH, high(szVEnd*2)
rcall LCD_Out ; вивести кінець повідомлення ( V)
rcall UART_NextLine ; перейти на наступний рядок
reti
Alarm: ; Виводимо на LCD Alarm
ldi ZL, low(szAlarm*2) ; завантажити в регістрову пару Z
ldi ZH, high(szAlarm*2) ; адресу рядка
rcall LCD_Out; ; вивести на LCD слово Alarm
rcall UART_NextLine ; перейти на наступний рядок
reti
;///////////////////////////////////////////////
LCD_Setup:
; затримка ~20 мс після включення живлення
ldi Delay1, low(N_20ms)
ldi Delay2, high(N_20ms)
ldi Delay3, byte3(N_20ms)
rcall Delay
; виставляємо команду
ldi temp, 0x20 ; 4-бітний інтерфейс, 1-рядок, шрифт - 5х7 точок
out PORTB, temp
sbi PORTD, E ; сформувати передній фронт імпульсу на виводі Е
rcall Pause
cbi PORTD, E ; сформувати задній фронт імпульсу на виводі Е
ldi lcd, 0x20 ; 4-бітний інтерфейс, 1-рядок, шрифт - 5х7 точок
rcall LCD_Com ; послати команду в LCD
ldi lcd, 0x20 ; 4-бітний інтерфейс, 1-рядок, шрифт - 5х7 точок
rcall LCD_Com ; послати команду в LCD
ldi lcd, 0b00000001 ; очистити LCD встановити курсор на початок
rcall LCD_Com ; послати команду в LCD
rcall wait_2ms ; почекати поки LCD обробить команду
ldi lcd, 0b00000110 ; інкремент АС без зсуву дисплею
rcall LCD_Com ; послати команду в LCD
ldi lcd, 0b00001100 ; включити дисплей, виключити курсор
rcall LCD_Com ; послати команду в LCD
rcall wait_2ms ; почекати поки LCD обробить команду
ret
;///////////////////////////////////////////////
; передає через UART байт даних з регістра lcd
UART_PutChar:
sbis USR, UDRE
rjmp UART_PutChar ; очікуємо щоб передати наступний байт
out UDR, lcd
ret
;///////////////////////////////////////////////
UART_NextLine: ; відіслати через UART символи переходу на новий рядок
ldi lcd, 13
rcall UART_PutChar
ldi lcd, 10
rcall UART_PutChar
ret
;///////////////////////////////////////////////
; підпрограма запису в LCD слова (адрес в рег. парі Z)
LCD_Out:
lpm ; зчитати символ з флеш-пам'яті програм
mov lcd, r0 ; переслати символ в регістр lcd
cpi lcd, 0
breq LCD_Out_Exit ; якщо наступний символ 0 - виходимо
rcall LCD_Dat ; викликати підпрограму виводу символу на індикатор
adiw ZL, 1 ; перейти до наступного символу
rjmp LCD_Out
LCD_Out_Exit:
ret ; повернення з підпрограми
;///////////////////////////////////////////////
LCD_Com: ; підпрограма запису в LCD команд
cbi PORTD, RS ; обнулити RS - передається команда
mov temp, lcd ; завантажити в temp команду
andi temp, 0xf0 ; обнулити обнулити молодшу тетраду
out PORTB, temp ; встановити дані на шині та сигнал RS

sbi PORTD, E ; сформувати передній фронт імпульсу на виводі Е
rcall Pause
cbi PORTD, E ; сформувати задній фронт імпульсу на виводі Е
cbi PORTD, RS ; обнулити RS - передається команда
mov temp, lcd ; завантажити в temp команду
swap temp ; поміняти місцями тетради
andi temp, 0xf0 ; обнулити обнулити молодшу тетраду
out PORTB, temp ; встановити дані на шині даних
sbi PORTD, E ; сформувати передній фронт імпульсу на виводі Е
rcall Pause
cbi PORTD, E ; сформувати задній фронт імпульсу на виводі Е
rcall wait_50mks ; почекати поки LCD обробить команду
ret
;///////////////////////////////////////////////
; підпрограма запису в LCD байту даних та пересилки його через UART
LCD_Dat:
rcall UART_PutChar ; переслати дані також через UART
sbi PORTD, RS ; встановити флажок опереції з даними
mov temp, lcd ; завантажити в temp дані
andi temp, 0xf0 ; обнулити молодшу тетраду
out PORTB, temp ; вивести в В старшу тетраду даних
sbi PORTD, E ; сформувати передній фронт імпульсу на виводі Е
rcall Pause
cbi PORTD, E ; сформувати задній фронт імпульсу на виводі Е
mov temp, lcd ; завантажити в temp дані
swap temp ; поміняти місцями тетради
andi temp, 0xf0 ; обнулити молодшу тетраду
out PORTB, temp ; вивести в В молодшу тетраду даних
sbi PORTD, E ; сформувати передній фронт імпульсу на виводі Е
rcall Pause
cbi PORTD, E ; сформувати задній фронт імпульсу на виводі Е
rcall wait_50mks ; почекати поки LCD обробить команду
ret
;///////////////////////////////////////////////
GetACP_Res: ; отримання даних з АЦП
clr ACP_res3
clr ACP_res4
sbi PORTB, nCS ; ініціалізуємо вихід
cbi PORTB, SCLK ; ініціалізація ацп
cbi PORTB, nCS ; початок перетворення
acp_wait:
sbis PINB, DOUT ; очікуємо на одиницю на виході DOUT
rjmp acp_wait
clr ACP_res1 ; очищаємо регістри результату
clr ACP_res2
ldi count, 12 ; кількість ітерацій - необхідно зчитати 12 біт
acp_read: ; зчитуємо результат
sbi PORTB, SCLK
cbi PORTB, SCLK ; формуємо один тактовий імпульс
lsl ACP_res1
rol ACP_res2
sbic PINB, DOUT ; пропускаємо інструкцію якщо вхід в 0
ori ACP_res1, 1 ; виставляємо в молодший біт 1
dec count
brne acp_read
ret ; читання успішно завершено
;///////////////////////////////////////////////
Mul_2: ; підпрограма множить ACP_Res на 2
lsl ACP_Res1
rol ACP_Res2
rol ACP_Res3
rol ACP_Res4
ret
;///////////////////////////////////////////////
Mul_10: ; підпрограма множить ACP_Res на 10
rcall Mul_2 ; знайдемо 2x
mov B1, ACP_Res1
mov B2, ACP_Res2
mov B3, ACP_Res3
mov B4, ACP_Res4
ldi temp, 2
loop: ; знайдемо 8х
rcall Mul_2
dec temp
brne loop
add ACP_Res1, B1 ; 2x + 8x = 10x
adc ACP_Res2, B2
adc ACP_Res3, B3
adc ACP_Res4, B4
ret
;///////////////////////////////////////////////
; функція множить результат з АЦП на 50000 і ділить на 4096
Calc_Mul_Div: ; 50000/4096 = 100000/8192
ldi count, 5
loop_mul_10:
rcall Mul_10
dec count
brne loop_mul_10 ; 100000x
ldi count, 13 ; тепер ділимо на 8192 (зсув вправо 13 раз)
div_8192:
lsr ACP_Res4
ror ACP_Res3
ror ACP_Res2
ror ACP_Res1
dec count
brne div_8192
mov B1, ACP_Res1
mov B2, ACP_Res2 ; тут в двох байтах маємо [B2:B1] = ACP_Res*50000/4096
ret
;///////////////////////////////////////////////
GetDigits:
ldi count, 8
ldi ZL, low(CmpDig*2)
ldi ZH, high(CmpDig*2)
next_push:
lpm
adiw ZL, 1 ; перейти до наступного символу
push r0 ; занести в стек старші та молодші розряди чисел порівнняння
dec count
brne next_push
ldi ZL, 24
clr ZH
ldi count, 4
next_num:
clr num ; тут повинна зберігатися цифра
pop cmp1 ; забрати з стеку молодший байт числа з яким порівнюємо
pop cmp2 ; взяти старший байт числа з яким порівнюємо
num_loop:
cp B1, cmp1 ; порівняти молодші байти
cpc B2, cmp2 ; порівняти старші байти і перенос
brcs store_num ; зберігаємо знайдене число
sub B1, cmp1
sbc B2, cmp2
inc num ; збільшуємо кількість одиниць
rjmp num_loop
store_num:
subi num, -'0' ; обчислюємо ANSI код цифри
st Z+, num
dec count
brne next_num
ret
;///////////////////////////////////////////////
Pause: ; Затримка на 2 мкс
ldi Delay1, N_2mks
m1: subi Delay1, 1
brcc m1
nop
ret
wait_50mks:
; підпрограма часової затримки на 50 мкс при тактовій частоті 7.3728 МГц
ldi Delay1, low(N_50mks)
ldi Delay2, high(N_50mks)
ldi Delay3, byte3(N_50mks)
rcall Delay
ret
wait_2ms:
; підпрограма часової затримки на 2 мс при тактовій частоті 7.3728 МГц
ldi Delay1, low(N_2ms)
ldi Delay2, high(N_2ms)
ldi Delay3, byte3(N_2ms)
rcall Delay
ret
Delay:
subi Delay1, 1
sbci Delay2, 0
sbci Delay3, 0
brcc Delay
nop
ret
szAlarm: .db 'A', 'l', 'a', 'r', 'm',0
szVBegin: .db 'U', ' ', '=', ' ', 0
szVEnd: .db ' ', 'V', 0
CmpDig: .db 0, 10, 0, 100, high(1000), low(1000), high(10000), low(10000)
Результат симуляції схеми в Proteus

Висновок: під час виконання даної розрахункової роботи, я написав програму на мові асемблер під MK AT90S2313 для дискретизації вхідного аналогового сигналу аналого-цифровим перетворювачем і який відображає значення напруги на рідкокристалічному дисплеї.