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