Page 2 of 4

Re: Improved MENSCH™ Microcomputer Software

Posted: Thu Jan 29, 2026 6:02 am
by BigDumbDinosaur
Martin_H wrote:

Code: Select all

          bit VIA_BASE+VIA_IFR   ; time out? Side effect: also read the IER.
          beq @loop
With the accumulator set wide¹, the above instruction will not do what you expect.  What will happen is BIT will copy bits 14 and 15, respectively, to v and n in SR.  The outcome of the above will be unpredictable, since it will be bits 6 and 7 in the interrupt enable register (IER) that will end up in v and n.
Dr Jefyll wrote:
Perhaps the side effects are tolerable under the present circumstances, but at the very least you'd need to add some conspicuous comments to the code to warn your future self about the potential booby trap that awaits if you start using additional VIA functions (and a nasty little trap it could turn out to be, with subtle and un-intuitive symptoms).
I concur with Jeff.  Working with I/O almost always involves touching one register at a time.  As soon as you start read/writing words to I/O hardware, you introduce all sorts of opportunities to shoot yourself in the foot.  Sixteen-bit operations are best suited for compute-bound work, especially arithmetic, processing bitmaps, scanning data tables, and the like.

Setting the VIA timer with a 16-bit write should work on paper.  In practice, I have found that some I/O hardware messes up when two different registers are written only one Ø2 cycle apart.  As always, YMMV.

————————————————————
¹There is no “8-bit mode” or “16-bit mode”—the 816’s operating mode isn’t changed when you change register widths.

Re: Improved MENSCH™ Microcomputer Software

Posted: Thu Jan 29, 2026 3:06 pm
by Martin_H
Thanks to both of you for the feedback. I'll stick with an eight-bit wide accumulator when working with the VIA control registers to avoid side effects from setting unintended registers.

One question about the data and direction registers. In my code I'm treating port A and B together as a sixteen-bit wide pin array and setting the value of a single pin using a sixteen-bit wide accumulator. This isn't a side effect as I'm intending for both registers to be altered. Below is an example.

Code: Select all

;
; Static data
;
bitsetMask:
	.word $0100, $0200, $0400, $0800, $1000, $2000, $4000, $8000
	.word $0001, $0002, $0004, $0008, $0010, $0020, $0040, $0080

; pbOutput - puts the specified pin into output mode.
; Inputs:
;   A - index (0-15) of the I/O pin to use. Pin is set to output mode.
; Outputs:
;   None
PUBLIC pbOutput
	and #$000f		; constrain input to valid values.
	asl			; convert to word index.
	tax
	lda bitsetMask,x	; transform the index into a bitmask.
	ora VIA_BASE+VIA_DDRB	; or with existing pin state.
	sta VIA_BASE+VIA_DDRB	; set the pin's bit high.
	rts
ENDPUBLIC

; pbHigh - puts the specified pin in output mode and high state.
; Inputs:
;   A - index (0-15) of the I/O pin to use. Pin is set to output mode.
; Outputs:
;   None
PUBLIC pbHigh
	jsr pbOutput
	lda bitsetMask,x	; transform the index into a bitmask.
	ora VIA_BASE+VIA_PRB	; use mask to set pin high
	sta VIA_BASE+VIA_PRB
	rts
ENDPUBLIC
In my testing this produces the correct result. But I'm wondering if this could run afoul of BDD's warning about I/O hardware messing up when two different registers are written only one Ø2 cycle apart. If that's a risk, I'll need to tweak my code to treat port A and B separately.

Basically, is their ominous background music that I'm missing like the swimmers in a shark film?

Re: Improved MENSCH™ Microcomputer Software

Posted: Thu Jan 29, 2026 5:05 pm
by BigDumbDinosaur
Martin_H wrote:
One question about the data and direction registers. In my code I'm treating port A and B together as a sixteen-bit wide pin array and setting the value of a single pin using a sixteen-bit wide accumulator.  This isn't a side effect as I'm intending for both registers to be altered. Below is an example...
I slightly tweaked your code to slightly reduce its size, slightly increase its performance and make it slightly easier to read:

Code: Select all

Code:
;
; Static data
;
bitsetMask:
;
;         port 'A' masks...
;
         .word %0000000100000000
         .word %0000001000000000
         .word %0000010000000000
         .word %0000100000000000
         .word %0001000000000000
         .word %0010000000000000
         .word %0100000000000000
         .word %1000000000000000
;
;        port 'B' masks...
;
         .word %0000000000000001
         .word %0000000000000010
         .word %0000000000000100
         .word %0000000000001000
         .word %0000000000010000
         .word %0000000000100000
         .word %0000000001000000
         .word %0000000010000000
;
PUBLIC pbOutput
         and #%1111            ; constrain input to valid values...
;
;        ^^^ is this really necessary? ^^^
;
         asl                   ; convert to word index.
         tax
         lda bitsetMask,x      ; transform the index into a bitmask.
         tsb VIA_BASE+VIA_DDRB ; set the corresponding pin's bit high...
;
;        ^^^ doesn't affect .C ^^^
;
         rts
ENDPUBLIC
;
; pbHigh - puts the specified pin in output mode and high state.
;
;  Inputs: None.
;
; Outputs: Port mask in .C, mask table index in .X
;
;   Notes: pbHigh calls pbOutput, which results in .C returning with the
;          required mask.  Hence pbHigh doesn't need to reload it.
;
PUBLIC pbHigh
         jsr pbOutput          ; set data direction...see above note
         tsb VIA_BASE+VIA_PRB  ; use mask to set pin high
         rts
ENDPUBLIC

TSB sets the desired bit(s) in a single, atomic operation, replacing the two-instruction sequence you were using.  A side-effect of TSB is to condition z in SR according to what the port’s state was before ORing the mask with the port—the conditioning of z is the result of an AND operation.  In this case, said effect is unimportant...and harmless, but can be quite useful when manipulating bit fields (I use it in my four-channel, serial I/O driver).  TRB has the opposite effect; it clears bit(s) that correspond to the mask.  Neither of these instructions affects any MPU registers other than SR.

Also, I reorganized your mask table to make it clear which port bit is being manipulated.  Entering the masks as hex values doesn’t really tell the casual reader which bit is being affected by any given mask.  If your assembler supports it, you can “automate” such tables and greatly reduce the likelihood of a typo creeping into your code.

Quote:
In my testing this produces the correct result.  But I'm wondering if this could run afoul of BDD's warning about I/O hardware messing up when two different registers are written only one Ø2 cycle apart.  If that's a risk, I'll need to tweak my code to treat port A and B separately.
I’d expect that the WDC 65C22 wouldn’t have a problem with a 16-bit access as you are doing, since it is rated for 14 MHz and tested at 20 MHz, with current production parts use a 0.6µ TSMC die.  Older versions of the 65C22 might stumble.  As I said, YMMV.

Re: Improved MENSCH™ Microcomputer Software

Posted: Thu Jan 29, 2026 6:16 pm
by Martin_H
@BDD, thanks for your suggestions, they're exactly what I needed. I am embarrassed to admit I didn't know about the TSB and TRB instructions. My 6502-assembly knowledge is a bit frozen in the early 1980's.

As you noticed the "AND #%1111" isn't strictly needed. I wanted to constrain the LDA to a range of addresses that are certain to fall within the bitmask array. On this hardware an out-of-range value would only produce an erroneous result. But in the late 80's I earned a living doing kernel mode assembler programming. We added "bug checks" to our code to prevent page or access faults. While the program bug would still produce an erroneous output, it wouldn't cause a kernel panic which was considered worse.

Re: Improved MENSCH™ Microcomputer Software

Posted: Thu Jan 29, 2026 7:06 pm
by BigDumbDinosaur
Martin_H wrote:
...I am embarrassed to admit I didn't know about the TSB and TRB instructions...
Those two are also available with the 65C02 and, in my opinion, are more generally useful than the RMB and SMB Rockwell extensions, which have only zero page addressing and can only affect one bit.
Quote:
As you noticed the "AND #%1111" isn't strictly needed.
Unless the bit number being used to select the mask is coming from an unreliable source, there shouldn’t be any reason to mask .C to stay within table limits.  Presumably, the range of memory in which the program is loaded won’t magically change on its own.  :D

Re: Improved MENSCH™ Microcomputer Software

Posted: Fri Jan 30, 2026 6:19 pm
by Martin_H
I'm writing function to measure an incoming pulse width on an arbitrary pin. These pulses are generated by sensors using a pulse width to transmit an analog value. The classic example is the Ping))) ultrasonic sensor, but other sensors use a similar approach. The response pulses are positive TTL pulses with a width from 115 µs minimum to 18.5 ms maximum. That should be easily measurable with 3.6864 MHz clock and a VIA timer as an interval timer.

After writing and reviewing my initial draft, a question occurred to me. How do I stop the timer 2? I read the 6522 data sheet and learned how to start a one-shot timer, letting it count down to zero, and clearing the interrupt. But I didn't see anything about halting the timer. If I leave the function, I have a timer which will elapse while other code is running leaving the timer 2 interrupt bit set.

Here's a first draft of my code:

Code: Select all

; pbPulsin - measures the width of a pulse on a pin and return the results.
; Inputs:
;   C - index (0-15) of the I/O pin to use. Pin is set to input mode.
;   X - boolean (0-1) that specifies whether the pulse to be measured is
; 	low (0) or high (1). A low pulse begins with a 1-to-0 transition and
;	a high pulse begins with a 0-to-1 transition. Both end with the
;	reverse transition.
; Outputs:
;   C - the measured pulse duration in cpu cycles.
PUBLIC pbPulsin
	INITIAL_VALUE = 2	; stack offsets for local variables
	PORT_MASK = 0
	pea $0000		; reserve space for the initial value
	txy			; retain parameter in x
	jsr pbInput		; set pin to input and get port mask
	pha			; initialize port mask stack local
	cpy #$0001		; test desired initial value
	bne @leading_edge
	sta INITIAL_VALUE,s	; set initial value bit using port mask.
@leading_edge:
	lda PORT_MASK,s		; wait for pulse leading edge transition.
	and VIA_BASE+VIA_PRB	; get the current value.
	eor INITIAL_VALUE,s	; test for leading edge transition.
	bne @leading_edge
	OFF16MEM		; start VIA timer
	stz VIA_BASE+VIA_ACR	; select one shot mode
	lda #$ff		; load timer with maximum value.
	sta VIA_BASE+VIA_T2CL	; set lower latch
	sta VIA_BASE+VIA_T2CH	; set upper latch
	ON16MEM
@while:
	lda PORT_MASK,s
	and VIA_BASE+VIA_PRB	; get the current value.
	eor INITIAL_VALUE,s	; test for edge transition.
	bne @trailing_edge

	OFF16MEM		; has timer reached zero?
	lda #T2IF		; start mask
	bit VIA_BASE+VIA_IFR	; time out?
	ON16MEM
	beq @while
@trailing_edge:
	lda #$ffff
	sbc VIA_BASE+VIA_T2CL	; get value and clear timer 2 interrupt
	plx			; clean up stack
	plx
	rts
ENDPUBLIC
Update: revised code for pulse leading edge detection and cleaned stack.

Re: Improved MENSCH™ Microcomputer Software

Posted: Sun Feb 01, 2026 6:32 pm
by Martin_H
I ported the Arduino button sample, and it works like a champ. But that led to an interesting observation and a question.

Interesting observation. If I push the button and pull the wire off the 6522 header. The 6522 pin retains its prior state and so does the LED. The 6522 must latch the value rather than float which is probably for the best.

My question. Are current limiting resistors required when using a 65c22s pin as an input?

I read the datasheet and it said that the 65c22s does not have current limiting resistors. But in context it seemed related to pins as outputs and the 65c22s being a source of current. It was vague when using a pin to sink current.

In any event, I used a 220-ohm resistor on the input, and I always use a 220-ohm resistor on the output pin because the LED would smoke instantly. The next function to debug is the pbPulsout which I can use to drive servos.

Re: Improved MENSCH™ Microcomputer Software

Posted: Sun Feb 01, 2026 7:58 pm
by GARTHWILSON
Martin_H wrote:
I read the datasheet and it said that the 65c22s does not have current limiting resistors. But in context it seemed related to pins as outputs and the 65c22s being a source of current. It was vague when using a pin to sink current.
The W65C22S's outputs are symmetrical, able to pull up as hard as down, and in my experiments, could even pull 19mA to within 0.8V of either rail (with a 5V power supply).

Re: Improved MENSCH™ Microcomputer Software

Posted: Sun Feb 01, 2026 8:22 pm
by BigDumbDinosaur
GARTHWILSON wrote:
Martin_H wrote:
I read the datasheet and it said that the 65c22s does not have current limiting resistors. But in context it seemed related to pins as outputs and the 65c22s being a source of current. It was vague when using a pin to sink current.
The W65C22S's outputs are symmetrical, able to pull up as hard as down, and in my experiments, could even pull 19mA to within 0.8V of either rail (with a 5V power supply).
Also, note that the W65C22S has a totem-pole IRQ output, not open collector.

Re: Improved MENSCH™ Microcomputer Software

Posted: Mon Feb 02, 2026 6:19 am
by Dr Jefyll
Martin_H wrote:
Basically, is their ominous background music that I'm missing like the swimmers in a shark film?
I think we've backed away from the nervousness about having a pair of reads only one Ø2 cycle apart, and rightly so. In any case, that isn't quite what I meant a few posts back when I mentioned "the potential booby trap that awaits if you start using additional VIA functions." Allow me to elaborate on the subject of interrupts.

When you replace an 8-bit read of the VIA with a 16-bit read, there's obviously an extra, unnecessary read cycle. We may be accustomed to thinking of read cycles as being harmless, but that's only true for ordinary memories and for simple IO devices. Most of us are aware that some of the fancier devices -- mostly IO but also certain memories such as those with sequence-actuated locking features -- may attach extra significance to a read. Beyond being a request to retrieve some data, the read also serves as a cue or semaphore. And if that cue gets sent erroneously it's no surprise that it'll gum up the works!

The chart below shows how several registers of the 6522/65C22 VIA are sensitive to this. A read of ORA, the ShiftReg, ORB, T2L or T1L has potential to alter what's contained in the Interrupt Flag Register. This behavior is a feature that can be used to streamline the Interrupt Service Routine. But it also means a spurious read of one of those registers can cause an interrupt to go unserviced... and this sort of problem can be treacherously misleading to troubleshoot because the code that misbehaves (the ISR) typically isn't the same code that generated the spurious read. :cry:
6522 IFR (InterruptFlag Register).png
It's worth noting that Martin's cautiously proposed coding shortcut is not the only source of spurious reads; they can also arise during so-called Dead Cycles, which have been a perverse reality of 65xx bus behavior ever since the family originated. During the dead cycle, the CPU is typically performing some useful and necessary internal operation. But, although dead cycles are always reads (not writes), a problem arises if the address on the bus happens to touch a sensitive device.

When the 6502 was superseded by the 65C02, the new chip had certain features added which largely eliminated the risk of a dead cycle touching a sensitive device. (For example, during most dead cycles PC would appear on the address bus.) Then later for the 65C816 this feature was removed (!), but in its place we got the new VDA and VPA signals. VDA is low during dead cycles, and, acting solo, is usually sufficient for protecting sensitive devices -- see "Plan A" below.

But AFAICT the 65C265 isn't capable of outputting VDA and VPA... nor, I'm guessing, does it have the protective features offered by the 65C02! This means we're thrown back into the dark days of NMOS 6502. :shock: But with careful coding and an awareness of how the hardware behaves, it's possible to avoid the maddeningly misleading bugs I mentioned. (For more on dead cycles, including some workarounds, see Drass's highly detailed document here.)

Hmmm... does the IO of the 65C265 include read-sensitive registers? I haven't checked. But certainly an externally attached VIA will be vulnerable... :|

-- Jeff

Re: Improved MENSCH™ Microcomputer Software

Posted: Mon Feb 02, 2026 5:02 pm
by Martin_H
@Garth, BDD, and Jeff, thanks for the detailed responses. All were helpful.

Now I am facing a design decision around units. Should my API use units of microseconds or machine cycles? If I use microseconds, I'll need a division routine to convert microseconds to machine cycles. While I have one, I'm thinking that putting the burden on the caller who could do that once rather than each call.

The original Basic Stamp used machine cycles for Pulsout and RCTime and I'm starting to think avoiding division was probably why.

Re: Improved MENSCH™ Microcomputer Software

Posted: Mon Feb 02, 2026 7:17 pm
by BigDumbDinosaur
Dr Jefyll wrote:
In any case, that isn't quite what I meant a few posts back when I mentioned "the potential booby trap that awaits if you start using additional VIA functions." Allow me to elaborate on the subject of interrupts...
The effect of touching an adjacent register was what I was getting at when I earlier wrote:
Quote:
With the accumulator set wide, the above instruction will not do what you expect.  What will happen is BIT will copy bits 14 and 15, respectively, to v and n in SR.
While I don’t have the 65C22 in any of my POC units, the same potential booby trap exists with the I/O I do have (RTC, serial and SCSI).

As a generally rigid rule, I never set the accumulator wide during I/O operations.  It’s too easy to forget that the accumulator is wide and then do a read, or worse yet, a write on a chip register and touch the adjacent register in the next cycle.  In the case of the 28L92 DUART I use for serial I/O, that sort of scenario can be “deadly” during a write.  It is possible to unintentionally touch the command register and send the DUART off into la-la land, killing the jiffy IRQ and bringing everything to a halt.  :(

Quote:
It's worth noting that Martin's cautiously proposed coding shortcut is not the only source of spurious reads; they can also arise during so-called Dead Cycles, which have been a perverse reality of 65xx bus behavior ever since the family originated...When the 6502 was superseded by the 65C02, the new chip had certain features added which largely eliminated the risk of a dead cycle...for the 65C816 this feature was removed (!), but in its place we got the new VDA and VPA signals...
The regression of the 816’s behavior to the NMOS behavior was due to Apple’s (more specifically, Steve Wozniak’s) brain-dead disk control design that was dependent on the spurious bus behavior of the 6502, especially with the R-M-W instructions.  The original 816 design eliminated that behavior, but Bill Mensch was forced to restore it if he wanted to sell 816s to Apple.  Hence the appearance of VDA and VPA.
Quote:
But AFAICT the 65C265 isn't capable of outputting VDA and VPA...
It isn’t—no pins have been designated for those signals.
Quote:
nor, I'm guessing, does it have the protective features offered by the 65C02!
The 265’s data sheet says nothing in that regard, and unlike the 816, doesn’t have a “caveats” section.  Given what I noted above about spurious bus signals, I suspect the 265 doesn’t exhibit that behavior.  One would probably have to hook up a logic analyzer to make a positive determination.
Quote:
Hmmm... does the IO of the 65C265 include read-sensitive registers? I haven't checked. But certainly an externally attached VIA will be vulnerable... :|
The 265 has a set of UARTs that suspiciously look like the 65C51...which means it would theoretically be possible to upset the apple cart with a poorly-chosen 16-bit access.

Re: Improved MENSCH™ Microcomputer Software

Posted: Mon Feb 02, 2026 7:24 pm
by BigDumbDinosaur
Martin_H wrote:
Should my API use units of microseconds or machine cycles?
It would seem machine cycles would be the better choice, as they are a dimensionless quantity.  Let the caller convert that to a time period, if desired.  APIs should be careful to not make potentially-unwarranted assumptions about what the caller wants/needs from the call.  In other words, emit only what is necessary to make the API useful.

Re: Improved MENSCH™ Microcomputer Software

Posted: Tue Feb 03, 2026 8:22 pm
by Martin_H
BigDumbDinosaur wrote:
It would seem machine cycles would be the better choice, as they are a dimensionless quantity.  Let the caller convert that to a time period, if desired.
I went with this approach as it simplified the code and improved performance. So, I now have pbPulsout and the Arduino servo sample working.

Doing the conversion at assembly time in the consuming program looks perfectly readable and reasonable:

Code: Select all

; Machine dependent constants that are useful for real time control.
CLK_FREQ = 3686400		; cycles per second
ONE_US = CLK_FREQ / 1000000	; cycles per us

SERVO_MIN = 1000 * ONE_US
SERVO_MAX = 2000 * ONE_US
SERVO_STEP = (SERVO_MAX - SERVO_MIN)/100

	PULSE_WIDTH = 1		; stack offset for the 
	pea $0000		; stack local for PULSE_WIDTH

	lda #SERVO_MIN		; sweep 0° to 180° (1000 to 2000 µs)
@sweep_up:
	sta PULSE_WIDTH,s

	tax
	lda #SERVO_PIN
	jsr pbPulsout		; send control pulse

	lda #20
	jsr pbPause		; Wait 20 ms between pulses

	lda #SERVO_STEP		; increment the pulse width
	clc
	adc PULSE_WIDTH,s

	cmp #SERVO_MAX		; loop until 180°
	bcc @sweep_up
The next step is pbPulsin and the ultrasonic sensor example.

As an aside, I'm loving stack relative addressing. Stack locals make code so much cleaner. Honestly that sample is almost a line for line port from the C code.

Re: Improved MENSCH™ Microcomputer Software

Posted: Wed Feb 04, 2026 6:46 am
by BigDumbDinosaur
Martin_H wrote:
As an aside, I'm loving stack relative addressing.  Stack locals make code so much cleaner.  Honestly that sample is almost a line for line port from the C code.
Yep!  Stack-relative is very useful, as are some of the other stack-oriented instructions.  Being able to use the stack as a scratch-pad is one of the reasons why I say programming the 816 in native mode requires a different mindset than with the eight-bit MPUs.

One thing to watch out for when using stack-relative with the (<offset>,S),Y addressing mode is the source/destination bank of a read/write is whatever happens to be in DB.  Unfortunately, changing DB tends to be a bit of a pain, since the only way to read or write the register is through the stack.  For that reason, I tend to not use (<offset>,S),Y addressing.  In fact, very early in a program’s start-up code, I do PHK followed by PLB.  I can then make 16-bit references to the program’s local run-time data (e.g., tables, non-direct page temp values, etc.), which maintains performance.  To access the data being processed, e.g., incoming data flow from the outside world that is going somewhere other than the execution bank, indirect-long addressing modes become very useful.

Now that you’ve gotten acquainted with stack-relative addressing, the next step is for you to try out the trick of reserving stack space in your functions and pointing direct page at it.  You can then access the stack with direct-page addressing, which means you can use stack parameters as pointers, counters, flags, etc.—it’s a more flexible method of using the stack as fugacious workspace.  Follow that with a little housekeeping and the caller is none the wiser to the goings-on in your function.  :D

See the attached for an example of writing black-box functions in 816 assembly language.  It’s a function from my SCSI library and is in Kowalski assembler syntax.  All of the call parameters are passed through the stack and the function cleans up everything before it returns.

scsi_blkread.asm
65C816 Function Example
(11.96 KiB) Downloaded 10 times