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