Adding NMI timer interrupts to a NMI.

Basic and Machine Language

Moderator: Moderators

Post Reply
z9leca
Vic 20 Newbie
Posts: 6
Joined: Thu Apr 13, 2023 1:02 pm
Location: Sweden

Adding NMI timer interrupts to a NMI.

Post by z9leca »

Hi all,

First of all - the disclaimer:
I'm fairly new to assembly, both in general, and on the VIC-20. So I apologize in advance for any and all newbie/idiot misstakes. And I appreciate all help in improving my work.
* end disclaimer

I am working on a project where I want to connect a MIDI keyboard to a microcontroller. The microcontroller is connected to the VIC-20:s user port where I want the VIC 20 to pass on the data to the VIC sound registers. I use CB1 and CB2 for flow control and PORTB for the data. The communication is one-way, i.e. I use both CB1 and CB2 as inputs on the VIC. I use NMI:s to catch incoming data packets. A complete "data transmission" consists of one address byte and one data byte.
I have managed to get this working fairly well. I haven't done really extensive tests but playing the MIDI keyboard plays the intended notes on the intended channel. So far so good.

BUT - this is where I get into trouble : I want to incorporate more interrupt sources so I can add sound modulation. The first thing I want to try is to add Aleksi's 10-bit oscillator frequency resolution routine. I've tried it earlier when I had polling communication with the microcontroller (i.e. NOT on NMI) and that worked fine, apart from lost data packets which I want to avoid.

What happens is that the interrupts stop firing after a random amount of time. If I leave the interrupts for CB1 and CB2 untouched (i.e. I don't play the MIDI keyboard) the timer keeps ticking and the 10-bit sound NMI routine works. I guess I am missing/doing the ACK of the interrupt wrong? I added a BIT $9114 in the end of the NMI routine to get this result, if I don't the NMI will only fire once. I really only want to do the BIT $9114 when the interrupt fired is the timer interrupt (i.e. at the end of the 10-bit routine at $14C0).

I can't see what I'm doing wrong but when it comes to the VIA timers and how to acknowledge interrupts I'm obviously on thin ice.

Anyway - here is the complete and uncensored code. I am writing it on real hardware using my only tools available, which is a memory expansion and VIC Monitor.

Code: Select all

Memory Map
------------------------
$1300-1342 : setup

$1450-1465 : hextodec
$1470-148D : display NMI flags
$1490-14BF : Variables:
	     	14B8..9..A..B..C..D..E..F = data table for 10-bit osc. 
		1499 = Flag for address byte received or not
$14C0-14DE: 10-bit routine
$1500-1535 : NMI handler
$1530      : endNMI 



;-------------setup routine

;setup user port communication
1300 a9 00    lda #$00
1302 8d 99 14 sta $1499 	;Variable to flag whether an address byte has been received or not
1305 8d 12 91 sta $9112	 	;Set Port B to in
1308 ad 1c 91 lda $911c
130b 29 0f    and #$0f
130d 09 00    ora #$00		
130f 8d 1c 91 sta $911c		;Configure VIA peripheral register for correct CB behaviour

;Clear screen and set foreground color
1312 20 5f e5 jsr $e55f		;Clear screen
1315 a9 02    lda #$02
1317 a2 ff    ldx #$ff
1319 9d ff 93 sta $93ff,x	;Set foregroud color on first "half" of screen
131c ca       dex 
131d d0 fa    bne $1319
131f ea       nop 

;Point NMI Vector to my NMI routine at $1500
1320 a9 00    lda #$00
1322 8d 18 03 sta $0318
1325 a9 15    lda #$15
1327 8d 19 03 sta $0319		

;Setup 10-bit oscillator routine
132a a9 80    lda #$80
132c 8d 14 91 sta $9114
132f 85 fe    sta $fe
1331 a2 05    ldx #$05
1333 8e 15 91 stx $9115
1336 a9 00    lda #$00
1338 95 f8    sta $f8,x
133a ca       dex 
133b 10 fb    bpl $1338

;Enable NMI interrupts
133d a9 d8    lda #$d8		;%11011000
133f 8d 1e 91 sta $911e
1342 60       rts 
--------- end setup routine


;10 bit oscillator routine
14c0 	      asl $fe
14c2          bcc $14c6
14c4 e6 fe    inc $fe
14c6 a2 02    ldx #$02
14c8 b4 fb    ldy $fb,x
14ca b9 b8 14 lda $14b8,y
14cd b4 f8    ldy $f8,x
14cf 25 fe    and $fe
14d1 f0 01    beq $14d4
14d3 c8       iny 
14d4 98       tya 
14d5 9d 0a 90 sta $900a,x
14d8 ca       dex 
14d9 10 ed    bpl $14c8
14db 2c 14 91 bit $9114
14de 4c 30 15 jmp $1530 	;endnmi

;NMI routine
1500 48       pha 
1501 98       tya 
1502 48       pha 
1503 8a       txa 
1504 48       pha 
1505 ad 1d 91 lda $911d
1508 29 08    and #$08
150a f0 50    beq $155c
150c ad 1d 91 lda $911d
150f 29 10    and #$10
1511 f0 29    beq $153c
1513 ee 00 10 inc $1000
1516 ad 1d 91 lda $911d
1519 29 40    and #$40
151b f0 a3    beq $14c0
151d ea       nop 
151e ea       nop 
151f ea       nop 
1520 ea       nop 
1521 ea       nop 
1522 ea       nop 
1523 ea       nop 
1524 ea       nop 
1525 ea       nop 
1526 ea       nop 
1527 ea       nop 
1528 ea       nop 
1529 ea       nop 
152a ea       nop 
152b ea       nop 
152c ea       nop 
152d ea       nop 
152e ea       nop 
152f ea       nop 
1530 a9 00    bit $9114
 	      nop
1535 68       pla 
1536 aa       tax 
1537 68       pla 
1538 a8       tay 
1539 68       pla 
153a 40       rti 

;address byte received
153c ee 2c 10 inc $102c
153f ea       nop 
1540 ad 10 91 lda $9110
1543 ae 99 14 ldx $1499
1546 e0 00    cpx #$00
1548 d0 e6    bne $1530
154a 8d 97 14 sta $1497
154d a9 01    lda #$01
154f 8d 99 14 sta $1499
1552 4c 30 15 jmp $1530

;data byte received
155c ee 2d 10 inc $102d
155f ea       nop 
1560 ad 10 91 lda $9110
1563 ae 99 14 ldx $1499
1566 e0 01    cpx #$01		;check that we have an address byte, otherwise toss the data and end NMI
1568 d0 c6    bne $1530
156a a2 00    ldx #$00
156c 8e 99 14 stx $1499
156f ae 97 14 ldx $1497
1572 e0 0a    cpx #$0a
1574 d0 06    bne $157c
1576 8d 0a 90 sta $900a
1579 4c a1 15 jmp $15a1
157c e0 0b    cpx #$0b
157e d0 06    bne $1586
1580 8d 0b 90 sta $900b
1583 4c a1 15 jmp $15a1
1586 e0 0c    cpx #$0c
1588 d0 06    bne $1590
158a 8d 0c 90 sta $900c
158d 4c a1 15 jmp $15a1
1590 e0 0d    cpx #$0d
1592 d0 06    bne $159a
1594 8d 0d 90 sta $900d
1597 4c a1 15 jmp $15a1
159a e0 0e    cpx #$0e
159c d0 15    bne $15b3
159e 8d 0e 90 sta $900e
15a1 8e 95 14 stx $1495
15a4 20 50 14 jsr $1450
15a7 ae 95 14 ldx $1495
15aa 8d 16 10 sta $1016
15ad 8c 17 10 sty $1017
15b0 4c 30 15 jmp $1530
15b3 8e 95 14 stx $1495
15b6 20 50 14 jsr $1450
15b9 8d 58 10 sta $1058
15bc 8c 59 10 sty $1059
15bf ad 95 14 lda $1495
15c2 20 50 14 jsr $1450
15c5 8d 5a 10 sta $105a
15c8 8c 5b 10 sty $105b
15cb 4c 30 15 jmp $1530


;Hex to dec routine
1450 f8       sed 
1451 aa       tax 
1452 29 0f    and #$0f
1454 c9 0a    cmp #$0a
1456 69 30    adc #$30
1458 a8       tay 
1459 8a       txa 
145a 4a       lsr a
145b 4a       lsr a
145c 4a       lsr a
145d 4a       lsr a
145e c9 0a    cmp #$0a
1460 69 30    adc #$30
1462 d8       cld 
1463 60       rts 

;display NMI flags routine
1470 a2 00    ldx #$00
1472 ad 1d 91 lda $911d
1475 0a       asl a
1476 48       pha 
1477 b0 08    bcs $1481
1479 a9 2d    lda #$2d
147b 9d 0e 10 sta $100e,x
147e 4c 86 14 jmp $1486
1481 a9 2b    lda #$2b
1483 9d 0e 10 sta $100e,x
1486 68       pla 
1487 e8       inx 
1488 e0 08    cpx #$08
148a d0 e9    bne $1475
148c 60       rts 
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: Adding NMI timer interrupts to a NMI.

Post by chysn »

Hi, z9leca, and welcome to Denial!

I haven't had a chance to really dig into your code yet (stupid work meetings!). But meanwhile, I can share my experience with this. It may be a good exercise for me to apply my MIDI system to the 10-bit oscillator, so I can be of more help.

To start with: Some info about my hardware is on the second page of this topic (viewtopic.php?t=10191). That topic also has some info about my software (The MIDI KERNAL), which is here (https://github.com/Chysn/MIDI-KERNAL/bl ... kernal.asm).

My approach is roughly this: When a byte comes in on the serial port, bit 3 of the interrupt flag register ($911D) is set and the NMI is fired. So in the NMI, if that bit is set, I read the user port ($9110). So far, this seems like what you're doing. In my case, the Arduino Nano is a stupid relay; it sends the actual MIDI byte to the user port, and the MIDI KERNAL parses actual MIDI bytes. A MIDI status byte has bit 7 set, so I simply keep track of what type of message it is, and how many additional bytes are expected (0~2). I also account for running status by using the most recent status when a status byte is expected but a data byte is received. Since you have only address and data bytes (rather than MIDI command/channel, data 1, sometimes data 2), I'm sure your microcontroller is doing some of the heavy lifting.

One major difference I see is that my microcontroller brings CB2 high for every byte received. It's up to the MIDI KERNAL to determine whether it's a status byte, a first data byte, or a second data byte. I think using CB2 and CB1 to differentiate between types of data is questionable. I'd say, set a flag somewhere when an address byte is received, clear it when a new address byte is expected, but check CB2 for both bytes. I can't say this is why you're having trouble, but I'll dig it more when I can.

Note: I read pages 229-232 of the Programmer's Reference Guide about a million times. It's a small number of pages, but it's dense as hell.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
srowe
Vic 20 Scientist
Posts: 1340
Joined: Mon Jun 16, 2014 3:19 pm

Re: Adding NMI timer interrupts to a NMI.

Post by srowe »

On 6502 NMI is edge triggered (unlike IRQ which is level triggered). This means your interrupt routine must service and clear all sources otherwise it will never be called again. Can you drop into the monitor when you no longer get called and dump the contents of the IFR?
User avatar
srowe
Vic 20 Scientist
Posts: 1340
Joined: Mon Jun 16, 2014 3:19 pm

Re: Adding NMI timer interrupts to a NMI.

Post by srowe »

This looks odd

Code: Select all

1505 ad 1d 91 lda via1ifr
1508 29 08    and #$08		; CB2
150a f0 50    beq $155c
150c ad 1d 91 lda via1ifr
150f 29 10    and #$10		; CB1
1511 f0 29    beq $153c
1513 ee 00 10 inc $1000
1516 ad 1d 91 lda via1ifr
1519 29 40    and #$40		; T1
151b f0 a3    beq $14c0
shouldn't those be bne tests?
User avatar
srowe
Vic 20 Scientist
Posts: 1340
Joined: Mon Jun 16, 2014 3:19 pm

Re: Adding NMI timer interrupts to a NMI.

Post by srowe »

I think your underlying issue is that a new interrupt source can appear while your NMI routine is running. In my rewrite of the RS-232 subsystem I first disable and clear all NMIs before checking for each source

Code: Select all

	LDA	VIA1IER			; get VIA 1 IER, enabled interrupts
	STA	ENABL			; save for re-enabling on exit
	LDA	#$7F			; disable all interrupts, clears b7 of IFR and raises NMI
	STA	VIA1IER			; set VIA 1 IER
then just before returning I re-enable them

Code: Select all

NMIDONE
	LDA	ENABL			; get enabled interrupts
	ORA	#$80			; enable interrupts
	STA	VIA1IER			; set VIA 1 IER
z9leca
Vic 20 Newbie
Posts: 6
Joined: Thu Apr 13, 2023 1:02 pm
Location: Sweden

Re: Adding NMI timer interrupts to a NMI.

Post by z9leca »

Thanks for the warm welcome and quick replies!

@chysn - Interesting project(s) you've got going there. I'll dig deeper into it, I might learn quite a bit. And - yes, you're correct - in my solution the microcontroller does all the heavy lifting that can be done outside of the VIC. I imagine real time critical things will have to be done locally on the VIC (such as the 10 bit oscillator routine for example).
Also - yes, thanks for the tip one the programmers reference guide. I´ve read it about as many times as you :) and I've also studied Compute!'s mapping the VIC. But I guess my limited experience with programming the VIC makes it difficult for me to draw correct conclusions sometimes.

@srowe
* Thank you very much for giving me more insight in how NMIs work. Regarding that all interrupts must be serviced, it's not possible to "service" the interrupts by storing zeroes in $911D/VIAIFR ? (not the optimal solution maybe but I'm curious, it doesn't seem to work so I must be misunderstanding something here )

* When the timer "randomly" stops and I drop out the IFR is %11011000, so CB1,CB2 and the timer is set.

* Regarding the BEQ:s that should be BNE tests. Yeah, I must have mixed it up. I was thinking that if I AND and the bit i mask out was one, the zero flag would be set. Which is an incorrect conclusion, right? Excues my n00b confusion here.

* Yeah it is reasonable to assume that new interrupt sources can appear while the NMI routine is running. The timer runs at predictable intervals but data packets arrive at the user port at completely random intervals. But if I disable the IER as you propose, that still won't prevent the interrupt sources to change flags in IFL even though the IER doesn't cause an interrupt?


Another thing - am I drawing the wrong conclusion here when I believe that, if I load $9114 with $80 and $9115 with $05 (Timer 1 lo and hi bytes) the interrupt fires every $580 clock cycles? And that gives me quite a lot of time to do things in between those interrupts (for example I have time to do ADC #$00 about $2C0 times?).
User avatar
srowe
Vic 20 Scientist
Posts: 1340
Joined: Mon Jun 16, 2014 3:19 pm

Re: Adding NMI timer interrupts to a NMI.

Post by srowe »

z9leca wrote: Thu May 18, 2023 9:35 am * Thank you very much for giving me more insight in how NMIs work. Regarding that all interrupts must be serviced, it's not possible to "service" the interrupts by storing zeroes in $911D/VIAIFR ? (not the optimal solution maybe but I'm curious, it doesn't seem to work so I must be misunderstanding something here )
You might also be able to clear the interrupts by using the IFR but you would need to clear them in one instruction again to avoid a possible race with another interrupt.
* When the timer "randomly" stops and I drop out the IFR is %11011000, so CB1,CB2 and the timer is set.
That suggests an interrupt was pending when the interrupt routine finished.
* Regarding the BEQ:s that should be BNE tests. Yeah, I must have mixed it up. I was thinking that if I AND and the bit i mask out was one, the zero flag would be set. Which is an incorrect conclusion, right? Excues my n00b confusion here.
Let's talk through an example, when a CB1 interrupt occurs the IFR will contain %10010000 (assuming the timer doesn't go off as well).

Code: Select all

%10010000 &
%00010000
---------
%00010000
.A has a non-zero value so the Zb will be 0. BNE stands for "branch on result not equal to zero" so the branch will be taken.

I think fixing this is important, at the moment you could well just be dropping out of the NMI routine without clearing an interrupt.
* Yeah it is reasonable to assume that new interrupt sources can appear while the NMI routine is running. The timer runs at predictable intervals but data packets arrive at the user port at completely random intervals. But if I disable the IER as you propose, that still won't prevent the interrupt sources to change flags in IFL even though the IER doesn't cause an interrupt?
If you disable all interrupt sources by clearing the IER then the NMI line clears immediately. You still have the bits in the IFR to tell you which interrupts are
pending, and more can be added while you're processing them. At the point you add them back to the IER if any corresponding bits in the IFR are set the CPU will receive a new interrupt.
Another thing - am I drawing the wrong conclusion here when I believe that, if I load $9114 with $80 and $9115 with $05 (Timer 1 lo and hi bytes) the interrupt fires every $580 clock cycles? And that gives me quite a lot of time to do things in between those interrupts (for example I have time to do ADC #$00 about $2C0 times?).
Correct, you don't run the risk of multiple timer interrupts occurring. What you do need to handle is a timer interrupt and line interrupt happening at the same time (or nearly the same time).
z9leca
Vic 20 Newbie
Posts: 6
Joined: Thu Apr 13, 2023 1:02 pm
Location: Sweden

Re: Adding NMI timer interrupts to a NMI.

Post by z9leca »

@srowe

Thank you very much for clearing up my confusion about... well, all things I´ve asked. You´ve been extremely helpful.

I've adjusted the code accordingly and now it never drops out as it did before. Starting the NMI routine with saving the IER and clearing it, and then ending it with restoring the IER according to your example did the trick.
Post Reply