Shop OBEX P1 Docs P2 Docs Learn Events
My second SX program... Any advice? — Parallax Forums

My second SX program... Any advice?

UghaUgha Posts: 543
edited 2009-02-19 19:41 in General Discussion
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.

Comments

  • JonnyMacJonnyMac Posts: 9,214
    edited 2009-02-17 01:26
    Your ISR isn't using any internal (__PARAMx) variables so you can specify NOPRESERVE to save a lot of cycles -- this is really important as you continue to attempt to use SERIN and SEROUT while the ISR is running (not reliable).

    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.

    ' ==========================
      INTERRUPT NOPRESERVE 1_000
    ' ==========================
    
    ISR_Start:
      isrFlag = 1
    
    
    PWM_Motors:
      INC cnt
      IF cnt = 10 THEN
        cnt = 0
        IF speed1 > 0 THEN
          Motor1 = IsOn
        ENDIF
        IF speed2 > 0 THEN
          Motor2 = IsOn
        ENDIF
      ELSE
        IF cnt = speed1 THEN
          Motor1 = IsOff
        ENDIF
        IF cnt = speed2 THEN
          Motor2 = IsOff
        ENDIF
      ENDIF
    
    ISR_Exit:
      RETURNINT
    

    Post Edited (JonnyMac) : 2/17/2009 1:32:50 AM GMT
    807 x 632 - 161K
  • JonnyMacJonnyMac Posts: 9,214
    edited 2009-02-17 01:47
    You have a lot of redundant routines that could easily be reduced; for example: instead of your pin_0 through pin_7 routines you could replace them with SET_RB and pass parameters to it (pin# and mode).

    ' Use: SET_RB pin, mode
    ' -- pin is 0 (RB.0) to 7 (RB.7)
    ' -- mode is 0: output low, 1:output high; 2:input
    
    SUB SET_RB
      sbPin         VAR     tmpB1
      sbMode        VAR     tmpB2
      sbMask        VAR     tmpB3
    
      sbPin = __PARAM1                              ' save parameters
      sbMode = __PARAM2
    
      IF sbPin <= 7 THEN                            ' qualify pin
        IF sbMode <= 2 THEN                         ' qualify mode
          sbMask = 1 << sbPin                       ' create pin mask
          IF sbMode = 2 THEN
            TRIS_B = TRIS_B | sbMask                ' make pin an input
          ELSEIF sbMode = 1 THEN
            RB = RB | sbMask                        ' write 1 to pin
            sbMask = ~sbMask                        ' invert mask bits
            TRIS_B = TRIS_B & sbMask                ' make pin output
          ELSE
            sbMask = ~sbMask                        ' invert mask bits
            RB = RB & sbMask                        ' write 0 to pin
            TRIS_B = TRIS_B & sbMask                ' make pin output
          ENDIF
        ENDIF
      ENDIF
      ENDSUB
    

    Post Edited (JonnyMac) : 2/17/2009 1:56:43 AM GMT
  • JonnyMacJonnyMac Posts: 9,214
    edited 2009-02-17 02:13
    Here's a version of the subroutine above that works with all the pins or RB and RC.

    ' Use: SET_PIN pin, mode
    ' -- pin is 0 (RB.0) to 15 (RC.7)
    ' -- mode is 0: output low, 1:output high; 2:input
    
    SUB SET_PIN
      spPin         VAR     tmpB1
      spMode        VAR     tmpB2
      spMask        VAR     tmpB3
    
      spPin = __PARAM1                              ' save parameters
      spMode = __PARAM2
    
      IF spMode <= 2 THEN                           ' validate mode
        IF spPin <= 7 THEN                          ' check for RB pin
          spMask = 1 << spPin                       ' create pin mask
          IF spMode = 2 THEN
            TRIS_B = TRIS_B | spMask                ' make pin an input
          ELSEIF spMode = 1 THEN
            RB = RB | spMask                        ' write 1 to pin
            spMask = ~spMask                        ' invert mask bits
            TRIS_B = TRIS_B & spMask                ' make pin output
          ELSE
            spMask = ~spMask                        ' invert mask bits
            RB = RB & spMask                        ' write 0 to pin
            TRIS_B = TRIS_B & spMask                ' make pin output
          ENDIF
        ELSEIF spPin <= 15 THEN                     ' check for RC pin
          spPin.3 = 0                               ' align, 0 to 7
          spMask = 1 << spPin
          IF spMode = 2 THEN
            TRIS_C = TRIS_C | spMask
          ELSEIF spMode = 1 THEN
            RC = RC | spMask
            spMask = ~spMask
            TRIS_C = TRIS_C & spMask
          ELSE
            spMask = ~spMask
            RC = RC & spMask
            TRIS_C = TRIS_C & spMask
          ENDIF
        ENDIF
      ENDIF
      ENDSUB
    
  • UghaUgha Posts: 543
    edited 2009-02-17 03:04
    JonnyMac:
    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.
  • Bill ChennaultBill Chennault Posts: 1,198
    edited 2009-02-17 03:16
    JonnyMac and Ugha--

    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.
  • JonnyMacJonnyMac Posts: 9,214
    edited 2009-02-17 04:33
    Ugha: I've been working with small microcontrollers since high school (graduated in 1980) so I [noparse][[/noparse]probably] have a few years on you -- don't think that after a few weeks you're going to know all the tricks; it takes time and applying the tricks you find. I'll give you working code but I'm going to leave it to you to figure out; the comments and the information in the help file give you everything you need.
  • UghaUgha Posts: 543
    edited 2009-02-17 23:35
    JonnyMac: I finally get the samples you provided... it was actually a source you wrote for some other site that gave me the last pieces to puzzle out how it all works.

    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?
  • JonnyMacJonnyMac Posts: 9,214
    edited 2009-02-18 01:04
    Why are you so dead set on using SERIN and SEROUT when ISR-based UARTs would serve your project so well? You stated at some point that you want to send and receive at the same time but seem not to want to acknowledge that you will never be able to do this with SERIN and SEROUT. Here's my point: you probably don't understand the mechanics of SERIN and SEROUT -- though you know what they do -- so why are you so concerned about using tested Assembly snippets that you don't understand? Isn't it really the same thing? Do you think I or anyone else would spend our time feeding you bogus code?
  • UghaUgha Posts: 543
    edited 2009-02-18 01:21
    I don't doubt in the slightest that your code works great.
    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?
  • $WMc%$WMc% Posts: 1,884
    edited 2009-02-18 02:18
    Ugha

    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
  • Bill ChennaultBill Chennault Posts: 1,198
    edited 2009-02-18 02:53
    Ugha--

    I agree with $WMc%. I am learning a tremendous amount by following JonnyMac and you.

    --Bill

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    You are what you write.
  • UghaUgha Posts: 543
    edited 2009-02-18 03:32
    I wasn't planning on undercutting Parallax... I was only planning on enhancing user base by encouraging people to stick with stamps.

    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.
  • PJMontyPJMonty Posts: 983
    edited 2009-02-19 00:06
    Ugha,

    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:

    [b]A  B  Out[/b]
    0  0   0
    0  1   0
    1  0   0
    1  1   1
    



    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:

    [b]A  B  Out[/b]
    0  0   0
    0  1   1
    1  0   1
    1  1   1
    



    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:

    ChangeThis = ChangeThis | BitTwoOrMask
    



    ...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.

    ChangeThis    00000[b]0[/b]00
    BitTwoOrMask  00000[b]1[/b]00
    
    Result        00000[b]1[/b]00
    



    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:

    ChangeThis    10110[b]0[/b]01
    BitTwoOrMask  00000[b]1[/b]00
    
    Result        10110[b]1[/b]01
    



    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:

    ChangeThis = ChangeThis & BitTwoAndMask
    



    And the binary values look like this:

    ChangeThis     00000[b]1[/b]00
    BitTwoAndMask  11111[b]0[/b]11
    
    Result         00000[b]0[/b]00
    



    As in the OR example, pre-existing bit values are unchanged when you AND them with a 1:

    ChangeThis     10110[b]1[/b]01
    BitTwoAndMask  11111[b]0[/b]11
    
    Result         10110[b]0[/b]01
    



    Thanks,
    PeterM

    Post Edited (PJMonty) : 2/19/2009 7:33:35 PM GMT
  • Bill ChennaultBill Chennault Posts: 1,198
    edited 2009-02-19 01:12
    PeterM--

    Most excellent!

    Are your uses of "BitTwoOrMask" and "BitThreeOrMask" consistent?

    --Bill

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    You are what you write.
  • PJMontyPJMonty Posts: 983
    edited 2009-02-19 01:31
    Bill,

    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
  • UghaUgha Posts: 543
    edited 2009-02-19 02:43
    PJMonty: This is a great help... I understand it much better now, thanks [noparse]:)[/noparse]

    Do the SX/B 2.0 putbit and getbit commands do something like this? Are they slower than bitmasking?
  • PJMontyPJMonty Posts: 983
    edited 2009-02-19 19:41
    Ugha,

    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
Sign In or Register to comment.