6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Nov 24, 2024 12:49 pm

All times are UTC




Post new topic Reply to topic  [ 6 posts ] 
Author Message
PostPosted: Wed Apr 29, 2020 6:30 am 
Offline

Joined: Tue Jun 19, 2018 8:28 am
Posts: 122
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.h
Code:
#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.c
Code:
#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.s
Code:
; ---------------------------------------------------------------------------
; 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?


Top
 Profile  
Reply with quote  
PostPosted: Wed Apr 29, 2020 8:06 am 
Offline

Joined: Tue Sep 03, 2002 12:58 pm
Posts: 336
I don't know if it's the problem, but these lines concern me:
Code:
    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

The 6840 requires the two bytes of the timer registers to be accessed in a specific order. You're letting the compiler decide what that order is. It might be correct, or it might not. There's no way to know without looking at the code it produces. I would strongly recommend accessing these registers one byte at a time, as described in section 3.5 of the datasheet.

Another, sillier, possibility is that you've mapped a device called 6840 to address $6480, and thus offended it.


Top
 Profile  
Reply with quote  
PostPosted: Sun May 03, 2020 5:26 pm 
Offline

Joined: Tue Jun 19, 2018 8:28 am
Posts: 122
John West wrote:
The 6840 requires the two bytes of the timer registers to be accessed in a specific order. You're letting the compiler decide what that order is. It might be correct, or it might not. There's no way to know without looking at the code it produces. I would strongly recommend accessing these registers one byte at a time, as described in section 3.5 of the datasheet.


It turned out that I forgot that decrements value of the register, so I initialized it with wrong value.
Issue you mentioned also surfaced, but somewhere else. Compiler tried to access value in wrong order. After rewriting this bit in pure assembly everything is fine.

Quote:
Another, sillier, possibility is that you've mapped a device called 6840 to address $6480, and thus offended it.


Damn! I just realized it is a case. I did it accidentally. :)


Top
 Profile  
Reply with quote  
PostPosted: Sun May 03, 2020 6:31 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8546
Location: Southern California
It looks like you might have CLI and SEI backwards, like in these:
Code:
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;
}

CLI is CLear Interrupt disable bit, and SEI is SEt Interrupt disable bit. When you want to disable interrupting, you must set the bit, not clear it.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Mon May 04, 2020 11:20 am 
Offline

Joined: Tue Jun 19, 2018 8:28 am
Posts: 122
GARTHWILSON wrote:
It looks like you might have CLI and SEI backwards, like in these:
CLI is CLear Interrupt disable bit, and SEI is SEt Interrupt disable bit. When you want to disable interrupting, you must set the bit, not clear it.


Weird... According to the documentation you are right.
But interrupts work just fine anyway. :)


Top
 Profile  
Reply with quote  
PostPosted: Mon May 04, 2020 6:32 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8546
Location: Southern California
Atlantis wrote:
GARTHWILSON wrote:
It looks like you might have CLI and SEI backwards, like in these:
CLI is CLear Interrupt disable bit, and SEI is SEt Interrupt disable bit. When you want to disable interrupting, you must set the bit, not clear it.

Weird... According to the documentation you are right.
But interrupts work just fine anyway. :)

They're getting serviced in the short times between the CLIs and SEIs, but sometimes it causes the very problem you're trying to prevent with them.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC


Who is online

Users browsing this forum: Google [Bot] and 23 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: