My second SX program... Any advice?
This is my second ever SX/B program so I hope you guys will be gentle with me.
The general intent is a kind of co-processor for a two-stamp network.
My goals were two fold, create as flexible and useful a program as possible, and
use only SX/B... no assembly.
Compared to what most of you guys write, this program's speed is crawling. It uses flow
control to wait for instructions then executes one instruction at a time.
The program has the following features:
- Serial buffering for the Stamps to communicate.
- 45 bytes of directly accessable RAM
- 10 general purpose I/O pins that can be set HIGH, LOW or for Input (to detect high or low)
- PWM motor/LED control for 2 motors/LEDs
- Single pin motor direction control (requires external inverter) for both motors.
The PWM control is extremely crude. I doubt it'll work very well.
I'm horrible at math, so I couldn't figure out how to do the duty cycle, so I set the bits
by hand in a word variable and parse them one at a time for the motor's highs and lows.
I've never worked with interrupts before so I just picked a random rate that seems to work
without affecting the serial communication. Any recommendations on this would be great.
The code to set the pins is horrible. I can't figure out a better way to do it as I don't
completely grasp how to AND/OR/XOR stuff together to change the TRIS and such registers...
and the fact I can't use variables for either pins OR bits (IE: RB.var1 instead of RB.0)
makes it beyond me how to do the pin assignment in a better way.
Here's how the chip is used...
Using hardware flow control (thanks JonnyMac for teaching me about this), serial is sent to
the SX with a string of command bytes (See below for the byte assignment for each task). Then
the SX either queues up a message to be transmitted serially, or it executes the task. All of
the tasks except transmitting serial data and PWMing the motors is executed at once, and all other
attempts at contacting the SX have to wait until its done (This is actually amazingly fast even
at 4MHZ... I love this little chip!)
Serial communication is sent from the SX 1 byte at a time which leaves room for doing other routines
while transmitting (Currently I have none). All incoming serial is put on hold until all outgoing
is finished (handled by hardware flow control) to prevent overwriting the buffer.
Here's the byte order for the various commands:
Transmitting serial to another stamp:
Byte 0··· = TRANSMIT ($A1 hex) Task
Byte 1··· = Destination Stamp (1 or 2)
Byte 2-13 = Message to transmit
Last Byte = END_BYTE ($EE hex) (Should go at the end of the message. Max byte 14)
Store data in memory:
Byte 0··· = STORE ($A2 hex) Task
Byte 1··· = Bank to store data in (1-3)
Byte 2··· = Start location in bank(0-14)
Byte 3-13 = Data to be stored
Last Byte = END_BYTE ($EE hex) (Should go at the end of the data. Max byte 14)
Retrieve data from memory:
Byte 0··· = RETRIEVE ($A3 hex) Task
Byte 1··· = Destination Stamp (1 or 2)
Byte 2··· = Bank to read data from (1-3)
Byte 3··· = Start location to read from (0-14)
Byte 4··· = Number of bytes to read (0-14) (Optional. If not used, all data until end of bank is read)
Byte 5··· = END_BYTE ($EE hex) (If option above is not used, place END_BYTE on Byte 4 instead)
Setting a general purpose pin:
Byte 0··· = PIN_STATE ($A4 hex) Task
Byte 1··· = Pin to set (0-9)
Byte 2··· = State to set pin (0 = LOW, 1 = HIGH, 2 = INPUT)
Byte 3··· = END_BYTE ($EE hex)
Read the state of a general purpose pin (Most useful if pin set to input):
Byte 0··· = GET_PIN ($A5 hex) Task
Byte 1··· = Destination Stamp (1 or 2)
Byte 2··· = Pin to read (0-9)
Byte 3··· = END_BYTE ($EE hex)
Set PWM duty cycle:
Byte 0··· = PWM_MOTOR ($A6 hex) Task
Byte 1··· = Motor to PWM (1 or 2)
Byte 2··· = Speed (0-10)
Byte 3··· = END_BYTE ($EE hex)
Set motor direction:
Byte 0··· = MOTOR_DIR ($A7 hex)
Byte 1··· = Motor to control (1 or 2)
Byte 2··· = Direction (0 or 1)
At this time I've about used up every ounce of available programming memory due to my inexperience and
poor programming skills. I hope to eventually shrink it down to do some of the following:
Future plans/hopes:
- I hope to add additional outgoing buffers so the SX can still receive even when transmitting serial.
- I'd like to expand to PWMing 4 motors but I think the math of calculating cycles is beyond me.
- I'd also like to expand to a possible 4 stamps (reducing the general I/O pins as needed).
- Auto detection of connected stamps is another goal... currently if the SX attempts to send data to
a disconnected stamp it'll freeze forever.
- External EEPROM added for long-term storage of data from anywhere in the network.
- And my #1 hope is to somehow rewrite the horrible pin assignment code!
Any info, tips or suggestions you guys are willing to part with I'd gladly accept. Just please remember
that this is SX/B only. No assembly please.
The general intent is a kind of co-processor for a two-stamp network.
My goals were two fold, create as flexible and useful a program as possible, and
use only SX/B... no assembly.
Compared to what most of you guys write, this program's speed is crawling. It uses flow
control to wait for instructions then executes one instruction at a time.
The program has the following features:
- Serial buffering for the Stamps to communicate.
- 45 bytes of directly accessable RAM
- 10 general purpose I/O pins that can be set HIGH, LOW or for Input (to detect high or low)
- PWM motor/LED control for 2 motors/LEDs
- Single pin motor direction control (requires external inverter) for both motors.
The PWM control is extremely crude. I doubt it'll work very well.
I'm horrible at math, so I couldn't figure out how to do the duty cycle, so I set the bits
by hand in a word variable and parse them one at a time for the motor's highs and lows.
I've never worked with interrupts before so I just picked a random rate that seems to work
without affecting the serial communication. Any recommendations on this would be great.
The code to set the pins is horrible. I can't figure out a better way to do it as I don't
completely grasp how to AND/OR/XOR stuff together to change the TRIS and such registers...
and the fact I can't use variables for either pins OR bits (IE: RB.var1 instead of RB.0)
makes it beyond me how to do the pin assignment in a better way.
Here's how the chip is used...
Using hardware flow control (thanks JonnyMac for teaching me about this), serial is sent to
the SX with a string of command bytes (See below for the byte assignment for each task). Then
the SX either queues up a message to be transmitted serially, or it executes the task. All of
the tasks except transmitting serial data and PWMing the motors is executed at once, and all other
attempts at contacting the SX have to wait until its done (This is actually amazingly fast even
at 4MHZ... I love this little chip!)
Serial communication is sent from the SX 1 byte at a time which leaves room for doing other routines
while transmitting (Currently I have none). All incoming serial is put on hold until all outgoing
is finished (handled by hardware flow control) to prevent overwriting the buffer.
Here's the byte order for the various commands:
Transmitting serial to another stamp:
Byte 0··· = TRANSMIT ($A1 hex) Task
Byte 1··· = Destination Stamp (1 or 2)
Byte 2-13 = Message to transmit
Last Byte = END_BYTE ($EE hex) (Should go at the end of the message. Max byte 14)
Store data in memory:
Byte 0··· = STORE ($A2 hex) Task
Byte 1··· = Bank to store data in (1-3)
Byte 2··· = Start location in bank(0-14)
Byte 3-13 = Data to be stored
Last Byte = END_BYTE ($EE hex) (Should go at the end of the data. Max byte 14)
Retrieve data from memory:
Byte 0··· = RETRIEVE ($A3 hex) Task
Byte 1··· = Destination Stamp (1 or 2)
Byte 2··· = Bank to read data from (1-3)
Byte 3··· = Start location to read from (0-14)
Byte 4··· = Number of bytes to read (0-14) (Optional. If not used, all data until end of bank is read)
Byte 5··· = END_BYTE ($EE hex) (If option above is not used, place END_BYTE on Byte 4 instead)
Setting a general purpose pin:
Byte 0··· = PIN_STATE ($A4 hex) Task
Byte 1··· = Pin to set (0-9)
Byte 2··· = State to set pin (0 = LOW, 1 = HIGH, 2 = INPUT)
Byte 3··· = END_BYTE ($EE hex)
Read the state of a general purpose pin (Most useful if pin set to input):
Byte 0··· = GET_PIN ($A5 hex) Task
Byte 1··· = Destination Stamp (1 or 2)
Byte 2··· = Pin to read (0-9)
Byte 3··· = END_BYTE ($EE hex)
Set PWM duty cycle:
Byte 0··· = PWM_MOTOR ($A6 hex) Task
Byte 1··· = Motor to PWM (1 or 2)
Byte 2··· = Speed (0-10)
Byte 3··· = END_BYTE ($EE hex)
Set motor direction:
Byte 0··· = MOTOR_DIR ($A7 hex)
Byte 1··· = Motor to control (1 or 2)
Byte 2··· = Direction (0 or 1)
At this time I've about used up every ounce of available programming memory due to my inexperience and
poor programming skills. I hope to eventually shrink it down to do some of the following:
Future plans/hopes:
- I hope to add additional outgoing buffers so the SX can still receive even when transmitting serial.
- I'd like to expand to PWMing 4 motors but I think the math of calculating cycles is beyond me.
- I'd also like to expand to a possible 4 stamps (reducing the general I/O pins as needed).
- Auto detection of connected stamps is another goal... currently if the SX attempts to send data to
a disconnected stamp it'll freeze forever.
- External EEPROM added for long-term storage of data from anywhere in the network.
- And my #1 hope is to somehow rewrite the horrible pin assignment code!
Any info, tips or suggestions you guys are willing to part with I'd gladly accept. Just please remember
that this is SX/B only. No assembly please.
Comments
Here's an alternative to your ISR that uses only 3 bytes (instead of five) and doesn't rely on external constants or tables -- simply set speed1 or speed2 to to a value between 0 (off) and 10 (full on). I have verified this code with a 'scope -- it works. The attached image shows the Motor1 output with speed1 set to 9.
Post Edited (JonnyMac) : 2/17/2009 1:32:50 AM GMT
Post Edited (JonnyMac) : 2/17/2009 1:56:43 AM GMT
Thank you!
I knew there was some easier way to figure out those speed settings.
As far as the Pin_0 - pin_0 subs, I spent a good 3 hours just staring at them and studying helpfiles
TRYING to figure out a better way. I figured it most likely had something to do with &ing somehow...
but I didn't understand it enough to give it a shot. Thank you so much.
I hate to bother you with newbie questions... but what is the pin mask and how does it work?
I don't get this:
sbMask = 1 << sbPin
or this:
TRIS_B = TRIS_B | sbMask
or any of the others...
I don't mean to sound like an annoying newbie again but could you explain how these binary operations
work and how they are used in this case? (keep in mind I've read the help file so I understand the
basics of what the operators do... but I don't understand them in this context or in most real-world
applications outside of the basic theory. I AM a total newbie when it comes to bitwise manipulation).
You mentioned that using interrupts while using SERIN and SEROUT is unreliable... I figured that much
but had hoped that with a very small interrupt and slow rate, I could get away with it. Do you have
another recommendation that might be within my newbieish understanding?
I know it would be useless for PWMing motors/leds but for my future applications, is there a way to temp
suspend interrupts so I can execute high-risk commands? Is that why you have the isrFlag = 1 line? I
don't see where its used anywhere.
On a more personal note...
I know we don't see eye to eye often and I know you've become quite annoyed with me in the past, but I
wanted to say that I very much appreciate what you do for me and all the others here on the forum.
Not many people would go out of their way just to write a program or snippet of a program to explain
something to someone they've never met, will most likely not bother learning it and you'll never hear
from them again. Thank you.
Ugha, I am glad you are asking the questions NOW that I would be asking soon. Your code is full of comments which makes it far easier to understand. It looks good, as well.
JonnyMac, thank you very much for the fantastic instruction! I follow ALL the newbie threads to which you contribute, plus many of the others as well. I find that I am beginning to understand those that used to be far above my head.
--Bill
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
You are what you write.
I did have a question though... SERIN/SEROUT with interrupts is unreliable in that the SX can miss bits being transmitted while the interrupt is taking place.
Would a shorter interrupt routine help reduce this? Perhaps some way to skip the rest of the routine if serial communication is in progress?
In other words... would something like this prevent missed bits:
if transmitting = TRUE then
goto ISR_Exit
endif
Or would it really not be that much faster/make a difference beyond the existing snippet you provided earlier?
I was kind of hoping to keep this all in SX/B for three reasons...
One was so I could actually understand everything... Or at least how to do everything.
Another was kind of selfish... I wanted to be able to point to it and say "I did that!" This is the most complex microcontroller application I've ever attempted, and I wanted the pride of actually
doing all aspects myself. I guess that's kind of stupid.
The final reason is I plan on offering this code (or perhaps offering preprogrammed chips at cost) to people in the Basic Stamp forum as a kind of co-processor to offload some work from their
stamps but at a fraction of the cost of a new stamp. If it was in SX/B they may understand it better and be better able to adjust it to their needs.
I guess your right though. What's the difference between me using your assembly code and me using Bean's SX/B?
You win... I give. Do you at least understand why I put up such a fight though? To have something this complex, this useful and this detailed I did completely on my own would have been a
great accomplishment to me.
I'm going to go study the original code you posted in my first thread to try and figure out how this stuff works.
I know your frustrated with me, but would you mind if I asked more questions about it if any come up?
Don't ruin this for the rest of us!
You once chastised Me over a PIC16C57C, And cutting Profit From Parallax.
Now what are You donig?
____________$WMc%______________
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
The Truth is out there············································ BoogerWoods, FL. USA
I agree with $WMc%. I am learning a tremendous amount by following JonnyMac and you.
--Bill
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
You are what you write.
What happens when people need to go beyond the abilities of a stamp? They move on to another processor... usually a cheaper one like the PIC.
If they had an option to enhance a stamp... a simple, cheap, co-processor that is designed to work with a stamp's ease of use, then they'd stay with
stamps longer.
A co-processor may even encourage more people to buy and stick with one of Parallax's leading products.
You wanted some clarification of Boolean logic. Here goes.
At its simplest, Boolean operators simply work on a pair of bits. Depending on the value of those two bits and the boolean operator we are applying, we get different results.
The truth table for the AND operator:
Note that in order for the result to be a 1, both A AND B must be a 1.
The truth table for the OR operator:
Note that in order for the result to be a 1, either A OR B must be a 1. If both are set to 1, you still get a 1 as the result.
Now, suppose you want to set a bit high in a register or memory location. Let's further suppose that the bit is bit number 2. So, you want to change this:
00000000
into this:
00000100
To turn a bit on, we use the OR operator. Why? Because regardless of whether the existing bit is a 0 or a 1, we can set it to a 1 using a mask that has the bit set already. Lets assume you have a variable called "ChangeThis", which currently equals 0. You want to set bit 2, so you create a mask called "BitThreeOrMask" which has the binary value of "00000100". If you write code like this:
...you'll force bit 2 to be a 1. Let me stack the binary numbers so you can see how they work more easily. Note that I have put bit two in bold to make it easier to see.
The key is to look at the binary numbers as pairs of input bits. In other words, look at bit 0 in "ChangeThis" and bit 0 in "BitTwoOrMask". Both values are 0. This same pattern holds for every pair except bit number 2. In that case, the bit in "ChangeThis" is a 0, but the bit in "BitTwoOrMask" is a 1. Now, refer to the truth table for the OR function at the top of this post. Remember, we need either input (or both inputs) to be a 1 in order to have a 1 as the result. Since our mask only has a bit set at bit number 2, the only place where we are going to get a 1 is bit number 2. All other mask bits are set to 0, so all the other bits will be left alone regardless of what value they originally have.
So, what happens if some of the bits in "ChangeThis" are already set to 1 and we don't want to change them? Simple. Nothing will happen to those bits as long as the corresponding bits in the mask are 0, which is exactly the case we have. Here's how it works with "ChangeThis" set to a value other than 0:
Notice that all the bits are unchanged except for bit number 2 which flips to a 1. If you look through the truth table you'll see why. If bit two was already a one, then it wouldn't change. This is often handy when we want to set a bit without checking to see if it has been set already.
Clearing a bit works similarly, except we have to create a mask where the bit we want to change is a 1, and all the bits we want to leave alone are set to 1. We'll use the "ChangeThis" variable, but this time we'll create and use a mask called "BitTwoAndMask". The code to use it looks like this:
And the binary values look like this:
As in the OR example, pre-existing bit values are unchanged when you AND them with a 1:
Thanks,
PeterM
Post Edited (PJMonty) : 2/19/2009 7:33:35 PM GMT
Most excellent!
Are your uses of "BitTwoOrMask" and "BitThreeOrMask" consistent?
--Bill
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
You are what you write.
Oops! Good catch. I edited the post to fix the inconsistencies. I accidentally left a couple of "BitThree" masks in the original post.
Thanks,
PeterM
Do the SX/B 2.0 putbit and getbit commands do something like this? Are they slower than bitmasking?
Who knows? Who cares? Write your code first, then worry if it's too slow. If it is, figure out what parts are slow, rather than spending energy optimizing areas that don't need to be optimized. As Donald Knuth famously said, "Premature optimization is the root of all evil."
Thanks,
PeterM