Chris (dude_1967),
Is the above post equally applicable to a linux environment not compiled with real-time support?
Kind Regards
Chris
Printable View
Chris (dude_1967),
Is the above post equally applicable to a linux environment not compiled with real-time support?
Kind Regards
Chris
I do not know enough abuot linux systems to be able to fully comment on their real-time characteristics. However, I do suspect that standard linux configurations in normal user-mode are just as incapable of real-time performance as windows in normal-user mode.Quote:
Originally Posted by *io*
The real-time performance tick, accessed through the rdtsc assembler command, is present for all pentiums and their clones. The reading of the rdtsc can be programmed in the linux environment using inline assembler and the language of gas (Gnu assembler) in the GCC. No problem here...
Sincerely, Chris.
Does any one know if Vista have any advantages in terms of realtime apps?
Chris, I'm liking the microcontrollers. I might forego the PC altogether! If only... Thanks to all those who oriented me towards the micro route.
Ben.
Absolutely not!Quote:
Originally Posted by shoppinit
Yes, they can be of great assistance when timing or other simplicity really counts.Quote:
Originally Posted by shoppinit
You are welcome, Ben. That's what the CodeGuru forums are all about.Quote:
Originally Posted by shoppinit
So now I have a question for you. Could you please list the microcontroller, compiler, debug system, OS, starter-kit, etc. which you decided to use for your project?
Sincerely, Chris.
Hi Chris,Quote:
Originally Posted by dude_1967
THe micro is an NXP LPC 2138 mounted on an Olimex evaluation board. I got this one first:
http://www.olimex.com/dev/lpc-mt-2138.html
and I've just ordered this one which is essentially the same but without all the toys:
http://www.olimex.com/dev/lpc-p2138.html
I like the LPC2138 - it's got loads of useful peripherals and has loads of memory. It also seems to be quick, but I have nothing to compare it to, so I could be wrong about that. Runs at 60MHz in any case.
Also from Olimex I got the parallel port JTAG debugger / programmer. Cheap and effective.
The compiler / debugger environment is Rowley Cross Studio for ARM - I'm particularly pleased with this. Worked right off the bat, is easy to use for anyone that knows Visual Studio and is very reasonable in terms of price. The compiler is based (I believe) on the open source one, so all the examples for the LPC2xxx can be compiled on there. This is one of the reasons I chose it - the availability of example code. That's how I learn best.
I also like that I can put breakpoints in my program just like VS and run step by step. Helps if you're like me and generate 2 bugs per line of code written!
The evaluation board was about $80, the JTAG about $30 and Rowley about $150. I consider that pretty cost effective to get started. I'd definitely recommend this set-up for someone considering getting started in micros for the first time.
I decided to use the FreeRTOS realtime OS for my application. There is a port for the Rowley IDE and LPC2138. It's for a Kiel evaluation board, but it's pretty straightforward to modify it for the Olimex one.
http://www.freertos.org
All the best!
Ben.
Hi Chris,
Here's a question for you, which kind of loops nicely back to where this thread started...
I've got the micro working just the way I like it (just a shame I can't do everything with it) and it's doing what it's supposed to do. Detecting objects, sending and receiving info to / from pc.
All works great... but at low speed. Once the speed goes up (ie. nb of objects to analyse per second), the PC starts missing the RS232 events. Well not miss, but react unpredictably. The micro is unshakeable.
This is not too surprising because task manager show the CPU usage at between 99 and 100% so I guess everything is struggling to keep up.
This is what I have difficulty understanding: I've timed everything and to completely analyse one object the whole process, from reception of message to finishing and sending the message back to micro takes about 25ms. So in theory, with nothing else running on the PC, at 12 objects / second I should be nowhere near 100% CPU usage. More like 30% I would have thought.
I've tried with various numbers of worker threads to carry out the analyses, but the result is always similar.
Where's my 70% of processor time gone? I need to be up to about 50 objects / second and I'm starting to look at extreme quad cores and/or multiplexing PCs!
All the best.
Ben.
Ben,
Did you design performance counters into your PC application. If you did, then perfmon would help you out here. If not, then you may want to consider adding some.
David
Hi David,
I use QueryPerformanceCounter calls punctually to find bottle necks.
I use perfmon to see what's going on, but I didn't know that you can specifically use code to monitor performance. How would I go about doing that?
I've also tried LTProf in the past, I might dig that out again...
Thanks.
Ben.
You might still be viewing the role of the PC as a real-time role. For example, if proper operation of your overall system requires the PC to get some reponse back to an inquiry from the mirco within some time interval (like within 25 ms), then the PC is still being used incorrectly in a real-time role, and the system will eventually fail.
Tell us (in words) the overall operation of your current system. For example, micro detects an object and sends RS-232 notification to PC, which then does what? And what does the micro do in the meantime? And does the micro require a response from the PC in order to continue processing the object (hopefully not).
Also, post your PC's code. Maybe you are still using a polling paradigm, which should no longer be necessary since you can now rely on WaitCommEvent() to wait for an RS-232 notification from the micro.
Mike
Hi Mike,
This is the sequence of events:
1. The micro physically detects an object and gives it a number
2. The micro sends (serial) the number to the PC
3. The PC receives a serial event and unblocks a thread to start the analysis, giving the object the number that the micro sent.
4. Once the PC is done, it sends the result of the analysis (good or bad), as well as the object number, back to the micro so that the micro can take appropriate action. There is about a 1 second window for this.
If the micro doesn't get the message from the PC in time, it takes a default action. It's basically just looping all the time to see if it's time to take an action. It loops every 1 ms.
There's definitely no polling going on in the PC. (I learned my lesson ;) ) Everything is event driven.
My application is, however, a console app (since I don't need any GUI output)... it just occured to me that maybe this might be potentially a problem?
Thanks.
Ben.
Just as a follow up, if I don't carry out the analysis (ie return instead) then the structure that receives, sends and unblocks the analysis threads uses negligeable CPU time. I can run at pretty much any speed I like with no problems at all.
It's when the CPU usage gets above 95% that things start to fall down.
The point is, though, that my QueryPerformanceTimers tell me that the analysis is taking 25ms which at 12 / s should be about 300ms total... Even with the other overheads, I shouldn't be at more than 50% CPU usage.
Could it be context changes? I don't know how fast the windows scheduler is...
Ben.
Ben,
I just looked at post #56 as well as your most recent contribution in post #61. It is surprising that CPU load is even an issue here and it points to design rather than the limitations of the mighty giga-flop desktop PC.
The send and recv of serial characters should be no stress whatsoever to the PC given the right driver architecture.
You need to design or acquire from internet a suitable RS232 driver within the Win32-API domain.
I almost get the impression that you might be polling for individual characters on the PC side. This is not necessary. You can use a buffer and read this periodically with a timeout.
We need to re-open this thread and talk about the design of your serial driver on the PC side. You need to use buffers and timeouts in your console application.
Well if you can believe it, I'm actually on vacation now so who knows if I can really get into this. Nevertheless, I am disturbed by your troubles on the PC-side and think that these need to be smoothed out.
I can send some sample RS232 codes, but there are also many examples on the net at CodeGuru as well as CodeProject.
Sincerely, Chris.
There should be no probelm using a console application.
Please post your PC code. Naturally, we do not need to see the actual processing, which you can replace with a call to some place-holder function like DoLengthyProcessing(). Just show us the framework: how does the PC know that the micro has sent an object's number, how are the threads implemented (i.e, pool of persistent threads (better) vs. creation of new thread for each new number (less good)), how many threads are there, etc.
Mike
Hi Chris,
I hope you're having a good vacation. I'm using the CSerial class for my serial communications. Details below.
Mike, I'll post below the simplified code. I think this is actually related to another post I made in the multithreading forum because after testing with just one analyse thread running I'm starting to get the performance results I expected to see.
http://www.codeguru.com/forum/showth...91#post1613791
Anyway, the inits are here:
and the threads:Code://inits
for (int k = 0; k<NUM_THREADS; k++) //=2
{
interface[k] = CDataInterface::create(0,Buffer[0]->GetSize(),3,300);
}
for (int k=0; k<NUM_THREADS; k++)
{
thread_num = k;
hThread_Analyse[k] =::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread_Analyse,this,0,&ThreadId__Analyse[k]);
//SetThreadPriority(hThread_Analyse[k],THREAD_PRIORITY_BELOW_NORMAL );
Sleep(100);
}
hThread_RxCommsRS232 =::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread_RxCommsRS232,this,0,&ThreadId__RxCommsRS232);
SetThreadPriority(hThread_RxCommsRS232,THREAD_PRIORITY_HIGHEST );
Code:long WINAPI Thread_RxCommsRS232(LPVOID lpParam)
{
CAnalyser* the_analyser=(CAnalyser*)lpParam;
//CSerial serial;
LONG lLastError = ERROR_SUCCESS;
LARGE_INTEGER date_courante;
DWORD pdwWritten = 0;
// Attempt to open the serial port (COM1)
lLastError = the_analyser->serial.Open(_T("COM4"),0,0,true);
if (lLastError != ERROR_SUCCESS)
return the_analyser->ShowError(the_analyser->serial.GetLastError(), _T("Unable to open COM-port"));
// Setup the serial port (9600,8N1, which is the default setting)
lLastError = the_analyser->serial.Setup(CSerial::EBaud115200,CSerial::EData8,CSerial::EParNone,CSerial::EStop1);
if (lLastError != ERROR_SUCCESS)
return the_analyser->ShowError(the_analyser->serial.GetLastError(), _T("Unable to set COM-port setting"));
lLastError = the_analyser->serial.SetupHandshaking(CSerial::EHandshakeOff);
if (lLastError != ERROR_SUCCESS)
return the_analyser->ShowError(the_analyser->serial.GetLastError(), _T("Unable to set COM-port handshaking"));
// Register only for the receive event
lLastError = the_analyser->serial.SetMask(CSerial::EEventBreak |
CSerial::EEventError |
CSerial::EEventRecv);
if (lLastError != ERROR_SUCCESS)
return the_analyser->ShowError(the_analyser->serial.GetLastError(), _T("Unable to set COM-port event mask"));
// Use 'non-blocking' reads, because we don't know how many bytes
// will be received. This is normally the most convenient mode
// (and also the default mode for reading data).
lLastError = the_analyser->serial.SetupReadTimeouts(CSerial::EReadTimeoutNonblocking);
if (lLastError != ERROR_SUCCESS)
return the_analyser->ShowError(the_analyser->serial.GetLastError(), _T("Unable to set COM-port read timeout."));
lLastError = the_analyser->serial.Purge();
if (lLastError != ERROR_SUCCESS)
return the_analyser->ShowError(the_analyser->serial.GetLastError(), _T("Couldn't Purge."));
// Create a handle for the overlapped operations
HANDLE hevtOverlapped = ::CreateEvent(0,TRUE,FALSE,0);;
if (hevtOverlapped == 0)
return the_analyser->ShowError(the_analyser->serial.GetLastError(), _T("Unable to create manual-reset event for overlapped I/O."));
// Setup the overlapped structure
OVERLAPPED ov = {0};
ov.hEvent = hevtOverlapped;
// Open the "STOP" handle
HANDLE hevtStop = ::CreateEvent(0,TRUE,FALSE,_T("Overlapped_Stop_Event"));
if (hevtStop == 0)
return the_analyser->ShowError(the_analyser->serial.GetLastError(), _T("Unable to create manual-reset event for stop event."));
// Keep reading data, until an EOF (CTRL-Z) has been received
bool fContinue = true;
//do
while ( fContinue)
{
// Wait for an event
//lLastError = the_analyser->serial.WaitEvent();
lLastError = the_analyser->serial.WaitEvent(&ov);
if (lLastError != ERROR_SUCCESS)
return the_analyser->ShowError(the_analyser->serial.GetLastError(), _T("Unable to wait for a COM-port event."));
// Setup array of handles in which we are interested
HANDLE ahWait[2];
ahWait[0] = hevtOverlapped;
ahWait[1] = hevtStop;
// Wait until something happens
switch (::WaitForMultipleObjects(sizeof(ahWait)/sizeof(*ahWait),ahWait,FALSE,INFINITE))
{
case WAIT_OBJECT_0:
{
// Save event
const CSerial::EEvent eEvent = the_analyser->serial.GetEventType();
// Handle error event
if (eEvent & CSerial::EEventError)
{
ALERT;
printf("\n### ERROR: ");
switch (the_analyser->serial.GetError())
{
case CSerial::EErrorBreak: printf("Break condition"); break;
case CSerial::EErrorFrame: printf("Framing error"); break;
case CSerial::EErrorIOE: printf("IO device error"); break;
case CSerial::EErrorMode: printf("Unsupported mode"); break;
case CSerial::EErrorOverrun: printf("Buffer overrun"); break;
case CSerial::EErrorRxOver: printf("Input buffer overflow"); break;
case CSerial::EErrorParity: printf("Input parity error"); break;
case CSerial::EErrorTxFull: printf("Output buffer full"); break;
default: printf("Unknown"); break;
}
printf(" ###\n");
}
// Handle data receive event
if (eEvent & CSerial::EEventRecv)
{
//printf("serial event \n");
QueryPerformanceCounter(&date_courante);
// Read data, until there is nothing left
DWORD dwBytesRead = 0;
char szBuffer[101];
//do
//{
// Read data from the COM-port
//printf("Reading buffer\n");
//EnterCriticalSection(&the_analyser->SerialPortAccess);
the_analyser->serial.Read(szBuffer,3/*sizeof(szBuffer)-1*/,&dwBytesRead);
//LeaveCriticalSection(&the_analyser->SerialPortAccess);
//printf("Finished Reading buffer '%s' bytes read %d\n", szBuffer, dwBytesRead );
if (lLastError != ERROR_SUCCESS)
return the_analyser->ShowError(the_analyser->serial.GetLastError(), _T("Unable to read from COM-port."));
//printf("serial : nb_bytes read: %d\n",dwBytesRead);
if (dwBytesRead == 3)
{
//printf("serial : nb_bytes read: %d\n",dwBytesRead);
szBuffer[3] = 0x00;
//printf("THREAD RxCommsRS232 '%s' received at: %I64d\n", szBuffer, date_courante.QuadPart);
if (szBuffer[0]==1)
{
byte object_number;
char xxx,yyy;
sscanf(szBuffer,"%c%c%c",&xxx,&object_number,&yyy);
the_analyser->object_no_recd = object_number -48;
//the_analyser->object_no_recd = (unsigned int)(szBuffer[1] - 48);
the_analyser->lastRxTime[0] = date_courante.QuadPart;
int event_ok = 0;
event_ok = SetEvent(the_analyser->hRS232RxEvent);
if (!event_ok)
{
...
}
}
if (szBuffer[0]==3) //Status message received from µC
{
the_analyser->last_message_uC = (byte)szBuffer[1];
SetEvent(the_analyser->hStatusRx_uC);
}
}
the_analyser->serial.Purge();
//}
//while (dwBytesRead == sizeof(szBuffer)-1);
//while (dwBytesRead > 0);
}
}
break;
void CAnalyser::CallBackFunction2()
{
CObjecttiming* local_object = new CObjecttiming();
int at_least_one_buffer_found = 0;
int buffer_to_use = 0;
if (WaitForSingleObject(hRS232RxEvent), INFINITE) == WAIT_OBJECT_0)
{
ResetEvent(hRS232RxEvent);
local_object->object_number = object_no_recd;
at_least_one_buffer_found = 0;
for (int j=0; j<BUFFER_SIZE; j++)
{
if (at_least_one_buffer_found)
continue;
if (BufferCready[j])
{
at_least_one_buffer_found++;
local_object->Num_circ_buffer = j;
BufferCready[j] = false;
}
}
if (at_least_one_buffer_found)
{
if ( (buffer_to_use >=0)&&(buffer_to_use < BUFFER_SIZE) )
{
pBuffer->Copy(Buffer);
bool found_a_thread = false;
bool no_thread_available = false;
int thread_to_call = -1;
thread_to_call = 0;
while ((!found_a_thread) && (!no_thread_available))
{
EnterCriticalSection(&AnalyseThreadStatus[thread_to_call]);
if (AnalyseThreadBusy[thread_to_call]==false)
{
found_a_thread = true;
local_object->ready_for_analysis = true;
object_to_analyse[thread_to_call] = local_object;
QueryPerformanceCounter(&stop);
SetEvent(hObjectReady[thread_to_call]);
INFO;
printf("Time up to setevent = %f\n", (( (float)stop.QuadPart - (float)start.QuadPart )/ (float)the_analyser->freq.QuadPart) *1000.0f );
}
else
{
TRACE;
printf("thread[%d] is busy\n",thread_to_call);
}
LeaveCriticalSection(&AnalyseThreadStatus[thread_to_call]);
if (thread_to_call > NUM_THREADS)
{
no_thread_available = true;
ALERT;
printf("!!!No threads available \n");
}
else
{
thread_to_call++;
}
}
if (!found_a_thread)
{
...
delete local_object;
}
}
}
else
{
nb_not_enough_buffers++;
ALERT;
printf("CALLBACK FUNCTION: Not enough buffers %d", nb_not_enough_buffers);
}
}
long WINAPI Thread_Analyse(LPVOID lpParam)
{
CAnalyser* the_analyser=(CAnalyser*)lpParam;
int this_thread_number = the_analyser->thread_num;
CObjecttiming* local_object = NULL;
while (Keep_thread_alive)
{
WaitForSingleObject(the_analyser->hObjectReady[this_thread_number], INFINITE);
local_object = the_analyser->object_to_analyse[this_thread_number];
EnterCriticalSection(&the_analyser->AnalyseThreadStatus[this_thread_number]);
the_analyser->AnalyseThreadBusy[this_thread_number] = true;
LeaveCriticalSection(&the_analyser->AnalyseThreadStatus[this_thread_number]);
ResetEvent(the_analyser->hObjectReady[this_thread_number]);
assert(local_object != NULL);
if ( (local_object->ready_for_analysis == true) && (local_object->is_analysing == false) )
{
local_object->is_analysing = true;
//Analyse object
local_object->analysis_result = the_analyser->Analyse_Object(local_object, this_thread_number);
local_object->is_analysing = false;
the_analyser->BufferCready[local_object->Num_circ_buffer] = true;
local_object->Num_circ_buffer = -1;
local_object->finished_analysing = true;
}
EnterCriticalSection(&the_analyser->AnalyseThreadStatus[this_thread_number]);
the_analyser->AnalyseThreadBusy[this_thread_number] = false;
LeaveCriticalSection(&the_analyser->AnalyseThreadStatus[this_thread_number]);
}
I am not certain that the following would explain the behavior you are seeing (although it might explain the behavior at your related thread at http://www.codeguru.com/forum/showth...91#post1613791, in which you state tht the threads are using the wrong information), but the following code is wrong and should be changed:
The code is wrong because you can't guarantee that a newly-started thread will start up and read the this_thread_number member variable, before the thread-starting code changes the variable. I see that you tried to guarantee it, by insertion of the Sleep(100) call, but that's no guarantee. For example, even after the Sleep(100) call, the thread scheduler might not yet have scheduled the newly-created thread for execution, or maybe the scheduler will schedule your newly-created threads for out-of-order execution, such that both threads get exactly the same value for this_thread_number.Code:// thread starting code
for (int k=0; k<NUM_THREADS; k++)
{
thread_num = k;
hThread_Analyse[k] =::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread_Analyse,this,0,&ThreadId__Analyse[k]);
Sleep(100);
}
// then, in the thread code
long WINAPI Thread_Analyse(LPVOID lpParam)
{
CAnalyser* the_analyser=(CAnalyser*)lpParam;
int this_thread_number = the_analyser->thread_num;
// etc, which uses this_thread_number as an index into shared data
}
Here's one way to do it better, without the race condition:
MikeCode:class ThreadCookie
{
public:
CAnalyser* pThis;
int thread_num;
};
// thread starting code
for (int k=0; k<NUM_THREADS; k++)
{
ThreadCookie* pCookie = new ThreadCookie;
pCookie->pThis = this;
pCookie->thread_num = k;
hThread_Analyse[k] =::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread_Analyse, pCookie, 0,&ThreadId__Analyse[k]);
// no need for Sleep(100);
}
// then, in the thread code
long WINAPI Thread_Analyse(LPVOID lpParam)
{
ThreadCookie* pCookie = (ThreadCookie*)lpParam;
CAnalyser* the_analyser=pCookie->pThis;
int this_thread_number = pCookie->thread_num;
delete pCookie;
// etc, which uses this_thread_number as an index into shared data
}
Hi Mike,
That seems a much better way of doing it. Thanks for that. Although, using the same name for the various instances of ThreadCookie worries me. I could be wrong about that. Wouldn't it be safer to do it this way:
Thanks.Code:for (int k=0; k<NUM_THREADS; k++)
{
ThreadCookie* pCookie[k] = new ThreadCookie();
pCookie[k]->pThis = this;
pCookie[k]->thread_num = k;
hThread_Analyse[k] =::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread_Analyse, pCookie[k], 0,&ThreadId__Analyse[k]);
// no need for Sleep(100);
}
Ben.
Hi Ben,
I just took a look at the larger portion of your code sample in thread #64. I am a little bit concerned about the length of the code and the mixture of various functions within the thread. In addition, it is hard for me to recognize any kind of RS232 protocol which you may have implemented. Finally, there is a mixture of old-school C-idioms combined with some elements of C++.
This might not be what you want to hear but your code seems a bit compressed yet simultaneously over-dimensioned and somewhat disorganized for the modest assignment at hand (RS232 communication with a simple protocol with a microcontroller RS232). Now I am only going in this direction with you because I know that you have been working very hard on this project for quite some time. I have also followed your work with the microcontroller side. But we have to work on the PC side now. Please do not confuse my bluntness with any kind of criticism. Far from it, I want to make sure that you are programming to the best of your abitilies also on the PC side.
If you worked with me, then I would tell you to redesign this work. Take some time and think about the design goals. Choose one language (C or C++) and use it consistently. But more important than the language choice is the architecture. You should design some kind of an object oriented architecture in which all of the serial operations (open, send, receive, timestamp, etc.) are encapsulated in one class.
I think that you should use a dedicated, separate worker-thread for you receive loop.
Your protocol, in so far as one is present for your communications with the microcontroller, should be in a different class. And it should be present in some recognizable form.
You need to minimize your use of pointers and operator new. Also, your code is screaming for assistance through the sensible use of STL.
I need to re-establish my orientation with the PC-side of your communications software.
Could you please describe very briefly the main goals of your PC-side software in combination with the microcontroller as well as any kind of protocol which you are using?
Sincerely, Chris.
Hi Chris,
Thanks for the constructive criticism... sadly I'm not a natural born coder, but rather a nuts and bolts guy that's found himself having to code! That and the fact that most of the programming experience i have is C and not C++ / object. I'm making progress though, largely thanks to you guys. One of the reasons I like the micro is the simplicity of the programming (C). If I had to do this in DOS then it would probably be done and dusted months ago :)
Concerning the RS232 comms, that was always a concern of mine and I pretty much lifted the CSerial class (from codeproject.com) and put it in a worker thread. Right now that thread is just waiting for an event and reacting to it. When you say that I should use a dedicated, separate worker thread, I thought this was what I was doing :s
My goal, in terms of the serial communication, is uniquely to receive a 3 byte message from the micro and deal with it. The first byte of which is an identifier for the kind of message, the 2nd is the number of the object and the 3rd is redundant for the time being.
I also need to send a 3 byte message to the micro where the 1st byte is the message identifier, the 2nd is the number of the object and the 3rd is the action to take.
There is no other serial communication going on. I wanted to keep the messages as short as possible to avoid too much traffic on the serial port.
In light of your post, I'm a bit embarassed by the quality of my code :( but this represents the best of my abilities.
When you say use STL, where / how exactly would you recommend doing this?
Thanks for all this. I realise you're on vacation and should be sitting in the sun with a martini, not trudging through my code!
Ben.
Not necessary because of "pass by value" semantics. In each iteration of the loop, an new ThreadCookie object is created, and the value of a pointer to it is stored in pCookie (my original code). When CreateThread is called, its value is eventually passed to the thread, which leaves the pCookie variable free to store a new pointer to a new ThreadCookie object in the next iteration of the loop.Quote:
Originally Posted by shoppinit
The difference from your original code is that you were sharing the variable itself, which of course can hold only one single value at a time.
But in light of comments from dude_1967 (Chris), all that might be irrelevant:
I'm not completely certain, but what I think Chris is telling you is that there is no need for more than a single worker thread. The thread should wait for a comm event from the micro, quickly do all processing that it needs to, including sending the answer back to the micro, and then wait for the next event. You would need more than a single worker thread only if the processing required for each message took a remarkably long time, such that it might overlap significantly with receipt of a subsequent comm event.Quote:
Originally Posted by shoppinit
Mike
If you are referring to CSerialPort, then the version at Codeproject is several years old now. You should go directly to PJ Naughter's site and get the lastest and greatest: http://www.naughter.com/serialport.htmlQuote:
Originally Posted by shoppinit
Mike
A lot of good programmers have this kind of background.Quote:
Originally Posted by shoppinit
This sounds very straight forward. I do like the MFC-based serial driver which you have been using. However, I believe that your design requirements are so simple that this thing is over dimensioned for yuor design requirements. However, you can definitely use this class if you want to. This would save time but also cut down on your learning as well as the lean-ness of your final implementation.Quote:
Originally Posted by shoppinit
In general, you are talking about a classic and simple architecture involving the OSI communications layers 1-3 and 7 (http://en.wikipedia.org/wiki/OSI_model). I will suggest an architecture below.
Don't be embarassed. Everyone needs help every now and then. I also ask a lot of questins here at CodeGuru.Quote:
Originally Posted by shoppinit
.Here is one possible way to approach this problem...Quote:
Originally Posted by shoppinit
The following will seem like quite a lot and it is a hard way to go over the internet. But I am sure we can work through the points one at a time. And at the end of it all you might have really good results.
I assume you are writing a console application. Is this correct?
First of all you need OSI layers 1-3. Design a serial driver class which is capable of sending and receiving raw byte arrays. Let's call is ComSerial. This class needs to support such functions as open, close, send, recv, etc. The use of an STL container such as vector is recommended for the send and recv interface. The raw-byte send and recv fucntions can remain private at this point.
Parallel to this class, design a single frame protocol object (a class or structure) which encapsulates the three byte messages. Let's call this class Message_3uc. It would be advisable to replace the third redundant byte with a CRC8 checksum which can be implemented in the uc as well as the PC with the same algorithm. I can provide you with an algorithm. This class needs an internal 3-byte array (possibly an STL vector). In addition, there should be support for command creation as well as response parsing including service-ID checks and validity checks with the CRC8.
Armed with the above mentioned two classes, you can create the public interface to OSI layer 1-3 including most importantly the send and recv functions. These can either be implemented as an extension of your serial class ComSerial or as a derived class ComSerialMsg. For the actual send and receive functions now the interface could be a queue of Message_3uc objects. In exact terms, then this can be a std deque of message objects.
Now you are done with OSI layers 1-3.
The application layer (OSI layer 7) can be implemented through the use of a higher level object or even a simple functional interface. Basically this class or interface needs to be responsible for creating single messages or message queus and receiving them as well as dispatching any kinds of services or actions needed (such as control, logging and error reaction). I do not know enough abut the application to be able to talk about this yet.
I can generate some sample codes, including sample interfaces with STL for all of this junk if you need a hand getting started. But that's about all I can do based on the time constraints I have. So you would have to finish it yourself.
One final question: How much RS232 communication are you talking about? How many messages, what frequencies and total amounts (bursts) of data are involved here? Also what kinds of PC response times do you need? We need this information for the lower OSI layers.
Give me a feedback if you want to take it on with this kind of design.
Sincerely, Chris.
Hi Chris,
Thanks very much for that. Sorry I haven't got back to you, but I've been extremely busy getting the other bits of the project sorted - mechanical, electrical, etc, which has turned into a nightmare of its own.
I've discovered the downside of using a micro for this kind of thing: interfacing it with everything else. I've been dogged by EMI noise problems and other difficult to resolve electrical problems. My electronics knowlegde is worse than my coding ;)
Coming back to your post, I confirm that I am using a console application. The comms layer information was interesting.
In terms of the amount of RS232 communication going on, there could be up to 50 messages / sec one way, or 100 both ways.
I appreciate your offer for help with the serialcom classes. I'm aware, though, that you've surely got better things to be doing!
Many thanks.
Ben.
Hi Ben,
What kind of EMC problems have you encountered? There are several types of problems: radiative emissions, wire-based interference, supply line instability, etc. It's a bit beyond the scope of this forum, but it might be interesting to discuss this aspect of the design. Sometimes it can be very important to have a clean grounding design for your µC board (important for the radiative stuff).Quote:
Originally Posted by shoppinit
Sounds like less than 1kB data / sec. With the right design this should not be a problem for the PC host system.Quote:
Originally Posted by shoppinit
We can discuss the software design further if you would like.
Sincerely, Chris.
Hi Chris,Quote:
Originally Posted by dude_1967
The micro has 2 external interrupts that are driven (via industrial optos) by 2 inductive sensors (24v PNP). The EINTs proved extremely sensitive to line noise, EMI emissions, etc. I'm getting a lot of false activations - exactly as if the EINT inputs were floating, but they're pulled high. I tried shielding everything (and I mean everything!) and this helps slightly.
I don't think it's a supply line problem since, out of desperation, I tried running the micro off batteries (right now it's powered by the PC it's connected to for RS232).
I think the problem is impedance matching between the opto and the micro since I've got about 6ft of wire connecting them. This is way beyond my abilities in electronics, having only a vague idea about impedance matching.
The only solution I can think of is to get rid of the industrial optos, solder some 4n37 optos onto the micro PCB as close to the micro as possible and have the +24v coming into the box where I've mounted the micro. I might put an RC circuit on there while I'm at it to be doubly sure.
I can see this being a big problem once I've got everything in a noisy industrial environment with power relays going on and off nearby and it worries me.
Yes, the amount of actual data is small, but the frequency of the transmissions is relatively high. I'd very much appreciate some input on how best to make the communications as robust as possible.Quote:
Originally Posted by dude_1967
Thanks.
Ben.
Incidentally, I resolved the multithreading problem. It turns out that a function that I was calling in Intel's IPP library wasn't thread safe. Or at least, not the way I was implementing it. I've since dumped the IPP library since it didn't bring any performance improvements (quite the opposite sometimes).
Now I'm getting the kind of CPU usage I expected to see.
Ben.
Ben,Quote:
Originally Posted by shoppinit
It sounds like you need to improve the input structures of the external interrupt lines.
When you say 24v PNP what exactly do you mean? Are you using something like a Natinal Instruments NI 6224? What are the output voltage levels of the signals? It sounds llike you might need to make a high-pass filter combined with a voltage divider. Possibly the use of a schmitt-trigger could clean up the inputs, but that should not really be necessary.
Tell me how you have designed the input circuitry for the external interrupt pins and how these are connected to the sensor outputs.
You are right: You need to get this straightened out on the test bench before you get into the production facility. Otherwise it will really be a disaster.
BTW: Your software should also have some sort of mechanism which disables the external interupts within the ISR. They can be re-enabled several tens or hundreds of microseconds later with an independent time-base. This is necessary in order to prevent a CPU overload exactly in case there is high-frequency noise on the external interrupt pins. But the hardware has to be clean in the first place. This piece of software is simply a double-check mechanism.
Sincerely, Chris.
Hi Chris,
The proximity sensors I'm using are 3 wire PNP ones - I feed them 24v and when there's an object, the 3rd wire goes up to 24V which activates an opto. When the opto activates, the signal goes to ground and the interrupt fires.
See attached image. The 'output' goes straight to my EINT pin.
The proximity sensor is similar to this:
http://www.am.pepperl-fuchs.com/prod...oduct_id=15642
Someone has suggested that I put a second pull-up on the micro side with a 1nF cap on both sides. I've yet to try this, but am leaning more towards putting the opto on the PCB. Possibly to reduce the impedance difference.
Ben.
Is the raw output going into the microcontroller? What are the logic levels of the output?
If the raw output is directly fed to the microcontroller, then the system could benefit from a simple low-pass filter. Use something like 10k-20k resistor between the output and the microcontoller pin in combination with something around 1nF from the microcontroller pin to ground. Optionally you can use an additional block capacitance of about 1nF in series with the output resistor placed on the signal side.
Impedence mismatch should not be playing a role because the external interrupt pins should be set to input and as such high-Z.
You can, in addition to the low-pass described above, follow up on the suggestion sketched in your previous post. This will augment the low-pass.
Can you follow these ideas?
Sincerely, Chris.
Hi Chris,
Thanks for that. Yes, the raw input is going straight into the micro. The signal is 5v that comes from the same source that's powering the micro, ie. the PC PSU 5V rail.
I'm going to try putting the caps and resistors as you suggested on one set-up and I'll try with the 4N37 optos on the other then I'll use my trusty 1970's EMI generator (AKA oscilloscope) to see what happens.
I'll let you know!
Ben.
Sorry. The description of the optional block capacitor was wrong. It is on the signal side before the 10k-20k resistor, but to ground. It could even be a bit larger like 10nF.Quote:
Originally Posted by dude_1967
Code:Optional pull-up Optional pull-up
~10k to Vcc ~10k to Vcc
| |
| 10k-20k |
Output ------------------/\/\/\------------------ Extern Int Pin µC
| |
| |
----- -----
~10n ----- <= 1n -----
| |
| |
----- -----
--- ---
- -
Hi Chris,
Thanks for that. I've attached my interpretation of your schematic (opto2.jpg) - does that look right?
Also attached is another schematic for replacing the whole lot with the 4N37 opto (RC.jpg). How does that look to you?
Thanks again.
Ben.
PS. It looks like the noise (or some of it) may be coming from the +24V sensor cable which isn't shielded. That's why I added the 100nF cap there. It's funny how when I use the DIO card in the PC instead of the micro I don't get these noise problem. I guess the DIO card is doing some filtering...
Ben, I can comment on the µC input structure, but not on the entire system. Here is what I mean for the µC input circuit.Quote:
Originally Posted by shoppinit
Sincerely, Chris.
Chris, that seems to work nicely. I can't generate enough noise to falsely trigger the EINTs. Despite turning the scope on and off next to the unshielded board. I'm giving this a tentative thumbs up.
My confidence in the micro is slightly restored. Now if I can sort the communication out, and that seems to work reasonably well if the CPU isn't too loaded, then I think I'll have found my solution.
Thanks for all your help.
Ben.
Good. I had hoped it would be sufficient. Make a mental note of that input circuit and store it away in your bag of tricks.Quote:
Originally Posted by shoppinit
That sounds promising. Remember Ben, you are 'taking it on' with a difficult design involving a µC target and peripheral hardware as well as a PC host system. I know you have worked hard on this and I encourage you to continue with your design. You have already solved many key problems.Quote:
Originally Posted by shoppinit
Sincerely, Chris.
Hi Chris,
I've got the system installed and am testing it on site. The micros doing a great job. Thanks you and everyone else for steering me in that direction.
The noise issue still turned out to be a slight problem, despite the shielding and the pull-up circuit. I seem to have solved that in the micro code by deactivating the interrupt for a couple of milliseconds then testing the input to see if it's still high. I'm not sure if that's good practice, but it seems to work pretty good.
The PC side is a little less stable, as to be expected. I'm doing some serious debugging - mostly due to some poor coding (buffer overruns being amongst the most serious).
I have one issue that keeps arising concerning the comms between the micro and the PC. My RS232 thread occasionally terminates with a ERROR_IO_INCOMPLETE error. Having looked it up a little, it doesn't seem like this is a real error, just a message saying that there's some ongoing comms. What's your take on this?
Thanks.
Ben.