Solved. The issue was that using a global pointer to the serial driver object which contains the pointer to the ring buffer did not work outside a debug session. The buffer pointer was NULL when using the global driver pointer. Making the driver pointer local solved the issue, see below.
My guess is that globals (pointers as well as objects) are initialized differently inside versus outside a debug session.
Code snippets from rs485_driver.hpp:
// Using a global pointer to the driver object does not work outside a debug session.
//RS485Driver *rs485 = ProcessorC28::instance.GetRS485Driver();
int CliUartWrite(int fileDescriptor, const char *buffer, unsigned count)
{
// We have to use a local instead of a global pointer.
// Otherwise, SendChar will find the pointer to the ring buffer of its class to be NULL.
RS485Driver *rs485 = ProcessorC28::instance.GetRS485Driver();
rs485->SetBusTxMode(Gpio::eHIGH);
return rs485->SendCharBuffer((char *) buffer, count);
}
I leave my first version of the problem description intact (see far below), but I think my problem is the SCI tx fifo interrupt not firing at the time of enabling it (sciRegisters->SCIFFTX.bit.TXFFIENA = 1;). My SendCharBuffer function fills a ring buffer and then enables this interrupt. Everything works inside a debug session but not outside of it. It also works outside a debug session when I connect stdio to my serial driver before sending the first characters.
int SerialPortUartDriver::SendCharBuffer(char *pBuf, int len)
{
int bufferSize = buffers->bufferSizeTx;
if (bufferSize <= len)
{
return -1;
}
// Get current capacity of ring buffer.
uint16_t indexHead = buffers->indexTxHead;
// Wait for capacity becoming big enough to hold data.
while (GetCapacityTx(indexHead) <= (len + 1));
// Copy data into ring buffer.
char *buffer = buffers->bufferTx + indexHead;
while (len--)
{
*buffer++ = *pBuf++;
if (++indexHead >= bufferSize)
{
indexHead = 0;
buffer = buffers->bufferTx;
}
}
buffers->indexTxHead = indexHead;
// Enable FIFO interrupt. The ISR disables it if the ring buffer is empty.
volatile SCI_REGS *sciRegisters =
#ifdef DEBUG
channel == SCI_B ?
&ScibRegs :
#endif
&ScicRegs;
sciRegisters->SCIFFTX.bit.TXFFIENA = 1;
// Wait until first byte has gone into the FIFO.
//while (sciRegisters->SCICTL2.bit.TXEMPTY == 1);
return len;
}
I implemented a function that switches stdio at startup to SCI_B (RS485) and then allows switching stdio between SCI_B and SCI_C. It works inside a debug session but the same project configuration (DEBUG, no optimization, CIO Function in Debug Configuration/Target disabled) does not work outside a debug session.
To reproduce:
- Open appropriate COM ports on PC in terminal windows. In my case, SCI_B (RS232) is connected to COM6, SCI_C (RS485) is connected to COM1. Both run at 115200 baud.
- Start debug session. -> Application initializes peripherals, switches stdio to SCI_C (see function below), and prints test menu to COM1.
- User chooses Switch stdio from test menu. -> Application calls switch function (see below).
- Switch function removes current device if it exists (for example stdio connected to SCI_B), adds stdio device connected to SCI_C and returns.
- Switch test function prints test menu. Since stdio had been switched the menu is printed over SCI_B to COM6.
Below is the log of the terminals when running in a debug session.
COM1 / RS485 (stdio is being switched to):
stdout is now using SCI-C.
stdin is now using SCI-C.
stdio is now using SCI-C
SM320F28335 Processor initialized...
Test Menu - Version 0.3.0
0 Acquisition - Acquire waveform (<channel[0..2]>, <average count>, <delay in ms
...
COM6 / RS232 (initialized SCI_C peripheral):
stdio is now using SCI-C
...
The log outside a debug session shows nothing on COM1 and the same line (as expected) on COM6.
----------------------stdio switching function -----------------------------------
CliReturnCode Cli::CliCommand_switchCli(Cli *pCli, CliCommand *pCmd, CliParams *pParams)
{
int returnValue;
bool switchToCli = false;
if (pParams->numParams == 2)
{
switchToCli = (atoi(pParams->str[1]) > 0);
}
else if (pParams->numParams > 2)
{
switchToCli = true;
// todo Implement SetBaud() in serial driver.
// clockSpeedRS485 = atol(pParams->str[2]);
}
if (switchToCli == true)
{
// printf("stdin and stdout are being switched to SCI-C\r\n");
// Switch stdout and stdin to SCI-C.
#ifdef DEBUG
if (ProcessorC28::instance.GetStdioUart() == ProcessorC28::STDIO_RS232)
{
// Remove debug device that uses SCI-B for stdout and stdin.
extern char *debugFname;
returnValue = remove_device(debugFname);
const char *cliRemoved = "Removed SCI-B device...\r\n";
DebugUart.SendCharBuffer((char *) cliRemoved, strlen(cliRemoved));
if (returnValue)
{
const char *cliErrorRemove = "Error removing SCI-B device\r\n";
DebugUart.SendCharBuffer((char *) cliErrorRemove, strlen(cliErrorRemove));
return eCliReturn_Failed;
}
ProcessorC28::instance.SetStdioUart(ProcessorC28::STDIO_NONE);
}
#endif
returnValue = add_device(cliFname, _MSA, CliUartOpen, CliUartClose, CliUartRead, CliUartWrite, NULL, NULL, NULL);
if (returnValue)
{
const char *cliErrorAdd = "Error adding SCI-C device\r\n";
CliUartWrite(0, cliErrorAdd, strlen(cliErrorAdd));
return eCliReturn_Failed;
}
FILE *fid = fopen(cliFname, "w");
freopen(cliFnameReopen, "w", stdout); // redirect stdout to uart
setvbuf(stdout, NULL, _IONBF, 0); // turn off buffering for stdout
printf("stdout is now using SCI-C.\r\n");
fid = fopen(cliFname, "r");
freopen(cliFnameReopen, "r", stdin); // redirect stdin to uart
setvbuf(stdin, NULL, _IONBF, 0); // turn off buffering for stdin
printf("stdin is now using SCI-C.\r\n");
const char *cliSwitchSuccess = "stdio is now using SCI-C\r\n";
// The line below proves outside a debug session that this function was executed.
DebugUart.SendCharBuffer((char *) cliSwitchSuccess, strlen(cliSwitchSuccess));
rs485->SendCharBuffer((char *) cliSwitchSuccess, strlen(cliSwitchSuccess));
ProcessorC28::instance.SetStdioUart(ProcessorC28::STDIO_RS485);
}
else
{
returnValue = remove_device(cliFname);
if (returnValue)
{
return eCliReturn_Failed;
}
pCli->dCliNextState = eCliState_Exit;
#ifdef DEBUG
returnValue = DebugUartAsStdio(ProcessorC28::instance.GetCpuClockSpeedHz());
if (returnValue)
{
return eCliReturn_Failed;
}
#endif
}
return eCliReturn_Success;
}