]> git.sur5r.net Git - cc65/blob - libsrc/c64/rs232.s
This commit was generated by cvs2svn to compensate for changes in r2,
[cc65] / libsrc / c64 / rs232.s
1 ;
2 ; SwiftLink/Turbo-232 v0.90 device driver, by Craig Bruce, 14-Apr-1998.
3 ;
4 ; This software is Public Domain.  It is in Buddy assembler format.
5 ;
6 ; This device driver uses the SwiftLink RS-232 Serial Cartridge, available from
7 ; Creative Micro Designs, Inc, and also supports the extensions of the Turbo232
8 ; Serial Cartridge.  Both devices are based on the 6551 ACIA chip.  It also
9 ; supports the "hacked" SwiftLink with a 1.8432 MHz crystal.
10 ;
11 ; The code assumes that the kernal + I/O are in context.  On the C128, call
12 ; it from Bank 15.  On the C64, don't flip out the Kernal unless a suitable
13 ; NMI catcher is put into the RAM under then Kernal.  For the SuperCPU, the
14 ; interrupt handling assumes that the 65816 is in 6502-emulation mode.
15 ;
16 ;--------------------------------------------------------------------------
17 ;
18 ; Adapted for the use with the cc65 runtime library by
19 ; Ullrich von Bassewitz (uz@musoftware.de) 02-May-1999.
20 ;
21 ; All external functions are C callable, the return value is an error code.
22 ;
23
24
25         .importzp       ptr1, ptr2, tmp1, tmp2
26         .import         popa, popax
27         .export         _rs232_init, _rs232_params, _rs232_done, _rs232_get
28         .export         _rs232_put, _rs232_pause, _rs232_unpause, _rs232_status
29
30
31 useC64  = 1
32 .if useC64
33    NmiExit  = $febc     ;exit address for nmi
34 .else
35    NmiExit  = $ff33     ;exit address for nmi
36 .endif
37
38 ACIA            = $DE00
39
40
41
42 ;----------------------------------------------------------------------------
43 ;
44 ; Global variables
45 ;
46
47 .bss
48 DropCnt:        .res    4       ; Number of bytes lost from rx buffer full
49 Initialized:    .res    1       ; Flag indicating driver is initialized
50 Stopped:        .res    1       ; Flow-stopped flag
51 RtsOff:         .res    1       ;
52 Errors:         .res    1       ; Number of bytes received in error, low byte
53 Turbo232:       .res    1       ; Flag indicating turbo-232
54 HackedFlag:     .res    1       ; Flag indicating hacked-crystal swiftlink
55 CpuSpeed:       .res    1       ; In MHz
56 RecvHead:       .res    1       ; Head of receive buffer
57 RecvTail:       .res    1       ; Tail of receive buffer
58 RecvFreeCnt:    .res    1       ; Number of bytes in receive buffer
59 SendHead:       .res    1       ; Head of send buffer
60 SendTail:       .res    1       ; Tail of send buffer
61 SendFreeCnt:    .res    1       ; Number of bytes free in send buffer
62 BaudCode:       .res    1       ; Current baud in effect
63
64 ; Send and receive buffers: 256 bytes each
65 RecvBuf:        .res    256
66 SendBuf:        .res    256
67
68 .data
69 NmiContinue:    .byte   $4c     ; JMP instruction for NMI save -- continue
70 NmiSave:        .res    2       ; normal NMI handler
71
72 ; Switftlink register offsets
73 RegData                 = 0     ; Data register
74 RegStatus               = 1     ; Status register
75 RegCommand              = 2     ; Command register
76 RegControl              = 3     ; Control register
77 RegClock                = 7     ; Turbo232 external baud-rate generator
78
79 ; Error codes. Beware: The codes must match the codes in the C header file
80 ErrNotInitialized       = $01
81 ErrBaudTooFast          = $02
82 ErrBaudNotAvail         = $03
83 ErrNoData               = $04
84 ErrOverflow             = $05
85
86
87 .code
88
89 ;----------------------------------------------------------------------------
90 ;
91 ; unsigned char __fastcall__ rs232_init (char hacked);
92 ; /* Initialize the serial port, install the interrupt handler. The parameter
93 ;  * must be true (non zero) for a hacked swiftlink and false (zero) otherwise.
94 ;  */
95 ;
96
97 _rs232_init:
98         bit     Initialized     ;** shut down if started
99         bpl     @L1
100         pha
101         jsr     _rs232_done
102         pla
103
104 ;** set hacked-crystal
105
106 @L1:    sta     HackedFlag
107
108 ;** check for turbo-232
109
110         lda     #$00
111         sta     ACIA+RegControl
112         tax
113         lda     ACIA+RegClock
114         beq     @L3
115         dex
116 @L3:    stx     Turbo232
117
118 ;** get C128/C64 cpu speed
119 .if useC64
120         lda     #1
121         sta     CpuSpeed
122 .else
123         lda     $d030
124         and     #$01
125         clc
126         adc     #1
127         sta     CpuSpeed
128 .endif
129
130 ;** check for super-cpu at 20 MHz
131
132         bit     $d0bc
133         bmi     @L4
134         bit     $d0b8
135         bvs     @L4
136         lda     #20
137         sta     CpuSpeed
138
139 ;** initialize buffers & control
140
141 @L4:    lda     #0
142         sta     RecvHead
143         sta     SendHead
144         sta     RecvTail
145         sta     SendTail
146         sta     Errors
147         sta     Stopped
148         lda     #255
149         sta     RecvFreeCnt
150         sta     SendFreeCnt
151
152 ;** set up nmi's
153
154         lda     $318
155         ldy     $319
156         sta     NmiSave+0
157         sty     NmiSave+1
158         lda     #<NmiHandler
159         ldy     #>NmiHandler
160         sta     $318
161         sty     $319
162
163 ;** set default to 2400-8N1, enable interrupts
164
165         lda     ACIA+RegData
166         lda     ACIA+RegStatus
167         lda     #$18
168         bit     HackedFlag
169         bpl     @L5
170         lda     #$1a
171 @L5:    sta     ACIA+RegControl
172
173         lda     #$01
174         sta     RtsOff
175         ora     #$08
176         sta     ACIA+RegCommand
177         lda     #$06
178         sta     BaudCode
179
180 ;** return
181         lda     #$ff
182         sta     Initialized
183         lda     #$00
184         tax
185         rts
186
187 ;----------------------------------------------------------------------------
188 ;
189 ; unsigned char __fastcall__ rs232_params (unsigned char params, unsigned char parity);
190 ; /* Set the port parameters. Use a combination of the #defined values above. */
191 ;
192 ; Set communication parameters.
193 ;
194 ; baud rates              stops     word    |   parity
195 ; ---------------------   -----     -----   |   ---------
196 ; $00=50     $08=9600     $00=1     $00=8   |   $00=none
197 ; $01=110    $09=19200    $80=2     $20=7   |   $20=odd
198 ; $02=134.5  $0a=38400              $40=6   |   $60=even
199 ; $03=300    $0b=57600              $60=5   |   $A0=mark
200 ; $04=600    $0c=115200                     |   $E0=space
201 ; $05=1200   $0d=230400
202 ; $06=2400   $0e=future
203 ; $07=4800   $0f=future
204 ;
205
206 _rs232_params:
207         jsr     CheckInitialized        ;** check initialized
208         bcc     @L1
209         rts
210
211 ; Save new parity
212
213 @L1:    and     #%11100000
214         ora     #%00000001
215         sta     tmp2
216
217 ; Check cpu speed against baud rate
218
219         jsr     popa
220         sta     tmp1
221         and     #$0f
222         cmp     #$0c
223         bcc     @L3
224         ldx     CpuSpeed
225         cpx     #1+1
226         bcc     @L2
227         cmp     #$0c
228         beq     @L3
229         cpx     #4
230         bcs     @L3
231 @L2:    lda     #ErrBaudTooFast
232         bne     @L9
233
234 ; Set baud/parameters
235
236 @L3:    lda     tmp1
237         and     #$0f
238         tax
239         lda     NormBauds,x
240         bit     HackedFlag
241         bpl     @L4
242         lda     HackBauds,x
243 @L4:    cmp     #$ff
244         bne     @L5
245         lda     #ErrBaudNotAvail
246         bne     @L9
247
248 @L5:    tax
249         and     #$30
250         beq     @L6
251         bit     Turbo232
252         bmi     @L6
253         lda     #ErrBaudNotAvail
254         bne     @L9
255
256 @L6:    lda     tmp1
257         and     #$0f
258         sta     BaudCode
259         lda     tmp1
260         and     #%11100000
261         ora     #%00010000
262         sta     tmp1
263         txa
264         and     #$0f
265         ora     tmp1
266         sta     ACIA+RegControl
267         txa
268         and     #%00110000
269         beq     @L7
270         lsr
271         lsr
272         lsr
273         lsr
274         eor     #%00000011
275         sta     ACIA+RegClock
276
277 ; Set new parity
278
279 @L7:    lda     tmp2
280         sta     RtsOff
281         ora     #%00001000
282         sta     ACIA+RegCommand
283         lda     #0
284 @L9:    ldx     #0
285         rts
286
287 .rodata
288
289 NormBauds:
290    .byte $ff,$ff,$ff,$05,$06,$07,$08,$0a,$0c,$0e,$0f,$10,$20,$30,$ff,$ff
291 HackBauds:
292    .byte $01,$03,$04,$06,$07,$08,$0a,$0c,$0e,$0f,$ff,$ff,$00,$ff,$ff,$ff
293      ;in:  0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
294      ;baud50 110 134   3   6  12  24  48  96  19  38  57 115 230 exp exp
295      ;out masks: $0F=Baud, val$FF=err
296      ;           $30=t232ExtBaud: $00=none, $10=57.6, $20=115.2, $30=230.4
297 .code
298
299 ;----------------------------------------------------------------------------
300 ;
301 ; unsigned char __fastcall__ rs232_done (void);
302 ; /* Close the port, deinstall the interrupt hander. You MUST call this function
303 ;  * before terminating the program, otherwise the machine may crash later. If
304 ;  * in doubt, install an exit handler using atexit(). The function will do
305 ;  * nothing, if it was already called.
306 ;  */
307 ;
308
309
310 _rs232_done:
311         bit     Initialized             ;** check initialized
312         bpl     @L9
313
314 ; Stop interrupts, drop DTR
315
316         lda     RtsOff
317         and     #%11100010
318         ora     #%00000010
319         sta     ACIA+RegCommand
320
321 ; Restore NMI vector
322
323         lda     NmiSave+0
324         ldy     NmiSave+1
325         sta     $318
326         sty     $319
327
328 ; Flag uninitialized
329
330 @L9:    lda     #$00
331         sta     Initialized
332         tax
333         rts
334
335 ;----------------------------------------------------------------------------
336 ;
337 ; unsigned char __fastcall__ rs232_get (char* B);
338 ; /* Get a character from the serial port. If no characters are available, the
339 ;  * function will return RS_ERR_NO_DATA, so this is not a fatal error.
340 ;  */
341 ;
342
343 _rs232_get:
344         jsr     CheckInitialized        ; Check if initialized
345         bcc     @L1
346         rts
347
348 ; Check for bytes to send
349
350 @L1:    sta     ptr1
351         stx     ptr1+1                  ; Store pointer to received char
352         ldx     SendFreeCnt
353         cpx     #$ff
354         beq     @L2
355         lda     #$00
356         jsr     TryToSend
357
358 ; Check for buffer empty
359
360 @L2:    lda     RecvFreeCnt
361         cmp     #$ff
362         bne     @L3
363         lda     #ErrNoData
364         ldx     #0
365         rts
366
367 ; Check for flow stopped & enough free: release flow control
368
369 @L3:    ldx     Stopped
370         beq     @L4
371         cmp     #63
372         bcc     @L4
373         lda     #$00
374         sta     Stopped
375         lda     RtsOff
376         ora     #%00001000
377         sta     ACIA+RegCommand
378
379 ; Get byte from buffer
380
381 @L4:    ldx     RecvHead
382         lda     RecvBuf,x
383         inc     RecvHead
384         inc     RecvFreeCnt
385         ldx     #$00
386         sta     (ptr1,x)
387         txa                             ; Return code = 0
388         rts
389
390 ;----------------------------------------------------------------------------
391 ;
392 ; unsigned char __fastcall__ rs232_put (char B);
393 ; /* Send a character via the serial port. There is a transmit buffer, but
394 ;  * transmitting is not done via interrupt. The function returns
395 ;  * RS_ERR_OVERFLOW if there is no space left in the transmit buffer.
396 ;  */
397 ;
398
399 _rs232_put:
400         jsr     CheckInitialized        ; Check initialized
401         bcc     @L1
402         rts
403
404 ; Try to send
405
406 @L1:    ldx     SendFreeCnt
407         cpx     #$ff
408         beq     @L2
409         pha
410         lda     #$00
411         jsr     TryToSend
412         pla
413
414 ; Put byte into send buffer & send
415
416 @L2:    ldx     SendFreeCnt
417         bne     @L3
418         lda     #ErrOverflow
419         ldx     #$00
420         rts
421
422 @L3:    ldx     SendTail
423         sta     SendBuf,x
424         inc     SendTail
425         dec     SendFreeCnt
426         lda     #$ff
427         jsr     TryToSend
428         lda     #$00
429         tax
430         rts
431
432 ;----------------------------------------------------------------------------
433 ;
434 ; unsigned char __fastcall__ rs232_pause (void);
435 ; /* Assert flow control and disable interrupts. */
436 ;
437
438 _rs232_pause:
439 ; Check initialized
440         jsr     CheckInitialized
441         bcc     @L1
442         rts
443
444 ; Assert flow control
445
446 @L1:    lda     RtsOff
447         sta     Stopped
448         sta     ACIA+RegCommand
449
450 ; Delay for flow stop to be received
451
452         ldx     BaudCode
453         lda     PauseTimes,x
454         jsr     DelayMs
455
456 ; Stop rx interrupts
457
458         lda     RtsOff
459         ora     #$02
460         sta     ACIA+RegCommand
461         lda     #0
462         tax
463         rts
464
465
466 .rodata
467 ; Delay times: 32 byte-receive times in milliseconds, or 100 max.
468 ; Formula = 320,000 / baud
469 PauseTimes:
470         .byte 100,100,100,100,100,100,100,067,034,017,009,006,003,002,001,001
471           ;in:  0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
472           ;baud50 110 134   3   6  12  24  48  96  19  38  57 115 230 exp exp
473 .code
474
475 ;----------------------------------------------------------------------------
476 ;
477 ; unsigned char __fastcall__ rs232_unpause (void);
478 ; /* Re-enable interrupts and release flow control */
479 ;
480
481 _rs232_unpause:
482 ; Check initialized
483         jsr     CheckInitialized
484         bcc     @L1
485         rts
486
487 ; Re-enable rx interrupts & release flow control
488
489 @L1:    lda     #$00
490         sta     Stopped
491         lda     RtsOff
492         ora     #%00001000
493         sta     ACIA+RegCommand
494
495 ; Poll for stalled char & exit
496
497         jsr     PollReceive
498         lda     #0
499         tax
500         rts
501
502 ;----------------------------------------------------------------------------
503 ;
504 ; unsigned char __fastcall__ rs232_status (unsigned char* status,
505 ;                                          unsigned char* errors);
506 ; /* Return the serial port status. */
507 ;
508
509 _rs232_status:
510         sta     ptr2
511         stx     ptr2+1
512         jsr     popax
513         sta     ptr1
514         stx     ptr1+1
515         jsr     CheckInitialized
516         bcs     @L9
517
518 ; Get status
519
520         lda     ACIA+RegStatus
521         ldy     #0
522         sta     (ptr1),y
523         jsr     PollReceive             ; bug-recovery hack
524         lda     Errors
525         sta     (ptr2),y
526         tya
527         tax
528 @L9:    rts
529
530 ;----------------------------------------------------------------------------
531 ;
532 ; NMI handler
533 ; C128 NMI overhead=76 cycles: int=7, maxLatency=6, ROMenter=33, ROMexit=30
534 ; C64  NMI overhead=76 cycles: int=7, maxLatency=6, ROMenter=34, ROMexit=29
535 ;
536 ; timing: normal=76+43+9=128 cycles, assertFlow=76+52+9=137 cycles
537 ;
538 ; C128 @ 115.2k: 177 cycles avail (fast)
539 ; C64  @  57.6k: 177 cycles avail, worstAvail=177-43? = 134
540 ; SCPU @ 230.4k: 868 cycles avail: for a joke!
541 ;
542
543 NmiHandler:
544 .if useC64
545         pha
546         lda     ACIA+RegStatus          ;(4) ;status ;check for byte received
547         and     #$08                    ;(2)
548         beq     @L9                     ;(2*)
549         cld
550         txa
551         pha
552         tya
553         pha
554 .else
555         lda     ACIA+RegStatus          ;(4) ;status ;check for byte received
556         and     #$08                    ;(2)
557         beq     NmiNorm                 ;(2*)
558 .endif
559         lda     ACIA+RegStatus          ;(4) opt ;status ;check for receive errors
560         and     #$07                    ;(2) opt
561         beq     @L1                     ;(3*)opt
562         inc     Errors                  ;(5^)opt
563 @L1:    lda     ACIA+RegData            ;(4) ;data  ;get byte and put into receive buffer
564         ldy     RecvTail                ;(4)
565         ldx     RecvFreeCnt             ;(4)
566         beq     @L3                     ;(2*)
567         sta     RecvBuf,y               ;(5)
568         inc     RecvTail                ;(6)
569         dec     RecvFreeCnt             ;(6)
570         cpx     #33                     ;(2)  ;check for buffer space low
571         bcc     @L2                     ;(2*)
572         jmp     NmiExit                 ;(3)
573
574 ; Assert flow control
575
576 @L2:    lda     RtsOff                  ;(3)  ;assert flow control if buffer space too low
577         sta     ACIA+RegCommand ;(4) ;command
578         sta     Stopped                 ;(3)
579         jmp     NmiExit                 ;(3)
580
581 ; Drop this char
582
583 @L3:    inc     DropCnt+0               ;not time-critical
584         bne     @L4
585         inc     DropCnt+1
586         bne     @L4
587         inc     DropCnt+2
588         bne     @L4
589         inc     DropCnt+3
590 @L4:    jmp     NmiExit
591
592 @L9:
593 .if useC64
594    pla
595 .endif
596    jmp NmiContinue
597
598 ;----------------------------------------------------------------------------
599 ;
600 ; CheckInitialized  -  internal check if initialized
601 ; Set carry and an error code if not initialized, clear carry and do not
602 ; change any registers if initialized.
603 ;
604
605 CheckInitialized:
606         bit     Initialized
607         bmi     @L1
608         lda     #ErrNotInitialized
609         ldx     #0
610         sec
611         rts
612
613 @L1:    clc
614         rts
615
616 ;----------------------------------------------------------------------------
617 ; Try to send a byte. Internal routine. A = TryHard
618
619 TryToSend:
620         sta     tmp1            ; Remember tryHard flag
621 @L0:    lda     SendFreeCnt
622         cmp     #$ff
623         beq     @L3             ; Bail out
624
625 ; Check for flow stopped
626
627 @L1:    lda     Stopped
628         bne     @L3             ; Bail out
629
630 ;** check that swiftlink is ready to send
631
632 @L2:    lda     ACIA+RegStatus
633         and     #$10
634         bne     @L4
635         bit     tmp1            ;keep trying if must try hard
636         bmi     @L0
637 @L3:    rts
638
639 ;** send byte and try again
640
641 @L4:    ldx     SendHead
642         lda     SendBuf,x
643         sta     ACIA+RegData
644         inc     SendHead
645         inc     SendFreeCnt
646         jmp     @L0
647
648
649 ;----------------------------------------------------------------------------
650 ;
651 ; PollReceive - poll for rx char
652 ;   This function is useful in odd cases where the 6551 has a character in
653 ;   it but it fails to raise an NMI.  It might be edge-triggering conditions?
654 ;   Actually, I'm not entirely sure that this condition can still arrise, but
655 ;   calling this function does no harm.
656 ;
657
658 PollReceive:
659         lda     #$08
660         and     ACIA+RegStatus
661         beq     @L9
662         and     ACIA+RegStatus
663         beq     @L9
664         lda     ACIA+RegData
665         ldx     RecvFreeCnt
666         beq     @L9
667         ldx     RecvTail
668         sta     RecvBuf,x
669         inc     RecvTail
670         dec     RecvFreeCnt
671 @L9:    rts
672
673 ;----------------------------------------------------------------------------
674 ;
675 ;  DelayMs : delay for given number of milliseconds
676 ;    This implementation isn't very rigerous; it merely delays for the
677 ;    approximate number of clock cycles for the processor speed.
678 ;    Algorithm:
679 ;       repeat for number of milliseconds:
680 ;          repeat for number of MHz of cpu speed:
681 ;             delay for 1017 clock cycles
682 ;
683
684 DelayMs:                        ;( .A=milliseconds )
685 @L1:    ldy     CpuSpeed
686 @L2:    ldx     #203            ;(2)
687 @L3:    dex                     ;(2)
688         bne     @L3             ;(3) // 1017 cycles
689         dey
690         bne     @L2
691         sec
692         sbc     #1
693         bne     @L1
694         rts
695
696 .end
697
698
699