I am currently working on a design based on WDC65C02 (clocked at 1 MHz) and some vintage NMOS peripherals. I use CC65 compiler to write a C code.
t his point I was able to run HD44780 display, MOS6551 UART, M6242 RTC, 8255 GPIO chip and 373 output port. Interrupts are also running (tested with RTC 1 Hz tick signal). Only Motorola MC6840 timer turned out to be problematic. At this point I'd like to use T3 to generate Arduino-like millis() function. Unfortunately there is something wrong. Basically function which counts milliseconds is only incremented few times a second. If I remove MC6840 chip from the socket, it is incremented once a second, as if it was triggered by RTC (I assume lack of the chip causes erroneous read of interrupt status flags).
My code looks as follow:
mc6840.hCode:
#ifndef _MC6840_H_
#define _MC6840_H_
#include <inttypes.h>
#define MC6840_CON13 (*(volatile uint8_t*)0x6480)
#define MC6840_CON2 (*(volatile uint8_t*)0x6481)
#define MC6840_STATUS (*(volatile uint8_t*)0x6481)
#define MC6840_TIMER1 (*(volatile uint16_t*)0x6482)
#define MC6840_TIMER2 (*(volatile uint16_t*)0x6484)
#define MC6840_TIMER3 (*(volatile uint16_t*)0x6486)
#define Swap2Bytes(val) \
( (((val) >> 8) & 0x00FF) | (((val) << 8) & 0xFF00) )
#define TM_COUNTER_OUTPUT_ENABLE 0x80
#define TM_COUNTER_OUTPUT_DISABLE 0x00
#define TM_INTERUPT_ENABLE 0x40
#define TM_INTERUPT_DISABLE 0x00
#define TM_CONT_OP_MODE1 0x00
#define TM_FREQ_COMP_MODE1 0x20
#define TM_CONT_OP_MODE2 0x10
#define TM_PUSE_WIDTH_COMP_MODE2 0x30
#define TM_SINGLE_SHOT_MODE1 0x08
#define TM_FREQ_COMP_MODE2 0x28
#define TM_SINGLE_SHOT_MODE2 0x18
#define TM_PULSE_WIDTH_COMP_MODE2 0x38
#define TM_NORMAL_16BIT 0x00
#define TM_DUAL_8BIT 0x04
#define TM_EXT_CLK 0x00
#define TM_SYS_CLK 0x02
#define TMCR1_ALL_TIMERS_ALLOWED 0x00
#define TMCR1_ALL_TIMERS_PRESET 0x01
#define TMCR2_WRITE_CR3 0x00
#define TMCR2_WRITE_CR1 0x01
#define TMCR3_T3_CLK_NO_PRESCALER 0x00
#define TMCR3_T3_CLK_DIV8_PRESCALER 0x01
void m6840_init (void);
uint32_t millis(void);
uint32_t uptime(void);
uint16_t get_geiger_pulses (void);
void set_sound_frequency (uint16_t freq);
#endif //_MC6840_H_
mc6840.cCode:
#include <6502.h>
#include "mc6840.h"
#include "ff.h"
volatile uint32_t milliseconds = 0;
volatile uint32_t uptime_value = 0;
volatile uint8_t geiger_ind = 0;
volatile uint16_t geiger_pulses[60];
volatile uint8_t geiger_ovf = 0;
uint32_t tmp;
uint8_t i;
void m6840_init (void) {
MC6840_CON13 = TM_COUNTER_OUTPUT_DISABLE | TM_INTERUPT_ENABLE | TM_CONT_OP_MODE1 | TM_NORMAL_16BIT | TM_SYS_CLK | TMCR3_T3_CLK_NO_PRESCALER; //CON3 first by default after reset. TIMER3 is used for systick. Output diable and sys clk.
MC6840_CON2 = TM_COUNTER_OUTPUT_ENABLE | TM_INTERUPT_DISABLE | TM_CONT_OP_MODE1 | TM_NORMAL_16BIT | TM_SYS_CLK | TMCR2_WRITE_CR1; //CON2 accessed directly. TIMER2 generates sound. Output enable and sys clk.
MC6840_CON13 = TM_COUNTER_OUTPUT_DISABLE | TM_INTERUPT_DISABLE | TM_CONT_OP_MODE1 | TM_NORMAL_16BIT | TM_EXT_CLK | TMCR1_ALL_TIMERS_ALLOWED; //CON1. TIMER1 counts Geiger pulses, so external source
MC6840_TIMER1 = Swap2Bytes(0xFFFF); //Remember about endianess - MC6800 family is big endian, 6502 is little endian. Remember that timer is decremented.
MC6840_TIMER2 = Swap2Bytes(0x07D0); //500 Hz signal on audio output
MC6840_TIMER3 = Swap2Bytes(0x03E8); //1ms interrupt
for (i=0; i<60; i++) geiger_pulses[i] = 0x00;
}
DWORD get_fattime (void)
{
return(0);
}
uint32_t millis(void) {
CLI();
tmp = milliseconds;
SEI();
return tmp;
}
uint32_t uptime (void) {
CLI();
tmp = uptime_value;
SEI();
return tmp;
}
void update_geiger_pulses (void) {
CLI();
geiger_pulses[geiger_ind] = 0xFFFF - Swap2Bytes(MC6840_TIMER1);
SEI();
geiger_ind++;
if (geiger_ind > 59) geiger_ind = 0;
}
uint16_t get_geiger_pulses (void) {
for (i=0; i<60; i++) tmp += geiger_pulses[i];
return tmp/60;
}
void set_sound_frequency (uint16_t freq) {
if (freq < 24) return; //Min required frequency. It will solve div/0 and word size issues.
MC6840_TIMER2 = Swap2Bytes(1000000/freq);
}
interrupt.sCode:
; ---------------------------------------------------------------------------
; interrupt.s
; ---------------------------------------------------------------------------
;
; Interrupt handler.
;
; Checks for a BRK instruction and returns from all valid interrupts.
.import _init, _milliseconds, _geiger_ovf, _uptime_value
.import _update_geiger_pulses
.export _irq_int, _nmi_int
MC6840_STA = $6481
M6242_STA = $640D
MC6840_TIMER1 = $6482
MC6840_TIMER3 = $6486
.segment "CODE"
.PC02 ; Force 65C02 assembly mode
; ---------------------------------------------------------------------------
; Non-maskable interrupt (NMI) service routine
_nmi_int: RTI ; Return from all NMI interrupts
; ---------------------------------------------------------------------------
; Maskable interrupt (IRQ) service routine
_irq_int: PHX ; Save X register contents to stack
TSX ; Transfer stack pointer to X
PHA ; Save accumulator contents to stack
INX ; Increment X so it points to the status
INX ; register value saved on the stack
LDA $100,X ; Load status register contents
AND #$10 ; Isolate B status bit
BNE break ; If B = 1, BRK detected
; ---------------------------------------------------------------------------
; IRQ detected
irq_chk_t1:
LDA MC6840_STA ; Load MC6840 status register
TAX ; Preserve A, it will be destroyed by AND
AND #$01 ; Check if T1 interrupt flag is set
BEQ irq_chk_t3 ; If flag is cleared, go to the next stage (check T3 interrupt flag)
INC _geiger_ovf ; Otherwise increment timer overflow variable
LDA MC6840_TIMER1 ; You must read T1 to clear interrupt flag!!!
LDA MC6840_TIMER1+1
irq_chk_t3:
TXA ; Restore A that might have been destroyed by AND
AND #$04 ; Check if T3 interrupt flag is set
BEQ irq_chk_rtc ; If flag is cleared, go to the next stage
LDA MC6840_TIMER3 ; You must read T3 to clear interrupt flag
LDA MC6840_TIMER3+1
INC _milliseconds ; Increment milliseconds variable
BNE irq_chk_rtc
INC _milliseconds+1
BNE irq_chk_rtc
INC _milliseconds+2
BNE irq_chk_rtc
INC _milliseconds+3
irq_chk_rtc:
LDA M6242_STA ; Load RTC status register
AND #04 ; Check if IRQ flag is set
BEQ irq_ret ; If not, this is not RTC interrupt, co continue
LDA #$00 ; Otherwise clear flag
STA M6242_STA
JSR _update_geiger_pulses ; goto the subroutine updating giger pulses
INC _uptime_value ; And increment uptime variable
BNE irq_ret
INC _uptime_value+1
BNE irq_ret
INC _uptime_value+2
BNE irq_ret
INC _uptime_value+3
BNE irq_ret
irq_ret: PLA ; Restore accumulator contents
PLX ; Restore X register contents
RTI ; Return from all IRQ interrupts
; ---------------------------------------------------------------------------
; BRK detected, stop
break: JMP _init ; If BRK is detected, something very bad
; has happened, restart
And small piece responsible for reporting current value with UART:
Code:
mos6551_puts("Millis: ");
ultoa(millis(), buffer, 10);
mos6551_puts(buffer);
mos6551_puts("\r\n");
What is wrong?