Monday, February 6, 2012

MKHBC-8-R1 now has Real Time Clock/Calendar based on DS1685 IC.

Connecting DS1685 to my system proven tricky (see previous post). Programming the chip was no less twisted than hardware part. But I finally got it right. The issues I had were in fact not related (in most part) to the IC itself, but rather to the proper passing and parsing of the arguments/parameters from compiled with cc65  'C' program to my written in assembler API library routines. New software module has been created and added to mkhbcos.lib archive.
Source code consists of assembler API implementation mkhbcos_ds1685.s and 'C' header file mkhbcos_ds1685.h to be used with 'C' programs using the API. I am yet to create simplified versions of these functions to be used/called directly from assembler code without all the unnecessary in such case overhead in coding (e.g: passing/retrieving arguments via stack).

Here is the DS1685 API implementation. Functions are compliant to cc65 calling convention:

;------------------------------------------------------------------
;
; File:     mkhbcos_ds1685.s
; Author:    Marek Karcz
; Purpose:    Implement's initialization routines and API for
;            Real Time Clock chip DS1685 with multiplexed
;           address bus connected to buffered I/O bus as an I/O
;           device.
;
; Revision history:
;    2012-01-31:
;        Initial revision.
;       (NOTE: These routines will eventually make their way
;              to EPROM as a part of firmware. At that time,
;              this file will be revised to call up the API
;              functions in the kernal table, instead of
;              being full implementation.)
;
;    2012-02-06:
;        Implementation.
;
;------------------------------------------------------------------

; M.O.S. API defines (kernal)

.define     mos_StrPtr       $E0
.define     tmp_zpgPt        $F6
.define     IOBase           $c000
.define     IO4              IOBase+4*256

.define     DSCALADDR        IO4+1
.define     DSCALDATA        IO4

.setcpu    "6502"
.import ldaxysp,pushax,popax,pusha,popa,staspidx
.import incsp2,ldauidx

.define        sp                $20

RegB    =    tmp_zpgPt
RegA    =    tmp_zpgPt+1
RegXB   =    tmp_zpgPt+2
RegXA   =    tmp_zpgPt+3
RegC    =    tmp_zpgPt+4
Temp    =    tmp_zpgPt+5

; DS RTC registers mask bits

; reg. A
DSC_REGA_UIP    =    %10000000
DSC_REGA_DV2    =    %01000000
DSC_REGA_DV1    =    100000
DSC_REGA_DV0    =    010000
DSC_REGA_RS3    =    001000
DSC_REGA_RS2    =    000100
DSC_REGA_RS1    =    000010
DSC_REGA_RS0    =    000001
; aliases
DSC_REGA_CTDWN  =    DSC_REGA_DV2
DSC_REGA_OSCEN  =    DSC_REGA_DV1
DSC_REGA_BSEL   =    DSC_REGA_DV0
DSC_REGA_BANK0  =    $EF
DSC_REGA_BANK1  =    $10

; reg. B
DSC_REGB_SET    =    %10000000
DSC_REGB_PIE    =    %01000000
DSC_REGB_AIE    =    100000
DSC_REGB_UIE    =    010000
DSC_REGB_SQWE   =    001000
DSC_REGB_DM     =    000100
DSC_REGB_24o12  =    000010
DSC_REGB_DSE    =    000001

; aliases

DSC_REGB_UNSET    =    %01111111


; code

.export _ds1685_init,_ds1685_rdclock,_ds1685_setclock,_ds1685_settime
;,_read

.segment "CODE"

; Initialize DS1685 RTC chip.
; unsigned char __fastcall__ ds1685_init (unsigned char regb,
;                                         unsigned char rega,
;                                         unsigned char regextb,
;                                         unsigned char regexta)

.proc _ds1685_init: near

    ; get parameters, put them in temp. buffer
    jsr pusha
    ldy #$03
    ldx #$00
    lda (sp),y
    sta RegB
    ldy #$02
    ldx #$00
    lda (sp),y
    sta RegA
    ldy #$01
    ldx #$00
    lda (sp),y
    sta RegXB
    ldy #$00
    ldx #$00
    lda (sp),y
    sta RegXA
    ; initialize control register B
    ldx RegB
    lda #$0b
    jsr WrRTC
    ; read status register C
    lda #$0c
    jsr RdRTC
    sta RegC
    ; initialize control register A, switch to bank 1
    lda RegA
    ora #DSC_REGA_BANK1        ; switch to bank 1
    tax
    lda #$0a
    jsr WrRTC
    ; initialize extended control register B
    ldx RegXB
    lda #$4b
    jsr WrRTC
    ; initialize extended control register A
    ldx RegXA
    lda #$4a
    jsr WrRTC
    ; switch to original bank
    lda #$0a
    jsr RdRTC
    and #DSC_REGA_BANK0
    tax
    lda #$0a
    jsr WrRTC
    ldx #$00
    lda RegC
    rts

.endproc

.segment "CODE"

; read clock data
; struct ds1685_clkdata *ds1685_rdclock(struct ds1685_clkdata *buf);
; struct ds1685_clkdata
; {
;    unsigned char seconds;
;    unsigned char minutes;
;    unsigned char hours;
;    unsigned char dayofweek;
;    unsigned char date; // day
;    unsigned char month;
;    unsigned char year;
;    unsigned char century;
; };

.proc _ds1685_rdclock:near

; disable update transfers

    lda #$0b
    jsr RdRTC
    sta RegB            ; save register B for later
    ora #DSC_REGB_SET
    tax
    lda #$0b
    jsr WrRTC


; determine mode (BCD or BIN)

    lda #DSC_REGB_DM
    sta Temp
    lda RegB
    bit Temp
    bne binmoderead

    ldy #$01
    jsr ldaxysp
    jsr incsp2
    rts

binmoderead:
    ; binary mode read
   
    lda #$00        ; load register address of seconds
    jsr RdRTC        ; read register value to Acc
    and #111111    ; mask 2 upper bits
    ldy #$00
    jsr Tfer2RetBuf    ; transfer to return buffer at index 0 (seconds)
   
    lda #$02        ; load register address of minutes
    jsr RdRTC        ; read register value to Acc
    and #111111    ; mask 2 upper bits
    ldy #$01
    jsr Tfer2RetBuf    ; transfer to return buffer at index 1 (minutes)

    lda #$04        ; load register address of hours
    jsr RdRTC        ; read register value to Acc
    pha
    lda #DSC_REGB_24o12
    sta Temp
    lda RegB        ; determine which hours mode (12/24 hours)
    bit Temp
    beq mode12hbin
    pla
    and #011111    ; mask 3 upper bits for 24H mode read
    clc
    bcc storehours
mode12hbin:
    pla
    and #001111    ; mask 4 upper bits for 12H mode read
storehours:
    ldy #$02
    jsr Tfer2RetBuf    ; transfer to return buffer at index 2 (hours)

    lda #$06        ; load register address of day (of week)
    jsr RdRTC        ; read register value to Acc
    and #000111    ; mask 5 upper bits
    ldy #$03
    jsr Tfer2RetBuf    ; transfer to return buffer at index 3 (dayofweek)

    lda #$07        ; load register address of date (day of month)
    jsr RdRTC        ; read register value to Acc
    and #011111    ; mask 3 upper bits
    ldy #$04
    jsr Tfer2RetBuf    ; transfer to return buffer at index 4 (date)

    lda #$08        ; load register address of month
    jsr RdRTC        ; read register value to Acc
    and #001111    ; mask 4 upper bits
    ldy #$05
    jsr Tfer2RetBuf    ; transfer to return buffer at index 5 (month)

    lda #$09        ; load register address of year
    jsr RdRTC        ; read register value to Acc
    and #%011111111    ; mask the highest bit
    ldy #$06
    jsr Tfer2RetBuf    ; transfer to return buffer at index 6 (year)

    lda #$0a
    jsr RdRTC
    ora #DSC_REGA_BANK1        ; switch to bank 1
    tax
    lda #$0a
    jsr WrRTC

    lda #$48        ; load register address of century
    jsr RdRTC        ; read register value to Acc
    ldy #$07
    jsr Tfer2RetBuf    ; transfer to return buffer at index 7 (century)

    lda #$0a
    jsr RdRTC
    and #DSC_REGA_BANK0        ; switch to original bank
    tax
    lda #$0a
    jsr WrRTC

    ; enable update transfers

    lda #$0b
    jsr RdRTC
    and #DSC_REGB_UNSET
    tax
    lda #$0b
    jsr WrRTC

    ldy #$01
    jsr ldaxysp
    jsr incsp2
    rts

.endproc


.segment "CODE"

; set clock data
; void ds1685_setclock (struct ds1685_clkdata *buf);

.proc _ds1685_setclock:near

    ; disable update transfers
    ; set binary mode
    lda #$0b
    jsr RdRTC
    ora #DSC_REGB_SET
    ora #DSC_REGB_DM
    tax
    lda #$0b
    jsr WrRTC

    ldy #$00            ; get argument 0 (seconds)
    jsr GetParFromSpIdx
    tax
    lda #$00            ; write to DS1685 seconds register
    jsr WrRTC

    ldy #$01            ; get argument 1 (minutes)
    jsr GetParFromSpIdx
    tax
    lda #$02            ; write to DS1685 minutes register
    jsr WrRTC

    ldy #$02            ; hours
    jsr GetParFromSpIdx
    tax
    lda #$04
    jsr WrRTC

    ldy #$03            ; day of week
    jsr GetParFromSpIdx
    tax
    lda #$06
    jsr WrRTC

    ldy #$04            ; date (day of month)
    jsr GetParFromSpIdx
    tax
    lda #$07
    jsr WrRTC

    ldy #$05            ; month
    jsr GetParFromSpIdx
    tax
    lda #$08
    jsr WrRTC

    ldy #$06            ; year
    jsr GetParFromSpIdx
    tax
    lda #$09
    jsr WrRTC

    ; enable update transfers

    lda #$0b
    jsr RdRTC
    and #DSC_REGB_UNSET
    tax
    lda #$0b
    jsr WrRTC
    ; disable update transfers
    lda #$0b
    jsr RdRTC
    ora #DSC_REGB_SET
    tax
    lda #$0b
    jsr WrRTC

    lda #$0a            ; get reg. A
    jsr RdRTC
    ora #DSC_REGA_BANK1        ; switch to bank 1
    tax
    lda #$0a
    jsr WrRTC

    ldy #$07            ; century
    jsr GetParFromSpIdx
    tax
    lda #$48
    jsr WrRTC

    lda #$0a
    jsr RdRTC
    and #DSC_REGA_BANK0        ; switch to original bank
    tax
    lda #$0a
    jsr WrRTC

    ; enable update transfers

    lda #$0b
    jsr RdRTC
    and #DSC_REGB_UNSET
    tax
    lda #$0b
    jsr WrRTC

    jsr incsp2

    rts

.endproc

.segment "CODE"

; set clock data
; void ds1685_settime (struct ds1685_clkdata *buf);

.proc _ds1685_settime:near

    ; switch to original bank

    lda #$0a
    jsr RdRTC
    and #DSC_REGA_BANK0
    tax
    lda #$0a
    jsr WrRTC

    ; disable update transfers
    ; set binary mode
    lda #$0b
    jsr RdRTC
    ora #DSC_REGB_SET
    ora #DSC_REGB_DM
    tax
    lda #$0b
    jsr WrRTC

    ldy #$00            ; get argument 0 (seconds)
    jsr GetParFromSpIdx
    tax
    lda #$00            ; write to DS1685 seconds register
    jsr WrRTC

    ldy #$01            ; get argument 1 (minutes)
    jsr GetParFromSpIdx
    tax
    lda #$02            ; write to DS1685 minutes register
    jsr WrRTC

    ldy #$02            ; hours
    jsr GetParFromSpIdx
    tax
    lda #$04
    jsr WrRTC

    ; enable update transfers

    lda #$0b
    jsr RdRTC
    and #DSC_REGB_UNSET
    tax
    lda #$0b
    jsr WrRTC

    jsr incsp2

    rts

.endproc

.segment "CODE"

; helper procedures

;-------------------------------------------------------------------------------
; Write DS1685 address (Acc).
;-------------------------------------------------------------------------------

WrRTCAddr:
    sta DSCALADDR
    rts

;-------------------------------------------------------------------------------
; Write DS1685 data (Acc).
;-------------------------------------------------------------------------------

WrRTCData:
    sta DSCALDATA
    rts

;-------------------------------------------------------------------------------
; Read DS1685 data (-> Acc).
;-------------------------------------------------------------------------------

RdRTCData:
    lda DSCALDATA
    rts

;-------------------------------------------------------------------------------
; Write DS1685 Acc = Addr, X = Data
;-------------------------------------------------------------------------------

WrRTC:
    jsr WrRTCAddr
    txa
    jsr WrRTCData
    rts

;-------------------------------------------------------------------------------
; Read DS1685 Acc = Addr -> A = Data
;-------------------------------------------------------------------------------

RdRTC:
    jsr WrRTCAddr
    jsr RdRTCData
    rts

;-------------------------------------------------------------------------------
; Transfer A to return buffer at index Y.
;-------------------------------------------------------------------------------

Tfer2RetBuf:

    pha
    tya
    pha
    ldy     #$01    ; transfer to return buffer
    jsr     ldaxysp
    jsr     pushax
    ldx     #$00
    pla
    tay
    pla
    jsr     staspidx
    rts

;-------------------------------------------------------------------------------
; Load to A from arguments buffer/stack at index Y.
;-------------------------------------------------------------------------------

GetParFromSpIdx:

    tya
    pha
    ldy     #$01    ; get buffer
    jsr     ldaxysp
    sta     Temp
    pla
    tay
    lda     Temp
    jsr     ldauidx
    rts
;------------------------------ END OF FILE -------------------------------------




Here is the 'C' header file:

/*
 * File:     mkhbcos_ds1685.h
 * Purpose:    Declarations and definitions for
 *          DS1685 RTC (Real Time Clock) chip API.
 * Author:    Marek Karcz
 * Created:    02/05/2012
 *
 * Revision history:
 *
 *
 */

#ifndef MKHBCOS_DS1685
#define MKHBCOS_DS1685

// DS RTC registers mask bits

// reg. A

#define DSC_REGA_UIP    0x80    // %10000000
#define DSC_REGA_DV2    0x40    // %01000000
#define DSC_REGA_DV1    0x20    // 100000
#define DSC_REGA_DV0    0x10    // 010000
#define DSC_REGA_RS3    0x08    // 001000
#define DSC_REGA_RS2    0x04    // 000100
#define DSC_REGA_RS1    0x02    // 000010
#define DSC_REGA_RS0    0x01    // 000001

// aliases

#define DSC_REGA_CTDWN    DSC_REGA_DV2
#define DSC_REGA_OSCEN    DSC_REGA_DV1
#define DSC_REGA_BSEL    DSC_REGA_DV0
#define DSC_REGA_BANK0    0xEF
#define DSC_REGA_BANK1    0x10

// reg. B

#define DSC_REGB_SET    0x80    // %10000000
#define DSC_REGB_PIE    0x40    // %01000000
#define DSC_REGB_AIE    0x20    // 100000
#define DSC_REGB_UIE    0x10    // 010000
#define DSC_REGB_SQWE    0x08    // 001000
#define DSC_REGB_DM        0x04    // 000100
#define DSC_REGB_24o12    0x02    // 000010
#define DSC_REGB_DSE    0x01    // 000001

// aliases

#define DSC_REGB_UNSET    0x7F    // %01111111


struct ds1685_clkdata
{
    unsigned char seconds;
    unsigned char minutes;
    unsigned char hours;
    unsigned char dayofweek;
    unsigned char date; // day
    unsigned char month;
    unsigned char year;
    unsigned char century;
};

unsigned char __fastcall__ ds1685_init (unsigned char regb,
                                        unsigned char rega,
                                        unsigned char regextb,
                                        unsigned char regexta);

struct     ds1685_clkdata *ds1685_rdclock     (struct ds1685_clkdata *buf);
void                     ds1685_setclock (struct ds1685_clkdata *buf);
void                     ds1685_settime     (struct ds1685_clkdata *buf);

#endif

My 'C' program code for enhanced shell is too long to be quoted here. I promise to publish it later. For now it is still work in progress.

Code and DS1685 in action:

Figure 1: Screenshot of my enhanced shell with transcript of programs for setting and then reading calendar/time information.


 I gained valuable experience from this project so far in digital circuit design and coding of 6502 CPU. Now I really really need to promise myself not to add any more hardware to the system while it is still on the bread board. Time to get dirty with the soldering iron.

Cheers!

Marek