6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat May 11, 2024 9:32 pm

All times are UTC




Post new topic Reply to topic  [ 15 posts ] 
Author Message
PostPosted: Mon Jul 24, 2023 4:11 pm 
Offline
User avatar

Joined: Sun Dec 26, 2021 8:27 pm
Posts: 182
Using a 6532 at 1 Mhz for 9600 baud software serial- the bit delay seems fine for the loop but unless I up the start bit delay to 203 cycles-ish it doesn't work.. Is something wrong with my math?

Code:

wait:
lda DRA ; Check serial 3c
and #$01 ; 2c
bne noserial ; 3c
jsr serial_rx ; 6c
jsr ssd1306_sendchar
jsr i2c_stop
jmp wait

delay_short:
sta WTD8DI ; Divide by 8 = A contains ticks to delay/8
shortwait:
lda READTDI
bne shortwait
rts
; jsr = 6 cycles
; sta (zp) = 3 cycles
; WTD8DI * 8 cycles
; We can ignore branches not taken while timer not 0
;lda (zp) = 3 cycles
; bne = 3 cycles
; rts = 6 cycles
; = 21 + (WTD8DI * 8) cycles


;Returns byte in A - assumes 9600 baud = ~104us/bit, 1 cycle = 1us (1 MHz)
;We should call this ASAP when RX pin goes low - let's assume it just happened (14 cycles ago)
;Since reading low: 14 incl. jsr
;lda #, 3c
;delay_short, 21 + 20*8 = 181c
;ldx 2c
; lda 3c
; = 203 cycles to hit the middle of first data bit instead of 156 cycles.

serial_rx:
lda #20 ; 1.5 period-ish ; 3 cycles --- Should be 156 cycles... Only works with 203 cycles??
jsr delay_short ; 21 + 181c
ldx #8 ; 2 cycles
serial_rx_loop:
lda DRA ; Read RX bit 0 ; 3 cycles
lsr ; Shift received bit into carry - in many cases might be safe to just lsr DRA ; 5 cycles
ror inb ; Rotate into MSB 5 cycles
lda #7 ; 3 cycles
jsr delay_short ; 21 + 56 cycles Delay until middle of next bit
nop; 2c
nop ; 2c
nop ; 2c
dex ; 2c
bne serial_rx_loop ; 3 cycles
;lda #4 Should already be in the middle of the stop bit
;jsr delay_short ; Delay at least until start of stop bit so we don't mistake it for a new byte.
; We can ignore the actual stop bit and use the time for other things
; Received byte in inb
lda inb ; Put in A
rts

_________________
---
New new new new new video out! Serial Bootloader for my 65uino
Also, check out: I2C on a 6502 Single Board Computer
and Complete hardware overview of my 6502 SBC R1 :)


Last edited by AndersNielsen on Mon Jul 24, 2023 5:33 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Mon Jul 24, 2023 4:57 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
Could be right. If you start timing from the leading edge of the start bit, you will ideally wait 1.5 bit times before sampling the first data bit, so you get it in the middle of the bit. And then every 1 bit time, to keep hitting the middle of each bit.

But I haven't checked the maths.


Top
 Profile  
Reply with quote  
PostPosted: Mon Jul 24, 2023 5:10 pm 
Offline
User avatar

Joined: Sun Dec 26, 2021 8:27 pm
Posts: 182
BigEd wrote:
Could be right. If you start timing from the leading edge of the start bit, you will ideally wait 1.5 bit times before sampling the first data bit, so you get it in the middle of the bit. And then every 1 bit time, to keep hitting the middle of each bit.

But I haven't checked the maths.


That’s what I’m going for but my math says the 1.5 bits should be 156 cycles but for some reason it only works with 203 cycles which seems waaaay off.

_________________
---
New new new new new video out! Serial Bootloader for my 65uino
Also, check out: I2C on a 6502 Single Board Computer
and Complete hardware overview of my 6502 SBC R1 :)


Top
 Profile  
Reply with quote  
PostPosted: Mon Jul 24, 2023 9:28 pm 
Offline

Joined: Fri Jul 09, 2021 10:12 pm
Posts: 741
"shortloop" seems to only delay 6 cycles per iteration. I don't know what READTDI does in hardware though, if it's an automatic hardware timer then I guess the loop length doesn't matter so much.

If you can set the state of output pins then maybe you can use an oscilloscope to test your delay routines with various numbers and see what's going on - or compare them against VIA timers if you have them.


Top
 Profile  
Reply with quote  
PostPosted: Mon Jul 24, 2023 9:40 pm 
Offline

Joined: Sun Jun 29, 2014 5:42 am
Posts: 337
AndersNielsen wrote:
That’s what I’m going for but my math says the 1.5 bits should be 156 cycles but for some reason it only works with 203 cycles which seems waaaay off.

It's possible that the additional delay up front is compensating for time to process each bit being too fast (which accumulates over the 8 bits).

Some of the cycle times you have in the comments are incorrect. For example LSR A and LDA # are both 2 cycles, not 5 cycles and 3 cycles.

I would also suggest re-writing your delay_short loop without using external hardware. i.e. make it a pure software delay loop. Then it's easier to count cycles, without having to make assumptions how the external hardware works.

Dave


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 25, 2023 7:21 am 
Offline
User avatar

Joined: Sun Dec 26, 2021 8:27 pm
Posts: 182
hoglet wrote:
AndersNielsen wrote:
That’s what I’m going for but my math says the 1.5 bits should be 156 cycles but for some reason it only works with 203 cycles which seems waaaay off.

It's possible that the additional delay up front is compensating for time to process each bit being too fast (which accumulates over the 8 bits).
Some of the cycle times you have in the comments are incorrect. For example LSR A and LDA # are both 2 cycles, not 5 cycles and 3 cycles.


I came to the same conclusion this morning. I'm compensating for the timer not delaying what I expect.

What's actually going on is probably delay_short being "off by one" - when it hits 0 the timer exits even though it hasn't triggered, so it's always 8 cycles less delay.

21 + ((WTD8DI - 1) * 8) cycles along with the incorrect cycle times should fix it.

Thanks for the help.. Time to test!

_________________
---
New new new new new video out! Serial Bootloader for my 65uino
Also, check out: I2C on a 6502 Single Board Computer
and Complete hardware overview of my 6502 SBC R1 :)


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 25, 2023 7:58 am 
Offline

Joined: Fri Jul 09, 2021 10:12 pm
Posts: 741
AndersNielsen wrote:
21 + ((WTD8DI - 1) * 8) cycles along with the incorrect cycle times should fix it.

Thanks for the help.. Time to test!

Because your loop is 6 cycles, it will round the delay up to a multiple of 6 as well, with some bias that depends on the hardware. So 21 plus ((WTD8DI-1)*8 + N) rounded up to a multiple of 6. It's kind of complicated. An accumulator-based delay loop using SEC and SBC #1 should be eight cycles as desired, I think, so would work well with your existing code.


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 25, 2023 8:48 am 
Offline
User avatar

Joined: Sun Dec 26, 2021 8:27 pm
Posts: 182
gfoot wrote:
AndersNielsen wrote:
21 + ((WTD8DI - 1) * 8) cycles along with the incorrect cycle times should fix it.

Thanks for the help.. Time to test!

Because your loop is 6 cycles, it will round the delay up to a multiple of 6 as well, with some bias that depends on the hardware. So 21 plus ((WTD8DI-1)*8 + N) rounded up to a multiple of 6. It's kind of complicated. An accumulator-based delay loop using SEC and SBC #1 should be eight cycles as desired, I think, so would work well with your existing code.


I don't think that's quite right :) The 6 cycles you are referring to should only be counted exactly once, since the other times the hardware timer the hardware timer hasn't counted to 0.
When we set WTD8DI on the 6532 it starts counting down and until it reaches 0 we keep looping - it shouldn't matter how many times we loop.

Code:
; jsr = 6 cycles
; sta (zp) = 3 cycles
; WTD8DI * 8 cycles
; We can ignore branches not taken while timer not 0
;lda (zp) = 3 cycles
; bne = 3 cycles
; rts = 6 cycles
; = 21 + (WTD8DI -1 * 8) cycles


Cycles for the timer routine = (A - 1) * 8 + 21

Am I missing something G? :D

_________________
---
New new new new new video out! Serial Bootloader for my 65uino
Also, check out: I2C on a 6502 Single Board Computer
and Complete hardware overview of my 6502 SBC R1 :)


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 25, 2023 10:19 am 
Offline

Joined: Fri Jul 09, 2021 10:12 pm
Posts: 741
AndersNielsen wrote:
I don't think that's quite right :) The 6 cycles you are referring to should only be counted exactly once, since the other times the hardware timer the hardware timer hasn't counted to 0.
When we set WTD8DI on the 6532 it starts counting down and until it reaches 0 we keep looping - it shouldn't matter how many times we loop.

But the CPU is only sampling the READTDI register once every 6 cycles. For reference here's your code:

Code:
delay_short:
sta WTD8DI ; Divide by 8 = A contains ticks to delay/8
shortwait:
lda READTDI
bne shortwait
rts


It will execute like this, for example, entering with A=2 - I've shown cycle numbers in the left column relative to the write for clarity:

Code:
  -2  sta WTD8DI      3 cycles, writes on cycle 0, value=2
   1  lda READTDI     3 cycles, reads on cycle 3, value=2
   4  bne shortwait   3 cycles
   7  lda READTDI     3 cycles, reads on cycle 9, value=1
  10  bne shortwait   3 cycles
  13  lda READTDI     3 cycles, reads on cycle 15, value=1
  16  bne shortwait   3 cycles
  19  lda READTDI     3 cycles, reads on cycle 21, value=0
  22  bne shortwait   2 cycles, not taken
  24  rts

Note that it will read from READTDI on cycles 3+6*n relative to the cycle when the write operation took place, the "bne" is always at 4+6*n for some n, and the "rts" is at 6*(n+1). Changing the value of A on entry can only offset this by a multiple of six one way or the other.


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 25, 2023 1:43 pm 
Offline
User avatar

Joined: Sun Dec 26, 2021 8:27 pm
Posts: 182
gfoot wrote:
AndersNielsen wrote:
I don't think that's quite right :) The 6 cycles you are referring to should only be counted exactly once, since the other times the hardware timer the hardware timer hasn't counted to 0.
When we set WTD8DI on the 6532 it starts counting down and until it reaches 0 we keep looping - it shouldn't matter how many times we loop.

But the CPU is only sampling the READTDI register once every 6 cycles. For reference here's your code:

Code:
delay_short:
sta WTD8DI ; Divide by 8 = A contains ticks to delay/8
shortwait:
lda READTDI
bne shortwait
rts


It will execute like this, for example, entering with A=2 - I've shown cycle numbers in the left column relative to the write for clarity:

Code:
  -2  sta WTD8DI      3 cycles, writes on cycle 0, value=2
   1  lda READTDI     3 cycles, reads on cycle 3, value=2
   4  bne shortwait   3 cycles
   7  lda READTDI     3 cycles, reads on cycle 9, value=1
  10  bne shortwait   3 cycles
  13  lda READTDI     3 cycles, reads on cycle 15, value=1
  16  bne shortwait   3 cycles
  19  lda READTDI     3 cycles, reads on cycle 21, value=0
  22  bne shortwait   2 cycles, not taken
  24  rts

Note that it will read from READTDI on cycles 3+6*n relative to the cycle when the write operation took place, the "bne" is always at 4+6*n for some n, and the "rts" is at 6*(n+1). Changing the value of A on entry can only offset this by a multiple of six one way or the other.

I think that makes sense!
If I throw a NOP in there it will sample every 8 cycles and the sampling will be even and simplify the math, right?

_________________
---
New new new new new video out! Serial Bootloader for my 65uino
Also, check out: I2C on a 6502 Single Board Computer
and Complete hardware overview of my 6502 SBC R1 :)


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 25, 2023 2:44 pm 
Offline

Joined: Fri Jul 09, 2021 10:12 pm
Posts: 741
AndersNielsen wrote:
If I throw a NOP in there it will sample every 8 cycles and the sampling will be even and simplify the math, right?

Yes I think so. You do also need to ensure the branch is not crossing a page boundary otherwise it costs an extra cycle.

But by this point the hardware timer is not really helping you as you're having to cycle-count anyway - you can instead just have A be a multiple of 5 and do:

Code:
    sec             ; 2 cycles
delayloop:
    sbc #1          ; 2 cycles
    bne delayloop   ; 3 cycles, -1 the last time


Or move the "sec" into the loop and have A be a multiple of 7, or... lots of options of course!

The vdelay project at GitHub has some nice routines that perform precise delays of any duration above about 30 cycles: https://github.com/bbbradsmith/6502vdel ... ay_short.s

All that said, one case where the hardware timer will work in your favour is if you set can it up during the start bit and have it automatically count down again from just the right value for subsequent bits - it removes some sources of accumulated drift during the byte and means you don't need to carefully cycle-count your byte reading loop, you just need to start the timer at the right time. If the hardware timer doesn't have VIA T1-like latches that you can use for automatically setting it to a new value when it hits zero, then you could maybe reset it with the correct bit-to-bit gap after each bit - however you lose a lot of the benefit.

Another option - if supported by the hardware - is to set the timer up as you are already doing for the start bit, but then just let it tick down through negative numbers after the start bit, without resetting it. Now you want to read the second bit when the timer reaches -13, the third when it reaches -26, the fourth at -39, etc - just subtracting 104/8 with each time. This is probably my favourite as it once again removes all possibility of drift during the course of a byte.

Code:
    ; store 0 in 'targetvalue' and set up timer to skip start bit
    ; ... then for each bit...
nextbit:
    lda targetvalue         ; starts at 0
waitforbit:
    cmp READTDI             ; compute targetvalue - TDI
    sec                     ; for the upcoming sbc
    bpl waitforbit          ; positive => wait longer
    sbc #13                 ; next bit is 13*8 = 104 cycles after this one
    sta targetvalue
    ; ... read bit, store bit, loop back to nextbit


waitforbit is an 8-cycle loop. The bit read timing may be off by +/-8us, but that should be OK and it won't lead to accumulated drift over the course of the byte. (Edit - it's probably less important for it to be an 8-cycle loop in this case as we've removed the need for precise cycle counting except between the interrupt and the point the timer register is loaded.)

(Another edit - by the way, a nice trick for shifting in exactly 8 bits is to initialise the target memory location with $80, then ROR the bits into it, and after each ROR, if the carry is clear, loop back for another bit. That single set bit will make its way along the byte, and ultimately terminate the read loop, without needing to also manipulate an index register.)


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 25, 2023 3:21 pm 
Offline
User avatar

Joined: Sun Dec 26, 2021 8:27 pm
Posts: 182
If I read the data sheet for the 6532 correctly it essentially disables the divisor when hitting 0 and switches to system clock so we can count cycles since interrupt was triggered - but the 6507 doesn’t have an IRQ pin so I’d have to poll a pin for interrupts manually - that’s a whole different thing.

Using the timer made more sense when I was only going for 4800 baud - less precision needed and the timer simplified things.

At this point I’m just trying to get a better understanding of the 6532 - cycle by cycle.

_________________
---
New new new new new video out! Serial Bootloader for my 65uino
Also, check out: I2C on a 6502 Single Board Computer
and Complete hardware overview of my 6502 SBC R1 :)


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 25, 2023 6:05 pm 
Offline

Joined: Fri Dec 21, 2018 1:05 am
Posts: 1076
Location: Albuquerque NM USA
What is your bit-bang transmitter routine looks like? Transmitter is more visible (observable with scope), so you can see the effects of instruction timings. My first 6502 design had hardware receiver but bit-bang transmitter so I just wrote a software delay loop and tweak the loop counter and watch with scope until I have the right timing. THEN I read about the instruction timings and annotated the software accordingly. It was a cart-before-the-horse approach but there were so much 6502 to learn initially.
Bill


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 25, 2023 9:09 pm 
Offline
User avatar

Joined: Sun Dec 26, 2021 8:27 pm
Posts: 182
plasmo wrote:
What is your bit-bang transmitter routine looks like? Transmitter is more visible (observable with scope), so you can see the effects of instruction timings. My first 6502 design had hardware receiver but bit-bang transmitter so I just wrote a software delay loop and tweak the loop counter and watch with scope until I have the right timing. THEN I read about the instruction timings and annotated the software accordingly. It was a cart-before-the-horse approach but there were so much 6502 to learn initially.
Bill


Things are working now - if I hammer my keyboard I can still induce bit errors, but that's probably just my main loop that's too long (just blinking an LED) - it should probably just be waiting for serial :)
In the TX routine the cycles I count and measure seem to be off by 2 - I would change a NOP to a LDA ZP if I knew for sure the bit time is now 103 instead of 104 or 105.

Code:
;Calling code:
main:
;LED STUFF HERE

wait:
lda DRA ; Check serial 3c
and #$01 ; 2c
bne noserial ; 2c
jsr serial_rx ; 6c
jsr ssd1306_sendchar
jsr i2c_stop
tya ; ssd1306_sendchar puts char in y
jsr serial_tx ; Echo
noserial:
lda READTDI
bne wait ; Loop until timer runs out
jmp main ; loop


; jsr = 6 cycles
; sta (zp) = 3 cycles
; WTD8DI * 8 cycles
; We can ignore branches while timer not 0
;lda (zp) = 3 cycles
; bne = 2 cycles (not taken since timer expired)
; rts = 6 cycles
; = 20 + ((WTD8DI - 1) * 8) cycles
delay_short:
sta WTD8DI ; Divide by 8 = A contains ticks to delay/8
shortwait:
nop; Sample every 8 cycles instead of every 6
lda READTDI
bne shortwait
rts

;Returns byte in A - assumes 9600 baud = ~104us/bit, 1 cycle = 1us (1 MHz)
;We should call this ASAP when RX pin goes low - let's assume it just happened (13 cycles ago)
serial_rx:
;Minimum 13 cycles before we get here
lda #15 ; 1.5 period-ish ; 2 cycles
jsr delay_short ; 140c
ldx #8 ; 2 cycles
;149 cycles to get here
serial_rx_loop: ;103 cycles
lda DRA ; Read RX bit 0 ; 3 cycles
lsr ; Shift received bit into carry - in many cases might be safe to just lsr DRA ; 2 cycles
ror inb ; Rotate into MSB 5 cycles
lda #9 ; 2 cycles
jsr delay_short ; Delay until middle of next bit - overhead; 84 cycles
nop ; 2c
dex ; 2c
bne serial_rx_loop ; 3 cycles
;Should already be in the middle of the stop bit
; We can ignore the actual stop bit and use the time for other things
; Received byte in inb
lda inb ; Put in A
rts

serial_tx:
sta outb
lda #$fd ; Inverse bit 1
and DRA
sta DRA ; Start bit
lda #8 ; 2c
jsr delay_short ; 20 + (8-1)*8 = 76c ; Start bit total 104 cycles - 104 cycles measured
nop ; 2c
nop ; 2c
ldx #8 ; 2c
serial_tx_loop:
lsr outb ; 5c
lda DRA ; 3c
bcc tx0 ; 2/3c
ora #2 ; TX bit is bit 1 ; 2c
bcs bitset ; BRA 3c
tx0:
nop ; 2c
and #$fd ; 2c
bitset:
sta DRA ; 3c
; Delay one period - overhead ; 101c total ; 103c measured
lda #8 ; 2c
jsr delay_short ; 20 + (8-1)*8 = 76c
dex ; 2c
bne serial_tx_loop ; 3c
nop; 2c ; Last bit 98us counted, 100us measured
nop; 2c
nop; 2c
nop; 2c
lda DRA ;3c
ora #2 ; 2c
sta DRA ; Stop bit 3c
lda #8 ; 2c
jsr delay_short
rts


Quote:
(Another edit - by the way, a nice trick for shifting in exactly 8 bits is to initialise the target memory location with $80, then ROR the bits into it, and after each ROR, if the carry is clear, loop back for another bit. That single set bit will make its way along the byte, and ultimately terminate the read loop, without needing to also manipulate an index register.)


Yup! That'll come in handy!!

_________________
---
New new new new new video out! Serial Bootloader for my 65uino
Also, check out: I2C on a 6502 Single Board Computer
and Complete hardware overview of my 6502 SBC R1 :)


Top
 Profile  
Reply with quote  
PostPosted: Wed Aug 09, 2023 12:13 pm 
Offline
User avatar

Joined: Sun Dec 26, 2021 8:27 pm
Posts: 182
Thank you all so much for the help debugging!
It turned into my new video about the serial bootloader for the 65uino.

https://youtu.be/nOmQd3y3pDw

Thanks again!

_________________
---
New new new new new video out! Serial Bootloader for my 65uino
Also, check out: I2C on a 6502 Single Board Computer
and Complete hardware overview of my 6502 SBC R1 :)


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 15 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 7 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: