1123 lines
35 KiB
C
1123 lines
35 KiB
C
/*
|
|
Hatari - acia.c
|
|
|
|
Copyright (C) 2012 by Nicolas Pomarède
|
|
|
|
This file is distributed under the GNU Public License, version 2 or at
|
|
your option any later version. Read the file gpl.txt for details.
|
|
|
|
MC6850 ACIA emulation.
|
|
*/
|
|
|
|
const char ACIA_fileid[] = "Hatari acia.c : " __DATE__ " " __TIME__;
|
|
|
|
|
|
/* 2012/09/28 [NP] Start of the full rewrite of the MC6850 ACIA emulation, using the official */
|
|
/* datasheets for maximum accuracy, as well as bit level serial transfers (start, */
|
|
/* stop and parity bits). */
|
|
/* 2012/12/21 [NP] Add accurate cycles delays when accessing an ACIA register, taking E Clock */
|
|
/* into account. */
|
|
/* 2013/04/24 [NP] Remove INTERRUPT_ACIA_MFP used to add a 4 cycle delay when IRQ is set, as this */
|
|
/* delay is now correctly handled directly in the MFP since 2013/03/01. */
|
|
|
|
|
|
|
|
|
|
/*
|
|
6850 ACIA (Asynchronous Communications Inferface Apdater)
|
|
|
|
References :
|
|
- MC6850 datasheet by Motorola (DS9493R4, 1985)
|
|
- A6850 datasheet by Altera (A-DS-A6850-01, 1996) (nearly identical component)
|
|
|
|
Others references :
|
|
- MAME's 6850acia.c for RTS, CTS and DCD behaviour
|
|
|
|
|
|
Pins :
|
|
Vss
|
|
RX DATA Receive Data
|
|
RX CLK Receive Clock
|
|
TX CLK Transmitter Clock
|
|
RTS Request To Send
|
|
TX DATA Transmitter Data
|
|
IRQ Interrupt Request
|
|
CS 0,1,2 Chip Select
|
|
RS Register Select
|
|
Vcc Voltage
|
|
R/W Read/Write
|
|
E Enable
|
|
D0-D7 Data
|
|
DCD Data Carrier Detect
|
|
CTS Clear To Send
|
|
|
|
Registers :
|
|
0xfffc00 Keyboard ACIA Control (write)/Status(read)
|
|
0xfffc02 Keyboard ACIA Data
|
|
0xfffc04 MIDI ACIA Control (write)/Status(read)
|
|
0xfffc06 MIDI ACIA Data
|
|
|
|
Control Register (0xfffc00 write) :
|
|
Bits 0,1 - These bits determine by which factor the transmitter and receiver
|
|
clock will be divided. These bits also are joined with a master reset
|
|
function. The 6850 has no separate reset line, so it must be
|
|
accomplished though software.
|
|
0 0 RXCLK/TXCLK without division
|
|
0 1 RXCLK/TXCLK by 16 (MIDI)
|
|
1 0 RXCLK/TXCLK by 64 (Keyboard)
|
|
1 1 Master RESET
|
|
Bits 2,3,4 - These so-called Word Select bits tell whether 7 or 8 data-bits are
|
|
involved; whether 1 or 2 stop-bits are transferred; and the type of parity
|
|
Bits 5,6 - These Transmitter Control bits set the RTS output pin, and allow or prevent
|
|
an interrupt through the ACIA when the send register is emptied. Also, BREAK signals
|
|
can be sent over the serial output by this line. A BREAK signal is nothing more than
|
|
a long seqence of null bits
|
|
0 0 RTS low, transmitter IRQ disabled
|
|
0 1 RTS low, transmitter IRQ enabled
|
|
1 0 RTS high, transmitter IRQ disabled
|
|
1 1 RTS low, transmitter IRQ disabled, BREAK sent
|
|
Bit 7 - The Receiver Interrupt Enable bit determines whether the receiver interrupt
|
|
will be on. An interrupt can be caused by the DCD line chaning from low to high, or
|
|
by the receiver data buffer filling. Besides that, an interrupt can occur from an
|
|
OVERRUN (a received character isn't properly read from the processor).
|
|
0 Interrupt disabled
|
|
1 Interrupt enabled
|
|
|
|
Status Register (0xfffc00 read) :
|
|
Bit 0 - When this bit is high, the RX data register is full. The byte must be read
|
|
before a new character is received (otherwise an OVERRUN happens)
|
|
Bit 1 - This bit reflects the status of the TX data buffer. An empty register
|
|
set the bit.
|
|
Bit 2 - A low-high change in pin DCD sets bit 2. If the receiver interrupt is allowable, the IRQ
|
|
is cancelled. The bit is cleared when the status register and the receiver register are
|
|
read. This also cancels the IRQ. Bit 2 register remains highis the signal on the DCD pin
|
|
is still high; Bit 2 register low if DCD becomes low.
|
|
Bit 3 - This line shows the status of CTS. This signal cannot be altered by a mater reset,
|
|
or by ACIA programming.
|
|
Bit 4 - Shows 'Frame Errors'. Frame errors are when no stop-bit is recognized in receiver
|
|
switching. It can be set with every new character.
|
|
Bit 5 - This bit display the previously mentioned OVERRUN condition. Bit 5 is reset when the
|
|
RX buffer is read.
|
|
Bit 6 - This bit recognizes whether the parity of a received character is correct. The bit is
|
|
set on an error.
|
|
Bit 7 - This signals the state of the IRQ pins; this bit make it possible to switch several
|
|
IRQ lines on one interrupt input. In cases where an interrupt is program-generated, bit 7
|
|
can tell which IC cut off the interrupt.
|
|
|
|
ST ACIA :
|
|
CTS,DCD and RTS are not connected
|
|
The keyboard ACIA addresses are 0xfffc000 and 0xfffc02.
|
|
The MIDI ACIA addresses are 0xfffc004 and 0xfffc06.
|
|
Default keyboard parameters are : 8-bit word, 1 stopbit, no parity, 7812.5 baud; 500KHz/64 (keyboard clock div)
|
|
Default MIDI parameters are as above but : 31250 baud; 500KHz/16 (MIDI clock div)
|
|
|
|
|
|
CPU cycles in the ST :
|
|
When accessing an ACIA register, an additional delay will be added to the usual number of
|
|
cycles for this CPU instruction. This delay is made of 2 parts (for a 68000 at 8 MHz) :
|
|
- a fixed delay of 6 cycles.
|
|
- a variable delay of 0 to 8 cycles to synchronise with the E Clock.
|
|
|
|
Examples for some common instructions measured on a real 520 STF
|
|
(with a0=$fffffc00 and 'n' the delay for E Clock) :
|
|
move.b (a0),d2 : 14 cycles = 8 + 6 + n
|
|
move.w (a0),d2 : 14 cycles = 8 + 6 + n
|
|
move.l (a0),d2 : 24 cycles = 12 + 6 + 6 + n
|
|
movep.w (a0),d2 : 28 cycles = 16 + 6 + 6 + n
|
|
movep.l (a0),d2 : 48 cycles = 24 + 6 + 6 + 6 + 6 + n
|
|
(on ST, those values might be rounded to the next multiple of 4 cycles)
|
|
|
|
When the ACIA's IRQ signal goes low, the resulting bit in the MFP is visible to the CPU only 4 cycles later.
|
|
From the hardware point of view, the ACIA's irq signal is immediately propagated to the MFP,
|
|
but the MFP will then add a 4 cycle delay before generating a 68000 interrupt.
|
|
|
|
*/
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
|
|
#include "main.h"
|
|
#include "log.h"
|
|
#include "memorySnapShot.h"
|
|
#include "configuration.h"
|
|
#include "acia.h"
|
|
#include "m68000.h"
|
|
#include "cycInt.h"
|
|
#include "ioMem.h"
|
|
#include "clocks_timings.h"
|
|
#include "mfp.h"
|
|
#include "screen.h"
|
|
#include "video.h"
|
|
|
|
|
|
#define ACIA_SR_BIT_RDRF 0x01 /* Receive Data Register Full */
|
|
#define ACIA_SR_BIT_TDRE 0x02 /* Transmit Data Register Empty */
|
|
#define ACIA_SR_BIT_DCD 0x04 /* Data Carrier Detect */
|
|
#define ACIA_SR_BIT_CTS 0x08 /* Clear To Send */
|
|
#define ACIA_SR_BIT_FE 0x10 /* Framing Error */
|
|
#define ACIA_SR_BIT_OVRN 0x20 /* Receiver Overrun */
|
|
#define ACIA_SR_BIT_PE 0x40 /* Parity Error */
|
|
#define ACIA_SR_BIT_IRQ 0x80 /* IRQ */
|
|
|
|
#define ACIA_CR_COUNTER_DIVIDE( CR ) ( CR & 0x03 ) /* CR1 + CR0 : 0x03 causes a master reset */
|
|
#define ACIA_CR_WORD_SELECT( CR ) ( ( CR >> 2 ) & 0x07 ) /* CR4 + CR3 + CR2 : size, parity, stop bits */
|
|
#define ACIA_CR_TRANSMITTER_CONTROL( CR ) ( ( CR >> 5 ) & 0x03 ) /* CR6 + CR5 : RTS + IRQ on send */
|
|
#define ACIA_CR_RECEIVE_INTERRUPT_ENABLE( CR ) ( ( CR >> 7 ) & 0x01 ) /* CR7 : Receive interrupt enable */
|
|
|
|
|
|
static const int ACIA_Counter_Divide[3] = { 1 , 16 , 64 }; /* Used to divide txclock/rxclock to get the correct baud rate */
|
|
|
|
|
|
/* Data size, parity and stop bits used for the transfer depending on CR_WORD_SELECT */
|
|
enum
|
|
{
|
|
ACIA_PARITY_NONE ,
|
|
ACIA_PARITY_EVEN ,
|
|
ACIA_PARITY_ODD
|
|
};
|
|
|
|
|
|
static struct {
|
|
int DataBits; /* 7 or 8 */
|
|
int Parity; /* EVEN or ODD or NONE */
|
|
int StopBits; /* 1 or 2 */
|
|
} ACIA_Serial_Params [ 8 ] = {
|
|
{ 7 , ACIA_PARITY_EVEN , 2 },
|
|
{ 7 , ACIA_PARITY_ODD , 2 },
|
|
{ 7 , ACIA_PARITY_EVEN , 1 },
|
|
{ 7 , ACIA_PARITY_ODD , 1 },
|
|
{ 8 , ACIA_PARITY_NONE , 2 },
|
|
{ 8 , ACIA_PARITY_NONE , 1 },
|
|
{ 8 , ACIA_PARITY_EVEN , 1 },
|
|
{ 8 , ACIA_PARITY_ODD , 1 }
|
|
};
|
|
|
|
|
|
|
|
/* Possible states when handling TX/RX interrupts */
|
|
enum
|
|
{
|
|
ACIA_STATE_IDLE = 0,
|
|
ACIA_STATE_DATA_BIT,
|
|
ACIA_STATE_PARITY_BIT,
|
|
ACIA_STATE_STOP_BIT
|
|
};
|
|
|
|
|
|
ACIA_STRUCT ACIA_Array[ ACIA_MAX_NB ];
|
|
ACIA_STRUCT *pACIA_IKBD;
|
|
ACIA_STRUCT *pACIA_MIDI;
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
/* Local functions prototypes */
|
|
/*--------------------------------------------------------------*/
|
|
|
|
static void ACIA_Init_Pointers ( ACIA_STRUCT *pAllACIA );
|
|
|
|
static void ACIA_Set_Line_IRQ_MFP ( int bit );
|
|
static Uint8 ACIA_Get_Line_CTS_Dummy ( void );
|
|
static Uint8 ACIA_Get_Line_DCD_Dummy ( void );
|
|
static void ACIA_Set_Line_RTS_Dummy ( int bit );
|
|
|
|
static void ACIA_Set_Timers_IKBD ( void *pACIA );
|
|
static void ACIA_Start_InterruptHandler_IKBD ( ACIA_STRUCT *pACIA , int InternalCycleOffset );
|
|
|
|
static Uint8 ACIA_MasterReset ( ACIA_STRUCT *pACIA , Uint8 CR );
|
|
|
|
static void ACIA_UpdateIRQ ( ACIA_STRUCT *pACIA );
|
|
|
|
static Uint8 ACIA_Read_SR ( ACIA_STRUCT *pACIA );
|
|
static void ACIA_Write_CR ( ACIA_STRUCT *pACIA , Uint8 CR );
|
|
static Uint8 ACIA_Read_RDR ( ACIA_STRUCT *pACIA );
|
|
static void ACIA_Write_TDR ( ACIA_STRUCT *pACIA , Uint8 TDR );
|
|
|
|
static void ACIA_Prepare_TX ( ACIA_STRUCT *pACIA );
|
|
static void ACIA_Prepare_RX ( ACIA_STRUCT *pACIA );
|
|
static void ACIA_Clock_TX ( ACIA_STRUCT *pACIA );
|
|
static void ACIA_Clock_RX ( ACIA_STRUCT *pACIA );
|
|
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Init the 2 ACIAs in an Atari ST.
|
|
* Both ACIAs have a 500 MHZ TX/RX clock.
|
|
* This is called only once, when the emulator starts.
|
|
* NOTE : when testing EmuTos on real hardware, it seems the tx/rx is working
|
|
* after a cold reset (ST switched on), even if Clock_Divider was not initialized yet.
|
|
* The default behaviour is not described in the ACIA's ref doc, but bits
|
|
* seem to be transmitted (maybe with errors ?). So we default
|
|
* to 9600 bauds to avoid a lock if a program uses tx/rx after a reset.
|
|
*/
|
|
void ACIA_Init ( ACIA_STRUCT *pAllACIA , Uint32 TX_Clock , Uint32 RX_Clock )
|
|
{
|
|
int i;
|
|
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia init tx_clock=%d rx_clock=%d\n" , TX_Clock , RX_Clock );
|
|
|
|
for ( i=0 ; i<ACIA_MAX_NB ; i++ )
|
|
{
|
|
memset ( (void *)&(pAllACIA[ i ]) , 0 , sizeof ( ACIA_STRUCT) );
|
|
|
|
pAllACIA[ i ].TX_Clock = TX_Clock;
|
|
pAllACIA[ i ].RX_Clock = RX_Clock;
|
|
pAllACIA[ i ].Clock_Divider = 0; /* Divider not initialized yet */
|
|
pAllACIA[ i ].FirstMasterReset = 1;
|
|
}
|
|
|
|
/* Set the default common callback functions + other pointers */
|
|
ACIA_Init_Pointers ( pAllACIA );
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Init some functions/memory pointers for each ACIA.
|
|
* This is called at Init and when restoring a memory snapshot.
|
|
*/
|
|
static void ACIA_Init_Pointers ( ACIA_STRUCT *pAllACIA )
|
|
{
|
|
int i;
|
|
|
|
|
|
for ( i=0 ; i<ACIA_MAX_NB ; i++ )
|
|
{
|
|
/* Set the default common callback functions */
|
|
pAllACIA[ i ].Set_Line_IRQ = ACIA_Set_Line_IRQ_MFP;
|
|
pAllACIA[ i ].Get_Line_CTS = ACIA_Get_Line_CTS_Dummy;
|
|
pAllACIA[ i ].Get_Line_DCD = ACIA_Get_Line_DCD_Dummy;
|
|
pAllACIA[ i ].Set_Line_RTS = ACIA_Set_Line_RTS_Dummy;
|
|
}
|
|
|
|
strcpy ( pAllACIA[ 0 ].ACIA_Name , "ikbd" );
|
|
strcpy ( pAllACIA[ 1 ].ACIA_Name , "midi" );
|
|
|
|
pACIA_IKBD = &(pAllACIA[ 0 ]);
|
|
pACIA_MIDI = &(pAllACIA[ 1 ]);
|
|
|
|
pACIA_IKBD->Set_Timers = ACIA_Set_Timers_IKBD;
|
|
// pACIA_MIDI->Set_Timers = ACIA_Set_Timers_MIDI; /* Not used for now */
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* There's no real hardware reset on the ACIA, but as the Reset_ST()
|
|
* functions turns off all internal interrupts, we must restart the ACIA's
|
|
* interrupt after a reset.
|
|
*/
|
|
void ACIA_Reset ( ACIA_STRUCT *pAllACIA )
|
|
{
|
|
int i;
|
|
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia reset\n" );
|
|
|
|
for ( i=0 ; i<ACIA_MAX_NB ; i++ )
|
|
{
|
|
if ( pAllACIA[ i ].Clock_Divider > 0 ) /* Divider already initialized */
|
|
pAllACIA[ i ].Set_Timers ( &(pAllACIA[ i ]) ); /* Restart the timer */
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type)
|
|
*/
|
|
void ACIA_MemorySnapShot_Capture ( bool bSave )
|
|
{
|
|
MemorySnapShot_Store(&ACIA_Array, sizeof(ACIA_Array));
|
|
|
|
if ( !bSave ) /* If restoring */
|
|
ACIA_Init_Pointers ( ACIA_Array ); /* Restore pointers */
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Set or reset the ACIA's IRQ signal.
|
|
* IRQ signal is inverted (0/low sets irq, 1/high resets irq)
|
|
* In the ST, the 2 ACIA's IRQ pins are connected to the same MFP input,
|
|
* so they share the same IRQ bit in GPIP4.
|
|
*/
|
|
static void ACIA_Set_Line_IRQ_MFP ( int bit )
|
|
{
|
|
LOG_TRACE ( TRACE_ACIA, "acia set irq line val=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL );
|
|
|
|
if ( bit == 0 )
|
|
{
|
|
/* There's a small delay on a real ST between the point in time
|
|
* the irq bit is set and the MFP interrupt is triggered - for example
|
|
* the "V8 music system" demo depends on this behaviour.
|
|
* This 4 cycle delay is handled in mfp.c */
|
|
MFP_GPIP_Set_Line_Input ( MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_LOW );
|
|
}
|
|
else
|
|
{
|
|
MFP_GPIP_Set_Line_Input ( MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_HIGH );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Read the Clear To Send (CTS) pin
|
|
* When CTS is high, TDRE should always be set to 0
|
|
* Note : this is not connected on an ST, so we always return 0.
|
|
*/
|
|
static Uint8 ACIA_Get_Line_CTS_Dummy ( void )
|
|
{
|
|
Uint8 bit;
|
|
|
|
bit = 0;
|
|
LOG_TRACE ( TRACE_ACIA, "acia get cts=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL );
|
|
return bit;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Read the Data Carrier Detect (DCD) pin
|
|
* Note : this is not connected on an ST, so we always return 0.
|
|
*/
|
|
static Uint8 ACIA_Get_Line_DCD_Dummy ( void )
|
|
{
|
|
Uint8 bit;
|
|
|
|
bit = 0;
|
|
LOG_TRACE ( TRACE_ACIA, "acia get dcd=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL );
|
|
return bit;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Set the Request To Send (RTS) pin.
|
|
* Note : this is not connected on an ST, so we ignore it.
|
|
*/
|
|
static void ACIA_Set_Line_RTS_Dummy ( int bit )
|
|
{
|
|
LOG_TRACE ( TRACE_ACIA, "acia set rts val=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Set the required timers to handle RX / TX, depending on the CR_DIVIDE
|
|
* value.
|
|
* When CR is changed with a new CR_DIVIDE value, we restart the timers.
|
|
*/
|
|
static void ACIA_Set_Timers_IKBD ( void *pACIA )
|
|
{
|
|
ACIA_Start_InterruptHandler_IKBD ( (ACIA_STRUCT *)pACIA , 0 );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Set a timer to handle the RX / TX bits at the expected baud rate.
|
|
* NOTE : on ST, TX_Clock and RX_Clock are the same, so the timer's freq will be
|
|
* TX_Clock / Divider and we only need one timer interrupt to handle both RX and TX.
|
|
* This freq should be converted to CPU_CYCLE : 1 ACIA cycle = 16 CPU cycles
|
|
* (with cpu running at 8 MHz)
|
|
* InternalCycleOffset allows to compensate for a != 0 value in PendingInterruptCount
|
|
* to keep a constant baud rate.
|
|
* TODO : we use a fixed 8 MHz clock and nCpuFreqShift to convert cycles for our
|
|
* internal timers in cycInt.c. This should be replaced some days by using
|
|
* MachineClocks.CPU_Freq and not using nCpuFreqShift anymore.
|
|
*/
|
|
static void ACIA_Start_InterruptHandler_IKBD ( ACIA_STRUCT *pACIA , int InternalCycleOffset )
|
|
{
|
|
int Cycles;
|
|
|
|
|
|
// Cycles = MachineClocks.CPU_Freq / pACIA->TX_Clock; /* Convert ACIA cycles in CPU cycles */
|
|
Cycles = 8021247 / pACIA->TX_Clock; /* Convert ACIA cycles in CPU cycles, for a 8 MHz STF reference */
|
|
Cycles *= pACIA->Clock_Divider;
|
|
Cycles <<= nCpuFreqShift; /* Compensate for x2 or x4 cpu speed */
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s start timer divider=%d cpu_cycles=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name ,
|
|
pACIA->Clock_Divider , Cycles , nVBLs , nHBL );
|
|
|
|
CycInt_AddRelativeInterruptWithOffset ( Cycles, INT_CPU_CYCLE, INTERRUPT_ACIA_IKBD , InternalCycleOffset );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Interrupt called each time a new bit must be sent / received with the IKBD.
|
|
* This interrupt will be called at freq ( 500 MHz / ACIA_CR_COUNTER_DIVIDE )
|
|
* On ST, RX_Clock = TX_Clock = 500 MHz.
|
|
* We continuously restart the interrupt, taking into account PendingCyclesOver.
|
|
*/
|
|
void ACIA_InterruptHandler_IKBD ( void )
|
|
{
|
|
int PendingCyclesOver;
|
|
|
|
|
|
/* Number of internal cycles we went over for this timer ( <= 0 ) */
|
|
/* Used to restart the next timer and keep a constant baud rate */
|
|
PendingCyclesOver = -PendingInterruptCount; /* >= 0 */
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia ikbd interrupt handler pending_cyc=%d VBL=%d HBL=%d\n" , PendingCyclesOver , nVBLs , nHBL );
|
|
|
|
/* Remove this interrupt from list and re-order */
|
|
CycInt_AcknowledgeInterrupt();
|
|
|
|
ACIA_Clock_TX ( pACIA_IKBD );
|
|
ACIA_Clock_RX ( pACIA_IKBD );
|
|
|
|
ACIA_Start_InterruptHandler_IKBD ( pACIA_IKBD , -PendingCyclesOver ); /* Compensate for a != 0 value of PendingCyclesOver */
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Interrupt called each time a new bit must be sent / received with the MIDI.
|
|
* This interrupt will be called at freq ( 500 MHz / ACIA_CR_COUNTER_DIVIDE )
|
|
* On ST, RX_Clock = TX_Clock = 500 MHz.
|
|
*/
|
|
void ACIA_InterruptHandler_MIDI ( void )
|
|
{
|
|
ACIA_Clock_TX ( pACIA_MIDI );
|
|
ACIA_Clock_RX ( pACIA_MIDI );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* - For each access to an ACIA register, a 6 cycles delay is added to the
|
|
* normal 68000 timing for the current CPU instruction. If the instruction
|
|
* accesses several registers at once, the delays are cumulated.
|
|
* - An additional delay will also be added to ensure the 68000 clock and
|
|
* the E clock are synchronised ; this delay can add between 0 and 8 cycles
|
|
* to reach the next multiple of 10 cycles. This delay is added only once
|
|
* per CPU instruction.
|
|
* These delays are measured for an 8 MHz 68000 CPU.
|
|
*/
|
|
void ACIA_AddWaitCycles ( void )
|
|
{
|
|
int cycles;
|
|
|
|
/* Add a default of 6 cycles for each access */
|
|
cycles = 6;
|
|
|
|
/* Wait for E clock only if this is the first ACIA access for this instruction */
|
|
/* (NOTE : in UAE, movep behaves like several bytes access with different IoAccessBaseAddress, */
|
|
/* so only the first movep's access should wait for E Clock) */
|
|
if ( ( ( MovepByteNbr == 0 ) && ( IoAccessBaseAddress == IoAccessCurrentAddress ) )
|
|
|| ( MovepByteNbr == 1 ) ) /* First access of a movep */
|
|
cycles += M68000_WaitEClock ();
|
|
|
|
M68000_WaitState ( cycles );
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Return SR for the IKBD's ACIA (0xfffc00)
|
|
*/
|
|
void ACIA_IKBD_Read_SR ( void )
|
|
{
|
|
ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
|
|
|
|
IoMem[0xfffc00] = ACIA_Read_SR ( pACIA_IKBD );
|
|
|
|
if (LOG_TRACE_LEVEL(TRACE_ACIA))
|
|
{
|
|
int FrameCycles, HblCounterVideo, LineCycles;
|
|
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
|
|
LOG_TRACE_PRINT("acia %s read fffc00 sr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name ,
|
|
IoMem[0xfffc00], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Return RDR for the IKBD's ACIA (0xfffc02) : receive a byte from the IKBD
|
|
*/
|
|
void ACIA_IKBD_Read_RDR ( void )
|
|
{
|
|
ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
|
|
|
|
IoMem[0xfffc02] = ACIA_Read_RDR ( pACIA_IKBD );
|
|
|
|
if (LOG_TRACE_LEVEL(TRACE_IKBD_ACIA))
|
|
{
|
|
int FrameCycles, HblCounterVideo, LineCycles;
|
|
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
|
|
LOG_TRACE_PRINT("acia %s read fffc02 rdr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name ,
|
|
IoMem[0xfffc02], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Write to CR in the IKBD's ACIA (0xfffc00)
|
|
*/
|
|
void ACIA_IKBD_Write_CR ( void )
|
|
{
|
|
int FrameCycles, HblCounterVideo, LineCycles;
|
|
|
|
ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
|
|
|
|
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
|
|
LOG_TRACE(TRACE_IKBD_ACIA, "acia %s write fffc00 cr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name ,
|
|
IoMem[0xfffc00], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
|
|
|
|
ACIA_Write_CR ( pACIA_IKBD , IoMem[0xfffc00] );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Write to TDR in the IKBD's ACIA (0xfffc02) : send a byte to the IKBD
|
|
*/
|
|
void ACIA_IKBD_Write_TDR ( void )
|
|
{
|
|
int FrameCycles, HblCounterVideo, LineCycles;
|
|
|
|
ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
|
|
|
|
Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
|
|
LOG_TRACE(TRACE_IKBD_ACIA, "acia %s write fffc02 tdr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name ,
|
|
IoMem[0xfffc02], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
|
|
|
|
ACIA_Write_TDR ( pACIA_IKBD , IoMem[0xfffc02] );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* The part below is the real core of the 6850's emulation. */
|
|
/* */
|
|
/* This core is not correlated to any specific machine. All the specific*/
|
|
/* code between the 6850 and the rest of Hatari is called through some */
|
|
/* callback functions (see above). */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Reset an ACIA.
|
|
* There's no RESET pin on the MC6850, so the only way to reset the ACIA
|
|
* is to set bit 0 an 1 to 0x03 in the CR to force a master reset.
|
|
* This will clear SR (except CTS and DCD) and halt/initialize both the
|
|
* receiver and transmitter.
|
|
* This also returns the new state of the RTS bit, that must be updated
|
|
* in ACIA_Write_CR.
|
|
*/
|
|
static Uint8 ACIA_MasterReset ( ACIA_STRUCT *pACIA , Uint8 CR )
|
|
{
|
|
Uint8 dcd_bit;
|
|
Uint8 cts_bit;
|
|
Uint8 rts_bit;
|
|
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s master reset VBL=%d HBL=%d\n" , pACIA->ACIA_Name , nVBLs , nHBL );
|
|
|
|
dcd_bit = pACIA->Get_Line_DCD ();
|
|
cts_bit = pACIA->Get_Line_CTS ();
|
|
|
|
pACIA->SR = ACIA_SR_BIT_TDRE | ( dcd_bit << 2 ) | ( cts_bit << 3 );
|
|
|
|
pACIA->TX_State = ACIA_STATE_IDLE;
|
|
pACIA->TSR = 0;
|
|
pACIA->TX_Size = 0;
|
|
pACIA->TX_SendBrk = 0;
|
|
|
|
pACIA->RX_State = ACIA_STATE_IDLE;
|
|
pACIA->RSR = 0;
|
|
pACIA->RX_Size = 0;
|
|
pACIA->RX_Overrun = 0;
|
|
|
|
/* On Master Reset, IRQ line is high */
|
|
/* If it's the 1st reset, RTS should be high, else RTS depends on CR bit 5 and 6 */
|
|
pACIA->Set_Line_IRQ ( 1 ); /* IRQ line goes high */
|
|
if ( pACIA->FirstMasterReset == 1 )
|
|
{
|
|
pACIA->FirstMasterReset = 0;
|
|
rts_bit = 1; /* RTS line goes high */
|
|
}
|
|
else
|
|
rts_bit = ( ACIA_CR_TRANSMITTER_CONTROL ( CR ) == 0x02 ) ? 1 : 0;
|
|
|
|
return rts_bit;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Check if the IRQ must be changed in SR.
|
|
* When there's a change, we must change the IRQ line too.
|
|
*/
|
|
static void ACIA_UpdateIRQ ( ACIA_STRUCT *pACIA )
|
|
{
|
|
Uint8 irq_bit_new;
|
|
|
|
irq_bit_new = 0;
|
|
|
|
if ( ACIA_CR_RECEIVE_INTERRUPT_ENABLE ( pACIA->CR ) /* Check for RX causes of interrupt */
|
|
&& ( ( pACIA->SR & ( ACIA_SR_BIT_RDRF | ACIA_SR_BIT_DCD ) )
|
|
|| ( pACIA->RX_Overrun ) ) )
|
|
irq_bit_new = ACIA_SR_BIT_IRQ;
|
|
//fprintf(stderr , "acia irq %x %x %x %d\n" , pACIA->CR , pACIA->SR , pACIA->RX_Overrun , irq_bit_new);
|
|
|
|
if ( pACIA->TX_EnableInt /* Check for TX causes of interrupt */
|
|
&& ( pACIA->SR & ACIA_SR_BIT_TDRE )
|
|
&& ( ( pACIA->SR & ACIA_SR_BIT_CTS ) == 0 ) )
|
|
irq_bit_new = ACIA_SR_BIT_IRQ;
|
|
|
|
/* Update SR and IRQ line if a change happened */
|
|
if ( ( pACIA->SR & ACIA_SR_BIT_IRQ ) != irq_bit_new )
|
|
{
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s update irq irq_new=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , irq_bit_new?1:0 , nVBLs , nHBL );
|
|
|
|
if ( irq_bit_new )
|
|
{
|
|
pACIA->SR |= ACIA_SR_BIT_IRQ; /* Set IRQ bit */
|
|
pACIA->Set_Line_IRQ ( 0 ); /* IRQ line goes low */
|
|
}
|
|
else
|
|
{
|
|
pACIA->SR &= ~ACIA_SR_BIT_IRQ; /* Clear IRQ bit */
|
|
pACIA->Set_Line_IRQ ( 1 ); /* IRQ line goes high */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Read SR.
|
|
* Also update CTS ; when CTS is high, TDRE should always be masked to 0.
|
|
*/
|
|
static Uint8 ACIA_Read_SR ( ACIA_STRUCT *pACIA )
|
|
{
|
|
Uint8 SR;
|
|
|
|
|
|
if ( pACIA->Get_Line_CTS() == 1 )
|
|
pACIA->SR |= ACIA_SR_BIT_CTS;
|
|
else
|
|
pACIA->SR &= ~ACIA_SR_BIT_CTS;
|
|
|
|
SR = pACIA->SR;
|
|
pACIA->SR_Read = 1; /* Used in ACIA_Read_RDR to clear Overrun and DCD IRQ */
|
|
|
|
if ( SR & ACIA_SR_BIT_CTS )
|
|
SR &= ~ACIA_SR_BIT_TDRE; /* Inhibit TDRE when CTS is set */
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s read sr data=0x%02x VBL=%d HBL=%d\n" , pACIA->ACIA_Name , SR , nVBLs , nHBL );
|
|
|
|
return SR;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Write to CR.
|
|
*/
|
|
static void ACIA_Write_CR ( ACIA_STRUCT *pACIA , Uint8 CR )
|
|
{
|
|
int Divide;
|
|
int Force_rts_bit;
|
|
Uint8 rts_bit=0;
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s write cr data=0x%02x VBL=%d HBL=%d\n" , pACIA->ACIA_Name , CR , nVBLs , nHBL );
|
|
|
|
/* Bit 0 and 1 : Counter Divide */
|
|
Divide = ACIA_CR_COUNTER_DIVIDE ( CR );
|
|
if ( Divide == 0x03 )
|
|
{
|
|
Force_rts_bit = ACIA_MasterReset ( pACIA , CR ); /* Special behaviour for RTS after a master reset */
|
|
}
|
|
else
|
|
{
|
|
if ( ACIA_CR_COUNTER_DIVIDE ( CR ) != ACIA_CR_COUNTER_DIVIDE ( pACIA->CR ) )
|
|
{
|
|
pACIA->Clock_Divider = ACIA_Counter_Divide[ Divide ];
|
|
pACIA->Set_Timers ( pACIA ); /* Set a timer at the baud rate computed from Clock_Divider */
|
|
}
|
|
Force_rts_bit = -1; /* Don't force RTS bit, use bit 5/6 in CR */
|
|
}
|
|
|
|
/* Bits 2, 3 and 4 : word select */
|
|
/* Don't do anything here, see ACIA_Prepare_TX and ACIA_Prepare_RX */
|
|
|
|
/* Bits 5 and 6 : transmitter control */
|
|
pACIA->TX_EnableInt = 0;
|
|
pACIA->TX_SendBrk = 0;
|
|
switch ( ACIA_CR_TRANSMITTER_CONTROL ( CR ) )
|
|
{
|
|
case 0x00 :
|
|
rts_bit = 0;
|
|
break;
|
|
case 0x01 :
|
|
rts_bit = 0;
|
|
pACIA->TX_EnableInt = 1;
|
|
break;
|
|
case 0x02 :
|
|
rts_bit = 1;
|
|
break;
|
|
case 0x03 :
|
|
rts_bit = 0;
|
|
pACIA->TX_SendBrk = 1; /* We will send break bit until CR is changed */
|
|
break;
|
|
}
|
|
|
|
if ( Force_rts_bit >= 0 )
|
|
rts_bit = Force_rts_bit; /* Use the value from ACIA_MasterReset */
|
|
pACIA->Set_Line_RTS ( rts_bit );
|
|
|
|
/* Bits 7 : receive interrupt enable, see ACIA_UpdateIRQ */
|
|
|
|
pACIA->CR = CR;
|
|
|
|
ACIA_UpdateIRQ ( pACIA );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Read RDR. This will clear RDRF and PE.
|
|
* - OVRN / DCD bits are cleared if SR was read before reading RDR.
|
|
* - OVRN bit is set only when reading RDR, not when the actual overrun happened
|
|
* during ACIA_Clock_RX.
|
|
* - IRQ bit should be updated depending on the new values of BIT_RDRF,
|
|
* BIT_DCD and BIT_OVRN.
|
|
*/
|
|
static Uint8 ACIA_Read_RDR ( ACIA_STRUCT *pACIA )
|
|
{
|
|
pACIA->SR &= ~( ACIA_SR_BIT_RDRF | ACIA_SR_BIT_PE );
|
|
|
|
/* If we read RDR after reading SR, we clear OVRN / DCD bits */
|
|
if ( pACIA->SR_Read == 1 )
|
|
{
|
|
pACIA->SR_Read = 0;
|
|
pACIA->SR &= ~( ACIA_SR_BIT_DCD | ACIA_SR_BIT_OVRN );
|
|
if ( pACIA->Get_Line_DCD () == 1 )
|
|
pACIA->SR |= ACIA_SR_BIT_DCD;
|
|
}
|
|
|
|
if ( pACIA->RX_Overrun )
|
|
{
|
|
pACIA->SR |= ACIA_SR_BIT_OVRN;
|
|
pACIA->RX_Overrun = 0;
|
|
}
|
|
|
|
ACIA_UpdateIRQ ( pACIA );
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s read rdr data=0x%02x new sr=0x%02x overrun=%s VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->RDR ,
|
|
pACIA->SR , ( pACIA->SR & ACIA_SR_BIT_OVRN ) ? "yes" : "no" , nVBLs , nHBL );
|
|
|
|
return pACIA->RDR;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Write to TDR.
|
|
* If the TX process is idle, we should not prepare a new transfer
|
|
* immediately, to ensure that BIT_TDRE remains clear until the next bit
|
|
* is sent (BIT_TDRE will be set again in ACIA_Clock_TX).
|
|
*/
|
|
static void ACIA_Write_TDR ( ACIA_STRUCT *pACIA , Uint8 TDR )
|
|
{
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s write tdr data=0x%02x overwrite=%s tx_state=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , TDR ,
|
|
( pACIA->SR & ACIA_SR_BIT_TDRE ) ? "no" : "yes" , pACIA->TX_State , nVBLs , nHBL );
|
|
|
|
pACIA->TDR = TDR;
|
|
pACIA->SR &= ~ACIA_SR_BIT_TDRE; /* TDR is not empty anymore */
|
|
|
|
ACIA_UpdateIRQ ( pACIA );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Prepare a new transfer. Copy TDR to TSR and initialize parity, data size
|
|
* and stop bits.
|
|
* Transfer will then start at the next call of ACIA_Clock_TX
|
|
*/
|
|
static void ACIA_Prepare_TX ( ACIA_STRUCT *pACIA )
|
|
{
|
|
pACIA->TSR = pACIA->TDR;
|
|
pACIA->TX_Parity = 0;
|
|
pACIA->TX_Size = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].DataBits;
|
|
pACIA->TX_StopBits = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].StopBits;
|
|
|
|
pACIA->SR |= ACIA_SR_BIT_TDRE; /* TDR was copied to TSR. TDR is now empty */
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s prepare tx tsr=0x%02x size=%d stop=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->TSR ,
|
|
pACIA->TX_Size , pACIA->TX_StopBits , nVBLs , nHBL );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Prepare a new reception. Initialize parity, data size and stop bits.
|
|
*/
|
|
static void ACIA_Prepare_RX ( ACIA_STRUCT *pACIA )
|
|
{
|
|
pACIA->RSR = 0;
|
|
pACIA->RX_Parity = 0;
|
|
pACIA->RX_Size = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].DataBits;
|
|
pACIA->RX_StopBits = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].StopBits;
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s prepare rx size=%d stop=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name ,
|
|
pACIA->RX_Size , pACIA->RX_StopBits , nVBLs , nHBL );
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Write a new bit on the TX line each time the TX clock expires.
|
|
* This will send TDR over the serial line, using TSR, with additional
|
|
* parity and start/stop bits.
|
|
* We send bit 0 of TSR, then TSR is shifted to the right.
|
|
*/
|
|
static void ACIA_Clock_TX ( ACIA_STRUCT *pACIA )
|
|
{
|
|
int StateNext;
|
|
Uint8 tx_bit;
|
|
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s clock_tx tx_state=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->TX_State , nVBLs , nHBL );
|
|
|
|
StateNext = -1;
|
|
switch ( pACIA->TX_State )
|
|
{
|
|
case ACIA_STATE_IDLE :
|
|
if ( pACIA->TX_SendBrk )
|
|
{
|
|
pACIA->Set_Line_TX ( 0 ); /* Send 1 break bit */
|
|
break;
|
|
}
|
|
|
|
/* If TDR is not empty when we are in idle state, */
|
|
/* this means we have a new byte to send */
|
|
if ( ( pACIA->SR & ACIA_SR_BIT_TDRE ) == 0 )
|
|
ACIA_Prepare_TX ( pACIA );
|
|
|
|
if ( pACIA->TX_Size == 0 ) /* TSR is empty */
|
|
pACIA->Set_Line_TX ( 1 ); /* Send stop bits when idle */
|
|
|
|
else /* TSR has some new bits to transfer */
|
|
{
|
|
pACIA->Set_Line_TX ( 0 ); /* Send 1 start bit */
|
|
StateNext = ACIA_STATE_DATA_BIT;
|
|
}
|
|
break;
|
|
|
|
case ACIA_STATE_DATA_BIT :
|
|
tx_bit = pACIA->TSR & 1; /* New bit to send */
|
|
pACIA->Set_Line_TX ( tx_bit );
|
|
pACIA->TX_Parity ^= tx_bit;
|
|
pACIA->TSR >>= 1;
|
|
pACIA->TX_Size--;
|
|
|
|
if ( pACIA->TX_Size == 0 )
|
|
{
|
|
if ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity != ACIA_PARITY_NONE )
|
|
StateNext = ACIA_STATE_PARITY_BIT;
|
|
else
|
|
StateNext = ACIA_STATE_STOP_BIT; /* No parity */
|
|
}
|
|
break;
|
|
|
|
case ACIA_STATE_PARITY_BIT :
|
|
if ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity == ACIA_PARITY_EVEN )
|
|
pACIA->Set_Line_TX ( pACIA->TX_Parity );
|
|
else
|
|
pACIA->Set_Line_TX ( ( ~pACIA->TX_Parity ) & 1 );
|
|
|
|
StateNext = ACIA_STATE_STOP_BIT;
|
|
break;
|
|
|
|
case ACIA_STATE_STOP_BIT :
|
|
pACIA->Set_Line_TX ( 1 ); /* Send 1 stop bit */
|
|
pACIA->TX_StopBits--;
|
|
|
|
if ( pACIA->TX_StopBits == 0 ) /* All stop bits were sent : transfer is complete */
|
|
{
|
|
StateNext = ACIA_STATE_IDLE; /* Go to idle state to see if a new TDR need to be sent */
|
|
}
|
|
break;
|
|
}
|
|
|
|
ACIA_UpdateIRQ ( pACIA );
|
|
|
|
if ( StateNext >= 0 )
|
|
pACIA->TX_State = StateNext; /* Go to a new state */
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Handle a new bit on the RX line each time the RX clock expires.
|
|
* This will fill RDR with bits received from the serial line, using RSR.
|
|
* Incoming bits are stored in bit 7 of RSR, then RSR is shifted to the right.
|
|
*/
|
|
static void ACIA_Clock_RX ( ACIA_STRUCT *pACIA )
|
|
{
|
|
int StateNext;
|
|
Uint8 rx_bit;
|
|
|
|
|
|
rx_bit = pACIA->Get_Line_RX();
|
|
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx rx_state=%d bit=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->RX_State , rx_bit , nVBLs , nHBL );
|
|
|
|
StateNext = -1;
|
|
switch ( pACIA->RX_State )
|
|
{
|
|
case ACIA_STATE_IDLE :
|
|
if ( rx_bit == 0 ) /* Receive 1 start bit */
|
|
{
|
|
ACIA_Prepare_RX ( pACIA );
|
|
StateNext = ACIA_STATE_DATA_BIT;
|
|
}
|
|
break; /* If no start bit, we stay in idle state */
|
|
|
|
case ACIA_STATE_DATA_BIT :
|
|
if ( rx_bit )
|
|
pACIA->RSR |= 0x80;
|
|
pACIA->RX_Parity ^= rx_bit;
|
|
pACIA->RX_Size--;
|
|
|
|
if ( pACIA->RX_Size > 0 ) /* All bits were not received yet */
|
|
{
|
|
pACIA->RSR >>= 1;
|
|
}
|
|
else
|
|
{
|
|
// [NP] : MC6850 doc is not very clear "the overrun condition begins at the midpoint of the last bit
|
|
// of the second character received [...]". Is it the last bit of the data word, or the stop bit ?
|
|
// It makes more sense to check for overrun after the stop bit, when RSR should be copied to RDR,
|
|
// because RDR could be read between the last data bit and the stop bit, so RX_Overrun and
|
|
// ACIA_SR_BIT_OVRN would need to be cancelled.
|
|
// if ( pACIA->SR & ACIA_SR_BIT_RDRF )
|
|
// {
|
|
// LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx overrun rsr=0x%02x VBL=%d HBL=%d\n" ,
|
|
// pACIA->ACIA_Name , pACIA->RSR , nVBLs , nHBL );
|
|
// pACIA->RX_Overrun = 1; /* Bit in SR will be set when reading RDR */
|
|
// }
|
|
if ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity != ACIA_PARITY_NONE )
|
|
StateNext = ACIA_STATE_PARITY_BIT;
|
|
else
|
|
StateNext = ACIA_STATE_STOP_BIT; /* No parity */
|
|
}
|
|
break;
|
|
|
|
case ACIA_STATE_PARITY_BIT :
|
|
if ( ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity == ACIA_PARITY_EVEN )
|
|
&& ( pACIA->RX_Parity != rx_bit ) )
|
|
pACIA->SR |= ACIA_SR_BIT_PE;
|
|
|
|
else if ( pACIA->RX_Parity == rx_bit ) /* Odd parity */
|
|
pACIA->SR |= ACIA_SR_BIT_PE;
|
|
|
|
if ( pACIA->SR & ACIA_SR_BIT_PE )
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx parity error VBL=%d HBL=%d\n" , pACIA->ACIA_Name , nVBLs , nHBL );
|
|
|
|
StateNext = ACIA_STATE_STOP_BIT;
|
|
break;
|
|
|
|
case ACIA_STATE_STOP_BIT :
|
|
if ( rx_bit == 1 ) /* Wait for 1 or 2 "1" stop bits */
|
|
{
|
|
pACIA->RX_StopBits--;
|
|
if ( pACIA->RX_StopBits == 0 ) /* All stop bits were received : reception is complete */
|
|
{
|
|
pACIA->SR &= ~ACIA_SR_BIT_FE;
|
|
|
|
if ( ( pACIA->SR & ACIA_SR_BIT_RDRF ) == 0 )
|
|
{
|
|
pACIA->RDR = pACIA->RSR;
|
|
pACIA->SR |= ACIA_SR_BIT_RDRF;
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx received rdr=0x%02x VBL=%d HBL=%d\n" ,
|
|
pACIA->ACIA_Name , pACIA->RDR , nVBLs , nHBL );
|
|
}
|
|
else
|
|
{
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx overrun rsr=0x%02x unread rdr=0x%02x VBL=%d HBL=%d\n" ,
|
|
pACIA->ACIA_Name , pACIA->RSR , pACIA->RDR , nVBLs , nHBL );
|
|
pACIA->RX_Overrun = 1; /* Bit in SR will be set when reading RDR */
|
|
}
|
|
StateNext = ACIA_STATE_IDLE; /* Go to idle state and wait for start bit */
|
|
}
|
|
}
|
|
else /* Not a valid stop bit */
|
|
{
|
|
LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx framing error VBL=%d HBL=%d\n" , pACIA->ACIA_Name , nVBLs , nHBL );
|
|
|
|
/* According to the A6850 doc, RSR is copied to RDR in case of a framing error */
|
|
/* (Should be the same for the MC6850 ?) */
|
|
pACIA->SR |= ACIA_SR_BIT_FE;
|
|
pACIA->RDR = pACIA->RSR;
|
|
StateNext = ACIA_STATE_IDLE; /* Go to idle state and wait for start bit */
|
|
}
|
|
break;
|
|
}
|
|
|
|
ACIA_UpdateIRQ ( pACIA );
|
|
|
|
if ( StateNext >= 0 )
|
|
pACIA->RX_State = StateNext; /* Go to a new state */
|
|
}
|
|
|
|
|
|
|