Two cogs controlling one LCD
sylvie369
Posts: 1,622
Short version:
I've got a program I'm running that takes in serial data from an XBee and displays them on an LCD. That part works fine.
I also am reading a pair of XBee DIO pins (which are linked through "digital line passing" to another XBee) into pins 22 and 23 on the Prop Proto board, and displaying their values on the LCD. That part also works fine.
What doesn't work fine is doing both at once. If I run the DIO program in a second cog, it doesn't display to the LCD, and when the serial data come in, they display as gibberish. I've double-checked that they're not trying to write to the same line of the LCD (the serial input program writes to lines 0, 1, and 3, and the DIO program only writes to line 2). When I comment out one or the other program, the one that runs works just fine, whichever one it is.
First question: Is it possible to have two cogs send data to one LCD?
Second question : If so, how do I keep them from interferring with each other? I assume that if it's possible, my problem is that I'm not initializing things in the proper place.
Context:
I'm working on the receiver for a rocket telemetry device. The transmitter is an XBee that uses line-passing to pass along the values of two pull-pin switches which are intended to signal when the drogue and then main parachutes have deployed. The pull-pins are connected by pulldown resistors to ground, but pulled high by the pull-pin until it is pulled out. That part is working reliably, both on the transmitter side and on the corresponding XBee pins on the receiver.
The XBee also receives serial data from an altimeter once the rocket has taken off, and sends those data to the receiver for display as well. That part I've flown successfully 7 times.
I want the pull-pin status to be read constantly beginning at power-up, and displayed on the receiver. The altitude data don't begin until the rocket is in flight. I want the pull-pin status to be read and displayed even while the altitude data are coming in.
This is the first time I'm trying to use a Propeller properly, with separate cogs for functions that I want to have running in parallel. It's quite a leap for me, mentally - I really want that pull-pin checking code to be inside the altitude receiver loop, but of course if I do that, I don't get pull-pin status except while receiving altitude data. I could, I suppose, simply have the pull-pin status displayed separately on LEDs, but that's not as fun as having the LCD show the current altitude, maximum altitude, current flight status (ready, in-flight, landed), and the status of the recovery events (drogue, main).
While I'm asking, let me also ask how I would know how much stack to allocate for the second cog. When I used just 9 longs, it apparently overwrote one of the pull-pin status variables, so that one always indicated that the event had occurred. I checked over the circuit to make sure·the receiver·was properly following the input voltage, and it was, so suspecting that a variable was being overwritten, I increased the stack size a lot (to 59 longs), which worked, then inched it down until it stopped working, then back up again. There must be a better way.
Here is the current version of the code:
I've got a program I'm running that takes in serial data from an XBee and displays them on an LCD. That part works fine.
I also am reading a pair of XBee DIO pins (which are linked through "digital line passing" to another XBee) into pins 22 and 23 on the Prop Proto board, and displaying their values on the LCD. That part also works fine.
What doesn't work fine is doing both at once. If I run the DIO program in a second cog, it doesn't display to the LCD, and when the serial data come in, they display as gibberish. I've double-checked that they're not trying to write to the same line of the LCD (the serial input program writes to lines 0, 1, and 3, and the DIO program only writes to line 2). When I comment out one or the other program, the one that runs works just fine, whichever one it is.
First question: Is it possible to have two cogs send data to one LCD?
Second question : If so, how do I keep them from interferring with each other? I assume that if it's possible, my problem is that I'm not initializing things in the proper place.
Context:
I'm working on the receiver for a rocket telemetry device. The transmitter is an XBee that uses line-passing to pass along the values of two pull-pin switches which are intended to signal when the drogue and then main parachutes have deployed. The pull-pins are connected by pulldown resistors to ground, but pulled high by the pull-pin until it is pulled out. That part is working reliably, both on the transmitter side and on the corresponding XBee pins on the receiver.
The XBee also receives serial data from an altimeter once the rocket has taken off, and sends those data to the receiver for display as well. That part I've flown successfully 7 times.
I want the pull-pin status to be read constantly beginning at power-up, and displayed on the receiver. The altitude data don't begin until the rocket is in flight. I want the pull-pin status to be read and displayed even while the altitude data are coming in.
This is the first time I'm trying to use a Propeller properly, with separate cogs for functions that I want to have running in parallel. It's quite a leap for me, mentally - I really want that pull-pin checking code to be inside the altitude receiver loop, but of course if I do that, I don't get pull-pin status except while receiving altitude data. I could, I suppose, simply have the pull-pin status displayed separately on LEDs, but that's not as fun as having the LCD show the current altitude, maximum altitude, current flight status (ready, in-flight, landed), and the status of the recovery events (drogue, main).
While I'm asking, let me also ask how I would know how much stack to allocate for the second cog. When I used just 9 longs, it apparently overwrote one of the pull-pin status variables, so that one always indicated that the event had occurred. I checked over the circuit to make sure·the receiver·was properly following the input voltage, and it was, so suspecting that a variable was being overwritten, I increased the stack size a lot (to 59 longs), which worked, then inched it down until it stopped working, then back up again. There must be a better way.
Here is the current version of the code:
''* XBee-MAWD Receiver with pull-pins * '' ''XBee RX on pin 0 ''XBee TX on pin 1 ''LCD connected to pin 8. Parallax 4x16 LCD. ''Pull-pins from XBee DIO0 to Prop pin 22, XBee DIO1 to Prop pin 23 CON _clkmode = xtal1 + pll16x '80MHz operating frequency. _xinfreq = 5_000_000 Hz = 100 OBJ XB : "XBee_Object" Comm : "FullDuplexSerialPlus" Num : "Numbers" LCD : "Debug_LCD" BS2 : "BS2_Functions" VAR byte Alt[noparse][[/noparse]6] ' Variable to hold the first 6 bytes of altitude data from MAWD. First 5 bytes are 5 digits of altitude, then CR (then LF, unread). word AltN ' Variable to hold numerical value of altitude, after conversion from string. word MaxAlt ' Variable to hold maximum altitude byte Land ' Has rocket flown and landed? byte Event0 ' Has pull-pin 0 pulled? byte Event1 ' Has pull-pin 1 pulled? long Stack[noparse][[/noparse]25] ' Stack for cog running PinsCheck (stack size set by trial and error: 9 is too small, and overwrites E1). Pub Start XB.start(0,1,0,9600) ' XBee Comms - RX,TX, Mode, Baud XB.AT_init XB.AT_Config(String("ATMY 0")) ' Config to receive as Module 0 to match XBee-MAWD telemetry output DL setting. DIRA[noparse][[/noparse]22..23]~ ' Set pins 22 and 23 as inputs from the XBee I/O pins 0 and 1 XB.AT_Config(String("ATD0 5")) ' Config to use XBee DIO pin 0 as a digital output, default HIGH XB.AT_Config(String("ATD1 5")) ' Config to use XBee DIO pin 1 as a digital output, default HIGH XB.AT_Config(String("ATIA 1")) ' Config to receive DIO data from module whose MY = 1. XB.AT_Config(String("ATIU 0")) ' Config so that DIO pin states are not sent out UART, where they would interfere with alt data Num.init ' Initialize Numbers object LCD.init(8, 9600, 4) ' Initialize LCD LCD.cls ' Clear the screen BS2.start (31,30) MaxAlt := 0 ' Start at 0 altitude Land := FALSE ' Set the Landing variable Event0 := %00000000 ' Set the pull-pin variables Event1 := %00000000 ' Event0 and Event1 Splash ' Show startup screens LCD.cls Screenlabels ' Label the values LCD.gotoxy(10,3) LCD.str(String("Ready ")) ' Repeat ' Currently commented out - this is how I checked to see if it works serially. ' PinsCheck Cognew(PinsCheck, @Stack) ' Run the PinsCheck continuously in a separate cog. Repeat while Land == FALSE If XMCheck ' If Altitude data received XMReceive ' Get and display the data Else XMBadData Pub XMCheck : Success ' Check to see if receiving data Success := XB.RxCheck Pub XMReceive ' Uses BS2 function to receive simply because I couldn't get the XBee object working BS2.SERIN_STR(0, @Alt, 9600, 1, 8) ' Receive a CR-terminated string to Alt through pin 0 Alt[noparse][[/noparse]5] := 0 ' Convert to z-string for use by LCD and Num objects LCD.gotoxy(10,0) LCD.str(@Alt) ' Display the altitude string AltN := Num.FromStr(@Alt, %000_000_000_0_0_000110_01010 ) ' Convert to 5-digit number for use by MaxAlt if (AltN > MaxAlt) & (AltN < 50000) ' < 50000 check is to avoid bad data at landing MaxAlt := AltN LCD.gotoxy(10,1) LCD.decf(MaxAlt,5) ' Display the Maximum altitude If AltN < 1 ' Sometimes the Alt drops below 0 after landing - this accounts for that. Land := TRUE LCD.gotoxy(10,3) LCD.str(String("Landed ")) else LCD.gotoxy(10,3) LCD.str(String("In flight")) Pub XMBadData LCD.cls LCD.gotoxy(10,3) LCD.str(String("No data ")) Pause(200) Pub PinsCheck ' This will have to go into a different cog to run at the same time at the altitude data receiver. Repeat LCD.gotoxy(0,2) Event0 := INA[noparse][[/noparse]22] ' Get value of input pin 22 Event1 := INA[noparse][[/noparse]23] ' Get value of input pin 23 If Event0 <> %00000001 LCD.str(String("E0 ")) else LCD.str(String("No ")) If Event1 <> %00000001 LCD.str(String("E1 ")) else LCD.str(String("No ")) Pub Splash ' Display startup screen LCD.home LCD.backlight(TRUE) LCD.gotoxy(0,0) LCD.str(@Splash1) LCD.gotoxy(0,1) LCD.str(@Splash2) LCD.gotoxy(0,2) LCD.str(@Splash3) LCD.gotoxy(0,3) LCD.str(@Splash4) Pause(1000) ' LCD.backlight(FALSE) Pub Screenlabels LCD.gotoxy(0,0) LCD.str(String("Alt : ")) LCD.gotoxy(0,1) LCD.str(String("Max Alt: ")) LCD.gotoxy(0,3) LCD.str(String("Status : ")) PUB Pause(mS) waitcnt(clkFreq/1000 * ms + cnt) DAT Splash1 byte "XM Telemetry ",0 Splash2 byte "Version 1.2 ",0 Splash3 byte "Paul C. Smith",0 Splash4 byte "Dec 29, 2009 ",0
Comments
Phil Pilgram (PhiPi) wrote a little utility that helps one empirically determine stack space -- I'd suggest you do a search but we all know how poorly that function works within this forum software....
edit: forgot the behaviour of this forum-software to eat up square-brackets if written without spaces
it is the same as if two people talk at the same time: This is hard too understand especially for computers.
two cogs can't send at the exact same time. If you have some kind of a lock mechanism you can could set the lock
then cog1 sends clear lock cog2 sets lock sends clear lock etc. etc. If would mean that the other cog has to wait
until the lock is cleared. If I understand right you want EVERY cog send whenever the cog wants to without waiting.
This requires a third cog! ??! Why that?
Cog1 and cog2 write to a bytebuffer NOT direct to the LCD. The third cog does nothing else but sending the content of the buffer
as soon as the content of the buffer has changed (since last sending) or after a constant amount of time.
If cog1 and cog2 write to DIFFERENT places in the buffer the data cannot interfere as each data has its onw place in the buffer.
Practically this would be organised in the same way as the characters on the display.
Let's assume you have a 4x40 display. This would mean that you send always 4x40 bytes and all the bytes are written sequentially
to the display. If this takes too long you can short down the datasize you need.
Example:
DIO has 3 bytes
altitude data has 5 bytes then you would declare a bytearray of 3 + 5 = 8 bytes
cog1 reads the dio and writes to byte 0 to 2
cog2 reads the altitude-data and writes to byte 3-8
now cog3 does
So this way only ONE cog is sending the data but 1 to n cogs can "send" (=write to a sendbuffer) at the same time.
If the writing to the altitude-bufferbytes hasn't finished yet you will get an inconsistent data displayed for a short time
then you get the right data.
If you want to avoid inconsistent data you could implement some kind of flagbytes for DIO-Data and altitude-data
only if flagbyte is set which means all data has been written to the sendbuffer.
best regards
Stefan
Post Edited (StefanL38) : 12/29/2009 7:33:10 PM GMT
FullDuplexSerial uses a what I call low-level-sendbuffer. What I suggest is a "sort-the-data-to-certain-places-buffer".
As Sylvie wants to send altitude-data from a rocket speeding into the sky a lock-mechanism is to slow.
best regards
Stefan
I don't need to worry about inconsistent data, at least if·it will·catch up within a fraction of a second anyway. I do hope to eventually send all of the data out the serial port to Excel, where it would matter if the data were inconsistent for any significant length of time, but I imagine we're talking 100ths of a second or better, right? And I imagine I'll have to do something similar for those writes out the serial port.
Thanks.
@Sylvie: You'll have to experiment, but I believe you can do something like this:
Use this at any place where an object needs to write to the LCD. If the lock is clear it will be set, you object can write to the LCD, and then clear the lock so that the other object can have access. Again, as per my exchange with Stephan, this is only valid using the LCD object you presently have employed that does not use a serial buffer; if you change LCD objects to one that buffers serial data, you will, as Stephan correctly suggests, have to create separate object for dealing with LCD messages.
I assume that I define the 8 byte variable up in my public variables definition section, available to the methods running on all of the cogs, and that I do not have to explicitly pass it to the method that is updating the LCD, right?
Then I'll put the code that initializes the LCD, and the code that displays the splash screen, and the permanent screen labels all into the start of that cog, and then after that code, a repeat loop that just keeps running, comparing the current state of the buffer with the state it was in previously, and if there is any change, writing the new values to the LCD.
(Trying it...)·· [noparse][[/noparse]Okay, I'm going to think aloud here as I try to figure this out ]
Yikes. Okay, I added the buffer variable, and moved the LCD init code and splash and labels code into a method named LCDUpdate. When I run that method itself serially (that is, when I just add a line LCDUpdate to my Start method), it runs just fine. But when I try to run it·in a new cog, I get gibberish on the screen. Okay, so maybe the stack is too small again. Make it larger - doesn't help. Larger still - doesn't help. Ridiculously large - doesn't help. So wait...maybe it's running just fine, but then it gets to the code in the Start method and tries to write to the LCD outside of the LCDUpdate method. Put in a long Pause after starting LCDUpdate·to see. Yup, that seems to be the problem. I'll have to change over all the rest of the LCD writes to see if it's working.
This is really interesting - after 35 years of serial programming, I feel like I'm on acid, what·with this "start a cog and let it run" stuff. I'm going to be obsessively thinking about what I can do with this while I'm on the treadmill this afternoon. I see a lot of "rush home to try something out" days coming up.
Hmm. I thought that the idea was that no two cogs would try to put data into the same place. That is, the main program puts altitude data into a couple of locations (say, 0 and 1), and updates flight status in another (2), but doesn't touch the others, while the PinCheck method (running in a separate cog) puts the current status of the pull-pins into a couple of other locations (say, 3 and 4) without touching locations 0-2. Like this:
I still have to work out how the flight status thing is going to work, since that could be determined by both altitude data and pull-pin status (I may just punt on that and leave it as altitude only), but otherwise I think that there's no way for two separate cogs to try to update the same memory location.
(incidentally, am I able to use this as an array of word variables? That's how large the altitude numbers need to be. )
Or are you saying that I have to set a lock while writing the data so that the cog running LCDUpdate doesn't try to update while the data are being changed in another cog? I *think* that I'm only *reading* the LCDBuffer while I'm in the LCDUpdate method, though I'll need to double-check that. Is there a risk of corruption from reading it while it's being updated in another cog?
Here's the code so far, in case it helps. It's not working yet, but you might be able to see how I'm thinking about this (and where I'm going wrong?):
Once I sorted everything away correctly, increased stack sizes a bit,·and remembered to put in a pause to let the LCD initialize (oops), the display part of this project is working perfectly.
I'm still not getting pull-pin data readings until the altitude data start coming in, but while those data are coming in and being displayed, the pull-pin status and the flight status update properly on the LCD. I'm sure it's just a little programming glitch preventing the pull-pins from being updated before takeoff.
(Later: )
Okay, I see why it's not displaying before takeoff. My flag to update the display is a line comparing the current LCDBuffer with the old LCDBuffer:
·I thought that would check the entire LCDBuffer (which is an array of 8 words) for any changes, but apparently it only looks at LCDBuffer[noparse][[/noparse]0], which is the altitude data. As a result,·it does change when those data change, but not when any of the data further into the array change.
·
strcomp(@LCDBuffer,@LCDBufOld)
As the name says its usually to compare strings that are zeroterminated.
strcomp compares each byte until it reaches a zero. So both buffers are not allowed to contain a zero except at the end of the buffer
to sign here is the end.
As you do already a case I think it will be no problem to just add 1 to each value before storing and increase each case-value by 1 too
best regards
Stefan
Of course as always in programming, when you pull on a loose thread over here, another one over there moves: I had to change the way that I check to see if the rocket is flying yet, because right after startup·the program·thought that the rocket had already landed because the current altitude was 0. I changed it to require the MaxAlt to be greater than 0 before concluding it was in the air, and that works fine.
Here is the current code. It all works - it displays the pull-pin status even before receiving any altitude data, and receives and displays altitude data and changes to pull-pin status simultaneously. I believe that this version would do what it's supposed to if I were to fly the setup.
There are still a few things to add, and some to clean up. I notice that somewhere in the revisions I stopped really using the "Land" flag. The XMBadData method does nothing. I need to document the XBee settings. I also want to add an indicator on the LCD that shows that data are being received - right now you can tell by pulling·a pull-pin and watching for a change in the display, but I would rather know just from looking even when no-one is out at the rocket on the pad.
Post Edited (sylvie369) : 1/1/2010 5:41:52 PM GMT