Newbie Struggle: Dividing a project into concurrent threads

New to XMOS and XCore? Get started here.
User avatar
LyleHaze
Experienced Member
Posts: 71
Joined: Wed Apr 11, 2012 6:21 am

Newbie Struggle: Dividing a project into concurrent threads

Postby LyleHaze » Mon Sep 24, 2012 10:30 pm

I hope all my questions aren't wearing out my welcome here.

I have a clear idea of what I want, and I _think_ I know how I'd like to make it work, but I am getting caught up in the unfamiliar details of writing for multiple threads.

Since my error may be in how things are being divided, I'll begin with a description of the project, and what I hope to accomplish:

I have an XS1-L2 chip, which has hardware for a serial port and a SD card. The serial port is using the fast_uart software, with separate threads for transmit and receive. It's just Tx, Rx, and ground. No handshaking in hardware or software, and it is working well.

The SD card interface is using the SD code from the github, and is wired for the faster "4-bit" mode. I have not tested yet but for this example we will assume that it's working.

There are a few other parts, but I think that's enough info to describe the problem. My goal is to set up a "pipeline" to log all serial input (plain text) to a file on the SD card. Once it's working I intend to raise the baud rate as much as possible, so speed is important.

I currently have a thread receiving the serial into a streaming chanend.
The Disk save routines all use a BYTE array and length as input. I assume that for any reasonable disk write speed, I should work in multiples of 512 bytes.

So the code to connect these must use the incoming serial data to fill a buffer of, say 512 bytes, and when it's full then call disk_write(), passing that buffer.

Since it is likely that more serial data will be arriving while the disk write is in progress, I have set up two buffers, and switching to use one while the other is out to disk_write().

it also will flush a partial buffer after a few seconds of idle time.

Something like this:

Code: Select all

void serialrcv(streaming chanend uartRX, chanend DiskIO)
{
	// double buffer streaming input , write buffer
	//to SDCard when full or idle for 5 seconds.
	unsigned char buffA[512];	// double buffers
	unsigned char buffB[512];
	unsigned char next;
	int aPlace = 0, bPlace = 0;		// double placemarkers
	int whichbuf = 0;
	unsigned time = 0;
	timer t;
	while(1)
	{
		select
		{		// new character from serial input
			case uartRX :> next:
				t :> time;
				time += FLUSH_DELAY;
				if(0 == whichbuf)
				{	// add to buffer A, check for buffer full
					buffA[aPlace++] = next;
					if(512 == aPlace)
					{
						whichbuf = 1;	// switch to other buffer
						WriteBuffer(buffA, aPlace, DiskIO);
						aPlace = 0;
					}
				}
				else
				{	// add to buffer B, check for buffer full
					buffB[bPlace++] = next;
					if(512 == bPlace)
					{
						whichbuf = 0;	// switch to other buffer
						WriteBuffer(buffB, bPlace, DiskIO);
						bPlace = 0;
					}
				}
				break;
			case t when timerafter(time):> t:
				t += FLUSH_DELAY;
			// the stream has paused, flush the active buffer.
				if(0 == whichbuf)
				{
					whichbuf = 1;
					if(aPlace)
					{
						WriteBuffer(buffA, aPlace, DiskIO);
						aPlace = 0;
					}
				}
				else
				{
					whichbuf = 0;
					if(bPlace)
					{
						WriteBuffer(buffB, bPlace, DiskIO);
						bPlace = 0;
					}
				}
				break;
		}
	}
}
Ok, (and thanks for bearing with me)
Now WriteBuffer has to signal a separate thread to actually do the operation.
If we do it on this threads context, then incoming serial will be lost.

I don't want to send a stream of individual characters, as I have it all in an
array, all ready for the disk_write(). Note that the length will NOT always be
512, as partial buffers are written occasionally as well.

I'm not sure how to send "commands" instead of character streams through a channel.
I'm not sure that I'm allowed to send an array by reference.
I fully expect the compiler to complain about data sharing between threads, as I am managing the double-buffers myself, and it doesn't have much faith in my ability to do so. ;)

And finally, I tried to
#define BUF_SIZE 512
and replace all instances of "512" with BUF_SIZE, and it choked on every one.
When defining a simple constant fails, I really begin to wonder if I have lost my mind.

And when that many questions come up at once, I am fairly sure that I'm going about this all wrong. I'm sure that this is just not the "XMOS" way of doing things.

I realize this is a long post, but since you provided a special "Getting Started" forum for beginners, I hope I am within the limits.

Thanks for your suggestions!
LyleHaze
User avatar
LyleHaze
Experienced Member
Posts: 71
Joined: Wed Apr 11, 2012 6:21 am

Postby LyleHaze » Tue Sep 25, 2012 12:57 am

Replying to my own topics.. maybe I have gone a bit crazy.

Searching around has shown some discussion about (not) sharing memory between threads.
So perhaps I'm not the first to ask those questions. Looking for a better way to make it work:

If there was an option (or even separate thread/code) to buffer a channel, it could just make this problem "go away".. as long as the consumer thread (after buffer) could somehow be signalled when the buffer reached a certain capacity (in this case, 512 bytes). As long as it knew "up front" whether it was reading a full sector or a partial sector, and as long as the buffer thread could continue to receive code while the disk_write was running.
I'm beginning to think this might be the easiest way.
That way I can stick with channels instead of sharing memory buffers.

I wonder if a block of consecutive channel reads can move data out of the buffer faster than a streaming channel might be delivering new data? Or if I'd need to use a streaming channel out?

I suppose I could write the disk_write() thread in C.. as the example code for the SD module is already written that way. It would make things easier for me, once I figure out how to read from a chan end in C, but that just doesn't seem to be in the spirit of learning a new technique.

As always, I welcome any comments that might help.

LyleHaze
User avatar
segher
XCore Expert
Posts: 843
Joined: Sun Jul 11, 2010 1:31 am

Postby segher » Tue Sep 25, 2012 4:26 am

LyleHaze wrote:And finally, I tried to
#define BUF_SIZE 512
and replace all instances of "512" with BUF_SIZE, and it choked on every one.
It should work. Your post is a little short on details here ("it chokes"
isn't very helpful in figuring out what is wrong) -- at least quote the
(exact!) error message :-)
User avatar
segher
XCore Expert
Posts: 843
Joined: Sun Jul 11, 2010 1:31 am

Postby segher » Tue Sep 25, 2012 4:46 am

LyleHaze wrote:I wonder if a block of consecutive channel reads can move data out of the buffer faster than a streaming channel might be delivering new data?
On a core, you can read or write four bytes per cycle from a channel.
When this channel is on one core, the channel is just as fast. If it is
inter-core, it is as fast as the link between those cores.

Going through memory is *slower*, because you get just the same
maximum transfer speed to/from memory as you have to/from channels,
but the cores also have to update the pointer to memory they use;
with channels that is implicit.

I usually have a thread and channel for every device, and some threads
and channels to glue them together. If you have very many devices,
that's not going to work so you have to do more complicated stuff
(multiplexing a thread to handle multiple tasks, either explicitly or
implicitly); but it's a good way to start.

In your case, I'd do a thread to read from the serial and write to a
channel, and a thread to read from a channel and write to the SD.
You can either make that second thread work with short packets
(by detecting control tokens sent to it, which you send after partial
packets for example; or have _two_ channels on it, one for data
and one for control, the control message then says how much data
belongs to the packet on the data channel; you can send the data
"ahead of" the control if the consumer thread buffers it).
Or, you can send the packet size in front of the data, and then you
need only one channel; but then you need to buffer the data before
the SD driver. It then is nice thing if that driver does not buffer it
itself as well but just fetches it from the channel (otherwise, it _is_
slower than sharing memory, of course).

In either case, you're best of if the device drivers do as little as
possible: do all the "bookkeeping" on a separate thread. The device
drivers should only be concerned with driving the device :-)
User avatar
LyleHaze
Experienced Member
Posts: 71
Joined: Wed Apr 11, 2012 6:21 am

Postby LyleHaze » Tue Sep 25, 2012 6:05 am

segher wrote:
LyleHaze wrote:And finally, I tried to
#define BUF_SIZE 512
and replace all instances of "512" with BUF_SIZE, and it choked on every one.
It should work. Your post is a little short on details here ("it chokes"
isn't very helpful in figuring out what is wrong) -- at least quote the
(exact!) error message :-)
Fair enough. and I should know better.
Line numbers added by me to make it more readable.

Code: Select all

(in Global space)
15	#define SER_BUF_SIZE 512;


69	unsigned char buffA[SER_BUF_SIZE];	// double buffers
70	unsigned char buffB[512];

From the Console window after a build:
Compiling ReTime.xc
../src/ReTime.xc: In function `dbuf_serial':
../src/ReTime.xc:69: error: parse error before ';' token
../src/ReTime.xc:69: error: parse error before ']' token
../src/ReTime.xc:85: error: `buffA' undeclared (first use in this function)
../src/ReTime.xc:85: error: (Each undeclared identifier is reported only once
../src/ReTime.xc:85: error: for each function it appears in.)
xmake[1]: *** [.build_Release/src/ReTime.xc.o] Error 1
xmake: *** [bin/Release/ReTime.xe] Error 2
Edit:
OUCH! Right after posting that I saw it.
Should not have a semicolon after a define value.

Sorry to bother you. Nevermind.
/me hangs his head in shame.

Who is online

Users browsing this forum: No registered users and 1 guest