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