Spoiler : this article does not squeeze nostalgic tears.
Having decided not so long ago to get acquainted closer (after the Arduino) with microcontrollers in general and with ATmega microcontrollers in particular, I quickly realized that just reading about MK, run the code in the emulator, “blinking LED” - this is somehow not the case. So I decided "Gash" any - even if useless - project, and deal with everything already along the way. Microprocessor labs came to mind and the training stand on which these works were performed - the so-called educational microprocessor set (UMK), which we called simply “Umka”.
About CMD
So, if you do not go into details (if you go - then the links below), then "Umka" is a microcomputer based on microprocessor K580IK80A, produced in the 80s of the last century (I was also lucky to work with him in the not-quite-distant 2009) is a weighty such box. More information about the story "Umok" and so on can be found on the second link below. This is how it looks. ')
(The photo is brazenly taken from the first link below; more photos are from the same link)
What is there: indication of data buses and addresses, indication of the processor status register, display to display the contents of memory cells and input data, input buttons data. The two columns of buttons to the left are the so-called directives sewn into “Umka” “Monitor” programs - they became the goal of my project (directives, not buttons). Briefly about directives. There is nothing special. "P" - view the contents of the memory cell: enter the address of the cell, see what is there lies, if desired - write another value. "WG" - view the contents of the registers: similar to viewing the contents of the cells. In my "Version" of this directive is missing: it is not particularly necessary, because in ATmega RAM, general registers and peripheral registers are located in address space, and it is assumed that they can be accessed through Directive "P". “CS” - calculation of the checksum of the data array: enter the address of the beginning of the array, the final address of the array, the sum of the contents of the cells of the array is displayed. “” - filling the memory array with a constant: enter the starting address, enter the ending address of the array, enter the variable - the cells of the array are filled with this variable. “PM” - relocation of the memory array: enter the starting address of the array being moved memory, enter its final address, enter the starting address of the array - array moves to the memory area starting with the entered allocation address. The directive "ST" - the implementation of the user program - I missed, more on that below. The “VP” button is the execution of the directive, the "_" button separates the variables as they are entered, "SB" - the button reset. Read more about entering and executing directives here.
The aim of my project was to create a device capable of fulfilling these directives and display information on the display, a kind of lite-version of "Umki" (well, very, very lite). What was used in the project: 1. The ATmega16 microcontroller. The program is written in assembly language in AVRStudio. 2. Matrix keyboard for entering values. I found the code for working with the keyboard here (thanks to the author), then just rewrote it in assembler. Since RAM ATmega16 limited to the address $ 045F, then the last three digits are entered. By the way, data / addresses are entered and displayed in hexadecimal format. 3. LCD display 16x2 on the controller HD44780. Understanding the display helped these Two articles - here and here (thanks to the authors), the datasheet helped to initialize the display correctly. 4. Simple buttons for directives. Another button was also added - “Enter” (on photo it with an arrow): needed to confirm the input of each digit of the address or data - a kind of protection from the chatter of the contacts of the matrix keyboard (without which it is quite possible it was a cost, but a clever idea, as they say, comes afterwards).
What is the result
It was possible to achieve everything that was planned: you can view and change contents of RAM and MK registers, write a constant to an array of cells, count checksum of the array, move the array of cells (here is the entertainment * sarcastic *); writing / moving an array somewhere for $ 0440 (approximately) makes a regular chaos in the stack area. You can perform as many as six items of the first laboratory work from Workshop on the link above. Judging by the information found, the “Monitor” program, wired into “Umka”, occupies 1 Kb. One of the tasks was to put the program in 1 KB, which was successfully (even with reserve), but because of the LCD-display and the desire to make the code more informative almost doubled. This “Umka” (professional edition) allowed you to write your program on microprocessor assembler K580IK80A, recording in memory the codes of the relevant commands, and then perform it either stepwise or cyclically. I do not know how it was implemented in Umka, and the solution does not seem obvious to me, so this option in there is no project (for a start, however, the directives were enough for me), I will gladly accept proposals for the implementation of this. For this, I affectionately nicknamed the resulting device "Wit."
This is the code
.include "m16def.inc" .device ATmega16 .def count = r18; r18 - counter .def mode = r19; r19 - signs of modes .def buf = r25; to enter values .def rLCD = r22; for working with LCD .def rKey = r20; keyboard and trivial values
; LCD commands .equ off = 0b00001000; off .equ clrSc = 0b00000001; clear .equ config = 0b00111000; 8bit, 2 lines .equ incr = 0b00000110; addr + (increase address, static screen) .equ on = 0b00001100; on .equ right = 0b00010100; right shift .qu down = 0b11000000; go to the second line .qu up = 0b10000000; on the first line .equitor = 0b00001111; flashing cursor .equ noCursor = 0b00001100 .equ left = 0b00010000; to the left
; LCD data (port D) .equ _dp = DDRD .equ _dpo = PORTD .equ _dpi = PIND
; LCD control (port C) .equ _cp = DDRC .equ _cpo = PORTC .equ rs = 0 .equ rw = 1 .equ e = 7
; keyboard (port A) .equ _mp = DDRA .equ _mpi = PINA .equ _mpo = PORTA
; directives (port B) .equ BUT = PINB .equ BUTddr = DDRB .equ BUTp = PORTB .equ P = 0; button P (view / change the contents of memory) .equ quit = 1; output . equ exe = 2; VP button (execution) .equ enter = 3; enter button .equ space = 4; "_" button .ququ CF = 5; button ZK (filling constant) .equ CS = 6; COP button (checksum) .equ AM = 7; PM button (array moving) ; T flag - data entry mode
; ======================= Macro to poll the matrix keyboard ==================== .macro matrix rcall _exit ldi rKey, 0 ldi r16, 0xff; load units in r16 cbi _mpo, @ 0; clear the bit corresponding to the column out _mpo, r16; units in portd in r16, _mpi; read port D data register cpi r16, @ 1 breq ein cpi r16, @ 2 breq zwei cpi r16, @ 3 breq drei cpi r16, @ 4 breq vier sbi _mpo, @ 0; set the bit corresponding to the column (return to the original state) rjmp @ 9; if none of the buttons in the column were pressed, ; then go to the label - the macro with the parameters of the next column or at the beginning of the survey
ein: ldi rKey, @ 5; loading the rKey value corresponding to the button pressed ldi rLCD, @ 5; load the same value for display rcall _BF rcall _charInput sbi _mpo, @ 0; set the bit corresponding to the column (return to the original state) rjmp _check; go to view label
; ========================== waiting to press the enter button =================== = _view: brts _input; go to input if the T-flag is set (i.e. data entry mode) rcall _exit sbic BUT, enter; if the enter button is not pressed, rjmp _view; then we close the loop and wait for pressing the EXIT, reset or input buttons cpi count, 1; comparison of the counter value: if 1, breq one; the first digit is entered cpi count, 2; if 2, breq two; then the second is introduced cpi count, 3; if 3, breq three; then the last digit is entered rjmp _view
; enter the first digit of the address one: mov xh, rKey inc count rjmp scan; transition to survey mat. clav
; Enter the second digit of the address two: mov xl, rKey swap xl inc count rjmp scan
; enter the third digit of the address three: rcall _noCursor; the cursor is not needed add xl, rKey inc count rjmp finish; after entering the third digit, go to finish ; ================================================ =================================
; ================================== input mode ============= =================== _input: rcall _exit sbic BUT, enter; if the input button is pressed, then jump over to compare the counter rjmp _input cpi count, 1; if the counter is 1, then the first digit is entered breq first cpi count, 2; if 2, then the second breq second rjmp _input
first :; input first digit mov buf, rKey swap buf inc count rjmp scan
second :; enter second digit rcall _noCursor add buf, rKey inc count rjmp _waitFor ; ================================================ ================================
; ======================= waiting after entering the third digit ===================== = finish :; go here after entering the third digit brts link; if the T-flag is set, then this is the data entry mode, go to scan rcall _exit sbis BUT, exe; EXIT button rjmp view; go to view if exp. sbic BUT, space; "_" button rjmp finish ldi count, 1; reset the counter set; set the T-flag in the status register ; display “WRITE DATA” in the first line rcall _clear rcall _labelWrite rcall _labelSpace rcall _labelData ; go to the second one rcall _down ; set the cursor rcall _cursor rjmp finish link: rjmp scan ; ================================================= ==========================
; =============== viewing the contents of a cell (after clicking the EX button -> here) view: ; output of the inscription "BROWSE XXXX" rcall _clear rcall _browse; Brose rcall _labelSpace; _ mov rLCD, xh; first XX rcall _charFromMemory mov rLCD, xl; second XX rcall _charFromMemory ; displaying the contents of a memory cell rcall _noCursor rcall _down ld rLCD, X + rcall _charFromMemory view1: rcall _exit ldi count, 1 sbic BUT, space; if the "_" button is pressed, then go to the cell with the address 1 more rjmp view1 rcall _delay1 rjmp view ; ================================================= ===========================
; ========================= Writing a value to a memory cell ==================== === input: st X, buf clt; clear T-flag after data entry mode is completed rjmp go ; ================================================ =================================
; ========== waiting for clicking EX. or "_" to continue typing =============== _waitFor :; after entering the second digit (data) - here sbrc mode, 0 rjmp _WC rcall _exit sbis BUT, exe; if EXIT is pressed, then go to input rjmp input sbic BUT, space; if "_" is pressed, rjmp _waitFor; then jump over it, st X +, buf; write the data entered into memory ldi count, 1; reset the counter rcall _clear rcall _labelWrite rcall _labelSpace rcall _labelData rcall _down rjmp scan
; =============================== LC mode (filling with a constant) ============= ===== _const: brts to_input rcall _exit sbic BUT, enter; if the enter button is not pressed, rjmp _const; then we close the loop and wait for pressing the EXIT, reset or input buttons cpi count, 1; comparison of the counter value: if 1, breq _one; the first digit is entered cpi count, 2; if 2, breq _two; then the second is introduced cpi count, 3; if 3, breq _three; then the last digit is entered rjmp _const; in cycle to_input: rjmp _input
; enter the first digit of the first address _one: sbrc mode, 4; if the 2nd bit is set (sign of entering the third address), rjmp _ad31; <- move here sbrc mode, 1; if the 1st bit in r19 is not set (sign of entering the second address), rjmp inpConst1; then we skip it mov xh, rKey inc count rjmp scan ; enter the first digit of the second address inpConst1: mov yh, rKey inc count rjmp scan
; enter the second digit of the first address _two: sbrc mode, 4; if the 2nd bit is set (sign of entering the third address), rjmp _ad32; <- move here sbrc mode, 1; if the 1st bit is set in r19 (sign of entering the second address), rjmp inpConts2; then go to inpConst1 mov xl, rKey swap xl inc count rjmp scan ; enter the second digit of the second address inpConts2: mov yl, rKey swap yl inc count rjmp scan
; enter the third digit of the first address _three: sbrc mode, 4; if the 2nd bit is set (sign of entering the third address), rjmp _ad33; <- move here sbrc mode, 1 rjmp inpConst3 rcall _noCursor add xl, rKey; rKey -> to the junior tetra mla. byte reg. X pairs inc count rjmp constEnd; after entering the third axis, go to constEnd ; enter the third digit of the second address inpConst3: rcall _noCursor add yl, rKey inc count rjmp constEnd
constEnd :; go here after entering the third digit brts _link; if the T-flag is set, then this is the data entry mode, go to scan sbrc mode, 1; if the sign of entering the second address is set, rjmp _ad2; then here sbrc mode, 2; if the “COP” feature is set, rjmp _ifCheckSum; then here sbrc mode, 3; if the sign of the PM mode is set, rjmp _ifArray; then here rcall _exit sbic BUT, space; if the "_" button is pressed, rjmp constEnd; then jump over it, ldi count, 1; reset the counter sbr mode, 3; in r19 is now 0b00000011 (sign of entering the second address in the LC) ; refining the screen content ; address2 rcall _down rcall _labelAddr rcall _labelTwo rcall _colon rcall _labelZero rcall _labelSpace rcall _labelSpace rcall _labelSpace rcall _left rcall _left rcall _left rjmp scan
_ad2 :; go here after entering the second address sbrc mode, 2; if the sign of "COP" is set, then go rjmp _toCheckSum sbrc mode, 3; if the sign "PM" is set, rjmp _toArray; <- here rcall _exit sbic BUT, space; if the "_" button is pressed, rjmp _ad2; then miss it ldi count, 1; reset the counter set; and set the sign of data entry ; refining the screen content ; const rcall _down rcall _labelConst rcall _colon rcall _labelSpace rcall _labelSpace rcall _labelSpace rcall _labelSpace rcall _labelSpace rcall _labelSpace rcall _labelSpace rcall _left rcall _left rcall _left rcall _left rcall _left rcall _left rcall _left rjmp constEnd; -> to cycle
_link: rjmp scan
_ifCheckSum :; A branch from “” to “”, if the 2nd bit in mode is set sbic BUT, space rjmp _ifCheckSum ldi count, 1 ; refining the screen contents: ; address2 rcall _down rcall _labelAddr rcall _labelTwo rcall _colon rcall _labelZero rcall _labelSpace rcall _labelSpace rcall _labelSpace rcall _left rcall _left rcall _left sbr mode, 7; sign of entering the second address "KS" rjmp scan
_toCheckSum :; on performance of "KS" rcall _exit sbic BUT, exe; if “EX” is pressed, then go to “COP” rjmp _toCheckSum ldi rKey, 0x00; pre-zero the registers used ldi buf, 0x00 rjmp _SUM
_toArray :; transition after entering the second address ldi count, 1 sbr mode, 27; 0b00011011 -> r19 - sign of entering the third address rcall _exit sbic BUT, space rjmp _toArray ; refining the screen content ; address3 rcall _down rcall _labelAddr rcall _labelThree rcall _colon rcall _labelZero rcall _labelSpace rcall _labelSpace rcall _labelSpace rcall _left rcall _left rcall _left rjmp scan
; first digit of the third address (PM) _ad31: mov zh, rKey inc count rjmp scan
; second digit of the first address _ad32: mov zl, rKey swap zl inc count rjmp scan
; third digit of the third address _ad33: rcall _noCursor add zl, rKey inc count rjmp _arrayEnd
; waiting for pressing the VI after entering the third address _arrayEnd: rcall _exit sbic BUT, exe rjmp _arrayEnd ldi buf, 0x00; pre-zeroing buf rjmp _execArray
; ============================== ZK implementation ================= ================ _execConst: ; first comparison _ccp1 :; yh> xh? cp yh, xh brlo _errConst; if yh <xh breq _eq1; if yh = xh rjmp _ccp2
_eq1 :; if yh = xh cp yl, xl; yl> xl? brlo _errConst; if yl <xl
; if the first number is less than the second, ; then go to the second comparison _ccp2 :; if yh> xh cp yh, xh; yh> xh? breq _ccp22; if yh = xh st X +, buf rjmp _ccp2
_ccp22 :; if yh = xh cp yl, xl; yl> xl? brlo _end; if xl> yl st X +, buf rjmp _ccp22
_errConst :; “ERROR” if the first number is more than the second rcall _clear rcall _labelWrite rcall _labelSpace rcall _labelConst rcall _point rcall _down rcall _noCursor rcall _labelErr rjmp _wait
_end :; output rjmp go
; ============== calculation of the checksum of the array ======================= _SUM: ; first comparison _scp1 :; yh> xh? cp yh, xh brlo _errSum; if yh <xh breq _eq2; if yh = xh rjmp _scp2
_eq2 :; if yh = xh cp yl, xl; yl> xl? brlo _errSum; if yl <xl
; if the first number is less than the second, ; then go to the second comparison _scp2 :; if yh> xh cp yh, xh; yh> xh? breq _scp22; if yh = xh rcall _exCS brcs _overflow rjmp _scp2
_errSum :; “ERROR” if the first number is more than the second rcall _clear rcall _labelSum rcall _down rcall _noCursor rcall _labelErr rjmp _wait
_overflow :; when exceeding COP 255 - here ; dumping of flags of transfer and half transfer (just in case) clh clc ldi rKey, 0xff; if the COP is greater than 0xFF, then FF is displayed on the screen. rjmp _showSum
_exCS :; subroutine implementation of the COP ld buf, X + add rKey, buf ; rcall _cpixl ret
; ============================ Array Motion ==================== =========== _execArray: ; first comparison _acp1 :; yh> xh? cp yh, xh brlo _errArr; if yh <xh breq _eq3; if yh = xh rjmp _acp2
_eq3 :; if yh = xh cp yl, xl; yl> xl? brlo _errArr; if yl <xl
; if the first number is less than the second, ; then go to the second comparison _acp2 :; if yh> xh cp yh, xh; yh> xh? breq _acp22; if yh = xh rcall _exMA rjmp _acp2
_acp22 :; if yh = xh cp yl, xl; yl> xl? brlo _end; if xl> yl rcall _exMA rjmp _acp22
_errArr :; “ERROR” if the first number is more than the second rcall _clear rcall _mArray rcall _down rcall _noCursor rcall _labelErr rjmp _wait
_exMA :; subroutine execution PM ld rKey, X + st Z +, rKey ret ; ================================================= =================
_exit: sbis BUT, quit; if the exit button is pressed, rjmp go; then exit from go mode ret ; ================================================= ==================
rcall _delay1 ldi rLCD, incr; increase of the address, the screen is static rcall _cWrite ret
; busy bit check _BF: rcall _portIn; input data port rcall _modeB; command reading mode _loop: sbi _cpo, e rcall _delay cbi _cpo, e in r24, _dpi; read data bus andi r24, 0x80; check 7th bit brne _loop ret
; delay _delay: ldi r23, 20 _del: dec r23 brne _del ret
// more delay _delay1: ldi r24, 0xff; 255 _d: ldi r23, 0xff; 255 _cmp_d: dec r23 brne _cmp_d dec r24 brne _cmp_d ret
; team record _cWrite: rcall _modeC rcall _portOut out _dpo, rLCD rcall _delay cbi _cpo, e ret
; data record _dWrite: rcall _modeD rcall _portOut out _dpo, rLCD rcall _delay cbi _cpo, e ret
; data output port _portOut: ldi r23, 0xff out _dp, r23 ret
; data port input with a suspender _portIn: ldi r23, 0x00 out _dp, r23 ldi r23, 0xff out _dpo, r23 ret
; command record mode _modeC: cbi _cpo, rw cbi _cpo, rs sbi _cpo, e ret
; data recording mode _modeD: sbi _cpo, rs cbi _cpo, rw sbi _cpo, e ret
; standby BF _modeB: sbi _cpo, rw; reading cbi _cpo, rs; teams ret
; generation of character code when entering addresses and data _charInput: cpi rLCD, 0x0a; compare with 10 brge _grt; if more, then go to _grtA rcall _lstA rjmp _return _grt: rcall _grtA _return: ret
_lstA: ldi r21, 0x30; if less add rLCD, r21; then add 48 rcall _dWrite; write to LCD ret
_grtA :; if the number from A ... F ldi r21, 0x37; 55 - add to rLCD to get character code add rLCD, r21; rcall _dWrite; write to LCD ret
; character formation when reading from memory ; works through rLCD _charFromMemory: push rLCD; sent to the stack ; high byte formation andi rLCD, 0xf0; clear junior nibble swap rLCD; swap nibbles rcall _BF rcall _charInput ; high byte formation pop rLCD; get out of the stack andi rLCD, 0x0f; clear senior nibble rcall _BF rcall _charInput ret
; ========================================== all sorts of inscriptions and screen settings === ============================