Завдання: Написати та відлагодити програму для схеми, яка б здійснювала дискретизацію вхідного аналогового сигналу з діапазоном 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
;/////////////////////////////////////////////// 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 ; отримуємо значення напруги
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 для дискретизації вхідного аналогового сигналу аналого-цифровим перетворювачем і який відображає значення напруги на рідкокристалічному дисплеї.