1 Востаннє редагувалося MCS-51 (05.11.2015 09:57:41)

Тема: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Доброго дня. Невелика передісторія.
Вирішив створити дану тему, у якій хотів би ділитися своїми скромними досягненнями у програмуванні мікроконтролерів даної архітектури. Чому зупинився саме на них ? Почитав багато чого, і вирішив для себе, що варто почати із асемблеру, бо потім людям він важко дається після звикання до високорівневих мов. Сам на мінімальному рівні знаю Паскаль (колись трохи вивчав), у Сі - ні в зуб ногою. Спершу почав вивчати мікроконтроллери архітектури AVR фірми Atmel. Але якось воно важко давалося, міг у кращому випадку повторити чийсь чужий проект, вершиною розвитку був темометр на базі Attiny2313A, зі світлодіодним індикатором та датчиком DS18B20 (1-wire). Потім випадково натрапив на середовище програмування Algorithm Builder. Візуалність додала розуміння процесу, і я створив декілька власних програм (зокрема контроллер освітлення драбини). Потім випадково прочитав, що асемблер MCS-51 вважається одним із найзручніших. Спробував, і зрозумів, що це те, що треба. Поступово почав робити прості навчальні проекти. Щось вичитував сам, щось запитував на форумах. Вивчав би собі і далі потихеньку (не створюючи цієї теми на даному форумі), але мене неприємно зачепила майже повна відсутність будь-яких практичних наробіток у цій сфері, викладених українською. Свого часу я не зіг зацікавити свого друга радіоелектронікою лише тому, що його мозок абсолютно не сприймає російську, а 95 відсотків інформації цієї тематики у інтернеті викладено саме на ній. Я вирішив, що маю викласти те, чого досяг (і викладати те, чого досягну) державною мовою, щоб люди, подібні моєму другові, могли знайти хоча б щось, якщо їх це зацікавить.
Для початку пропоную у якості середовища розробки використовувати MCStudio: http://www.radioliga.com/insert_2007.htm (у самому низу сторінки, 1 номер журналу "Радиолюбитель" за 2007 рік).
Вона російськомовна, але безкоштовна до 2Кб коду. Розроблена колишнім харківським студентом. Наразі він її закинув і не підтримує. Але вона настільки проста та зручна у використанні, що я обрав її. Я не зміг зв'язатися із розробником, тому не буду поки викладати повну версію без обмежень по коду (я знайшов і таке), але якщось комусь таке знадобиться, звертайтесь, для розвитку технічної освіченості нації готовий викласти у доступ. Середовище розробки дозволяє легко додавати власні мікроконтролери архітектури MCS-51. У середовищі уже був доданий AT89S8252, але його ціна доволі висока, тому було вирішено вибрати щось дешевше та простіше, але при цьому достатньо фінкціональне для навчальних проектів, що я і зробив, додавши AT89S52, який зміг недорого купити. Далі буде...

Подякували: 0xDADA11C7, machinegun3

2 Востаннє редагувалося MCS-51 (05.11.2015 09:56:32)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Доброго дня. Для початку викладу свою невеличку "бібліотеку" для виведення данних на рідинно-кристалічний дисплей з контроллером HD44780 формату LCD1602 (2 строки по 16 символів). Одразу попереджаю, що я вирішив робити свої програми модульними, тому для кожної великої задачі я роблю свій файл, для мене це дуже зручно. Але так як асемблер у MCStudio однопрохідний, доводиться при кожній зміні компілювати програму двічі: перший раз компілятор знаходить, як перекриваються шматки коду (і лається на помилки), а на другий уже правильно розміщує їх один за одним. Тож надаю головний файл, у якому ми конкретно вказуємо адреси, і файл бібліотеки, який ми "приписуємо" до головного, і який "втиснеться" туди, де ми його пропишемо. Бібліотека не претендує на високу швидкодію, затримки зроблені циклами очікування, професіонали так не роблять, але для навчання підійде. На просторах Інтернету таку роботу з дисплеєм називають "ногодригом". Для усіх моїх дисплеїв підійшли дані значення затримок, але для якогось конкретного диспею може знадобитися їх корекція.

Прихований текст

Main.asm

; Заносимо у змінну znak код символа и визиваємо процедуру Indication
cseg    at    0            ; Сегмент коду починаємо з 0 адреси, він і так по замовчуванню починаєься звідси, але правила хорошого тону рекомендують вказувати це явно
    jmp    Start

org    30h                ; Залишимо місце для майбутніх переривань

include 'HD44780.asm'

Start:
    call    Ini_LCD            ; Іниціалізуємо LCD

    mov     znak,#41h        ; Виведемо англ. літеру "А"
    call    Indication
    
    mov     znak,#42h        ; І т.д.
    call    Indication
    
    mov     znak,#43h
    call    Indication
    
    mov     znak,#44h
    call    Indication
    
    mov     znak,#45h
    call    Indication
    
    mov     znak,#46h
    call    Indication
    
    mov     znak,#47h
    call    Indication
    
    mov     znak,#48h
    call    Indication
    
    mov     znak,#49h
    call    Indication
    
    mov     znak,#4Ah
    call    Indication
    
    mov     znak,#4Bh
    call    Indication
    
    mov     znak,#4Ch
    call    Indication
    
    mov     znak,#4Dh
    call    Indication
    
    mov     znak,#4Eh
    call    Indication
    
    mov     znak,#4Fh
    call    Indication
    
    mov     znak,#50h
    call    Indication
    
    call Second_line        ; Переведемо курсор на 2 стрічку
    
    mov     znak,#51h
    call    Indication
    
    mov     znak,#52h
    call    Indication
    
    mov     znak,#53h
    call    Indication
    
    mov     znak,#54h
    call    Indication
    
    mov     znak,#55h
    call    Indication
    
    mov     znak,#56h
    call    Indication
    
    mov     znak,#57h
    call    Indication
    
    mov     znak,#58h
    call    Indication
    
    mov     znak,#59h
    call    Indication
    
    mov     znak,#5Ah
    call    Indication
    
    jmp    $            ; Зациклимося
    
end
Прихований текст

HD44780.asm

; Дисплей підключений по 4-bit шині
; D4-P1.4, D5-P1.5, D6-P1.6, D7-P1.7
; RW - на землю (тільки запис у дисплей, читання немає)
LCD_Port    equ    P1

RS        equ    P3.6
E        equ    P3.7
Control_port    equ     P3

znak        equ    R2

cseg

Ini_LCD:
        call    Del_15ms        ; Стартова затримка на 15 мс по даташиту, без неї будуть глюки
    mov    Control_port,#00111111b    ; E=0, RS=0, R/W-на землю, тому вставимо там 1 за компанію із більшістю
        mov    LCD_Port,#00110000b    ; При інициализації спершу виставимо 8-bit шину, так треба (DL=1, N=0, F=0)
    call    Impulse            ; Імпульс Е

    call    Del_4ms            ; Друга затримка на 4,1 мс по даташиту, без неї будуть глюки

    mov    LCD_Port,#00110000b    ; Другий раз виставимо 8-bit шину, так треба (DL=1, N=0, F=0)
    call    Impulse            ; Імпульс Е

                    ; Третя затримка на 100 мкс по даташиту, без неї будуть глюки, вона вже є всередині процедури Impulse вище

    mov    LCD_Port,#00110000b    ; І втретє виставимо 8-bit шину, так треба (DL=1, N=0, F=0)
    call    Impulse            ; Імпульс Е

                    ; Далі треба було б читати стан флагу шини (BF), але легше тупити затримками по 100-200 мкс (у складі процедури Impulse)

    mov    LCD_Port,#00100000b    ; Виставляємо 4-bit шину (DL=0), старший напівбайт
    call    Impulse            ; Імпульс Е
        mov    LCD_Port,#00100000b    ; Знову старший напівбайт, тому що ми перед цим переключились на 4-х битную шину і його треба переслати Знову
    call    Impulse                ; Імпульс Е
        mov    LCD_Port,#10000000b    ; Молодший напівбайт (N = 1 - 2 строки, F = 0 - символи 5*7)
    call    Impulse            ; Імпульс Е

     mov    LCD_Port,#00000000b    ; Команда включения дисплея, старший напівбайт пустой
    call    Impulse            ; Імпульс Е
        mov    LCD_Port,#11000000b    ; Младший напівбайт (D=1 - дисплей увімкнено, C=0 - курсор вимкнено, B=0 - мерехтіння курсора вимкнено)
    call    Impulse            ; Імпульс Е

    mov    LCD_Port,#00000000b    ; Встановлення параметров відображення інформації, старший напівбайт пустий
    call    Impulse            ; Імпульс Е
        mov    LCD_Port,#01100000b    ; Молодший напівбайт (I/D=1 - інкремент позиції курсору, S=0 - немає здвигу дисплею)
    call    Impulse            ; Імпульс Е
    
Reset_LCD:                ; Скидання дисплею
    mov    LCD_Port,#00000000b    ; Старший напівбайт
        call    Impulse            ; Імпульс Е
        mov    LCD_Port,#00010000b    ; Молодший напівбайт
        call    Impulse            ; Імпульс Е

    call    Del_4ms            ; Затримка після скидання, без нее глюки
        setb    RS            ; Тепер виводимо не команди, а символи, тому піднимаемо RS

    ret

Del_15ms:                ;(R1*(2*R0+1)+2*R1)+1mks(mov R1)+2mks(call)+2mks(ret)
        mov    R1,#2Eh           
Loop_15ms:
    mov    R0,#0A1h
    djnz    R0,$
    djnz    R1,Loop_15ms
        ret

Del_4ms:                ;(R1*(2*R0+1)+2*R1)+1mks(mov R1)+2mks(call)+2mks(ret)
        mov    R1,#9h
Loop_4ms:
    mov    R0,#0DCh
    djnz    R0,$
    djnz    R1,Loop_4ms
        ret

Del_100mks:
        mov    R0,#50
    djnz    R0,$
    ret

Impulse:                ; Імпульс на вході Е, з наступною затримкою на 100 мкс
    setb    E
    nop                ; Потумпимо 1 мкс (при кварці 12 Мгц), щоб на ножці встановилося значення
     clr    E
    call    Del_100mks
        ret
        
Indication:                ; Виводимо зміст змінної znak, у якій зберігається код нашого символу
    mov     LCD_Port,znak        ; Старший напівбайт
    call    Impulse            ; Імпульс Е
    mov    A,znak
    swap    A
    mov    LCD_Port,A        ; Младший напівбайт
        call    Impulse            ; Імпульс Е
        ret
        
Second_line:                ; Переведемо курсор на початок 2 стрічки
        clr    RS            ; Тепер виводимо не символи, а подаемо команди, тому опускаемо RS
    mov     znak,#0C0h
    call    Indication
    setb    RS            ; Курсор перевели, тепер знову не подаемо команди, а виводимо символи на дисплей, тому піднимаемо RS
    ret
    
First_line:                ; Переведем курсор на початок 1 строки
        clr    RS            ; Тепер виводимо не символи, а подаемо команди, тому опускаемо RS
    mov     znak,#80h
    call    Indication
    setb    RS            ; Курсор перевели, тепер знову не подаемо команди, а виводимо символи на дисплей, тому піднимаемо RS
    ret
    
end
Подякували: 0xDADA11C7, leofun013

3

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Дякую за ваш внесок, я приємно вражений вашою наполегливістю і бажанням вивчати програмування мікроконтролерів, та ще й ділитися власними напрацюваннями в цій галузі з иншими. До речі, ви бачили мою мої дописи і ярославові з цієї теми?

Я не згідний з вами в виборі архітектури/асемблера/баченні мовної ситуації в цій галузі, але ви початківець в програмуванні і щирі в своїх переконаннях, тому я всіляко підтримую вас в ваших починаннях, можливо навіть зможу допомогти чимось.

4 Востаннє редагувалося MCS-51 (05.11.2015 22:49:43)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

0xDADA11C7 написав:

... До речі, ви бачили мою мої дописи і ярославові з цієї теми? ...

Так, усі теми на форумі, що стосуються програмування цих мікрококнроллерів, я передивився. Якраз зараз вивчаю вивід і приймання даних по UART, так що 2 тема (стаття) у мене уже у закладинках)) Завтра постараюся оформити і викласти свій варіант коду опитування матричної клавіатури. Так як уся ця тематика доволі близька і до "заліза", чи доречним буде крім коду викладати і принципові схеми ? Для кращого розуміння читачами, та можливості повторити побачине/прочитане.

Update
Додав схему підключення дисплею для перевірки працездатності бібліотеки LCD HD44780 (для контролеру у корпусі TQFP44, бо у мене такі).
Коротко по схемотехнічному рішенню та обгрунтуванні такого вибору розподілу портів та вибору компонентів.
Усього у класичному MCS-51 4 повні 8-ми бітні порти. Port 0 не має зовнішніх підтягуючих резисторів, а також є найбільш "потужним" портом. Він може "протягнути" через себе струм до 26 мА (8 світлодіодів по 3 мА кожен). Для порівняння, 3 інші порти обмежені по 15 мА кожен. Тому у своїх проектах я буду чіпляти на нього світлодіоди та світлодіодні індикатори, щоб по можливості обійтися без підсилюючих трензисторів і збірок дарлінгтона, та отримати при цьому  максимальну яскравість.
Port 3 має спеціальні функції (UART, зовнішні переривання та інше), тому лінії з 0 по 3 я поки залишив у спокої, і нічого на них не чіплятиму, щоб зберегти можливість використання цих спец. функцій у подальшому. Лінії з 4 по 7 мають нецікаві (особисто для мене) можливості спец. функцій: входи лічильників Timer0 і Timer1, а також лінії генерації стробів при роботі із зовнішньою пам'ятю. Тому на них я буду вішати якісь поодинокі функції, які не потребують цілого порта, щоб не псувати цілий порт (віднімати лінії і робити його неповноцінним). Вирішив на лінії 6 та 7 повісити лінії RS та E для роботи з дисплеєм HD44780. Лінію R/W дисплею я посадив на землю, бо не планую нічого читати з дисплею, а лише записувати, і таким чином зекономлю 1 ногу мікроконтроллера. На лінію 5 планую підчепити динамік (комп'ютерний бузер) через підсилюючий транзистор, щоб озвучувати якісь події. 4 лінія поки що лишається вільною, для майбутнього розширення функціоналу.
Port 2 планую повністю відвести під матричну клавіатуру, тим більше що ніких цінних спеціальних функцій він не має.
Port 1. Для використовуваного мною мікроконтроллера AT89S52 лінії 0 та 1 цього порта мають спеціальні функції пов'язані із Timer2. Вони мені зараз впринципі непотрібні, але взагалі Timer2 має набагато ширший функціонал ніж Timer0 і Timer1, а тому на базі лінії 1 цього порту можна побудувати непоганий лічильник імпульсів, із допустимою частотою лише вдвічі меншою за тактову (для порівняння входи Timer0 і Timer1 допускають частоту максимум у 6 разів меншу за тактову). Будемо мати це на увазі, і підлючимо шину данних дисплею HD44780 до цього порту по 4 провідникам (до ліній 4-7, як на прикріпленому малюнку). У той же час, якщо припече, ми зможемо без проблем розширити шину до 8-біт, не втрачаючи нічого, крім можливості сприймати зовнішні імпульси для Timer2 (тобто просто втратимо можливість зробити такий лічильник).
Даташит на використовуваний мною мікроконтроллер можна знайти отут http://www.atmel.com/images/doc1919.pdf. Мова асемблеру передбачає його ретельне вивчення, та постійне звернення до нього.
Мікроконтроллери даної архітектури є доволі старими і просто не мають внутрішнього тактового генератору, і потребують використання зовнішнього кварцевого резонатору. Оптимальною частотою є 12 Мгц. У звязку із особливістю архітектури, при данній частоті час виконання однієї машинної інструкції буде становити рівно 1 мкс. Це дуже зручно для підрахунку часових інтервалів. Я використав кварц на 11,0592 МГц лише тому, що у майбутньому планую освоювати UART, і для цього частота 12 МГц є недопустимою.
Для роботи із внутрішньою пам'ятю програм, необхідно приєднати ввивід EA/VPP до плюса живлення, бажано через резистор (до 100 кОм), але для економії радіодеталей можна і напряму.

Post's attachments

Схема HD44780.JPG 81.9 kb, 282 downloads since 2015-11-05 

Подякували: 0xDADA11C7, leofun013

5

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Гадаю, що доречно буде давати принципові схеми (зі словесним описом, бо гугла малюнків не розуміє), а ще краще -- анімовані гіфки та фотографію зібраного пристрою  :)

6

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

зацікавити свого друга радіоелектронікою лише тому, що його мозок абсолютно не сприймає російську, а 95 відсотків інформації цієї тематики у інтернеті викладено саме на ній

напевно це буде для вас одкровенням, але 70% технічної інформації в Інтернеті - англійською мовою, а далi буде й китайскою. Проте захоплення похвально.

Розроблена колишнім харківським студентом

школярем.

тому не буду поки викладати повну версію без обмежень по коду

по закінченні школи проект був покинутий, 10 рокiв тому.

Подякували: 0xDADA11C71

7 Востаннє редагувалося MCS-51 (05.11.2015 23:07:01)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

raxp написав:

... 70% технічної інформації в Інтернеті - англійською мовою, а далi буде й китайскою...
...школярем...

Я дійсно не вірно висловив свою думку. Я мав на увазі відсоток інформації, прийнятної для засвоєння із темпом, більший за черепаший. Бо саме таким буде темп освоєння даної галузі для людини, що не володіє вільно технічною англійською. Володіючих технічною російською значно більше.
Ви були знайомі з розробником MCSudio (хоча б онлайн) ?

8

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Технічна англійська це міжнародний канцелярит, що мало перетинається з розмовною. Тим більше для розуміння вільного володіння зовсім не треба.

9 Востаннє редагувалося MCS-51 (06.11.2015 11:30:23)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Отже, як і обіцяв, код бібліотеки матричної клавіатури.

Прихований текст

Matrix_keyboard.asm

; Підпрограма опитування матричної клавіатури  4х4 кнопки.
; Опитування організовано по переповненню Timer0, у який попередньо заноситься необіхдне значення, для досягнення періодичності опитування раз на 50 мс (можна підправити під себе) 

col1    equ    P2.0            ; Column 1 (з діодом на порт)
col2    equ    P2.1            ; Column 2 (з діодом на порт)
col3    equ    P2.2            ; Column 3 (з діодом на порт)
col4    equ    P2.3            ; Column 4 (з діодом на порт)
row1    equ    P2.4            ; Row1
row2    equ    P2.5                       ; Row2
row3    equ    P2.6                       ; Row3
row4    equ    P2.7                       ; Row4

znak    equ    R3            ; Змінна, у яку заноситься код натиснутої кнопки
zumer   equ     P3.5            ; До цієї лінії підключений динамік для озвучування натиснення кнопок

; При доробці коду під власні потреби, Не використовувати для своїх цілей RO,R1 и R2, вони використовуються для формування затримок

cseg

Key_scan:
    clr    TR0                 ; Зупинимо Timer0
    clr    EA                  ; Заборонимо усі преривання

    clr    col1                ; Опустимо 1 стовпчик
    mov    A,P2                ; Зчитаемо порт клавіатури у А
    cjne    A,#11111110b,Check_1    ; Порівнямо зі зразком
    jmp    Skip_checking_col1      ; Якщо співпало, значить у цьому стовпці не натиснута жодна кнопка
Check_1:                            ; Якщо не співпало, значить була натиснута якась із кнопок цього стовпця
        clr     zumer                   ; Озвучимо натиснення
    setb    F0            ; Запам'ятаємо факт натиснення кнопки
    jnb    row1,Key_1_Far          ; Перевіримо побітно кожну строку
        jnb    row2,Key_4_Far          ; І перейдемо на обработчик конкретної кнопки
        jnb    row3,Key_7_Far          ; Вийшли за межі зміщення +-128 байт, тому потрібно через промежуточную мітку
    jnb    row4,Key_Zvezdochka_Far ; Вийшли за межі зміщення +-128 байт, тому потрібно через промежуточную мітку
Skip_checking_col1:                 ; Пропускаємо перевірку усіх наступних строк для економії часу
    setb    col1                ; Піднимемо назад 1 стовпчик

        clr    col2                ; Опустимо 2 стовпчик
           mov    A,P2                ; Зчитаемо порт клавіатури у А 
    cjne    A,#11111101b,Check_2    ; Порівнямо зі зразком
    jmp    Skip_checking_col2      ; Якщо співпало, значить у цьому стовпці не натиснута жодна кнопка
Check_2:                            ; Якщо не співпало, значить була натиснута якась із кнопок цього стовпця
        clr     zumer                   ; Озвучимо натиснення
    setb    F0            ; Запам'ятаємо факт натиснення кнопки
        jnb    row1,Key_2          ; Перевіримо побітно кожну строку
        jnb    row2,Key_5_Far          ; І перейдемо на обработчик конкретної кнопки
        jnb    row3,Key_8_Far        ; Вийшли за межі зміщення +-128 байт, тому потрібно через промежуточную мітку
    jnb    row4,Key_0_Far          ; Вийшли за межі зміщення +-128 байт, тому потрібно через промежуточную мітку
Skip_checking_col2:                 ; Пропускаємо перевірку усіх наступних строк для економії часу
        setb    col2                ; Піднимемо назад 2 стовпчик

        clr    col3                ; Опустимо 3 стовпчик
           mov    A,P2                ; Зчитаемо порт клавіатури у А 
    cjne    A,#11111011b,Check_3    ; Порівнямо зі зразком
    jmp    Skip_checking_col3      ; Якщо співпало, значить у цьому стовпці не натиснута жодна кнопка
Check_3:                            ; Якщо не співпало, значить була натиснута якась із кнопок цього стовпця
        clr     zumer                   ; Озвучимо натиснення
    setb    F0            ; Запам'ятаємо факт натиснення кнопки
        jnb    row1,Key_3          ; Перевіримо побітно кожну строку
        jnb    row2,Key_6          ; І перейдемо на обработчик конкретної кнопки
        jnb    row3,Key_9_Far
        jnb    row4,Key_Reshotka_Far
Skip_checking_col3:                 ; Пропускаємо перевірку усіх наступних строк для економії часу
    setb    col3                ; Піднимемо назад 3 стовпчик

        clr    col4                ; Опустимо 4 стовпчик
           mov    A,P2                ; Зчитаемо порт клавіатури у А 
    cjne    A,#11110111b,Check_4    ; Порівнямо зі зразком
    jmp    End_Check        ; Якщо співпало, значить у цьому стовпці не натиснута жодна кнопка
Check_4:                            ; Якщо не співпало, значить була натиснута якась із кнопок цього стовпця
        clr     zumer                   ; Озвучимо натиснення
    setb    F0            ; Запам'ятаємо факт натиснення кнопки
        jnb    row1,Key_A_Far          ; Перевіримо побітно кожну строку
        jnb    row2,Key_B_Far          ; І перейдемо на обработчик конкретної кнопки
        jnb    row3,Key_C_Far
        jnb    row4,Key_D_Far

End_Check:                              ; Закінчуємо усі перевірки
    mov     P2,#0FFh                ; Поднімаємо усі стовпчики, ну і строки заодно
    mov    TH0,#3Ch        ; Заново занесемо значення у лічильний регістр Timer0
    mov    TL0,#0AFh               ; При даному значенні преривання виникаються кожні 50 мс
    setb    EA                  ; Дозволяємо усі переривання
    setb    TR0                 ; Запустимо Timer0

    reti                ; Виходимо із обработчика

Key_1_Far:
    jmp Key_1

Key_4_Far:
    jmp Key_4
    
Key_5_Far:
    jmp Key_5
    
Key_7_Far:
    jmp Key_7

Key_8_Far:
    jmp Key_8
    
Key_9_Far:
    jmp Key_9
    
Key_0_Far:
    jmp Key_0

Key_Zvezdochka_Far:
    jmp Key_Zvezdochka

Key_Reshotka_Far:
    jmp Key_Reshotka

Key_A_Far:
    jmp Key_A

Key_B_Far:
    jmp Key_B

Key_C_Far:
    jmp Key_C
    
Key_D_Far:
    jmp Key_D
    
Key_1:
        mov    znak,#31h        ; Присвоюємо змінній znak код натиснутої клавіші
    call    Del_100ms
    jmp     End_Check

Key_2:
    mov    znak,#32h        ; І т.д.
    call    Del_100ms
    jmp     End_Check

Key_3:
    mov    znak,#33h
    call    Del_100ms
    jmp     End_Check

Key_4:
    mov    znak,#34h
    call    Del_100ms
        jmp     End_Check

Key_5:
    mov    znak,#35h
    call    Del_100ms
        jmp     End_Check

Key_6:
    mov    znak,#36h
    call    Del_100ms
        jmp     End_Check

Key_7:
    mov    znak,#37h
    call    Del_100ms
        jmp     End_Check

Key_8:
    mov    znak,#38h
    call    Del_100ms
        jmp     End_Check

Key_9:
    mov    znak,#39h
    call    Del_100ms
        jmp     End_Check

Key_Zvezdochka:
    mov    znak,#2Ah
    call    Del_100ms
        jmp     End_Check

Key_0:
    mov    znak,#30h
    call    Del_100ms
        jmp     End_Check

Key_Reshotka:
    mov    znak,#23h
    call    Del_100ms
        jmp     End_Check
        
Key_A:
    mov    znak,#41h
    call    Del_100ms
        jmp     End_Check
        
Key_B:
    mov    znak,#42h
    call    Del_100ms
        jmp     End_Check
        
Key_C:
    mov    znak,#43h
    call    Del_100ms
        jmp     End_Check
        
Key_D:
    mov    znak,#44h
    call    Del_100ms
        jmp     End_Check
    
Del_100ms:                ; Затримка для усунуння брязкоту контактів кнопки, можливо завелика, але для мене якраз норм. Можете підправити під себе.
        mov    R2,#4h
    mov    R1,#0A0h
    mov    R0,#4Ch
     djnz    R0,$
    djnz    R1,$-4
    djnz    R2,$-8
    mov    R0,#49h
    djnz    R0,$
        ret
        
end

Файл основного циклу:

Прихований текст

Main.asm

cseg    at    0            ; Сегмент коду починаємо з 0 адреси, він і так по замовчуванню починаєься звідси, але правила хорошого тону рекомендують вказувати це явно
    jmp    Main

org 0Bh
    jmp    Key_scan        ; Обработчик перевання від Timer0    

org    30h                ; Залишимо місце для майбутніх переривань

include 'Matrix_keyboard.asm'        ; Ось тут прописуємо нашу бібліотеку

Main:                    ; Основний цикл
; Тут, у основному циклі ми можемо робити зі змінною znak (у якій міститься код натиснутої клавіші) усе, що потрібно, наприклад
    mov    A,znak            ; Закинемо код натиснутої клавіші у Акумулятор    
    jmp    $            ; Зациклимося
end

Схема підключення у прикріпленому файлі.
Невеликі роздуми вголос стосовно підключення матричної клавіатури.
Інколи бачу і інтернеті. як люди підключають матричну клавіатуру по схемі 3х5 або навіть 2х6. Поясню, чому це не дуже доцільно. Вибір схеми підключення схожий на вибір розмірів будинку, що має будуватися - треба прагнути зробити його якомога "квадратнішим", бо саме у квадрата (схема підключення 4х4) з усіх прямокутників, найбільша площа (кількість кнопок) при найменшому периметрі (довжині стін - кількості задіяних ліній порта). Адже подивіться самі, при схемі 4х4 ми отримаємо 16 кнопок (4*4=16), затрачаючи на це 8 ліній (цілий порт), при схемі 3х5 и затрачаємо цих самих 8 ліній, а отримаємо уже на 1 кнопку менше (3*5=15), а при схемі 2х6 взагалі лише 12 кнопок (2*6=12). Якщо вам достатньо 12 кнопок, то краще підключіть клавіатуру по схемі 3х4, зекономите 1 лінію для якихось додаткових функцій.
Також, із власного невеликого досвіду, можу сказати, що робити матричну клавіатуру менша ніж 3х3 недоцільно, бо наприклад при схемі підключення 2х3 ми будемо мати 6 кнопок (лише на 1 кнопку більше , ніж при використанні окремої кнопки на кожну лінію порта), а програма при цьому значно ускладниться.
Щодо захистних діодів. Взагалі, завдяки будові портів, для мікроконтролерів архітектури MCS-51 вони не є обв'язковими, але для можливості безпечного підключення вашої матричної клавіатури до інших мікроконтроллерів їх краще встановити, вони захистять лінії портів від випалювання при одночасному натисненні декількох кнопок. Вони встановлюються на лінії портів (колонки чи строки, не важливо як ви їх назвете), які ви "опускаєте" перед скануванням. Зазначу, що зручніше для написання програми, коли колонки та строки йдуть 2 тетрадами підряд, а не вперемішку.
Доречі про одночасне натиснення декількох кнопок. Мій алгоритм не передбачає розпізнання одночасного натиснення декількох клавіш, такі алгоритми є доволі складними для реалізаціх, і в принципі не потрібні новачкам.

Post's attachments

Matrix_keyboard_AT89S52.JPG 80.59 kb, 274 downloads since 2015-11-06 

Подякували: 0xDADA11C72

10

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Будь ласка, розкажіть про себе в темі знайомств, не хочу тут писати щось окрім питань по вашим схемам.

11

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Володіючих технічною російською значно більше.

знову вас засмучу: у рашен початківців (на всіх форумах) така ж проблема як і у вашій історії - небажання вчити англійську і очікування, що матеріал звалиться російською як манна.

Ви були знайомі з розробником MCSudio (хоча б онлайн) ?

нi, але автор досліджуваної вами публікації в редколегії нашого журналу і він впритул контачив з розробником MC-Sudio, контакт втрачений 10 років тому.

12

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

спілкувався з старожилами що програмують мікроконтролери, навіть AVR, косяків повно в них, за цього стоять по деклька версій студій, якщо баги не закриті то доводиться писати власний міні компілятор для власних задач. Бо контролери це "процессори 80-х років"

13 Востаннє редагувалося MCS-51 (06.11.2015 21:59:20)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

raxp написав:

знову вас засмучу...

Ви мене не засмучуєте. Мені поки що вистачає матеріалів російською (звичайно на даному етапі вичення, тому це тимчасово). Я ще і 1/10 не прочитав від того, що накачав, а вже голова пухне. А ті новачики на російських форумах просто читати не хочуть, навіть своєю рідною мовою, хочуть, щоб за них усе зробили, і готове видали. Мені стало "за дєржаву абідна" із-за того, що не вистачає матеріалів українською. І тут ще історія із другом згадалася, от і вирішив трохи зрушити це питання. Хоча стосовно новітніх мікроконтролерів, то тут я звичайно можу лише погодитись, що навіть на "веліком і могучем" матеріалу як кіт налакав, тут онлі інгліш однозначно.
На вихідних буду страшенно зайнятий, а от після постараюся опублікувати матеріал, як із 2 уже наявних бібліотек, для вводу інформації за допомогою матричної клавіатури, та її виведення на дисплей LCD HD44780, зліпити проект "друкарської машинки" - будемо виводити на дисплей символи, які вводимо з матричної клавіатури. Буде схема, і можливо фото пристрою.

Подякували: 0xDADA11C7, raxp, leofun013

14 Востаннє редагувалося MCS-51 (09.11.2015 08:11:27)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Отже, як і обіцяв, викладаю готовий проект "друкарської машинки". Бібіліотека LCD HD44780 зазнала незначного доопрацювання, тому викладаю її повторно. Бібліотека матричної клавіатури без змін.

Прихований текст

HD44780.asm

; Дисплей підключений по 4-bit шині
; D4-P1.4, D5-P1.5, D6-P1.6, D7-P1.7
; RW - на землю (тільки запис у дисплей, читання немає)
LCD_Port    equ    P1

RS        equ    P3.6
E        equ    P3.7
Control_port    equ     P3

znak_copy    equ    R3              ; Змінна, з якої ми беремо код натиснутої кнопки, _copy тому що оригінал уже визначений у бібліотеці опитування матричної клавітури

; При доробці коду під власні потреби, Не використовувати для своїх цілей RO і R2, вони використовуються для формування затримок

cseg

Ini_LCD:
        call    Del_15ms        ; Стартова затримка на 15 мс по даташиту, без неї будуть глюки
    mov    Control_port,#00111111b    ; E=0, RS=0, R/W - 0, бо сидить на землі
        mov    LCD_Port,#00110000b    ; При інициализації спершу виставимо 8-bit шину, так треба (DL=1, N=0, F=0)
    call    Impulse            ; Імпульс Е

    call    Del_4ms            ; Друга затримка на 4,1 мс по даташиту, без неї будуть глюки

    mov    LCD_Port,#00110000b    ; Другий раз виставимо 8-bit шину, так треба (DL=1, N=0, F=0)
    call    Impulse            ; Імпульс Е

                    ; Третя затримка на 100 мкс по даташиту, без неї будуть глюки, вона вже є всередині процедури Impulse вище

    mov    LCD_Port,#00110000b    ; І втретє виставимо 8-bit шину, так треба (DL=1, N=0, F=0)
    call    Impulse            ; Імпульс Е

                    ; Далі треба бы читати стан флагу шини (BF), але легше тупити затримками по 100-200 мкс (у складі процедури Impulse)

    mov    LCD_Port,#00100000b    ; Виставляємо 4-bit шину (DL=0), старший напівбайт
    call    Impulse            ; Імпульс Е
        mov    LCD_Port,#00100000b    ; Знову старший напівбайт, тому що ми перед цим переключились на 4-х битную шину і його треба переслати Знову
    call    Impulse                ; Імпульс Е
        mov    LCD_Port,#10000000b    ; Молодший напівбайт (N = 1 - 2 строки, F = 0 - символи 5*7)
    call    Impulse            ; Імпульс Е

     mov    LCD_Port,#00000000b    ; Команда включения дисплея, старший напівбайт пустой
    call    Impulse            ; Імпульс Е
        mov    LCD_Port,#11000000b    ; Младший напівбайт (D=1 - дисплей увімкнено, C=0 - курсор вимкнено, B=0 - мерехтіння курсора вимкнено)
    call    Impulse            ; Імпульс Е

    mov    LCD_Port,#00000000b    ; Встановлення параметров відображення інформації, старший напівбайт пустий
    call    Impulse            ; Імпульс Е
        mov    LCD_Port,#01100000b    ; Молодший напівбайт (I/D=1 - інкремент позиції курсору, S=0 - немає здвигу дисплею)
    call    Impulse            ; Імпульс Е
    
Reset_LCD:                ; Скидання дисплею, можна виконувати як окрему підпрограмму у будь-якомі місці основної програми
    clr     RS                      ; Ще раз опустимо RS, можливо ми визвали цю підпрограмму окремо
    mov    LCD_Port,#00000000b    ; Старший напівбайт
        call    Impulse            ; Імпульс Е
        mov    LCD_Port,#00010000b    ; Молодший напівбайт
        call    Impulse            ; Імпульс Е

    call    Del_4ms            ; Затримка після скидання, без нее глюки
        setb    RS            ; Тепер виводимо не команди, а символи, тому піднимаемо RS

    ret

Del_15ms:
        mov    R1,#2Eh            ;(R1*(2*R0+1)+2*R1)+1mks(mov R1)+2mks(call)+2mks(ret)
Loop_15ms:
    mov    R0,#0A1h
    djnz    R0,$
    djnz    R1,Loop_15ms
        ret

Del_4ms:                ;(R1*(2*R0+1)+2*R1)+1mks(mov R1)+2mks(call)+2mks(ret)
        mov    R1,#9h
Loop_4ms:
    mov    R0,#0DCh
    djnz    R0,$
    djnz    R1,Loop_4ms
        ret

Del_100mks:
        mov    R0,#50
    djnz    R0,$
    ret

Impulse:                ; Імпульс на вході Е, з наступною затримкою на 100 мкс
    setb    E
    nop                ; Потумпимо 1 мкс (при кварці 12 Мгц), щоб на ножці встановилося значення
     clr    E
    call    Del_100mks
        ret
        
Indication:                ; Виводимо зміст змінної znak, у якій зберігається код нашого символу
    mov     LCD_Port,znak_copy    ; Старший напівбайт
    call    Impulse            ; Імпульс Е
    mov    A,znak_copy
    swap    A
    mov    LCD_Port,A        ; Молдший напівбайт
        call    Impulse            ; Імпульс Е
        ret
        
Second_line:                ; Переведемо курсор на початок 2 стрічки
        clr    RS            ; Тепер виводимо не символи, а подаемо команди, тому опускаемо RS
    mov     znak_copy,#0C0h
    call    Indication
    setb    RS            ; Курсор перевели, тепер знову не подаемо команди, а виводимо символи на дисплей, тому піднимаемо RS
    ret
    
First_line:                ; Переведем курсор на початок 1 строки
        clr    RS            ; Тепер виводимо не символи, а подаемо команди, тому опускаемо RS
    mov     znak_copy,#80h
    call    Indication
    setb    RS            ; Курсор перевели, тепер знову не подаемо команди, а виводимо символи на дисплей, тому піднимаемо RS
    ret
    
end
Прихований текст

LCD+Matrix_key.asm

counter_symbol  equ     R4              ; Лічильник виведених символів, необхідний для переведення курсору на іншу стрічку

cseg    at    0            ; Сегмент коду починаємо з адреси 0, він і так по замовчуванню починаєься звідси, але правила хорошого тону рекомендують вказувати це явно
    jmp    Start
    
org 0Bh
    jmp    Key_scan        ; Обробник Timer0

org 30h                    ; Залишимо місце для майбутніх переривань
include 'HD44780.asm'            ; Файли 2 наших бібліотек
include 'Matrix_keyboard.asm'

Start:
    clr    F0                  ; Очищаємо флаг натиснення кнопки
    mov    counter_symbol,#0       ; Очищаємо лічильник введених символів
    call    Ini_LCD             ; Ініціалізуємо LCD

Init_Timer0:                ; Ініціалізуємо Timer0, по його перериваннях відбувається опитування матричної клавіатури
    mov    TMOD,#1            ; Режим роботи №2 (16-біт)
     setb    EA            ; Дозволяємо усі переривання
        setb    ET0            ; Дозволяємо переривання від Timer0
    setb    TR0            ; Запускаємо Timer0

Wait_press_butt_1:            ; Перевіряємо, чи була натиснута кнопка
        jbc    F0,Indication_1_line    ; Якщо була, переходимо на мітку, и тут же забуваємо, що натискали кнопку, бо команда jbc при переході скидує біт, якщо він був зведений
        jmp    Wait_press_butt_1    ; Не було натиснення, крутимося у циклі
Indication_1_line:            ; Було натиснення
     call    Indication          ; Виводимо змінну znak, у якій зберігається код натиснутої кнопки
    inc     counter_symbol          ; Вивели +1 символ
    setb    zumer               ; Кінець озвучки натиснення кнопки (початкок у підпрограммі опитування)
    cjne    counter_symbol,#16,Wait_press_butt_1  ; Перевіряємо, чи заповнен усі 16 символів 1 строки?

    call    Second_line         ; Переведемо курсор на початок 2 строки
    mov     counter_symbol,#0       ; Обнулимо лічильник виведених символів

Wait_press_butt_2:            ; Перевіряємо, чи була натиснута кнопка
        jbc    F0,Indication_2_line    ; Якщо була, переходимо на мітку, и тут же забуваємо, що натискали кнопку, бо команда jbc при переході скидує біт, якщо він був зведений
        jmp    Wait_press_butt_2    ; Не було натиснення, крутимося у циклі

Indication_2_line:            ; Було натиснення
     call    Indication          ; Виводимо змінну znak, у якій зберігається код натиснутої кнопки
    inc     counter_symbol          ; Вивели +1 символ
        setb    zumer               ; Кінець озвучки натиснення кнопки (початкок у підпрограммі опитування)
    cjne    counter_symbol,#17,Wait_press_butt_2  ; Перевіряємо, чи заповнен усі 16 символів 2 строки? (Значенння 16 - якщо будемо затирати попередні символи, 17 - якщо наступною командою буде скидання дисплею)

        call    Reset_LCD           ; Уся 2 строка заповнена, скидання дисплею
        call    First_line          ; Переведемо курсор на початок 1 строки, Символи що віводяться, будуть затирати попередні, якщо тільки попередньою командою не була команда скидання дисплею
    mov     counter_symbol,#0       ; Обнулимо лічильник виведених символів

    jmp    Wait_press_butt_1    ; Зациклимося

end

Трохи пояснень по проекту. Усі зміни у бібліотеці LCD HD44780 полягають у додаванні змінної "znak_copy". Причому вона розміщується за тією самою адресою, що і змінна "znak" з бібліотеки матричної клавіатури (регістр R3). Грубо кажучи бібліотека матричної клавіатури "кладе" у регістр R3 код натиснутої клавіші, а бібліотека LCD HD44780 "дістає" їз цього регістру код, і відображає символ, якому відповідає цей код. Тому я дав цьому спільному для них регістру схожі назви (znak і znak_copy), щоб не заплутатись (однакові назви давати не можна, бо транслятор свариться, що уже об'явлена змінна із такою назвою, але ніхто не забороняє одному регістру присвоювати декілька "псевдонімів", чим ми і скористалися). Регістр має бути обов'язково спільний для 2 бібліотек, тому що вони "абсолютно не здогадуються" про існування одна одної. Лише файл головного циклу дозволяє звертатися до змінних обох бібліотек, бо вони (бібліотеки) у ньому прописані. При цьому іх (змінні із цих бібліотек) не потрібно ніяк повторно прописувати у файлі головного циклу.
Також, на самому початку підпрограми Reset_LCD, додалася команда clr RS. Вона необхідна для того, щоб ми у довільному місці могли визвати цю підпрограмму і очистити дисплей від символів, що заповнили весь екран.
Схема у прикріпленому файлі. Проект відлагоджував на макеті, фото незнаю коли зможу зробити, бо по буднях працюю до темряви, а при штучному освітленні виходять фото нікудишньої якості, так що можливо аж на слідуючих вихідних.

Post's attachments

Друкарська машинка.JPG 95.82 kb, 278 downloads since 2015-11-08 

Подякували: raxp, 0xDADA11C7, leofun014

15 Востаннє редагувалося MCS-51 (21.11.2015 19:59:51)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Ввід та вивід інформації це звичайно добре, але я задумував цей пристрій, як виносну панель керування, тому у наступний раз (цього тижня постараюся викласти код) додамо сюди якийсь інтерфейс зв'язку. Думаю це буде UART, як один з найпростіших, і до того ж єдиний, який апаратно реалізований у цій моделі MCS-51. Усе інше у цьому мікроконтролері (AT89S52) можна реалізувати лише ногодригом(( Але нічого, якось прорвемося...

Update:
Нарешті є змога викласти код. Бо то часу не було, то учора форум увесь день був недоступний((
Значить стосовно коду. Бібліотеку UART поки що зробив без використання переривань, просто у основному циклі запускаю підпрограми, які аналізують флаги TI/RI. Так простіше, а я йду від простого до складного.
Файл основного циклу зазнав змін, бо я трохи оптимізував алгоритм. У звязку із цим зазнав мінімальних змін і файл бібліотеки дисплею, я переніс до нього з основного циклу лічильник введених (виведених на екран) символів. Логічніше, щоб він знаходився саме там. Бібліотека матричної клавіатури без змін. На даний момент макет вміє опитувати матричну клавіатуру, виводити натиснуту клавішу на екран, та відсилати її ASCII-код по UART, а також приймати будь-який символ по UART на швидкості 9600 біт/с, та виводити його на екран.

Прихований текст

LCD+Marix_key+UART.asm

cseg    at    0    
    jmp    Start
    
org 0Bh
    jmp    Key_scan
    
org 30h
include 'HD44780.asm'               ; Прописуємо файли бібліотек
include 'Matrix_keyboard.asm'
include 'UART.asm'

Start:
    mov     SP,#7h              ; Початок стеку з R0 1 банку
    clr    F0                  ; Очищаемо буфер кнопки
    clr     20h.1
    mov    counter_symbol,#0       ; Очищаемо лічильник введенних символів
    call    Ini_LCD             ; Инициализируем LC
        call    Init_Timer0         ; Ініціалізуємо таймер опитування матричної клавиатур
    call    Init_Serial         ; Ініціалізуємо UART

Wait_serial_recive:
    jnb     RI,Wait_press_butt      ; Немає принятого по UART байту, йдемо опитувати кнопку
    call    Serial_Recive           ; Процедура прийома байта по UART

Wait_press_butt:            ; Перевіряємо, чи була натиснута кнопка
        jbc    F0,Indication_line    ; Якщо була, переходимо на мітку, и тут же забуваємо, что натискали кнопку
        jmp    Wait_serial_recive    ; Не була натиснута, крутимося у циклі
Indication_line:
    call    Indication          ; Виводимо зміст змінної znak, у якій зберігається код натиснутої кнопки
        call    Serial_Send         ; Воводимо байт по UART
    setb    zumer               ; Кінець озвучки нажатиску кнопки
    cjne    counter_symbol,#16,No_16
                    ; Сюди ми потрапимо, якщо кількість введенних символів = 16
        call    Second_line         ; Переведемо курсор на початок 2 стрічки
No_16:                    ; Сюди ми потрапимо, якщо кількість введенних символів <> 16
        cjne    counter_symbol,#33,Wait_serial_recive   ; Перевіримо, може ми вже вивели 32 символи, якщо ні, то знову йдемо перевіряти приймач UART
                    ; Сюда мы попадем, если количество введенных символов равно 32
        call    First_line             ; Переведемо курсор на початок 1 стрічки
    call    Reset_LCD           ; Ну і очистимо дисплей, а размом із ним лічильник введенних символів
        jmp    Wait_serial_recive      ; Ну і знову йдемо перевіряти приймач UART

end
Прихований текст

HD44780.asm

; Дисплей підключений по 4-bit шині
; D4-P1.4, D5-P1.5, D6-P1.6, D7-P1.7
; RW - на землю (тільки запис у дисплей, читання немає)
LCD_Port    equ    P1

RS        equ    P3.6
E        equ    P3.7
Control_port    equ     P3

znak_copy    equ    R3              ; Змінна, з якої ми беремо код натиснутої кнопки, _copy тому що оригінал уже визначений у бібліотеці опитування матричної клавітури
counter_symbol  equ     R4              ; Лічильник виведених символів, необхідний для переведення курсору на іншу стрічку

; При доробці коду під власні потреби, Не використовувати для своїх цілей RO і R2, вони використовуються для формування затримок

cseg

Ini_LCD:
        call    Del_15ms        ; Стартова затримка на 15 мс по даташиту, без неї будуть глюки
    mov    Control_port,#00111111b    ; E=0, RS=0, R/W - 0, бо сидить на землі
        mov    LCD_Port,#00110000b    ; При інициализації спершу виставимо 8-bit шину, так треба (DL=1, N=0, F=0)
    call    Impulse            ; Імпульс Е

    call    Del_4ms            ; Друга затримка на 4,1 мс по даташиту, без неї будуть глюки

    mov    LCD_Port,#00110000b    ; Другий раз виставимо 8-bit шину, так треба (DL=1, N=0, F=0)
    call    Impulse            ; Імпульс Е

                    ; Третя затримка на 100 мкс по даташиту, без неї будуть глюки, вона вже є всередині процедури Impulse вище

    mov    LCD_Port,#00110000b    ; І втретє виставимо 8-bit шину, так треба (DL=1, N=0, F=0)
    call    Impulse            ; Імпульс Е

                    ; Далі треба бы читати стан флагу шини (BF), але легше тупити затримками по 100-200 мкс (у складі процедури Impulse)

    mov    LCD_Port,#00100000b    ; Виставляємо 4-bit шину (DL=0), старший напівбайт
    call    Impulse            ; Імпульс Е
        mov    LCD_Port,#00100000b    ; Знову старший напівбайт, тому що ми перед цим переключились на 4-х битную шину і його треба переслати Знову
    call    Impulse                ; Імпульс Е
        mov    LCD_Port,#10000000b    ; Молодший напівбайт (N = 1 - 2 строки, F = 0 - символи 5*7)
    call    Impulse            ; Імпульс Е

     mov    LCD_Port,#00000000b    ; Команда включения дисплея, старший напівбайт пустой
    call    Impulse            ; Імпульс Е
        mov    LCD_Port,#11000000b    ; Младший напівбайт (D=1 - дисплей увімкнено, C=0 - курсор вимкнено, B=0 - мерехтіння курсора вимкнено)
    call    Impulse            ; Імпульс Е

    mov    LCD_Port,#00000000b    ; Встановлення параметров відображення інформації, старший напівбайт пустий
    call    Impulse            ; Імпульс Е
        mov    LCD_Port,#01100000b    ; Молодший напівбайт (I/D=1 - інкремент позиції курсору, S=0 - немає здвигу дисплею)
    call    Impulse            ; Імпульс Е
    
Reset_LCD:                ; Скидання дисплею, можна виконувати як окрему підпрограмму у будь-якомі місці основної програми
    clr     RS                      ; Ще раз опустимо RS, можливо ми визвали цю підпрограмму окремо
    mov    LCD_Port,#00000000b    ; Старший напівбайт
        call    Impulse            ; Імпульс Е
        mov    LCD_Port,#00010000b    ; Молодший напівбайт
        call    Impulse            ; Імпульс Е

    call    Del_4ms            ; Затримка після скидання, без нее глюки
        setb    RS            ; Тепер виводимо не команди, а символи, тому піднимаемо RS
        
        mov    counter_symbol,#0       ; Очистимо лічильник введенних символів

    ret

Del_15ms:
        mov    R1,#2Eh            ;(R1*(2*R0+1)+2*R1)+1mks(mov R1)+2mks(call)+2mks(ret)
Loop_15ms:
    mov    R0,#0A1h
    djnz    R0,$
    djnz    R1,Loop_15ms
        ret

Del_4ms:                ;(R1*(2*R0+1)+2*R1)+1mks(mov R1)+2mks(call)+2mks(ret)
        mov    R1,#9h
Loop_4ms:
    mov    R0,#0DCh
    djnz    R0,$
    djnz    R1,Loop_4ms
        ret

Del_100mks:
        mov    R0,#50
    djnz    R0,$
    ret

Impulse:                ; Імпульс на вході Е, з наступною затримкою на 100 мкс
    setb    E
    nop                ; Потумпимо 1 мкс (при кварці 12 Мгц), щоб на ножці встановилося значення
     clr    E
    call    Del_100mks
        ret

Indication:                ; Виводимо зміст змінної znak, у якій зберігається код нашого символу
    mov     LCD_Port,znak_copy    ; Старший напівбайт
    call    Impulse            ; Імпульс Е
    mov    A,znak_copy
    swap    A
    mov    LCD_Port,A        ; Молодший напівбайт
        call    Impulse            ; Імпульс Е
        inc    counter_symbol        ; +1 символ виведений
    ret
    
Second_line:                ; Переведемо курсор на початок 2 стрічки
        clr    RS            ; Тепер виводимо не символи, а подаемо команди, тому опускаемо RS
    mov     znak_copy,#0C0h
    call    Indication
    setb    RS            ; Курсор перевели, тепер знову не подаемо команди, а виводимо символи на дисплей, тому піднимаемо RS
    ret
    
First_line:                ; Переведем курсор на початок 1 строки
        clr    RS            ; Тепер виводимо не символи, а подаемо команди, тому опускаемо RS
    mov     znak_copy,#80h
    call    Indication
    setb    RS            ; Курсор перевели, тепер знову не подаемо команди, а виводимо символи на дисплей, тому піднимаемо RS
    ret
    
end
Прихований текст

UART.asm

znak_copy_2    equ    R3        ; Змінна, у якій зберігається код натиснутої клавіші, "_copy_2" тому що оригінал уже взначений в файлі Matrix_keyboard.asm

Init_Serial:
    mov    SCON,#50h        ; Uart in mode 1 (8 bit)
    orl    TMOD,#20h        ; Timer1 in mode 2
    mov    TH1,#0FDh        ; 9600 Bds at 11.059MHz
    mov    TL1,#0FDh        ; 9600 Bds at 11.059MHz
    anl     PCON,#7FH               ; Очистимо SMOD
    setb    EA            ; Дозволили усі переривання
    setb    TR1            ; Запуск Timer1
    ret

Serial_Send:
       mov    SBUF,znak_copy_2        ; Відправимо байт
    jnb     TI,$                    ; Немає флагу ТІ (кінець посилки) - чекаємо на місці
    clr    TI                      ; Був флаг ТІ, байт пішов, скидаємо флаг ТІ
    ret
    
Serial_Recive:
    mov     znak_copy_2,SBUF        ; Приймаємо байт
    clr     RI                      ; Скидаємо флаг RI
    setb    F0                      ; Зведемо флаг натиснутої кнопки, щоб зберігалася умова виводу на дисплей
    ret

Додав фото макету.

Post's attachments

100_4615.JPG 1008.12 kb, 302 downloads since 2015-11-21 

16

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Test

17 Востаннє редагувалося MCS-51 (23.11.2015 13:59:59)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Якщо памятаєте, то я казав, що реалізація UART у мене трохи кострубата, тому що передача байту викликається просто підпрограмою, а прийом реалізований на основі аналізу флагу RI  у основному циклі. І якщо передача викликом підпрограми навіть економніша на 1 команду за передачу за допомогою переривання, то прийом за аналізом флагу у основному циклі це дуже не економно. Отже виправляюся, робимо усе по дорослому, на перериваннях. Власне файл біблеотки UART.asm зазнав незначних змін:

Прихований текст
symbol_copy_2    equ    R3        ; Змінна, у якій зберігається код натисгутої кнопки, "_copy_2" тому що оригінал уже визначений у файлі Matrix_keyboard.asm

zumer_copy   equ     P3.5

cseg

Init_Serial:
    mov    SCON,#50h        ; Uart in mode 1 (8 bit), REN=1
    orl    TMOD,#20h        ; Timer1 in mode 2
    mov    TH1,#0FDh        ; 9600 Bds at 11.059MHz
    mov    TL1,#0FDh        ; 9600 Bds at 11.059MHz
    setb    ES            ; Дозволяємо переривання від послідовного порта
    setb    TR1            ; Запуск Timer1
    setb    EA            ; Дозволяємо усі переривання
    ret

Serial_Int:
    clr     EA                      ; Заборонимо усі переривання
    jb    TI,Transmit        ; Якщо  є флаг TI, значить це передача
    clr     zumer_copy              ; Це не передача, а прийом. Озвучимо прийнятый символ, на жаль озвучування буде дуже коротким, і майже нечутним(((
        mov    symbol_copy_2,SBUF    ; Це прием, приймемо байт
    clr    RI            ; Скинемо флаг RI
    setb    F0                      ; Імітуємо натиснення кнопки, щоб виконувалася умова выводу на дисплей
    jmp     Serial_end
Transmit:                               ; Це передача
    clr    TI                      ; Скинемо флаг TI, ми визвали переривання встановленнм цього флагу, і щоб передати байт, нам буде потрібно дочекатися його встановлення, і скинути повторно
    mov    SBUF,symbol_copy_2    ; Відправимо байт
    jnb     TI,$                    ; Немає флагу ТІ (закінчення передачі) - чекаємо на місці
    clr    TI            ; Був флаг ТІ, байт пішов, скинемо флаг ТІ
Serial_end:
    setb    EA                      ; Дозволимо усі переривання
    reti
end

Тепер, якщо треба відправити байт, то просто встановлюємо флаг командою "setb TI" у потрібному місці основного циклу. На прийом взагалі нічого не потрібно робити, переривання усе зробить за нас, потрібно лише правильно прописати його вектор у основному файлі після вектору оброби Таймеру0:

org 23h
jmp Serial_Int
Подякували: 0xDADA11C72

18 Востаннє редагувалося MCS-51 (01.12.2015 21:54:21)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Доброго часу доби. Давненько нічого не писав. Не те щоб прогрес зовсім зупинився, проте часу на саморозвиток дуже бракує, тому усе просувається ну просто ліліпутськими кроками. Але просувається)) Наразі хочу поділитися не своїм авторським кодом, а просто знайденим на просторах інтернету, і більш-менш "причесаним". Закортіло мені поуправляти мікроконтроллером за допомогою інфрачервоного пульта. Почав гуглити, для MCS-51 ну просто маса прикладів для пультів стандарту RC-5, бо пік популятності пультів цього стандарту прийшовся якраз на пік популярності мікроконтроллерів MCS-51)) Але майже скрізь усе без внятних коментарів і пояснень, а використовувати таке я собі не можу дозволити, люблю у всьому розібратися. Уже майже втратив надію, і хотів кинути це діло до ліпших часів, аж випадково натрапив на згадування бібліотеки RC-5 від Рідіко. Про цього білоруського дядька я трохи чув на різних радіолюбительських сайтах, і це викликало надію, що його код має бути біль-менш "їстівним". Так і вивилося, по запиту "RC-5 Ридико" гугл видав декілька посилань на статтю, із нормальним описом цього протоколу. Її і взяв за основу, розписав декілька неочевидних для новачків моментів і оформив у вигляді окремої бібліотеки. Код запрацював майже з "півкопняка". Наразі у основному файлі маємо порт "Out_Port", на світлодіоди якого і виводимо код натиснутої на пульті клавіші. Сам код міститься у регісті R7 банку 3 (весь банк 3 використовується під потреби бібліотеки) але нічого не заважає змінити це. Що із усім цим добром робити далі обмежене лише вашою фантазією, наприклад можна зробити систему управління пристроями. Мою радість зіпсував лише 1 момент, із 8 наявних у моєму домі пультів, лише 1, найстаріший виявився сумісним із цим протоколом(( Чим старіший пульт, тим вищий шанс, що він працює у цьому протоколі. Код біблітеки потребує компіляції під конкретну частоту мікроконтроллера, бо використовються програмні затримки, часові параметри яких чітко визначені і не повинні порушуватися.

Прихований текст

Lib_RC5_mod.asm

; Підпрограма декодування протоколу RC5 в обробнику зовнішнього переривання на вході INT0 (чи INT1)
; Input - bit IR (interrupt line), активний рівень - низький.
; Out - byte, регістр R7 3 банка (безпосередня адреса ячейки памяті 1Fh),
; Його структура наступна: R7 = Х.С.D5.D4.D3.D2.D1.D0
; D0-D5 - командні біти коду RC5
; C - біт управління
; Х - значення підпрограмою не змінюється. Цей біт зручно використовувати як біт управління для місцевої клавіатури.
; Програма використовує регістровий банк 3. Інші регістри не змінюється.
; Константf CLK_KHZ використовується транслятором при визначенні значень для часових затримок,
; і може складати 4000 - 24000 KHz
; Основна програма повинна містити:
; org    03h            ;INT0 vector (or 13h for INT1 vector)
;    jmp    RC5
; переривання на зовнішньому вході повинно бути дозволено, і налаштоване по спаду (переходу із 1 в 0)
; Номера систем для деяких видів побутової техніки наведені нижче:
; 0 - Телевізор (TV)
; 2 - Телетекст
; 3 - Відеоданні
; 4 - Відеопрогравач (VLP)
; 5 - Кассетний відеомагнітофон (VCR)
; 8 - Відео тюнер (Sat.TV)
; 9 - Відеокамера
; 16 - Аудіо предусилитель
; 17 - Тюнер
; 18 - Магнітофон
; 20 - Компакт-проигравач (CD)
; 21 - Програвач (LP)
; 29 - Освітлення

IR    equ    INT0        ; Або INT1
SYS    equ    0        ; Номер системи
CLK_KHZ equ    11059        ; Тактова чатота (від 4000 до 24000 кГц)
RC5_DF    equ    B.0        ; Флаг даних
RC5_PF    equ    B.1        ; Флаг періода
RC5_N     equ    (1+2*(CLK_KHZ/188))/2
RC5_N1     equ    (1+2*(CLK_KHZ/249))/2
RC5_N2     equ    (1+2*(CLK_KHZ/382))/2
RC5_NT     equ    (1+2*(CLK_KHZ/135))/2

cseg

RC5:                            ; Підпограма декодування протоколу RC5
    push    PSW
    push    ACC
    push    B
    setb    RS0             ; Вибір банку 3
    setb    RS1
    mov    R6,DPH          ; Зберігаємо DPTR
    mov    R5,DPL
    mov    B,#2            ; Установка флагів
    mov    R2,#2        ; ІнІциализацІя байта системи для прийняття 7 бит
    mov    R3,#4        ; ІнІциализацІя байта команди для прийняття 6 бит
    mov    R0,#1Ah        ; Завантаження адреси регіста R2 у банку 3
    mov    R1,#RC5_N       ; Завантаження величини тайм-ауту 2362 мкс
WLow:    jb    IR,WHigh    ; Очікування одиниці на вході INT0 на протязі тайм-ауту
    mov    R4,#16
    djnz    R4,$
    djnz    R1,WLow
    jmp    No_RC5        ; Помилка, закінчився тайм-аут
WHigh:    mov    R1,#RC5_N       ; Завантаження величини тайм-ауту 2362 мкс
Sample:    mov    R4,#8
    djnz    R4,$
Sam1:    mov    R4,#3           ; Точки входа у цикл Sam1 .. Sam3 потрібні для вирівнювання часу виконання різних гілок
    djnz    R4,$
Sam2:    mov    R4,#2
    djnz    R4,$
Sam3:    mov    C,IR        ; Опитування входу
    mov    ACC.0,C
    xrl    A,B
    jnb    ACC.0,Trans     ; Очікування зміни стану
    djnz    R1,Sample    ; Sample IR up to 2362uS
    jmp    No_RC5        ; Помилка, закінчився тайм-аут
Trans:    cpl    RC5_DF        ; Передача зафіксована
    mov    A,R1
    ADD    A,#-RC5_N1
    JC    No_RC5        ; Помилка, час <577.5uS
    mov    A,R1
    ADD    A,#-RC5_N2
    JC    TRANS1
    jb    RC5_PF,No_RC5    ; Помилка, час >1200uS
    jmp    STData
Trans1:    cpl    RC5_PF          ; Інвертування флага періода
    mov    R1,#RC5_N    ; Завантаження величини тайм-ауту 2362 мкс
    jb    RC5_PF,SAM1     ; Прийом біта не завершено, перехід на опитування
STData:    mov    C,RC5_DF
    mov    A,@R0
    rlc    A               ; Збереження біта даних
    mov    @R0,A
    mov    R1,#RC5_N       ; Завантаження величини тайм-ауту 2362 мкс
    jnc    SAM2            ; Прийом системи не завершено, перехід на опитування
    inc    R0              ; Система прийнята, початок прийому команди
    mov    A,R0
    mov    R1,#RC5_N       ; Завантаження величини тайм-ауту 2362 мкс
    jnb    ACC.2,SAM3      ; Прийом системи не завершено, перехід на опитування
    mov    R1,#RC5_N       ; Завантаження величини тайм-ауту мкс
WLW:    jb    IR,TRM        ; Останній біт, очікування одиниці на вході
    mov    R4,#16
    djnz    R4,$
    djnz    R1,WLW        ; Wait up to 2362uS for IR = 1
    jmp    No_RC5        ; Помилка, закінчився тайм-аут
TRM:    mov    R1,#RC5_NT      ; Завантаження величини тайм-ауту 3000 мкс
TERM:    jnb    IR,No_RC5    ; Помилка, якщо на вході зафіксовано низький рівень
    mov    R4,#16
    djnz    R4,$
    djnz    R1,TERM
    mov    A,R2        ; Номер системи і біт управління в R2
    anl    A,#1Fh          ; Виділення номеру системи
    cjne    A,#SYS,No_RC5    ; Перевірка номеру системи
    mov    DPTR,#RC_Tab    ; Завантаження адреси таблиці
    mov    A,R3            ; Завантаження коду команди
    movc    A,@A+DPTR    ; Перекодування
    mov    R3,A            ; Збереження нового коду
    mov    A,R2            ; Номер системи і біт управління у R2
    anl    A,#20h          ; Виділення біта управління
    rl    A
    rl    A               ; Зсув біту управління у розряд D7
    orl    A,R3            ; Суміщення коду команди і біта управління
    mov    R3,A            ; Збереження результату
    mov    A,R7
    anl    A,#40h          ; Виділення біта управління місцевої клавіатури
    orl    A,R3            ; Суміщення коду команди і бітов управління
    mov    R7,A            ; Збереження результату
No_RC5:                ; Очищення флагу переривань
IF    IR=INT0
    clr    IE0
ELSE
    clr    IE1
ENDIF
    mov    DPL,R5
    mov    DPH,R6
    pop    B
    pop    ACC
    pop    PSW
    reti
    
RC_Tab:                         ; Таблиця кодів на 64 коди, це макимальна кількість. Щоб мікроконтроллер не реагував на непотрібні кнопки - просто закоментуйте зайві коди
    db    00h        ; key code 000H, key function - 0
    db    01h        ; key code 001H, key function - 1
    db    02h        ; key code 002H, key function - 2
    db    03h        ; key code 003H, key function - 3
    db    04h        ; key code 004H, key function - 4
    db    05h        ; key code 005H, key function - 5
    db    06h        ; key code 006H, key function - 6
    db    07h        ; key code 007H, key function - 7
    db    08h        ; key code 008H, key function - 8
    db    09h        ; key code 009H, key function - 9
    db    0Ah        ; key code 00AH, key function - 10
    db    0Bh        ; key code 00BH, key function - none
    db    0Ch        ; key code 00CH, key function - Черговий режим
    db    0Dh        ; key code 00DH, key function - mute
    db    0Eh        ; key code 00EH, key function - none
    db    0Fh        ; key code 00FH, key function - Дисплей
    db    10h        ; key code 010H, key function - FFD або гучність +
    db    11h        ; key code 011H, key function - REW або гучність -
    db    12h        ; key code 012H, key function - none
    db    13h        ; key code 013H, key function - none
    db    14h        ; key code 014H, key function - none
    db    15h        ; key code 015H, key function - none
    db    16h        ; key code 016H, key function - none
    db    17h        ; key code 017H, key function - none
    db    18h        ; key code 018H, key function - none
    db    19h        ; key code 019H, key function - none
    db    1Ah        ; key code 01AH, key function - none
    db    1Bh        ; key code 01BH, key function - none
    db    1Ch        ; key code 01CH, key function - none
    db    1Dh        ; key code 01DH, key function - none
    db    1Eh        ; key code 01EH, key function - OPEN або пошук вперед
    db    1Fh        ; key code 01FH, key function - пошук назад
    db    20h        ; key code 020H, key function - SEEK FFD
    db    21h        ; key code 021H, key function - SEEK REW
    db    22h        ; key code 022H, key function - SYNCHRO
    db    23h        ; key code 023H, key function - none
    db    24h        ; key code 024H, key function - none
    db    25h        ; key code 025H, key function - none
    db    26h        ; key code 026H, key function - none
    db    27h        ; key code 027H, key function - none
    db    28h        ; key code 028H, key function - none
    db    29h        ; key code 029H, key function - none
    db    2Ah        ; key code 02AH, key function - none
    db    2Bh        ; key code 02BH, key function - none
    db    2Ch        ; key code 02CH, key function - none
    db    2Dh        ; key code 02DH, key function - none
    db    2Eh        ; key code 02EH, key function - none
    db    2Fh        ; key code 02FH, key function - none
    db    30h        ; key code 030H, key function - none
    db    31h        ; key code 031H, key function - none
    db    32h        ; key code 032H, key function - none
    db    33h        ; key code 033H, key function - none
    db    34h        ; key code 034H, key function - none
    db    35h        ; key code 035H, key function - none
    db    36h        ; key code 036H, key function - none
    db    37h        ; key code 037H, key function - none
    db    38h        ; key code 038H, key function - PLAY
    db    39h        ; key code 039H, key function - none
    db    3Ah        ; key code 03AH, key function - none
    db    3Bh        ; key code 03BH, key function - PAUSE
    db    3Ch        ; key code 03CH, key function - none
    db    3Dh        ; key code 03DH, key function - none
    db    3Eh        ; key code 03EH, key function - none
    db    3Fh        ; key code 03FH, key function - STOP
end
Прихований текст

Файл основного циклу - RC-5_Ridiko.asm

Out_Port    equ     P0

cseg    at      0
              
    jmp    Ini             ; Reset heandler
org    03h                ; INT0 heandler
    jmp    RC5

org     30h
include 'Lib_RC5_mod.asm'               ; Піключимо нашу біліотеку RC5

Ini:
    mov     SP,#21h                 ; Ініціалізуємо стек

    mov     TMOD,    #00000001b    ; Режим роботи таймерів Т0 и Т1
    ;                ||||||||
    ;                ||||||++-------Режим роботи Т0 (01 - 16-біт, 10 - 8-біт с автоперезавантаженням, 11 - 2 незалежних 8-бітних)
    ;                |||||+---------С/Т0: 0-таймер, 1-счетчик
    ;                ||||+----------Gate_Т0: 0-управління по TR0, 1-управління по TR0 і INT0
    ;                ||++-----------Режим работы Т1 (01 - 16-біт, 10 - 8-біт с автоперезавантаженням, 11 - неможливо використовувати)
    ;                |+-------------С/Т1: 0-таймер, 1-счетчик
    ;                +--------------Gate_Т1: 0-управління по TR1, 1-управління по TR1 і INT1

    mov     TCON,   #00000001b      ; Управління таймерами Т0 и Т1 і режимом роботи зовнішніх переривань
    ;                ||||||||
    ;                |||||||+-------IT0 - Тип INT0: 0-спрацювання по низкому рівню, 1-динамічне по спаду (переходу із 1 в 0)
    ;                ||||||+--------IE0 - Запрос переривання INT0 (при IT0=1 автоматично скидається у обробнику)
    ;                |||||+---------IT1 - Тип INT1: 0-спрацювання по низкому рівню, 1-динамічне по спаду (переходу із 1 в 0)
    ;                ||||+----------IE1 - Запрос переривання INT1 (при IT1=1 автоматично скидається у обробнику)
    ;                |||+-----------TR0 - Запуск Т0
    ;                ||+------------TF0 - Флаг переповнення Timer0
    ;                |+-------------TR1 - Запуск Т1
    ;                +--------------TF1 - Флаг переповнення Timer1

    mov     IE,     #10000001b      ; Переривання
    ;                ||||||||
    ;                |||||||+-------EX0 - дозвіл обробки переривань від зовнішнього входу INT0
    ;                ||||||+--------ЕТ0 - дозвіл обробки переривань від Timer0
    ;                |||||+---------EX1 - дозвіл обробки переривань від зовнішнього входу INT1
    ;                ||||+----------ЕТ1 - дозвіл обробки переривань від Timer1
    ;                |||+-----------ES  - дозвіл обробки переривань від посл.порта
    ;                ||+------------ET2 - дозвіл обробки переривань від Timer2
    ;                |+-------------Резерв, не використовується
    ;                +--------------ЕА  - дозвіл обробки усіх переривань

Main:

    mov     Out_Port,1Fh
    jmp     Main
end

По схемі піключеня. Крім мікроконтроллера та світлодіодів на порт, який буде виводити код натиснутої кнопки, необхідний власне сам інфрачервоний приймач (TSOP), на частоту 36-38 кГц (залежить від Вашого пульту). Це можуть бути наприклад TSOP4836, або TSOP1838, можна навіть взяти приймач від якогось зламаного телевізора. Якщо Ви впевнені що ваш пульт підтримує цей протокол, але мікроконтроллер ніяк на нього не реагує, спробуйте змінити номер системи. Можна навіть створити універсальний приймач під пульти декількох систем, просто необхідно "х" джамперів, що будуть задавити номер системи. Тоді кількість підтримуваних систем буде складати 2 в степені "х". Єдине обмеження - одночасно можуть приматися команди лише 1 системи (до 64 команд).

Post's attachments

RC-5_AT89S52.JPG 91.12 kb, 307 downloads since 2015-12-01 

Подякували: 0xDADA11C7, vsprosto, leofun014

19 Востаннє редагувалося MCS-51 (09.01.2016 16:59:48)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Всіх із Новим Роком та Різдвом !!! Давненько нічого не писав, а розвиток то продовжується)) Думає доречно буде розмістити щось святкове. Варто це було зробити ще до свят, щоб бажаючі (якщо такі знайдуться) могли б повторити. Але краще пізно, ніж ніколи, може на наступний Новий рік комусь згодиться. Отож розміщую не власну розробку, а чесно здерту із книжки В.В.Сташинова та А.В.Урусова "Проектирование цифровых устройств на однокристальных микроконтроллерах" програму відтворення новорічної (різдвяної) мелодії "Jingle Bells".
Трохи про програму.
Кожна нота кодується 2 байтами: байтом довжини, і байтом частоти звуку. Такий спосіб кодування доволяє використовувати символічні імена нот (la,si,do,re,mi,fa,sol), попередньо присвоївши їм відповідні значення частот, що генеруються. При цьому кожен елемент нотного запису буде містити 2 байти. Перший байт D буде виражати довжину ноти. Так як довжина задається правильними дробами (1/2,1/4,1/8 і 1/16), то при кодуванні зручно вказувати тільки знаменник дробу. Перетворення знаменника у кількість шістнадцятих долей секунди, як цього вимагає процедура Sound, легко виконується діленням 16 на D, за допомогою команди DIV AB. Ви можете легко відтворити будь-яку свою мелодію, записавши у масив Song власні ноти замість тих що є. Також раджу змайструвати хоча б примітивний RC-фільтр для динаміка, щоб покращити якість звуку, вона і так буде не дуже висока (любителі midi-звуку оцінять).

Прихований текст
Buzzer  equ     P3.5

la      equ     0
la_     equ     1
si      equ     2
do      equ     3
do_     equ     4
re      equ     5
re_     equ     6
mi      equ     7
fa      equ     8
fa_     equ     9
sol     equ     0Ah
sol_    equ     0Bh

cseg    at      0

Main:
    mov     DPTR,#Song              ; Завантаження початкової адреси таблиці Song у Флеш-памяті
    mov     TMOD,#11h               ; Інціалізація T/C0 и T/C1
Music:
        clr     A
    movc    A,@A+DPTR               ; Завантаження коду ноти з таблиці Song у Флеш-памяті
    jz      Exit_                   ; Вихід, якщо код рівний 0
    mov     B,A                     ; Завантаження коду ноти
    mov     A,#16                   ; Занесемо у акумулятор число 16, щоб потім визначити кількість шістнадцятих долей секунди (довжину)
    div     AB                      ; за допомогою ділення 16 на D
    mov     r7,A                    ; Отриману довжину ноти занесемо у R7
    inc     DPTR                    ; Перехід до коду ноти
    clr     A
    movc    A,@A+DPTR               ; Повторне завантаження коду ноти
    rl      A                       ; Подвоєння коду ноти, тому що довжина півперіоду ноти задається 2 байтами
    mov     r6,A
Sound:                           ; Процедура генерації однієї ноти
    clr     TCON.4                  ; Зупинимо  Timer0, інціалізація
    mov     TL0,#low(not(62500)+1)
    mov     TH0,#high(not(62500)+1)
    setb    TCON.4                  ; Запустимо Timer0
S0:
    cpl     Buzzer                  ; Інвертуємо звуковой вихід
    jbc     TCON.5,S2               ; Перевірка флагу переповнення T/C0
                            ; Затримкаа на 1/2 періода
    clr     TCON.6                  ; Зупинимо Timer1
    mov     A,r6                    ; Сформуємо у акумуляторі різницю адрес старшого байта півперіоду ноти і команди звернення до таблиці Tab
    add     A,#(Tab-($+1))
    movc    A,@A+PC                 ; Звернення до таблиці Tab за старшим байтом півперіоду ноти
    mov     TH1,A                   ; Завантаження старшого байта у T/C1
    mov     A,r6
    add     A,#(Tab-($+2))
    movc    A,@A+PC                 ; Звернення до таблиці Tab за молодшим байтом півперіоду ноти
    mov     TL1,A                   ; Завантаження молодшого байту у T/C1
    setb    TCON.6                  ; Запустимо Timer1
S1:
    jbc     TCON.7,S0               ; Перевірка флагу переповнення T/C1
    jmp     S1
S2:                             ; Декремент лічильника довжини звуку
    djnz    r7,Sound                ; Повтор D, поки не дорівнює 0
    inc     DPTR                    ; Інкремент вказівника кодів
    jmp     Music            ; Перехід до початку процедури
Tab:
    dw      not(1136-16)             ; ля1   , 440 Гц
    dw      not(1073-16)             ; ля1#  , 466 Гц
    dw      not(1012-16)             ; сі1,  , 494 Гц
    dw      not(956-16)              ; до2   , 523 Гц
    dw      not(902-16)              ; до2#  , 554 Гц
    dw      not(850-16)              ; ре2   , 588 Гц
    dw      not(804-16)              ; ре2#  , 622 Гц
    dw      not(758-16)              ; мі2   , 660 Гц
    dw      not(716-16)              ; фа2   , 698 Гц
    dw      not(677-16)              ; фа2#  , 740 Гц
    dw      not(638-16)              ; соль2 , 784 Гц
    dw      not(602-16)              ; соль2#, 831 Гц
Exit_:                              ; Вихід з процедури
    jmp     Main                ; Заново проиграємо мелодію

Song:                            ; Таблиця мелодії
    db      4,mi,4,mi,2,mi,4,mi,4,mi,2,mi
    db      4,mi,4,sol,4,do,4,re,1,mi
    db      4,fa,4,fa,4,fa,4,fa,4,fa
    db      4,mi,4,mi,4,mi,4,mi
    db      4,re,4,re,4,mi,2,re,2,sol
    db      4,mi,4,mi,2,mi,4,mi,4,mi,2,mi
    db      4,mi,4,sol,4,do,4,re,1,mi
    db      4,fa,4,fa,4,fa,4,fa,4,fa
    db      4,mi,4,mi,4,mi,4,sol
    db      4,fa,4,mi,4,re,1,do
    db      0            ; Кінець
end
Подякували: raxp, leofun01, 0xDADA11C74

20 Востаннє редагувалося MCS-51 (07.02.2016 21:10:52)

Re: Програмування мікроконтролерів MCS-51 (Intel 8051 сумісних)

Доброго часу доби шановні форумчани. Знову я зі своїми мамонтами. Чи може динозаврами... Коротше продовжую вивчення. Успіхи є, просто не про все встигаю написати. Бо якщо писати про все, то і часу на успіхи не залишиться)) Значить один пост назад (насправді то було дуже давно) я зібрав систему розпізнавання кодів ІЧ пульта стандарту RC-5. І написав, що у мене зіпсувався настрій, коли я дізнався, що лише 2 наявних у мене пульти працюють у цьому стандарті. Переважна більшість сучасних пультів працюють у стандарті NEC. Почав я шукати готову бібліоткеу для пультів такого стандарту. І не знайшов. Точніше воно є. Але на Сі. Або не під мій МК. Або написане лівою ногою без нормальних коментарів, щоб я зрозумів як воно працює. А користуватися бібліотеками без їх розуміння я категорично відмовляюся. Я засмутився. Але з іншого боку це стало викликом. Я вирішив написати свою бібліотеку, і написав. Вона працює)) Допустив лише 1 банальну помилку, на яку мені люб'язно вказали на одному радіофорумі. При написанні біліотеки користувався одним єдиним малюнком, який прикріплюю. На ньому чітко видно усі вимоги до часових інтервалів. З 4 прийнятих байт я використовую лише 1 (байт команди), але вам ніщо не заважає зробити алгоритм набагато складнішим, я ж прагнув простоти.
Головний цикл майже ідентичний попередньому:

Прихований текст

Main.asm

Out_Port    equ     P0              ; На порту Р0 - светодіоди індикації

cseg    at      0
    jmp    Ini            ; Reset heandler
org    03h
    jmp    NEC            ; INT0 heandler

org     30h
include 'NEC.asm'

Ini:
    mov     SP,#Stack        ; Ініціалізуємо стек


    mov     TMOD,    #00000001b    ; Режим роботи таймерів Т0 и Т1
    ;                ||||||||
    ;                ||||||++-------Режим роботи Т0 (01 - 16-біт, 10 - 8-біт с автоперезавантаженням, 11 - 2 незалежних 8-бітних)
    ;                |||||+---------С/Т0: 0-таймер, 1-счетчик
    ;                ||||+----------Gate_Т0: 0-управління по TR0, 1-управління по TR0 і INT0
    ;                ||++-----------Режим работы Т1 (01 - 16-біт, 10 - 8-біт с автоперезавантаженням, 11 - неможливо використовувати)
    ;                |+-------------С/Т1: 0-таймер, 1-счетчик
        ;                +--------------Gate_Т1: 0-управління по TR1, 1-управління по TR1 і INT1

    mov     TCON,   #00000001b      ; Управління таймерами Т0 и Т1 і режимом роботи зовнішніх переривань
    ;                ||||||||
    ;                |||||||+-------IT0 - Тип INT0: 0-спрацювання по низкому рівню, 1-динамічне по спаду (переходу із 1 в 0)
    ;                ||||||+--------IE0 - Запрос переривання INT0 (при IT0=1 автоматично скидається у обробнику)
    ;                |||||+---------IT1 - Тип INT1: 0-спрацювання по низкому рівню, 1-динамічне по спаду (переходу із 1 в 0)
    ;                ||||+----------IE1 - Запрос переривання INT1 (при IT1=1 автоматично скидається у обробнику)
    ;                |||+-----------TR0 - Запуск Т0
    ;                ||+------------TF0 - Флаг переповнення Timer0
    ;                |+-------------TR1 - Запуск Т1
    ;                +--------------TF1 - Флаг переповнення Timer1

    mov     IE,     #10000001b      ; Переривання
    ;                ||||||||
    ;                |||||||+-------EX0 - дозвіл обробки переривань від зовнішнього входу INT0
    ;                ||||||+--------ЕТ0 - дозвіл обробки переривань від Timer0
    ;                |||||+---------EX1 - дозвіл обробки переривань від зовнішнього входу INT1
    ;                ||||+----------ЕТ1 - дозвіл обробки переривань від Timer1
    ;                |||+-----------ES  - дозвіл обробки переривань від посл.порта
    ;                ||+------------ET2 - дозвіл обробки переривань від Timer2
    ;                |+-------------Резерв, не використовується
    ;                +--------------ЕА  - дозвіл обробки усіх переривань
    
    mov    ShiftCounter,#8        ; Занесемо значенння у лічильник зсуву

Main:
    mov     Out_Port,ByteCommand    ; Виведемо у порт байт прийнятої команди
    jmp     Main            ; Зациклимося
end

При написанні бібліотки скористався прийомами з бібліотеки Рідіко, а саме директивою умовної компіляції та задання числових значень за допомогою формул. Виявляється це дуже зручно. Тепер для роботи з іншим входом переривання, або іншої частоти МК, потрібно лише змінити у "шапці" пару значень і перекомпілювати))
Файл бібліотеки:

Прихований текст

NEC.asm

IR        equ    INT0            ; Выбираємо INTO чи INT1 ніжку для підключення виходу фотоприймача
Frequency    equ    11059            ; Тактова частота мікроконтролера у кГц, ввести потрібне значення і скомпілювати, інші значення розрахуються автоматично
StartBitLow    equ    46*Frequency/12000    ; 46 - старший байт таймера (у десятковій системі) для фіксації мінімальної довжини старт-біта (13 мс)
StartBitHigh    equ    58*Frequency/12000    ; 58 - старший байт таймера (у десятковій системі) для фіксації максимальной довжини старт-біта (14 мс)
TimeBit        equ    6*Frequency/12000    ; 6 - старший байт таймера (у десятковій системі) для порогового значення біта даних (приблизно 1600 мкс, усе що менше - 0, усе что більше - 1)

dseg    at    8h
ShiftCounter:                    ; Лічильник 8 біт (1 байт)
    ds    1
Symbol:                        ; У цій змінній зявляється байт після кожного визову підрограми Receive
    ds    1
ByteAdress:                    ; Байт адреси пристрою
    ds    1
InvByteAdress:                    ; Інвертований байт адреси пристрою
    ds    1
ByteCommand:                    ; Байт прийнятої команди
    ds    1
InvByteCommand:                    ; Інвертований байт прийнятої команди
    ds    1
Stack:                        ; А далі піде стек

cseg

NEC:                        ; Процедура обробки вхідного сигналу, запускається по переходу рівня з 1 на 0 на ніжке зовнішнього переривання
    mov    TH0,#0                ; Обнулимо Timer0, нам рахувати довжину старт-біта
    mov    TL0,#0
    setb    TR0                ; Запустимо Timer0
    jb    TF0,Error            ; При переполненні таймера виходим з циклу
    jnb    IR,$-3                ; Чекаємо кінця низького рівня старт-біта
                        ; Дочекались зміни рівня с низького на високий
    jb    TF0,Error            ; При переполненні таймера виходим з циклу
    jb    IR,$-3                ; Чекаємо кінця високого рівня старт-біта
    clr     TR0                      ; Зупинимо Timer0
    mov     A,TH0                ; Завантажимо старший байт Timer0 в Акумулятор
    cjne    A,#StartBitlow,Compare_1    ; Порівняємо з мінімально допустимою довжиною старт-біта (13 мс)
    jmp     Error                           ; Рівно, значить неправильний старт-біт, значить це не наш пульт
Compare_1:
    jc      Error                           ; Менше мінімальної довжини, значить неправильний старт-біт, значить це не наш пульт
                        ; Більше мінімальної довжини, продовжимо порівняння
    mov     A,TH0                           ; Знову завантажимо старший байт Timer0 в Акумулятор
    cjne    A,#StartBitHigh,Compare_2    ; Порівняємо з максимально допустимою довжиною старт-біта (14 мс)
        jmp     Error                           ; Рівно, значить неправильний старт-біт, значить це не наш пульт
Compare_2:
    jnc    Error                ; Більше максимальної довжини, значить неправильний старт-біт, значить це не наш пульт
                        ; Старт-бит правильний, починаємо прийом інформації
    call    Receive                         ; Приймаємо 1 байт
    mov     ByteAdress,Symbol        ; 1 байт - адреса пристрою
    call    Receive                ; Приймаємо 2 байт
    mov     InvByteAdress,Symbol        ; 2 байт - інверсний байт адреси пристрою
    call    Receive                         ; Приймаємо 3 байт
    mov     ByteCommand,Symbol              ; 3 байт - байт команди
    call    Receive                         ; Приймаємо 4 байт
    mov     InvByteCommand,Symbol           ; 4 байт - інверсний байт команди

Error:                                          ; Якщо ми прийшли сюди джампом, значить була помилка
    clr     TF0                             ; Очистимо флаг переповнення таймера
if    IR=INT0                                 ; Очистимо флаг зовнішньогого переривання
    clr    IE0
else
    clr    IE1
endif
    reti
    
Receive:                                        ; Підпрограма прийому 1 байта (8 біт)
        mov    TH0,#0                          ; Обнулимо Timer0, нам заново рахувати довжину бітів
    mov    TL0,#0
    setb    TR0                      ; Запустимо Timer0
    jb      TF0,Error                       ; При переполненні таймера - закінчуємо прийом
    jnb     IR,$-3                ; Чекаємо кінця низького рівня біта даних
                        ; Дочекались зміни рівня з низького на високий
    jb      TF0,Error                       ; При переполненні таймера - закінчуємо прийом
        jb    IR,$-3                ; Чекаємо кінця високого рівня біта даних
    clr     TR0                      ; Зупинимо Timer0
    mov     A,TH0                ; Завантажимо старший байт Timer0
    cjne    A,#TimeBit,Compare_3        ; Перевіримо
    jmp     Error                           ; Порогове значення, скоріше всього помилка
Compare_3:
    jc      Log_0                           ; Якщо був встановлений флаг С, значить A менше за TimeBit
log_1:                                          ; Флаг С не був встановлений, значить А більше за TimeBit
    setb    C                               ; Це лог. 1
    jmp     Zsuv                ; Йдемо складати біт у байт
Log_0:
    clr     C                               ; Це лог. 0
Zsuv:
    mov     A,Symbol                        ; Закинемо значення у Асс (після 8 здвигів, туди 8 раз засунеться стан флагу С, и ми на виході отримаємо наш байт)
    rrc     A                               ; Зссунемо вправо, тому що команди передаються молодшим бітом вперед
    mov    Symbol,A                        ; І закинемо назад
    djnz    ShiftCounter,Receive            ; І йдемо принимати наступний біт, поки не примемо усі 8 біт
    mov    ShiftCounter,#8            ; Приняли усі 8 біт, заново занесемо значення у лічильник зсуву
    ret
end
Post's attachments

строение команд ИК пульта.gif 61.98 kb, 310 downloads since 2016-02-07 

Подякували: 0xDADA11C7, raxp, leofun013