First Project - Traffic Light

rbwilliams

New Member
Hello,
I am new to PICAXE. I am trying to create a Traffic Light with the 08M. It power up in "auto mode" (changing lights over time), at the same time is scanning inputs for a button press (pseudo-interrupt) that takes it out of "auto-mode" and goes into "manual mode" where the button press causes its corresponding output to stay lit, until another button is pressed. Resetting 08M puts it back into "auto-mode".

This is my code so far:

Code:
init:
let dirs=%0000111

main:	

	let pins = %00000001
	gosub chkbuttons_loop
	let pins = %00000010
	gosub chkbuttons_loop
	let pins = %00000100
	gosub chkbuttons_loop
	
	goto main


chkbuttons_loop:
		for b1 = 1 to 10
		gosub chkred
		gosub chkyellow
		gosub chkgreen
		next b1
	
	
	      	
chkred:
	if pin3 = 1 and pin4=0 then goto lightred
	pause 100
	return
	
chkyellow:
	if pin3=1 and pin4 = 1 then goto lightyellow
	pause 100
	return
	
chkgreen:
	if pin4=1 and pin3=0 then goto lightgreen	
	pause 10
	return
	
lightred:
	pause 200
	if pin3 = 1 and pin4=0 then goto RED
	goto chkyellow

	
lightyellow:
	pause 200
	if pin3=1 and pin4=1 then goto YELLOW
	goto chkgreen
	
	
lightgreen:
	pause 200
	if pin4=1 and pin3=0 then goto GREEN
	goto chkred

RED:
	High 0
	Low 1
	low 2
	goto chkbuttons2
	
YELLOW:
	Low 0
	High 1
	Low 2
	goto chkbuttons2
	
GREEN:
	Low 0
	Low 1
	High 2
      goto chkbuttons2
      
 chkbuttons2:
 		do
            gosub chkred
		gosub chkyellow
		gosub chkgreen
		loop
The stack overruns because I have too many nested loops. Any ideas about how I should change the code? Any suggestions as to how I show code in a message?

Thanks,
Roger
 

Attachments

Last edited:

rbwilliams

New Member
Traffic Light revised

Thanks for the advice of putting in comments.

I revised my code a little. Not sure it is helped, but maybe its easier to understand, it has comments too.

Code:
init:
let dirs=%0000111

auto_mode:	

	let pins = %00000001   'Light "RED" output 0
	gosub chkbuttons_loop  'Loop acts as delay
	let pins = %00000010   'Light "YELLOW" output 1
	gosub chkbuttons_loop  'Loop acts as delay
	let pins = %00000100   'Light "GREEN" output 2
	gosub chkbuttons_loop
	
	goto auto_mode


chkbuttons_loop:             'Loop acts as delay in "auto-mode"
		for b1 = 1 to 10 '10 times through seemed like a long enough delay
		gosub chkred     'This sub is looking for a particular mask, a pattern-specific interrupt?
		gosub chkyellow  'this is another mask
		gosub chkgreen   'and another
		next b1
	
	
	      	
chkred:                      'this is a masked interrupt I am trying to create.
	if pin3 = 1 and pin4=0 then 
		goto lightred
	else 
		pause 100        'delay that will be multiplied by "for next" loop.
	endif             
	return                 'if this pattern is not seen, continue to next check.
	
chkyellow:                   
	if pin3=1 and pin4 = 1 then 
		goto lightyellow 
	else	
		pause 100 
	endif              
	return
	 
chkgreen:                    
	if pin4=1 and pin3=0 then 
		goto lightgreen	 
	else
		pause 10
	endif
	return
	
lightred:                    
	pause 200
	if pin3 = 1 and pin4=0 then  'this sub asks "is that button still held after 200ms?"
 		pause 200              'If yes, then wait another 200ms to allow button to be released.
 	else
 		goto chkyellow         'if not, continue on with next check.
 	endif
 	if pin3=0 and pin4=0 then    'is button released?
 		goto RED               'If yes, then make corresponding outputs high.
 	else                       
		goto lightred
	endif
	
lightyellow:
	pause 200
	if pin3=1 and pin4=1 then 
		pause 200 
	else
		goto chkgreen
	endif
	
	if pin3=0 and pin4=0 then 
		goto YELLOW                         
	else
		goto lightyellow
	endif	
	
lightgreen:
	pause 200
	if pin4=1 and pin3=0 then 
		pause 200  
	else
		goto chkred
	endif
	
	if pin3=0 and pin4=0 then 
		goto GREEN
	else
		goto lightgreen
	endif
	
RED:  	                      'The exit from "auto-mode" to manual. These outputs now stay high until another button is pressed.
	High 0 
	Low 1
	low 2
	goto chkbuttons2            'A seperate button checker loop outside of the "auto-mode" loop.
	
YELLOW:
	Low 0
	High 1
	Low 2
	goto chkbuttons2
	
GREEN:
	Low 0
	Low 1
	High 2
      goto chkbuttons2
      
 chkbuttons2:                    'a seperate loop that uses the same input masks as in "auto-mode".
 		do
            gosub chkred      
		gosub chkyellow
		gosub chkgreen
		loop
Thanks to all those who took the time to even look at this.
 

InvaderZim

Senior Member
A golden rule is: gosubs must always return. I think you have some goto statements within a subroutine; you jump to another section of code, goto again, and again, and never get back to "returning" from the original gosub. Then you gosub again, and repeat the cycle. The picaxe can only do that a few times before it gets confused, because it is waiting to officially return from the first gosub.

I suggest re-writing it so that either you never use gosub (only goto), or so that you never use goto within a gosub. Once you have that working you can try to make it more efficient.

Some coding practice that might help is to refer to subroutine labels as 'subName', and goto labels as 'name', so that you can tell the subroutines from the non-subroutines. And the instant you create a subroutine section, type a "return" statement to go with it--gosubs must always return!
 

rbwilliams

New Member
Thanks InvaderZim.
It took me several error-outs to get what you were saying. I don't understand why it gets confused, though. But if it does get confused, why doesn't it error out right away? I don't understand why I can still jump around a few times before it errors out.

Regardless, I appreciate the critique. I will try the things you suggested.
 

BeanieBots

Moderator
I don't understand why I can still jump around a few times before it errors out.
Each time you "gosub", it has to remember where it has to return to.
Each time you "jump" out of the subroutine instead of "returning", it continues to "remember". Eventually, its brain goes into overload with all those return addresses it has to remember.
 

goom

Senior Member
Add a "return" at the end of the "chkbuttons_loop:" subroutine. Without it, the PICAXE will get confused when runs out of space to keep track of where it should return to (16 gosubs in the case of an -08M for example). It will not produce an error immediately, only when it gets to the gosub limit.
I suspect that it would be much simpler to use the interrupt feature. By connecting each of the 3 buttons to an input pin via a diode, any one of them would signal initiate the interrupt routine where you could check for which button was pressed, and take action accordingly. Connect the "pointy" end of each diode to the input pin, and also connect a resistor (4.7K) from the input pin to ground to make sure it is at logic 0 when no button is pressed.
Remember to reset the interrupt before the return at the end of the interrupt routine.
 

hippy

Ex-Staff (retired)
Thanks InvaderZim.
It took me several error-outs to get what you were saying. I don't understand why it gets confused, though.
It is quite hard to describe in words what is going wrong. One way of viewing the problem you have would be to draw your program as a flowchart with blocks representing the various routines with arrows showing how control passes from one block to another. If you do that you will see that there are loops between various blocks which keeps the program "trapped", running in circles; you do a GOSUB, end up back where you were without any RETURN, and do a GOSUB again.

What you really need is a Finite State Machine, something where code just sequentially moves from one state to another without using GOSUB.
 

InvaderZim

Senior Member
I'll make an attempt at explaining the oddity of a gosub; I hope I don't confuse things even more!

If your program is a book full of instructions, then a 'goto' is like a command to 'go to page 35' which is pretty straight forward. You just flip the pages, and don't care what page you had been reading a second ago.

But what if you want to just do something real fast, and then pick up where you had been reading a second ago?

That's where a 'gosub' is useful: you can call a subroutine, and the picaxe will "bookmark" your current spot. Nice, eh? Then when it goes to page 72, it will follow those commands until it sees a "return" instruction, which means "go back to the bookmark and pick up exactly where you left off". For some tasks, organizing your program with a few subroutines can really save code space and make everything easier to manage.

But there's a catch! It's this simple: you only have a very few bookmarks, and if you run out of bookmarks and try to gosub again, you'll crash out. The picaxe will cry because you've asked it to remember where you left off, and it can't remember without a bookmark. The good news is that you can reuse the bookmarks when they are no longer needed; every time you 'return' you get that bookmark back.

That's why there's the golden rule: always always always 'return' from a gosub.

If you are new to microprocessors, you might try just using the 'goto' command to start with. I think your program is small enough that you can do it all with 'goto' and not have to worry much about code size. Once you get everything working that way, you might look for places where a 'gosub' might make things better (and there might not be any, depending on the program). Best of luck!
 

rbwilliams

New Member
InvaderZim,

I like it!

It then makes sense that it can jump around a bit before crashing. Are the 'returns' stack pointers? I had a class on the 8086 8 years ago, and thats about it for microcontrollers. Lots of good that did me.
 

rbwilliams

New Member
I suspect that it would be much simpler to use the interrupt feature. By connecting each of the 3 buttons to an input pin via a diode, any one of them would signal initiate the interrupt routine where you could check for which button was pressed, and take action accordingly. Connect the "pointy" end of each diode to the input pin, and also connect a resistor (4.7K) from the input pin to ground to make sure it is at logic 0 when no button is pressed.
Remember to reset the interrupt before the return at the end of the interrupt routine.
Actually, goom, this won't work right off the bat. After sketching out the circuit, I realized I was right back where I started. I only have two input pins. So, what I could do, is create an interrupt routine that stops the auto-mode with any switch press, and then scan for another switch press. This would make the user interface a bit more complicated, but it could work. I was hoping to just have any switch press immediately cause its corresponding output to stay high.
 

InvaderZim

Senior Member
InvaderZim,

I like it!

It then makes sense that it can jump around a bit before crashing. Are the 'returns' stack pointers? I had a class on the 8086 8 years ago, and thats about it for microcontrollers. Lots of good that did me.
Great, glad that made sense. The returns are related to the stack; the stack is where current work is temporarily squirreled away to allow for a 'return' later on when the gosub processing is done. I've no idea exactly how that's implemented in a pic, but it doesn't really matter since it's nicely under the hood. The important thing is that if you never return, things will keep getting squirrelled away until the stack overflows, and then you're stuck.

The program editor won't report a syntax error because an overflow depends on too many things happening in the wrong order; you just have to simulate and see. As I said before, I like to define right up front if a section of code is a subroutine or not, and if it is a subroutine, make sure the first thing you do is put a 'return' at the end. Otherwise it's a trap that'll catch even experienced programmers off-guard.
 
Top