// Simple port switching controller
// A very light weight comms suite and command interpreter is implemented.
// Literal strings are used in-line - usually not an efficient practice.
//
#define NULL 0
#define FALSE 0
#define TRUE 1
// Buffer lengths are short to prevent the need for external RAM
// Only idata is used.
#define TX_BUFFER_LENGTH 16
#define RX_BUFFER_LENGTH 16
#define RX_CMD_TERMINATOR 0x0D // <CR>
#define RX_CMD_BACKSPACE 0x08 // <CR>
#define NUMBER_OF_PORTS 4
#define CLK_FREQ 3U*11059200UL // about 33.178 MHz
#define BAUD_RATE 57600UL
// Save ram space by keeping all strings in ROM if
// possible
#pragma romstring
// Globals - resource limited processor and simple
// demonstration program. Globals are being used.
// version string
__rom char sVersion[] = "PrtSw Ver 1.3 ";
__rom char sDate[] = __DATE__" "; // Date with appended space
__rom char sTime[] = __TIME__;
// RS-232 receive buffers
// These buffers are quite restricted in length and so
// overflow will have to be managed (it has to be managed
// regardless of length, but in this instance we will
// no doubt have plenty of instances of overflow).
// In the first instance overflow shall be managed by
// discarding characters up until the next command
// terminator (v. simple).
// Two buffers are provided to allow for ping-ponging
// between them as one is being dealt with at the the
// higher level and one is receiving characters from
// the receive ISR.
char rxBuffer1[RX_BUFFER_LENGTH];
char rxBuffer2[RX_BUFFER_LENGTH];
unsigned char rxBuffer1Length = 0;
unsigned char rxBuffer2Length = 0;
__bit rxBuffer1Ready = FALSE;
__bit rxBuffer2Ready = FALSE;
// RS-232 transmit buffers
// Very limited in length to conserve
// RAM space. Note that ROM-based
// messages can be longer.
char txBuffer[TX_BUFFER_LENGTH];
unsigned char txBufferLength = 0;
// ISR variables
const char *isrTXBuf;
__rom char *isrTXBufROM;
// Code space is more available than RAM
// so I use a separate pointer to dump
// ROM-based messages. Rather than copying
// the ROM messages into a RAM buffer.
unsigned char isrTXLength;
unsigned char isrTXIndex;
__bit isrTXFromRom; // TRUE if transmitting a ROM-based message
char *isrRXBuf;
unsigned char isrRXLength;
__bit isrUsingRXBuffer1;
// ROM-based messaged - see also Version variables at the top
__rom const char txBufferPrompt[] = "cmd>";
#define PROMPT_LENGTH sizeof(txBufferPrompt)/sizeof(char)-1
__rom const char txBufferCR = 0x0d;
// Shadow registers
// Some of the I/O ports require shadow registers for correct operation
// as not all the pins are the same direction and the port out and
// in busses are not hardwired together.
// This demonstrates a possible difference between an embedded 8051
// and a traditional chip.
unsigned char P1_ShadowReg;
// Forward declarations of function prototypes
void ClearBuffer(char *cmdBuffer);
_Bool DoCommand(const char *cmdBuffer); // Simple command interpreter
void PrintHelp(void); // Print simple help
void PrintPrompt(void); // Print a small prompt
void PrintVersion(void);
_Bool Transmit(const char *msg, const unsigned char length);
_Bool TransmitRomMsg(__rom char * msg, unsigned char length);
_Bool TransmitCR(void);
_Bool TransmitPrompt(void);
void Init(void);
void SetupSerialPort(void);
_Bool Connect(unsigned char destPort, unsigned char srcPort);
_Bool SetPortState(unsigned char port, _Bool enable);
_Bool GetPortState(unsigned char port);
_Bool QueryPort(unsigned char port);
_Bool DumpPort(unsigned char port);
signed char QueryConnection(unsigned char port);
unsigned char ConvertPortToIndex(unsigned char port);
void delay(void)
{
int i;
for(i=0; i<0x1fff; i++)
{
}
}
void main(void)
{
Init();
SetupSerialPort();
TransmitCR();
PrintVersion();
TransmitCR();
TransmitPrompt();
ClearBuffer(rxBuffer1);
ClearBuffer(rxBuffer2);
while(1)
{
// Simple Task Manager
// Loop forever and check status of
// signals from low level software.
// Trigger high level tasks as required.
if(rxBuffer1Ready)
{
DoCommand(rxBuffer1);
ClearBuffer(rxBuffer1);
rxBuffer1Ready = FALSE; // allow ISR to use this buffer if it wishes
}
if(rxBuffer2Ready)
{
DoCommand(rxBuffer2);
ClearBuffer(rxBuffer2);
rxBuffer2Ready = FALSE; // allow ISR to use this buffer if it wishes
}
}
}
void ClearBuffer(char *cmdBuffer)
{
for(int i=0; i < RX_BUFFER_LENGTH; i++)
{
cmdBuffer[i] = 0x00;
}
}
// DoCommand is the (simple) command interpreter.
// Command decoding and execution.
// Assumes fixed format commands - each command handler
// does its own parameter checking (though there is very
// limited error handling).
_Bool DoCommand(const char *cmdBuffer)
{
_Bool bRetVal = FALSE;
unsigned char i;
unsigned char ch1, ch2;
if(cmdBuffer != NULL)
{
TransmitCR();
switch(cmdBuffer[0])
{
case '?':
case 'h':
case 'H':
// Dump help
PrintHelp();
bRetVal = TRUE;
break;
case 'v':
case 'V':
// Dump version
PrintVersion();
bRetVal = TRUE;
break;
case 'e':
case 'E':
// port enable/disable command
// we expect an integer (port)
// followed by another integer (1 or 0)
// Subtract '1' from port to get zero based port.
// Subtract '0' from integer to get 0 for '0' and 1 for '1'.
// No error checking for now
ch1 = ConvertPortToIndex(cmdBuffer[2]);
if( ch1 == 0xff)
{
// Parameter error
TransmitRomMsg("Invalid port", 12);
}
else
{
if( SetPortState( ch1, (cmdBuffer[4])-'0'))
{
TransmitRomMsg("OK", 2);
}
else
{
TransmitRomMsg("Fail", 4);
}
}
bRetVal = TRUE;
break;
case 'm':
case 'M':
// port mapping command
// we expect an integer
// followed by another integer
// No error checking for now
// See if we have alpha or numeric port references
// Also deal with upper and lower case
ch1 = ConvertPortToIndex(cmdBuffer[2]);
ch2 = ConvertPortToIndex(cmdBuffer[4]);
if( ch1 == 0xff)
{
// Parameter error
TransmitRomMsg("Invalid destination port", 24);
}
if( ch2 == 0xff)
{
if( ch1 == 0xff)
{
// add in <CR> if we have already printed an error message
TransmitCR();
}
// Parameter error
TransmitRomMsg("Invalid source port", 19);
}
if((ch1 != 0xff) && (ch2 != 0xff))
{
if( Connect(ch1, ch2))
{
TransmitRomMsg("OK", 2);
}
else
{
TransmitRomMsg("Fail", 4);
}
}
bRetVal = TRUE;
break;
case 'q':
case 'Q':
// Query port mapping and enable state
// We expect an integer giving the port to query
ch1 = ConvertPortToIndex(cmdBuffer[2]);
if( ch1 == 0xff)
{
// Parameter error
TransmitRomMsg("Invalid port", 12);
}
else if( !DumpPort(ch1))
{
TransmitRomMsg("Fail", 4);
}
bRetVal = TRUE;
break;
case 'd':
case 'D':
// Query port mapping and enable state for all ports
for(i=0; i<NUMBER_OF_PORTS; i++)
{
if( !DumpPort(i))
{
TransmitRomMsg("Fail", 4);
}
TransmitCR();
}
bRetVal = TRUE;
break;
default:
TransmitRomMsg("Huh?", 4);
break;
}
TransmitCR();
TransmitPrompt();
}
return bRetVal;
}
unsigned char ConvertPortToIndex(unsigned char port)
{
unsigned char index = port;
if( (port >= '1') && (port <= '4'))
{
index -= '1';
}
else if( (port >= 'a') && (port <= 'd'))
{
index -= 'a';
}
else if( (port >= 'A') && (port <= 'D'))
{
index -= 'A';
}
else
{
index = 0xff; // signal error
}
return index;
}
void PrintVersion(void)
{
// Dump the version number and date and time.
// will have to wait around for the dump to occur
// as our buffers are too small to take the
// full dump in one go.
//
// This code is not all that robust. If there is a
// problem transmitting one part of the
// message the rest will still be attempted - this
// is probably not what is required.
// Also, the wait for the TX system is infinite.
// This should be a timed wait with error
// recovery.
TransmitRomMsg(sVersion, sizeof(sVersion)/sizeof(char)-1);
TransmitRomMsg(sDate, sizeof(sDate)/sizeof(char)-1);
TransmitRomMsg(sTime, sizeof(sTime)/sizeof(char)-1);
}
void PrintHelp(void)
{
// Dump the help
//
// This code is not all that robust. If there is a
// problem transmitting one part of the
// message the rest will still be attempted - this
// is probably not what is required.
// Also, the wait for the TX system is infinite.
// This should be a timed wait with error
// recovery.
TransmitRomMsg("v - print version", 25); TransmitCR();
TransmitRomMsg("? or h - print help", 22); TransmitCR();
TransmitRomMsg("e prt 1|0 - enable/disable prt (1=enable)", 41); TransmitCR();
TransmitRomMsg("m dst src - drive dst outputs from src inputs", 45); TransmitCR();
TransmitRomMsg("q prt - query prt connection and state" , 42); TransmitCR();
TransmitRomMsg("d - dump all port connections and states", 48); TransmitCR();
}
_Bool TransmitCR(void)
{
return TransmitRomMsg(&txBufferCR, 1);
}
_Bool TransmitPrompt(void)
{
return TransmitRomMsg(txBufferPrompt, PROMPT_LENGTH);
}
_Bool Transmit(const char * msg, unsigned char length)
{
// This is a blocking function - it will
// wait indefinitely until the serial port
// is free. Not ideal but simple for now.
_Bool bRetVal = TRUE;
// The following access of the isrTXLength variable
// is atomic (byte variable) so we do not need to
// disable interrupts.
while(isrTXLength != 0)
{
// Wait until the port is free.
// We could put a timeout in here and return FALSE if
// the TX fails.
}
isrTXBuf = msg; // not sure this will work on a ROM string?
isrTXLength = length;
isrTXIndex = 1; // we will send the 0'th character below
isrTXFromRom = FALSE;
SBUF = msg[0]; // start the transmission
return bRetVal;
}
_Bool TransmitRomMsg(__rom char * msg, unsigned char length)
{
// This is a blocking function - it will
// wait indefinitely until the serial port
// is free. Not ideal but simple for now.
_Bool bRetVal = TRUE;
// The following access of the isrTXLength variable
// is atomic (byte variable) so we do not need to
// disable interrupts.
while(isrTXLength != 0)
{
// Wait until the port is free.
// We could put a timeout in here and return FALSE if
// the TX fails.
}
isrTXBufROM = msg; // not sure this will work on a ROM string?
isrTXLength = length;
isrTXIndex = 1; // we will send the 0'th character below
isrTXFromRom = TRUE;
SBUF = msg[0]; // start the transmission
return bRetVal;
}
void Init(void)
{
// Initialise ports for loopback and disabled
unsigned char i;
for(i=0; i<NUMBER_OF_PORTS; i++)
{
SetPortState(i, FALSE);
Connect(i, i);
}
}
void SetupSerialPort(void)
{
isrTXBuf = NULL;
isrTXLength = 0;
isrRXBuf = rxBuffer1;
isrRXLength = 0;
isrUsingRXBuffer1 = TRUE;
SM0 = 0;
SM1 = 1; // Mode 1 - 8-bit UART
SM2 = 0; // Disable multiprocessor feature
TB8 = 0; // 9th bit (not used)
RB8 = 0; // This is the rx stop bit.
TI = 0; // Clr TX int
RI = 0; // Clr RX int
//Need to set up timer 1 for baud rate CLK_FREQ_MHZ
TR1 = 0; // stop timer 1
PCON_bit(7) = 1; // SMOD = 1 -> high speed baud rate
// baud rate divisor
TL1 = 0; // initialise to zero
TH1 = 256U - (2 * CLK_FREQ/(12U*32U*BAUD_RATE));
TMOD_bit(7) = 0; // disable GATE
TMOD_bit(6) = 0; // operate as timer
TMOD_bit(5) = 1;
TMOD_bit(4) = 0; // 8-bit timer auto-reload mode
IT0 = 0;
IE0 = 0;
IT1 = 0;
IE1 = 0;
TR0 = 0; // timer 0 stopped
TF0 = 0;
TR1 = 1; // timer 1 running
TF1 = 0;
ES = 1;
IE_bit(7) = 1;
REN = 1; // enable reception
}
// Connect - the destination port outputs are driven from the
// the inputs of the source port.
// __putbit and __getbit could be used in this function.
// Returns TRUE if both ports are within range, FALSE
// otherwise (and no mapping change is done in this case).
_Bool Connect(unsigned char destPort, unsigned char srcPort)
{
_Bool bRetVal;
// Are ports within range?
if(destPort >= NUMBER_OF_PORTS || srcPort >= NUMBER_OF_PORTS)
{
bRetVal = FALSE;
}
else
{
// first set save the current state of the dest port
_Bool bSave = GetPortState(destPort);
P0 &= ~(0x03 << (2*destPort)); // Clear the existing mapping for this port
// This little hack only works when
// NUMBER_OF_PORTS == 4!
// Generally a more elaborate mechanism would
// be required that cleared all the bits of
// the port that control this destination port.
P0 |= (srcPort << (2*destPort));
SetPortState(destPort, bSave); // Restore port state
bRetVal = TRUE;
}
return bRetVal;
}
// SetPortState - returns FALSE (0) if port is out of range.
// returns TRUE (1) if port is within range
// This function will only work correctly when
// NUMBER_OF_PORTS is 8 or less.
_Bool SetPortState(unsigned char port, _Bool enable)
{
_Bool bRetVal;
// Do some parameter checking
if(port >= NUMBER_OF_PORTS)
{
bRetVal = FALSE;
}
else
{
if(enable)
{
P1_ShadowReg |= (1 << port); // OR in the correct port
}
else
{
P1_ShadowReg &= ~(1 << port); // AND out the correct port
}
P1 = P1_ShadowReg;
bRetVal = TRUE;
}
return bRetVal;
}
// GetPortState - return FALSE (0) if port enable bit is not set or
// if port is out of range (TRUE otherwise).
_Bool GetPortState(unsigned char port)
{
if(port >= NUMBER_OF_PORTS)
{
return FALSE; // Warning - unstructured error return.
}
return (P1_ShadowReg & (1 << port));
}
_Bool DumpPort(unsigned char port)
{
_Bool bRetVal = FALSE;
signed char tmp;
if(port < NUMBER_OF_PORTS)
{
tmp = port + 'A';
Transmit((const char *)&tmp, 1);
TransmitRomMsg(" <- ", 4);
// Extract the mapping
tmp = QueryConnection(port);
if(tmp >= 0 && tmp < NUMBER_OF_PORTS)
{
tmp += 'A';
Transmit((const char *)&tmp, 1);
}
else
{
// If we received an error display a question mark
TransmitRomMsg("?", 1);
}
if(GetPortState(port))
{
TransmitRomMsg(" enabled", 8);
}
else
{
TransmitRomMsg(" disabled", 9);
}
bRetVal = TRUE;
}
return bRetVal;
}
signed char QueryConnection(unsigned char port)
{
signed char retVal = -1;
if(port < NUMBER_OF_PORTS)
{
// Extract the mapping
retVal = P0;
retVal >>= (2*port);
retVal &= 0x03; // Only works for NUMBER_OF_PORTS = 4!
}
return retVal;
}
__interrupt(0x23) void SerialISR( void )
{
char tempChar; // temporary variable to reduce the
// amount of array dereferencing.
// Is there a TX interrupt?
if(TI)
{
TI = 0; // clear int source as soon as possible to
// minimise chances of missing an interrupt
// send the next character (if any) out to the port
if(isrTXIndex < isrTXLength)
{
// Another char to send - determine what sort of pointer we should
// use. When we are sending a ROM-based message use a
// __rom char *. This saves having to copy the message into RAM.
// RAM is very limited in this application - only idata.
if(isrTXFromRom)
{
// We are sending a ROM message so use the ROM pointer
SBUF = isrTXBufROM[isrTXIndex++];
}
else
{
SBUF = isrTXBuf[isrTXIndex++];
}
}
else
{
// signal that we have finished transmitting
isrTXLength = 0;
}
} // end of if(TI) - transmit character handling
// Is there an RX interrupt?
if(RI)
{
RI = 0; // clear int source as soon as possible to
// minimise chances of missing an RX interrupt
tempChar = SBUF;
// save this character away - if there is space
if(isrRXLength < RX_BUFFER_LENGTH-1)
{
// OK save it
isrRXBuf[isrRXLength++] = tempChar;
}
else
{
// Lets signal something
// I know! We can put the count of received chars onto
// Port 2. But we will do this outside the test so it
// is a useful count at all times.
}
// Deal with back spaces - watch for underflow of the
// buffer.
if(tempChar == RX_CMD_BACKSPACE)
{
if(isrRXLength > 1)
{
isrRXLength -= 2; // take off two to account for the just-added
// backspace character
}
else
{
isrRXLength = 0; // only really useful for a signed variable
}
// Could print a space and then do the backspace again
// but I don't want to have to deal with potential recursion in
// calling the TX routines, or to possibly stuff up some outgoing
// transmission by forceably inserting a character into the transmit
// buffer.
}
// Debug ouput - Upper four bits of port 2 contain lower four bits of
// tempChar and lower four bits of port 2 contains lower four bits of
// isrRXLength
P2 = ((tempChar << 4) & 0xf0)|(0x0f & isrRXLength);
//P2 = isrRXLength; // signal the number of received characters onto port 2
// If the character was a command terminator then we need to
// pass it up to the high level.
if(tempChar == RX_CMD_TERMINATOR)
{
// new command buffer
if(isrUsingRXBuffer1)
{
rxBuffer1Length = isrRXLength;
// set up for further reception in the other RX buffer
isrRXBuf = rxBuffer2;
isrUsingRXBuffer1 = FALSE;
rxBuffer1Ready = TRUE; // signal high level code
}
else
{
rxBuffer2Length = isrRXLength;
isrRXBuf = rxBuffer1;
isrUsingRXBuffer1 = TRUE;
rxBuffer2Ready = TRUE;
}
isrRXLength = 0;
} // end of if(tempChar == RX_CMD_TERMINATOR )
} // end of if(RI) - receive character handling
}