diff options
Diffstat (limited to 'src/emu/diserial.cpp')
-rw-r--r-- | src/emu/diserial.cpp | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/src/emu/diserial.cpp b/src/emu/diserial.cpp new file mode 100644 index 00000000000..d21374cbc09 --- /dev/null +++ b/src/emu/diserial.cpp @@ -0,0 +1,504 @@ +// license:BSD-3-Clause +// copyright-holders:Carl, Miodrag Milanovic + +/*************************************************************************** + + Serial device interface + +***************************************************************************/ + +#include "emu.h" + +device_serial_interface::device_serial_interface(const machine_config &mconfig, device_t &device) + : device_interface(device, "serial"), + m_start_bit_hack_for_external_clocks(false), + m_df_start_bit_count(0), + m_df_word_length(0), + m_df_parity(PARITY_NONE), + m_df_stop_bit_count(STOP_BITS_0), + m_rcv_register_data(0x8000), + m_rcv_flags(0), + m_rcv_bit_count_received(0), + m_rcv_bit_count(0), + m_rcv_framing_error(false), + m_rcv_parity_error(false), + m_tra_flags(TRANSMIT_REGISTER_EMPTY), + m_rcv_clock(NULL), + m_tra_clock(NULL), + m_rcv_rate(attotime::never), + m_tra_rate(attotime::never), + m_rcv_line(0), + m_tra_clock_state(false), + m_rcv_clock_state(false) +{ + /* if sum of all bits in the byte is even, then the data + has even parity, otherwise it has odd parity */ + for (int i=0; i<256; i++) + { + int sum = 0; + int data = i; + + for (int b=0; b<8; b++) + { + sum+=data & 0x01; + data = data>>1; + } + + m_serial_parity_table[i] = sum & 0x01; + } +} + +device_serial_interface::~device_serial_interface() +{ +} + +void device_serial_interface::register_save_state(save_manager &save, device_t *device) +{ + const char *module = device->name(); + const char *tag = device->tag(); + save.save_item(device, module, tag, 0, NAME(m_df_start_bit_count)); + save.save_item(device, module, tag, 0, NAME(m_df_word_length)); + save.save_item(device, module, tag, 0, NAME(m_df_parity)); + save.save_item(device, module, tag, 0, NAME(m_df_stop_bit_count)); + save.save_item(device, module, tag, 0, NAME(m_rcv_register_data)); + save.save_item(device, module, tag, 0, NAME(m_rcv_flags)); + save.save_item(device, module, tag, 0, NAME(m_rcv_bit_count_received)); + save.save_item(device, module, tag, 0, NAME(m_rcv_bit_count)); + save.save_item(device, module, tag, 0, NAME(m_rcv_byte_received)); + save.save_item(device, module, tag, 0, NAME(m_rcv_framing_error)); + save.save_item(device, module, tag, 0, NAME(m_rcv_parity_error)); + save.save_item(device, module, tag, 0, NAME(m_tra_register_data)); + save.save_item(device, module, tag, 0, NAME(m_tra_flags)); + save.save_item(device, module, tag, 0, NAME(m_tra_bit_count_transmitted)); + save.save_item(device, module, tag, 0, NAME(m_tra_bit_count)); + save.save_item(device, module, tag, 0, NAME(m_rcv_rate)); + save.save_item(device, module, tag, 0, NAME(m_tra_rate)); + save.save_item(device, module, tag, 0, NAME(m_rcv_line)); + save.save_item(device, module, tag, 0, NAME(m_tra_clock_state)); + save.save_item(device, module, tag, 0, NAME(m_rcv_clock_state)); +} + +void device_serial_interface::interface_pre_start() +{ + m_rcv_clock = device().timer_alloc(RCV_TIMER_ID); + m_tra_clock = device().timer_alloc(TRA_TIMER_ID); + m_rcv_clock_state = false; + m_tra_clock_state = false; +} + +void device_serial_interface::set_rcv_rate(const attotime &rate) +{ + m_rcv_rate = rate/2; + receive_register_reset(); + m_rcv_clock->adjust(attotime::never); +} + +void device_serial_interface::set_tra_rate(const attotime &rate) +{ + m_tra_rate = rate/2; + transmit_register_reset(); + m_tra_clock->adjust(attotime::never); +} + +void device_serial_interface::tra_edge() +{ + if (!is_transmit_register_empty()) + { + tra_callback(); + if (is_transmit_register_empty()) + tra_complete(); + } + + if (is_transmit_register_empty() && !m_tra_rate.is_never()) + { + m_tra_clock->adjust(attotime::never); + } +} + +void device_serial_interface::rcv_edge() +{ + rcv_callback(); + if(is_receive_register_full()) + { + m_rcv_clock->adjust(attotime::never); + rcv_complete(); + } +} + +WRITE_LINE_MEMBER(device_serial_interface::tx_clock_w) +{ + if(state != m_tra_clock_state) { + m_tra_clock_state = state; + if(m_tra_clock_state) + tra_edge(); + } +} + +WRITE_LINE_MEMBER(device_serial_interface::rx_clock_w) +{ + if(state != m_rcv_clock_state) { + m_rcv_clock_state = state; + if(!m_rcv_clock_state) + rcv_edge(); + } +} + +WRITE_LINE_MEMBER(device_serial_interface::clock_w) +{ + tx_clock_w(state); + rx_clock_w(state); +} + +void device_serial_interface::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch(id) { + case TRA_TIMER_ID: tx_clock_w(!m_tra_clock_state); break; + case RCV_TIMER_ID: rx_clock_w(!m_rcv_clock_state); break; + } +} + + +void device_serial_interface::set_data_frame(int start_bit_count, int data_bit_count, parity_t parity, stop_bits_t stop_bits) +{ + m_df_word_length = data_bit_count; + + switch (stop_bits) + { + case STOP_BITS_0: + default: + m_df_stop_bit_count = 0; + break; + + case STOP_BITS_1: + m_df_stop_bit_count = 1; + break; + + case STOP_BITS_1_5: + m_df_stop_bit_count = 2; // TODO: support 1.5 stop bits + break; + + case STOP_BITS_2: + m_df_stop_bit_count = 2; + break; + } + + m_df_parity = parity; + m_df_start_bit_count = start_bit_count; + + m_rcv_bit_count = m_df_word_length + m_df_stop_bit_count; + + if (m_df_parity != PARITY_NONE) + { + m_rcv_bit_count++; + } +} + +void device_serial_interface::receive_register_reset() +{ + m_rcv_bit_count_received = 0; + m_rcv_flags &=~RECEIVE_REGISTER_FULL; + if (m_df_start_bit_count == 0) + { + m_rcv_flags |= RECEIVE_REGISTER_SYNCHRONISED; + m_rcv_flags &=~RECEIVE_REGISTER_WAITING_FOR_START_BIT; + } + else + { + m_rcv_flags &=~RECEIVE_REGISTER_SYNCHRONISED; + m_rcv_flags |= RECEIVE_REGISTER_WAITING_FOR_START_BIT; + } +} + +WRITE_LINE_MEMBER(device_serial_interface::rx_w) +{ + m_rcv_line = state; + if(m_rcv_flags & RECEIVE_REGISTER_SYNCHRONISED) + return; + receive_register_update_bit(state); + if(m_rcv_flags & RECEIVE_REGISTER_SYNCHRONISED) + { + if(m_rcv_clock && !(m_rcv_rate.is_never())) + // make start delay just a bit longer to make sure we are called after the sender + m_rcv_clock->adjust(((m_rcv_rate*3)/2), 0, m_rcv_rate); + else if(m_start_bit_hack_for_external_clocks) + m_rcv_bit_count_received--; + } + return; +} + +/* this is generic code to be used in serial chip implementations */ +/* the majority of serial chips work in the same way and this code will work */ +/* for them */ + +/* receive a bit */ +void device_serial_interface::receive_register_update_bit(int bit) +{ + int previous_bit; + + //LOG(("receive register receive bit: %1x\n",bit)); + previous_bit = (m_rcv_register_data & 0x8000) ? 1 : 0; + + /* shift previous bit 7 out */ + m_rcv_register_data = m_rcv_register_data>>1; + /* shift new bit in */ + m_rcv_register_data = (m_rcv_register_data & 0x7fff) | (bit<<15); + /* update bit count received */ + + /* asynchronous mode */ + if (m_rcv_flags & RECEIVE_REGISTER_WAITING_FOR_START_BIT) + { + /* the previous bit is stored in uart.receive char bit 0 */ + /* has the bit state changed? */ + if (((previous_bit ^ bit) & 0x01)!=0) + { + /* yes */ + if (bit==0) + { + //logerror("receive register saw start bit\n"); + + /* seen start bit! */ + /* not waiting for start bit now! */ + m_rcv_flags &=~RECEIVE_REGISTER_WAITING_FOR_START_BIT; + m_rcv_flags |=RECEIVE_REGISTER_SYNCHRONISED; + /* reset bit count received */ + m_rcv_bit_count_received = 0; + m_rcv_framing_error = false; + m_rcv_parity_error = false; + } + } + } + else + if (m_rcv_flags & RECEIVE_REGISTER_SYNCHRONISED) + { + m_rcv_bit_count_received++; + + if (!bit && (m_rcv_bit_count_received > (m_rcv_bit_count - m_df_stop_bit_count))) + { + m_rcv_framing_error = true; + } + + /* received all bits? */ + if (m_rcv_bit_count_received==m_rcv_bit_count) + { + m_rcv_bit_count_received = 0; + m_rcv_flags &=~RECEIVE_REGISTER_SYNCHRONISED; + m_rcv_flags |= RECEIVE_REGISTER_WAITING_FOR_START_BIT; + //logerror("receive register full\n"); + m_rcv_flags |= RECEIVE_REGISTER_FULL; + } + } +} + +void device_serial_interface::receive_register_extract() +{ + UINT8 data; + + receive_register_reset(); + + /* strip off stop bits and parity */ + assert(m_rcv_bit_count >0 && m_rcv_bit_count <= 16); + data = m_rcv_register_data>>(16-m_rcv_bit_count); + + /* mask off other bits so data byte has 0's in unused bits */ + data &= ~(0xff<<m_df_word_length); + + m_rcv_byte_received = data; + + if(m_df_parity == PARITY_NONE) + return; + + //unsigned char computed_parity; + //unsigned char parity_received; + + /* get state of parity bit received */ + //parity_received = (m_rcv_register_data>>m_df_word length) & 0x01; + + /* parity enable? */ + switch (m_df_parity) + { + /* check parity */ + case PARITY_ODD: + case PARITY_EVEN: + { + /* compute parity for received bits */ + //computed_parity = serial_helper_get_parity(data); + + if (m_df_parity == PARITY_ODD) + { + /* odd parity */ + + + } + else + { + /* even parity */ + + + } + + } + break; + case PARITY_MARK: + case PARITY_SPACE: + //computed_parity = parity_received; + break; + } +} + + +/***** TRANSMIT REGISTER *****/ + +void device_serial_interface::transmit_register_reset() +{ + m_tra_flags |=TRANSMIT_REGISTER_EMPTY; +} + +/* used to construct data in stream format */ +void device_serial_interface::transmit_register_add_bit(int bit) +{ + /* combine bit */ + m_tra_register_data = m_tra_register_data<<1; + m_tra_register_data &=~1; + m_tra_register_data|=(bit & 0x01); + m_tra_bit_count++; +} + + +/* generate data in stream format ready for transfer */ +void device_serial_interface::transmit_register_setup(UINT8 data_byte) +{ + int i; + unsigned char transmit_data; + + if(m_tra_clock && !m_tra_rate.is_never()) + m_tra_clock->adjust(m_tra_rate, 0, m_tra_rate); + + m_tra_bit_count_transmitted = 0; + m_tra_bit_count = 0; + m_tra_flags &=~TRANSMIT_REGISTER_EMPTY; + + /* start bit */ + for (i=0; i<m_df_start_bit_count; i++) + { + transmit_register_add_bit(0); + } + + /* data bits */ + transmit_data = data_byte; + for (i=0; i<m_df_word_length; i++) + { + int databit; + + /* get bit from data */ + databit = transmit_data & 0x01; + /* add bit to formatted byte */ + transmit_register_add_bit(databit); + transmit_data = transmit_data>>1; + } + + /* parity */ + if (m_df_parity!=PARITY_NONE) + { + /* odd or even parity */ + unsigned char parity = 0; + switch(m_df_parity) + { + case PARITY_EVEN: + case PARITY_ODD: + + /* get parity */ + /* if parity = 0, data has even parity - i.e. there is an even number of one bits in the data */ + /* if parity = 1, data has odd parity - i.e. there is an odd number of one bits in the data */ + parity = serial_helper_get_parity(data_byte); + break; + case PARITY_MARK: + parity = 1; + break; + case PARITY_SPACE: + parity = 0; + break; + } + transmit_register_add_bit(parity); + } + + /* stop bit(s) */ + for (i=0; i<m_df_stop_bit_count; i++) + { + transmit_register_add_bit(1); + } +} + + +/* get a bit from the transmit register */ +UINT8 device_serial_interface::transmit_register_get_data_bit() +{ + int bit; + + bit = (m_tra_register_data>>(m_tra_bit_count-1-m_tra_bit_count_transmitted))&1; + + m_tra_bit_count_transmitted++; + + /* have all bits of this stream formatted byte been sent? */ + if (m_tra_bit_count_transmitted==m_tra_bit_count) + { + /* yes - generate a new byte to send */ + m_tra_flags |= TRANSMIT_REGISTER_EMPTY; + } + + return bit; +} + +bool device_serial_interface::is_receive_register_full() +{ + return m_rcv_flags & RECEIVE_REGISTER_FULL; +} + +bool device_serial_interface::is_transmit_register_empty() +{ + return m_tra_flags & TRANSMIT_REGISTER_EMPTY; +} + +const char *device_serial_interface::parity_tostring(parity_t parity) +{ + switch (parity) + { + case PARITY_NONE: + return "NONE"; + + case PARITY_ODD: + return "ODD"; + + case PARITY_EVEN: + return "EVEN"; + + case PARITY_MARK: + return "MARK"; + + case PARITY_SPACE: + return "SPACE"; + + default: + return "UNKNOWN"; + } +} + +const char *device_serial_interface::stop_bits_tostring(stop_bits_t stop_bits) +{ + switch (stop_bits) + { + case STOP_BITS_0: + return "0"; + + case STOP_BITS_1: + return "1"; + + case STOP_BITS_1_5: + return "1.5"; + + case STOP_BITS_2: + return "2"; + + default: + return "UNKNOWN"; + } +} |