6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Thu May 09, 2024 3:26 am

All times are UTC




Post new topic Reply to topic  [ 48 posts ]  Go to page Previous  1, 2, 3, 4  Next
Author Message
 Post subject:
PostPosted: Mon Feb 13, 2012 2:34 pm 
Offline

Joined: Fri Mar 26, 2010 12:43 am
Posts: 12
Thanks all again, very insightful tips.

Now, I have, i believe, a clever idea. ;)

Consider only pin 6 changes on portb. and bit7 is 0.
What about this to sample one bit?

Code:
               ldx portb
               cpx #$3f
               ror @
               .....


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Feb 13, 2012 4:36 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3354
Location: Ontario, Canada
Nice! By using X, you leave A free to act as the input shift register. And there's no ROLing needed to get each input bit into Carry.

Here's a version that has no restrictions regarding the other bits on portb. And it lets you choose which bit will serve as your serial input -- you can use whatever bit happens to be convenient for the hardware. Just change the equate ComBit in the first line. Also we save memory by using a loop for the first 7 bits, rather than inline code throughout.

Code:
    ComBit equ     $10          ;in this example bit4 of portb is the serial data input

?RcvByte   lda     #ComBit      ;
?r         bit     portb        ; 4~
           beq     ?r           ; 2~ when the loop actually escapes

           lda     #$40         ; 2    Seed the data shift register
           sta     pom          ; 4      so it can count the incoming bits
           #cycle  #6           ; 6

           clc                  ; 2
?Loop      lda     #NOT(ComBit) ; 2    Acc=  NOT($10) = $ef. Carry=0.
           ora     portb        ; 4    Acc=  $ef | $ff
           adc     #$01         ; 2    Carry=  0 | 1
           ror     pom          ; 6    Use Absolute addressing mode!
           bcc     ?Loop        ; 3 (2 upon escape)

           #cycle #2            ; 2
           lda     #NOT(ComBit) ; 2    Acc=  NOT($10) = $ef. Carry=1
           ora     portb        ; 4    Acc=  $ef | $ff
           adc     #$00         ; 2    Carry=  0 | 1
           lda     pom          ; 4
           ror     @            ; 2
           eor     #$ff         ; 2   Invert the bits
           rts

For timing reasons, the shift register location pom is addressed using Absolute mode (whether or not it resides in Zero Page). I used Absolute mode because I needed to waste a cycle in the loop, and I suspect the #cycle macro can't provide a delay of 1. (Outside the loop pom could use Absolute or Zero-Pg addressing; for consistency I've used Absolute.)

Now, lemme see.... Did I add up all those cycles properly? :) (#cycle #2 after escaping the loop may seem excessive, but #cycle #1 is not an option. Things will be ahead of schedule here anyway, because we ideally should allow 17.36~ per bit, not 17.)

-- Jeff


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 14, 2012 3:04 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3354
Location: Ontario, Canada
syscall, I was looking at your code snippet above and decided I like your way of updating Carry better than mine. For some reason I don't tend to think of using CPX (and its siblings, CPY & CMP), but they're a better choice. Here's an excerpt; with slight variation this happens in two places in my routine. CMP is less bug-prone because it doesn't require Carry to be in a known state beforehand. (And earlier in the code I can save a byte by eliminating the CLC instruction.)
Code:
OLD:     lda     #NOT(ComBit) ; 2    Acc=  NOT($10) = $ef. Carry=0.
         ora     portb        ; 4    Acc=  $ef | $ff
         adc     #$01         ; 2    Carry=  0 | 1

NEW:     lda     #NOT(ComBit) ; 2    Acc=  NOT($10) = $ef. Carry= don't-care.
         ora     portb        ; 4    Acc=  $ef | $ff
         cmp     #$ff         ; 2    Carry=  0 | 1           <-------- altered


Also a footnote: I mentioned possibly using Absolute address mode on a Zero-Page memory location. It may be a challenge to convince the assembler to do this! -- usually it will use Z-Pg mode at every opportunity. However, I think some assemblers let you include a directive to force Absolute mode. Others on this forum could advise you on this. But as a default approach you can just cram the bytes in as if they're data. This ain't pretty, but it'll work...! :D Cheers,

Jeff
Code:
         db      $6e           ; Op-code for ror absolute instruction
         dw      pom           ; Operand for ror absolute instruction


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 14, 2012 7:34 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8432
Location: Southern California
I haven't spent much time on this so I hope I'm not behind the discussion curve—but if you're going to go that route, how about having FE or other high number in the accumulator so that when you add the port contents, it sets the carry if the bit is high and not otherwise.  (Starting with FE instead of FF would remove the requirement to CLC beforehand.)

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 15, 2012 7:11 am 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3354
Location: Ontario, Canada
GARTHWILSON wrote:
Starting with FE instead of FF would remove the requirement to CLC beforehand.
I think I see what you mean, and it'd work for syscall's original example, where the serial data comes in on bit 6 of portb. But perhaps you overlooked the spec for the revised routine -- namely that any bit of portb can be used (as determined by the equate ComBit).

IOW it may be bit 0 that gets our serial data. In that case, after masking the irrelevant bits we'll still be left with two possible values in A (according to the state of the serial data pin), but the values will only differ by 1. To reliably copy that into Carry by adding (or subtracting) a constant, the arithmetic must be exact. If I've misunderstood what you were getting at, let me know.

-- Jeff


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 15, 2012 8:14 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8432
Location: Southern California
The idea is that other bits are grounded (if you're going to waste the rest of the port anyway).  Another possibility is to make the other bits outputs while you're receiving RS-232, and put 00 in the output register, so when you read it back you get 0's in the other bits regardless of electrical connections.  If you use bit 0, then you'd have to CLC before adding, because adding anything (even a 0) to FF with C set will result in C being set.  Otherwise, if you use any other bit, you could start with FE, and:
FE + 02 = 00 if C had been clear or 01 if C had been set, and C ends up set either way;
FE + 04 = 02 if C had been clear or 03 if C had been set, and C ends up set either way;
FE + 08 = 06 if C had been clear or 07 if C had been set, and C ends up set either way;
etc., and:
FE + 00 = FE if C had been clear or FF if C had been set, and C ends up clear either way.

The arithmetic doesn't have to be exact that way, as long as you use any bit except bit 0.  C is set anytime you ADC a high-enough value from the port to cross the FF-to-00 line, even if you go way past it.  If you were to be watching for the Z flag, then yes, it would have to be exact; but not with the C flag.

Edit:  Hmmm... Actually, if you wanted to avoid constantly re-loading the FE into the accumulator (or other register you use for the same purpose as above), could you not keep 00 in the register (loaded before the start bit was detected), then CMP or CPX or CPY the port value (since that doesn't affect the register)?  Then you could even use bit 0 of the port if you wanted to, because subtracting any non-zero value from 00 leaves the C flag clear after any compare instruction.  The C flag then acts like the Z flag.  (Then you would have to EOR #$FF on the completed byte when it's done coming in, since the C flag was backwards.)  A or X or Y, whichever one you use for the comparison, would remain constant throughout the byte, and not have to be re-loaded with each bit.  If you use either X or Y for counting the bits, you could use the other one for the compare (either CPY or CPX), and rotate the incoming byte into A, then EOR #$FF at the end, leaving the result in A.  If the byte is rotated into a memory location instead of A, it takes more cycles for the read-modify-write ROR instruction, and then more cycles for bringing it into A to EOR it (or to LDA #$FF then EOR abs or zp).

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 15, 2012 4:39 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3354
Location: Ontario, Canada
GARTHWILSON wrote:
FE + 02 = 00 if C had been clear or 01 if C had been set, and C ends up set either way;
FE + 04 = 02 if C had been clear or 03 if C had been set, and C ends up set either way;
FE + 08 = 06 if C had been clear or 07 if C had been set, and C ends up set either way;
etc., and:
FE + 00 = FE if C had been clear or FF if C had been set, and C ends up clear either way.
Thank goodness! I thought that's what you meant, but you've explained it much better than I could. Still, I don't quite see how it'll gain us anything in this case.

Quote:
[...] (if you're going to waste the rest of the port anyway). Another possibility is to make the other bits outputs [...]
The routine is intended to allow you to use any bit -- even bit 0 -- for the serial input, disregarding any activity (including outputs) on the other bits. I think I succeeded in that... Here it is with the comments (hopefully) improved!

Code:
    ComBit equ     $01 ;To determine the serial data input, change ComBit as required and reassemble.
                       ;In this example the input is on bit 0.

?RcvByte   lda     #ComBit      ; 2~   A= 01 ie; the ComBit
?r         bit     portb        ; 4~   read portb and test ComBit
           beq     ?r           ; 2~   fall thru only when the Start Bit has been detected

           lda     #$40         ; 2    Seed the data shift register so it can
           sta     pom          ; 4      count the incoming bits.(No need to use X or Y for counting.)
           #cycle  #8           ; 8

?Loop                           ;      the loop inputs the first 7 bits
           lda     #NOT(ComBit) ; 2    A= fe
           ora     portb        ; 4    A= fe             else A= ff
           cmp     #$ff         ; 2       fe-ff clears C else    ff-ff sets C
           ror     pom          ; 6    Use Absolute addressing mode! (for timing)
           bcc     ?Loop        ; 3/2

           #cycle #2            ; 2
           lda     #NOT(ComBit) ; 2    A= fe (etc as above)
           ora     portb        ; 4
           cmp     #$ff         ; 2
           lda     pom          ; 4    Retrieve first 7 bits
           ror     @            ; 2    ror Accumulator
           eor     #$ff         ; 2    Invert the bits
           rts

Quote:
If you use bit 0, then you'd have to CLC before adding
Right; 65xx CPUs don't offer an "Add Without Carry" instruction. In order to omit the CLC and still get exact arithmetic I switched to using CMP instead -- which is "Subtract Without Carry" (aka /Borrow).

Quote:
If the byte is rotated into a memory location instead of A, it takes more cycles
True, of course, and the final rotate does occur in A. The interesting thing about this sort of routine is that a certain amount of delay (between input samples) is mandatory and cannot be "optimized." However, what you can do is reorder operations as much as possible so there's a minimum of processing before the first input sample and after the last. As leeeeee noted, it's beneficial to allow as many extra cycles as possible to do something with the byte once you have it.

-- Jeff


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Feb 16, 2012 2:45 am 
Offline

Joined: Tue Nov 18, 2003 8:41 pm
Posts: 250
syscall wrote:
Thanks all again, very insightful tips.

Now, I have, i believe, a clever idea. ;)

Consider only pin 6 changes on portb. and bit7 is 0.
What about this to sample one bit?

Code:
               ldx portb
               cpx #$3f
               ror @
               .....


Presumably the port will never read as 3F or else 40 (bit 6 = 1) and
3F (bit 6 = 0) will both result in C = 1


Instead, why not initialize the X register to 3F and then just cpx portb
and rotate A

ie

Code:

  ldx #$3F

  .              ; wait for start bit etc
  .

  .rept 8

  [bit timing delay]

  cpx portb
  ror @
 
  .endr

  rts


Bit 6 = 1 will produce a 0 in the carry and bit 6 = 0 will produce a 1


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Feb 26, 2012 3:15 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
It might be interesting to compare notes with MartinB on stardot - I mentioned his code previously

He got 115kBaud out of a (mostly) 2MHz 6502, which is a very similar problem, but the solution looks quite different. It's all unrolled to straight-line, and assumes the input is bit 7 of the port. The code to fetch two bits looks like this:
Code:
TAY:LDA&FE60:ASLA:TYA:RORA:NOP
TAY:LDA&FE60:ASLA:TYA:RORA:NOP:NOP


(That's BBC BASIC format assembler. The Beeb's 6502 slows to 1MHz to access the parallel port, which makes cycle counting a bit more interesting. I'm not sure if the mismatched NOPs is to account for the timing of 115kBaud or to account for the 1MHz slowdown which has to wait for the slower clock to be in the right phase.)

Cheers
Ed


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Feb 27, 2012 2:27 pm 
Offline

Joined: Tue Nov 18, 2003 8:41 pm
Posts: 250
bogax wrote:
Following on Leeeeee's suggestion.

IFF bit 6 is the only thing changing on the port you might be able to
do this:

Code:
?r              bit     portb
                bvc     ?r

                #cycle #8

                .rept 7

                #cycle #5
                eor     portb   ;4
                ora     #$40    ;2
                eor     portb   ;4
                ror     @       ;2
                .endr

                #cycle #5
                eor     portb   ;4
                ora     #$40    ;2
                eor     portb   ;4
                rol     @       ;2

                rts


However it spreads the port read out over 6 cycles and reduces
the margin for jitter/misalignment

It occurs to me that this might be better

Code:
                lda     #$FF
                sec

?r              bit     portb
                bvc     ?r

                #cycle #6

                .rept 7

                nop             ;2
                eor     portb   ;4
                and     #$40    ;2
                eor     byte    ;3
                ror     @       ;2
                sta     byte    ;4 absolute addressing to waste a cycle
 
               .endr

                nop             ;2
                eor     portb   ;4
                and     #$40    ;2
                eor     byte    ;3
                rol     @       ;2

                rts


But given that bit 7 is always 0 the comparison trick seems nicer all the way around.
So convolving that with Leeeeee's code
Code:
                lda     #$3F

?r              bit     portb
                bvc     ?r

                #cycle #3

                .rept 7

                #cycle #8
                cmp     portb   ;4
                ror     pom     ;5
                .endr

                #cycle #8
                cmp     portb   ;4
                lda     pom     ;3
                ror     @       ;2
                rts


It avoids using a register but costs 3 cycles


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Mar 05, 2012 9:36 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
BigEd wrote:
MartinB's code as posted on Stardot:
Code:
TAY:LDA&FE60:ASLA:TYA:RORA:NOP
TAY:LDA&FE60:ASLA:TYA:RORA:NOP:NOP


Just to note that bogax and MartinB have been discussing this, and MartinB has explained his cunning plan here - his per-bit cycle count is sometimes 16 and sometimes 18 cycles to get the best approximation he could to the right sampling rate, and successfully sample the final bit despite the initial uncertainties starting all the way from the 6522 handling the start bit edge.


Top
 Profile  
Reply with quote  
PostPosted: Sun Sep 09, 2012 10:59 pm 
Offline

Joined: Fri Mar 26, 2010 12:43 am
Posts: 12
Hello everyone, just thought I let you know that I managed to get 68,2kbit/s with on the fly checksuming from this sucker :)
Here's the code if anyone's interested. Sekbuf is buffer, count is 0=256bytes $80=128 bytes.

Code:
read_from_serial   ldx    #$3f
         lda   #0
         tay
         sta   cksum
         sta   sekbuf-1,y   

wb:      bit   portb   <- startbit
         bvc   wb      ;not taken = 3c

         lda   cksum   ;3
         clc      ;2
         adc   sekbuf-1,y   
                         adc   #0   ;2
         sta   cksum   ;3

         cpx   portb   ; b0   ;4c      ; 22 cycles from start bit - so in half of bit 0
         ror   @      ;2c
         #cycle #7

         cpx   portb   ; b1         ; 13 cycles (bittime) from previous bit ... etc
         ror   @
         #cycle #7

         cpx   portb   ; b2
          ror   @
         #cycle #7

         cpx   portb   ; b3
         ror   @
         #cycle #8

         cpx   portb   ;b4
         ror   @
         #cycle #9

         cpx   portb   ;b5
         ror   @
         #cycle #9

         cpx   portb   ;b6
         ror   @
         #cycle #8   ;9

         cpx   portb   ;b7
         ror   @


         sta   sekbuf,y   
         iny
         cpy   count
         bne wb

; add one byte that wasn't added because of loop's end
         dey
         lda   sekbuf,y   
         clc
         adc   cksum
         adc   #0
         sta   cksum
         rts




Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 10, 2012 6:57 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8177
Location: Midwestern USA
syscall wrote:
Hello everyone, just thought I let you know that I managed to get 68,2kbit/s with on the fly checksuming from this sucker :)

That's quite an achievement for the 6502 running at 1 MHz. I recall that the fake RS-232 routines in the Commodore 64 kernal could barely manage 1200 bits per second (bps) without error, and those were allegedly written by professionals. I also recall an article in the old Transactor magazine in which the author described how he got the C-64 up to 2400 bps by essentially rewriting the kernal code. He also worked out a solution for errata in the MOS 6526 complex interface adapter that would occasionally result in a dropped bit.

That said, one of the first hardware add-ons I did to my C-64 was to concoct an RS-232 board that plugged into the cartridge port, using the 6551 ACIA that the Commodore programmers had labored so hard to emulate in software. The board was good for 19.2 Kbps, although at the time, reasonably priced modems were only capable of 1200 bps.

Incidentally, the pedantic devil in me must point out that the 57kbaud reference in the topic subject line is technically incorrect terminology. :D What you mean is 57 Kbps. Baud and bits per second are two different measurements. The former refers to the rate of change used in the signaling method. Depending on the signaling format being used, the baud rate may happen to coincide with the number of bits being transmitted during the same period of time. In modem technology, that relationship only holds true if the signaling device (e.g., modem) is using audio frequency shift keying. Modems running at 1200 Bps and higher use some form of phase shift keying (PSK) or quadrature amplitude modulation (QAM), in which baud is some sub-multiple of bps. For example, the old Bell 212A standard used PSK to achieve 1200 bps. The baud was 600. QAM created a even greater disparity between baud and bps.

Ah, the good old days of a reliable 2400 bps modem. :lol:

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 10, 2012 7:39 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10800
Location: England
I'd describe baud as the symbol rate.

That article you mention might be this: George Hug, Toward 2400 (Transactor Vol. 9, Issue 3 - Feb. 1989 p.62)
In PDF here: http://ia700701.us.archive.org/21/items ... 3_text.pdf
In HTML reader here: http://archive.org/stream/transactor-ma ... 3/mode/1up
(via http://archive.org/details/transactor-magazines-v9-i03)

Interesting, thanks!

Cheers, Ed


Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 10, 2012 9:57 am 
Offline

Joined: Fri Mar 26, 2010 12:43 am
Posts: 12
BigDumbDinosaur wrote:
Incidentally, the pedantic devil in me must point out that the 57kbaud reference in the topic subject line is technically incorrect terminology. :D What you mean is 57 Kbps.

Yes, I always misuse both terms :( , so I appreciate the pedantic devil in you :) Thanks.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 48 posts ]  Go to page Previous  1, 2, 3, 4  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 14 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: