Control Code & LCD Communication Problems
hitolisolo
Posts: 11
Brief summary (you're welcome to read everything, of course)
Control Code
It works like it's supposed to. You're probably not getting what you think you are. Make sure you're not changing the variable before outputting it (e.g. in a DEBUG statement).
LCD
Make sure you aren't accidentally taking the LCD pin low (e.g. OUTS = 0); also, try explicitly setting the cursor position at the beginning of the SEROUT command (e.g. SEROUT pin, baudrate, [$80, other_data] for position 0, 0).
Original Post
Hello everyone,
I'm new here (and to forums in general). I've got a couple problems (and I'm not sure if I should post them separately or not, so I'm putting them both below). I was hoping somebody here would be able to catch what I'm missing.
I'm using the basic/plain BS2 module and the software is version 2.5.3. My program is rather long so I'm only going to post what should be the relevant functions.
Problem 1 - Serial LCD w/ Backlight (#27977)
I got a few LCDs, finally, thanks to Radio Shack. I've tried this code with two different displays and I'm seeing the same thing, so I don't thing it's the LCD modules.
Next I send $C to clear the screen and pause 5ms as instructed. Then $16 to turn the LCD on (no cursor) and $11 for the backlight.
Before I added SEROUT lcdPin, lcdBaud, [$80, REP " "\32] I would only see "ing..." on the LCD when it first started and the backlight didn't always come on.
However, when I return to Listen: from within the program it displays "Listening..." correctly
I've tried increasing the pause at the beginning of the program up to 1s and I've tried both baud rates with no effect.
I see this again in the bottom function where these three lines occur:
Does anybody know what the problem is or how to fix it?
Problem 2 - Control Code (i.e. SELECT CASE, IF...)
Am I missing something here (I'm quite positive dataVar is zero) or is it not compiling correctly (or something else)?
Appoligies for the length, but I'd really appreciate any help that can be offered.
Control Code
It works like it's supposed to. You're probably not getting what you think you are. Make sure you're not changing the variable before outputting it (e.g. in a DEBUG statement).
LCD
Make sure you aren't accidentally taking the LCD pin low (e.g. OUTS = 0); also, try explicitly setting the cursor position at the beginning of the SEROUT command (e.g. SEROUT pin, baudrate, [$80, other_data] for position 0, 0).
Original Post
Hello everyone,
I'm new here (and to forums in general). I've got a couple problems (and I'm not sure if I should post them separately or not, so I'm putting them both below). I was hoping somebody here would be able to catch what I'm missing.
I'm using the basic/plain BS2 module and the software is version 2.5.3. My program is rather long so I'm only going to post what should be the relevant functions.
Problem 1 - Serial LCD w/ Backlight (#27977)
I got a few LCDs, finally, thanks to Radio Shack. I've tried this code with two different displays and I'm seeing the same thing, so I don't thing it's the LCD modules.
' -----[Initialization ]-------------------------------------------------------------------------------------------------------- 'Start: HIGH lcdPin ' Set the lcdPin high meaning it's not sending data ' And wait 100ms for it to initialize PAUSE 100 ' This also prevents the computer from believing it's a mouse SEROUT lcdPin, lcdBaud, [clearLCD] ' Make sure the display will be clear PAUSE 5 ' Make sure it has time to clear the screen SEROUT lcdPin, lcdBaud, [lcdOn, backlightOn] ' Turn off the cursor and the backlight on. SEROUT lcdPin, lcdBaud, [$80, REP " "\32] ' For some reason the LCD ignores the first command to it so just manually clear it ltrCode(0) = "E" : ltrCode(1) = "R" : ltrCode(2) = "R" ' Setup ERR code array GOSUB Reset ' -----[ Main Routine ]--------------------------------------------------------------------------------------------------------- Listen: SEROUT lcdPin, lcdBaud, [$80, "Listening", REP "."\3, REP " "\4] ' State that the program is listening SERIN SerialPin, BaudMode, [STR cmdCode\4, STR actionCode\1] ' Listen for command code and action code CalcCartSize: 'SetMBCType: address = $0147 ' Address for MBC Type GOSUB ReadByte SEROUT lcdPin, lcdBaud, [$80, "M ", HEX2 dataVar, " "] ' Display the MBC Type on the LCD SELECT dataVar ' Determine MBC Type CASE $00, $08 TO $09 ' No MBC mbcType = 0 CASE $01 TO $03 ' MBC1 mbcType = 1 CASE $05 TO $06 ' MBC2 mbcType = 2 'CASE $0B TO $0D ' MMM01 - let Case Else handle CASE $0F TO $13 ' MBC3 mbcType = 3 CASE $15 TO $17 ' MBC4 mbcType = 4 CASE $19 TO $1E ' MBC5 mbcType = 5 CASE ELSE ' Report error and reset IF NOT (dataVar | $00) THEN mbcType = 0 ELSE SEROUT SerialPin, BaudMode, [STR ltrCode\3, "5", dataVar] GOSUB Reset SEROUT lcdPin, lcdBaud, [$94, STR ltrCode\3, "5 ", HEX2 dataVar] ' Echo error on LCD GOTO Listen ENDIF ENDSELECT ' RETURN 'CalROM: address = $0148 GOSUB ReadByte SEROUT lcdPin, lcdBaud, ["R ", HEX2 dataVar, " "] ' Display the ROM data on the LCD IF dataVar <= $07 THEN romSize = DCD (dataVar + 1) ' # banks = (2 ^ romSize + 1) ELSE SELECT dataVar ' Special cases CASE $52 romSize = 72 CASE $53 romSize = 80 CASE $54 romSize = 96 CASE ELSE ' Report error and reset on unknown size SEROUT SerialPin, BaudMode, [STR ltrCode\3, "3", romSize] GOSUB Reset SEROUT lcdPin, lcdBaud, [$94, STR ltrCode\3, "3 ", HEX2 romSize] ' Echo error on LCD GOTO Listen ENDSELECT ENDIF ' RETURN 'CalRAM: address = $0149 GOSUB ReadByte SEROUT lcdPin, lcdBaud, ["RAM ", HEX2 dataVar] ' Display the RAM data on the LCD SELECT dataVar 'CASE $00 AND (mbcType = 2) ' MBC2 has no external RAM ' ramSize = 1 ' But 1 internal bank ' ramLength = $A1FF ' With 512 address 'CASE $00 ' ramSize = 0 ' No RAM ' ramLength = 0 CASE $01 ramSize = 1 ' 1 bank ramLength = $A7FF ' 2K RAM CASE $02 ramSize = 1 ' 1 bank ramLength = $BFFF ' 8K RAM CASE $03 ramSize = 4 ' 4 banks ramLength = $BFFF ' 8K RAM each CASE ELSE ' Report error on unknown value and reset IF (dataVar = $00) AND (mbcType = 2) THEN ' Above case not compiling so this is here ramSize = 1 ramLength = $A1FF ' Here's the error report otherwise ELSEIF NOT (dataVar | $00) THEN ' = $00 fails to run correctly both above in SELECT CASE ramSize = 0 ' and here in the IF statement. NOT should dislike anything over 0 ramLength = 0 ' and OR-ing dataVar with $00 should only return 0 if it's already 0 ELSE SEROUT SerialPin, BaudMode, [STR ltrCode\3, "4", dataVar] ' Unknown value, report error GOSUB Reset ' Reset SEROUT lcdPin, lcdBaud, [$94, STR ltrCode\3, "4 ", HEX2 dataVar] ' Echo error on LCD GOTO Listen ENDIF ENDSELECT RETURNThe documentation says to pause 100ms at the beginning of the program to allow it to initialize and to raise the communication pin high first. Which are the very first things I do.
Next I send $C to clear the screen and pause 5ms as instructed. Then $16 to turn the LCD on (no cursor) and $11 for the backlight.
Before I added SEROUT lcdPin, lcdBaud, [$80, REP " "\32] I would only see "ing..." on the LCD when it first started and the backlight didn't always come on.
However, when I return to Listen: from within the program it displays "Listening..." correctly
I've tried increasing the pause at the beginning of the program up to 1s and I've tried both baud rates with no effect.
I see this again in the bottom function where these three lines occur:
SEROUT lcdPin, lcdBaud, [$80, "M ", HEX2 dataVar, " "] SEROUT lcdPin, lcdBaud, ["R ", HEX2 dataVar, " "] SEROUT lcdPin, lcdBaud, ["RAM ", HEX2 dataVar]However, all I get is "RAM xx" (where xx is the hexidecimal form of dataVar) at position 0, 0 - nothing from the first two lines displays.
Does anybody know what the problem is or how to fix it?
Problem 2 - Control Code (i.e. SELECT CASE, IF...)
SELECT dataVar CASE $00 ramSize = 0 ' No RAM ramLength = 0 CASE ... CASE ELSE ' Report error on unknown value and reset IF (dataVar = $00) AND (mbcType = 2) THEN ' Above case not compiling so this is here ... ELSEIF NOT (dataVar | $00) THEN ' = $00 fails to run correctly both above in SELECT CASE ramSize = 0 ' and here in the IF statement. NOT should dislike anything over 0 ramLength = 0 ' and OR-ing dataVar with $00 should only return 0 if it's already 0 ELSE ... ENDIF ENDSELECTHopefully that's already clear. Basically CASE 0 and IF (dataVar = 0) THEN will not run their code dispite dataVar being 0.
Am I missing something here (I'm quite positive dataVar is zero) or is it not compiling correctly (or something else)?
Appoligies for the length, but I'd really appreciate any help that can be offered.
Comments
Was part of the problem I was having so I removed it. This code displays "Listening..." as you intended, which makes me also wonder what happens in the Reset subroutine.
Note that I had to add my own constants as these weren't available to make the code run originally. I will look at the next part in a moment (multi-tasking like crazy today).
...after a pause to the original code. I had to declare the variable and guess that it was a byte value. I assigned it 255 which is $FF in HEX. Here's what I get:
This is exactly what I would expect to see based on the code. So if there is an issue perhaps I am not clear on what you're expecting here versus what I am seeing. I used the code below to get this.
CASE with ... not sure what that is. And the CASE ELSE relies on two variables, with one evaluation that won't ever get seen. Since the first CASE $00 handles dataVar being 0 then I would never expect the ELSE to be reached and be TRUE for dataVar being 0, since it would never make it there if dataVar was 0. I hope this helps.
Interesting. I found it was necessary for I'd only get "ing..." on the display, however I just removed the line and it's still displaying correctly (which I don't understand as it should be the same code that was causing problems earlier now).
That's what I would expect, but I'm only getting "RAM 00" starting at position 0, 0.
Hopefully that will due for now, because I've got to get to school now.
I'll address what I wasn't able to here when I get the chance.
When you run the code I posted do you get what I get or the same thing you got before? As I suspected by looking at your code you're properly initializing the LCD in the section called, "Start", however your "Reset" routine right after toggles the LCD pin which would cause the next command to most likely fail. Here is where this happens:
Two things are happening here...the first is that you're toggling the I/O pin used by the LCD low, and then HIGH again. In addition you've already initialized and cleared the LCD, but in this block you're attempting to clear it again which would most likely fail since the I/O pin was brought low then high immediately before the command. You have initialization code it two entirely different sections of the code which makes it hard to know what's going on during initialization since these blocks are at opposite ends of the source code. I would recommend initializing all your I/O and Variables in the same block of code so you know when you've already set an I/O pin to the state you need it during start up.
Aside of the problems I saw in your evaluations, I'm guessing you're not getting the data you expect sometimes. I would use a DEBUG statement here and there to see what variables are set to upon entry into a routine. You can also use a DEBUG statement to show when a certain routine is run. DEBUG can be a very helpful troubleshooting aid when things don't seem to be running right.
I hope this helps.
Ah, I completely missed that. I originally wrote this with a piezospeaker so clearing everything worked and I missed updating that when I added the LCD. That has been addressed now and the initialization has been fixed so it only clears the screen once.
The reason for the separate function is the LCD only needs its 100ms pause when the circuit's first powered and the Reset function let's me effectively restart the program without having to cycle the power.
Now, with the exception of those two commands (HIGH lcdPin : PAUSE 100) all initialization is within the function as I should have had it.
The file I uploaded, however - have you had a chance to run it? I can't get a clear picture of the screen, but the top line displays "RAM 00eader" rather than the expected "M 00 R 02 RAM 00".
I've updated it to reflect the above changes (so I'll upload it again), but I'm still getting the same output.
As for the control code, I'm almost certain I've found my problem. I'm sending data to the computer (and in this specific case) to the LCD as feedback (hence the lack of DEBUG statements - plus the full program doesn't have the extra memory). However, I just realized I'm resetting dataVar before outputting it on the LCD and I never checked the computer record to confirm they agreed - hence why I thought it was 0 yet still running the CASE ELSE code. Now that completely makes sense - I'll just have to rewrite that code. Now if I can get this last piece of code with the LCD to work everything will be great.
I did look at it, and unfortunately you're still toggling the LCD pin when you call the Reset subroutine, most likely causing the next byte sent to the LCD to be not recognized. This reset routine is really problematic, however I think the bigger issue is that you have multiple exit points from subroutines you've called with a GOSUB. This effectively means there are GOSUB without RETURN issues in the code. For example, at one point you call a routine:
However, in this subroutine you have code like:
This allows the code to branch without returning from the subroutine, causing an internal Stack Overflow, which can cause undesirable operation. And there are at least two of these within that subroutine.
Ok. I should have tested my solution code by itself. I tried OUTS = DCD lcdPin so as to clear everything but the lcdPin, however, the compiler apparently thought I wanted to use the pin state rather than the pin number for this calculation (because I declared it with PIN). Is there a way to tell the compiler you want the pin number, rather than its state, without having to use a separate constant? I've replaced it with a constant for now and it's keeping the pin high as I had originally expected.
However that didn't fix the output.
For some reason when I change the SEROUT commands to send the correct cursor position at the beginning of all three commands I'm now getting the correct results: I'm not sure why that fixes it (because the code works without $85, $8A when I run it by itself), but at least it's working correctly now.
I'm aware of that, but the reason is because I wanted to "restart" the program via its software and that was the only way I knew how when using functions. As I understand it, the four most recent function calls can always find there way home, so the overflow doesn't cause a problem (if it works as I understand it to, at least). Is there a better way to restart the program from within itself? (I understand RETURN without a previous GOSUB is supposed to restart the program, but since I'm using GOSUB that doesn't work.)
I've cleaned up the Reset function and moved it to the top of the program so it's a little nicer and incorporated all the changes above and the program appears to be working now, so I'll upload the file one more time in case you or somebody else would like to see the changes.
Thank you very much for all your help.
In looking at your code I can't see any reason why you would have to randomly restart the code. You seem to be trying to simulate a reset of the BASIC Stamp by resetting all the I/O etc. however I don't see a reason why you would need to do that. I've never seen an application where normal usage required a routine to reset everything to startup conditions save a watchdog timer which usually handles an issue where the CPU has gone into an unknown/undesirable state and it is reset. But you're not doing a reset, so internal stack pointers and such are not being reset.
That's just not the case...if you call a subroutine with a GOSUB and then exit that subroutine without a RETURN and then call the same subroutine again from the same point in the code you're never going to even things out. The best I can tell in your code that is what is happening. Good programming practice is to have a declaration section which defines your I/O, constants and variables. In a well-written program this should only need to be done once. There shouldn't be a need to dynamically create new variables and the I/O pins should be fine as far as initial settings unless you're putting the MCU to sleep. After declaration you would initialize any connected hardware and preset I/O pin states and variable values. Your main program should be a loop and in most programs this will all be encompassed within a DO..LOOP construct. Everything that happens inside that loop is your main program and much of it will rely on calls to subroutines using GOSUB..RETURN calls. Within each subroutine a series of things happen terminating with a RETURN. once you use something in there like, IF <condition> THEN GOTO <label> you break the subroutine, make it difficult to follow the code flow and have random, weird and difficult to find issues as you're seeing.
I used to do this all the time in BASIC languages on the PC. I had spaghetti code and while it seemed logical to me at the time, when I had a large application with issues I understood what people meant when I had trouble tracking down the issues and others who looked at my code didn't even want to try and follow it. While microcontrollers don't usually have such large applications it is easy to start writing spaghetti code. If you tend to think of the program in the manner I described above and perhaps consider using flowcharts to develop it, you could have some really functional programs in the end. It's worth doing this now while things are fresh in your head. There's nothing worse than going back to a program months or years later and not having a clue what you did or why you did it. Yes, I've done this. Still do. But it's much easier to follow now just due to formatting and arrangement. I hope this helps.
Well to start with, it's not random. Notice there are very specific addresses set before I read my data bus. If an unrecognized byte is read at one of these address it's (at this point in the program, at least) being considered an unrecoverable error. And this function is at least two calls deep in the full program (remember I only uploaded the code that was producing the bug), so a simple return statement wouldn't work. GOTO was all I found at the time to return from two-three functions deep and it's the only time GOTO was used (to return to the top of my main program "loop"). (Also the GOTO statements aren't the problem with the LCD not displaying the strings at the right positions, as the entire display would be overwritten if they were executed - and that does function correctly.)
All that said, I had another look at the program and determined I could set a one bit flag variable if an error occured and use IF statements to check for errors after the function calls in the higher functions and finally replaced my main program loop - GOTO Listen - with a proper DO...LOOP, so everything returns as it should (there's not one GOTO statement left in my code) and the internal stack should always be happy now. And I think I've come to conclude I only really need to reset the error flag at the beginning of the loop instead of trying to reinitialize almost everything. So thank you for encouraging me to write it properly. However, as I expected, that did nothing with the LCD misdisplaying the strings - I still have to explicitly set the cursor position at the beginning of each SEROUT dispite wanting to pick up from where the last command left it. I really don't mind that, however, as I have the 4 extra bytes needed - I just don't understand why I need to do that. And you didn't mention if there actually is a way to truly reset the chip through the program's code.
Anyway, seeing as everything's working like it should now, I'm going to mark this thread as solved and add a bullet note summary to my first entry.
And once again thank you for all your help and advice, Chris, I really appreciate you taking the time to go over all of this.