Another dumb Spin question?
Hi all,
Been away from the Propellers for a while and trying to get my head straight.
Or properly twisted?
Multi-bit (range) I/O syntax
outa [23..16] := $00
IS there any clever way to take the magic numbers out of that statement?
OTHER than something like this???
Byte LED1, LED8
LED8 := 23
LED1 := 16
outa [LED8..LED1] := LED
Comments
I would suggest that you make your MSB and LSB values of the range constants, since those connections will not move during the operation of the program
con LED8 = 23 LED1 = 16
To keep your code tidy you may want to write function like this:
pub update_leds(value) outa[LED8..LED1] := value dira[LED8..LED1] := %11111111
I still like the ~~ notation:
dira[LED8 .. LED1]~~
Saves having to count 1's.
-Phil
I've been trying to make that work as a constant for mumble something hours now.
I kept getting an error message (expected ":")
But since YOU said it, it now works.
That's some heavy juju you got there, JonnyMac.
I do not know what my problem was, but it works now, so back to work.
Thank you, Jon.
Hey Phil,
Yeah, that too!
Reading that old first-cut robot code from a few years ago...
Makes me wonder -
Who wrote this mess???
Oh...
(high squeeky voice) "Nevermind"
Richard
I prefer the raw bits because it's verbose and is faster than ~~. If I don't want to be bothered counting bit I use -1, but this is rarely the case.
Using
true
(which evals to -1) could be argued is better or more "idiomatic". Using -1 instead of the actual bit mask will also save 1 byte of code (-1 constant is single byte code, bitmask constant is 2 bytes).Also note that in flexspin, there's no codegen difference between a -1/0 assignment and a freestanding postset operator.
Indeed. Good point. Thank you.
all is -1.
If Shakespeare said it, it must be -1.
My ignorance is showing, but what is flexspin?
Flexspin is a compiler than understands Spin1, Spin2, C, and BASIC. I don't use it much (except for testing), but it has become quite popular with many forum members.
You can download from here:
-- https://github.com/totalspectrum/flexprop/releases
And combinations thereof
Craig
GIMMEE!
Well, until then?
My project is a small table-top robot that I'd like to NOT fall off the table.
Robots don't bounce very well.
This is a kind of cleaned-up version of where I left it a couple of years ago.
It's baby-talk level Spin. I get that. I'm back to trying to learn...
Lab .01 code to exercise the circuitry and machinery to see if anything works right.
There are some fun things I'd like to try, but I don't know how to say it in Spin yet.
Like PWM control of the drive motors.
Like Bluetooth comms instead of the TV remote.
Like Line-following, mapping, tracking, etc?
But I gotta get Spun up first...
In this episode the big thing I'm tripped up over...
Printing text to the LCD display.
Printing "literals" is obviously no problem.
The Serial-LCD driver handles that nicely.
But sending a string variable?
I haven't quite got a handle on that one yet.
Pointer to a variable array, but not sure how to stuff the array.
Or get it shipped over to the LCD display.
example:
In the code...
ir#chUp : 'Forward if Power == True LCD.CLS LCD.gotoxy(0, 0) ' col, line lcd.str(string("Forward ")) <<== printing a literal GoFwd other (loop)
What I'd like to do...
ir#chUp : 'Forward if Power == TRUE 'fill in Txt[] array with "Forward" <<== a string? other 'and after the case terminates ... if the string is not blank? LCD.CLS LCD.gotoxy(0, 0) ' col, line print the string ' lcd.str(string("????")) (loop)
Probably something like...
repeat strsize(strAddr) bytemove(byte[strAddr++]) ???
All files are in the zip.

I couldn't get the main program to format here...
_CLKMODE = XTAL1 + PLL16X ' 80 Mhz clock _XINFREQ = 5_000_000 OneSec = _XINFREQ MSec = _XINFREQ / 1_000 * 8 USec = _XINFREQ / 1_000_000 '==================================== ' Pin Definitions: ' MSB LSB ' port addresses ' 33222222_22222111_11111110_00000000 ' pin numbers are port +1 ' 10987654_32109876_54321098_76543210 ' ' Quicksart touch pads PADpins = %00000000_00000000_00000000_11111111 '00..07 ' Motor control pins for TB6612FNG dual motor controller MOTPins = %00000000_00000000_00001111_00000000 '08..11 MotA1 = 8 MotA2 = 9 MotB1 = 10 MotB2 = 11 PWMpins = %00000000_00000000_00110000_00000000 '12..13 PwmL = 12 PwmR = 13 ' 2 line liquid crystal display LCDpins = %00000000_00000000_01000000_00000000 '14 LCDpin = 14 ' Infra Red Remote COntrol Decoder IRCpin = 15 ' Quickstart LED display used as RANGE [LED8..LED1] LEDpins = %00000000_11111111_00000000_00000000 '16 to 23 LED8 = 23 LED4 = 19 LED1 = 16 ' GP2Y0D810Z0F IR obsticle sensors input pins OBXpins = %00001111_00000000_00000000_00000000 '24 to 27 ObxFL = 24 ObxFR = 25 ObxRL = 26 BoxRR = 27 ' Quickstart EPROM serial port EPCpins = %00010000_00000000_00000000_00000000 '28 EEPROM Clock EPDpins = %00100000_00000000_00000000_00000000 '29 EEPROM Data ' Quickstart USB serial port USBRX = %01000000_00000000_00000000_00000000 '30 USBTX = %10000000_00000000_00000000_00000000 '31 OUTpins = MOTPins | PWMpins |LCDpins| LEDpins |LCDpins '==================================== ' Motor Direction Masks for TB6612FNG ' Mot_Fwd = %0110 ' Go fwd Mot_Aft = %1001 ' Go Aft Mot_Rit = %0010 ' Turn Right Mot_Lft = %0100 ' Turn Left Mot_RoR = %1010 ' Rotate Right Mot_RoL = %0101 ' Rotate Left Mot_Off = %0000 ' All Stop '==================================== ' Liquid Crystal Display - 2 line' LCDon1 = $16 ' LCD on; cursor off, blink off LCDline1 = $80 ' move to line 1, column 0 LCDline2 = $94 ' move to line 2, column 0 LCD_TimeOut = 10 ' seconds '==================================== OBJ ir : "IRC" lcd : "serial_lcd" num : "simple_numbers" '==================================== VAR long Stack1[6] long IRcog, Power Byte LCD_Time, IRC_ret, TurnType Byte TxtLin1[16], TxtLin2[16] '==================================== PUB Init | freq, index, cog, IRcode ' *COG* ' variable inits Power := TRUE ' pin directions outa [LED8..LED1] := $00 ' all LED pins off ' dira [27..00] := OUTpins ' make all output pins be outputs outa [PwmL..PwmL] := %11 ' PWMpins enable motors 1=ON '==================================== ' init LCD if lcd.start(LCDpin, 9600, 2) ' *COG* lcd.putc(lcd#Lcd_On1) ' no cursor lcd.backlight(1) lcd.cls ProgramVersion ' Start LCD_Timeout ' *COG* LCD_Time := LCD_TimeOut ' reset LCD_Time each time a key is pressed cognew (LCD_Timer, @stack1 ) '==================================== ' Init IR remote IRcog := ir.Start(IRCpin, @IRC_ret) ' *COG* '==================================== ' Top of IR Code input loop: if IRcog > 0 repeat If LCD_Time >0 and power == TRUE LCD.backlight(1) ' turn it on (won't workwith TRUE/FALSE?) else ' timed out LCD.backlight(0) ' turn if off If IRC_ret <> ir#NoNewCode ' we have a key code IRcode := IRC_ret ir.Start(IRCpin, @IRC_ret) ' set up for next code LCD_Time := LCD_Timeout ' reset LCD_Timeout each time a key is pressed lcd.gotoxy(0,1) 'Start of user input parser case IRcode ' control keys ir#power : LCD.CLS if Power == TRUE Power := FALSE ' turn power off lcd.str(string("Power OFF")) GoStop ' shut down motors waitMS(2000) lcd.backlight(0) else Power := TRUE ' turn power on lcd.str(string("Power ON")) ir#chUp : 'Forward if Power == True LCD.CLS LCD.gotoxy(0, 0) ' col, line lcd.str(string("Forward ")) GoFwd ir#chDn : 'backward if Power == True LCD.CLS LCD.gotoxy(0, 0) ' col, line lcd.str(string("Back ")) GoBack ir#volDn : 'left if Power == True LCD.CLS lcd.str(string("Left ")) GoLeft ir#volUp: 'right if Power == True LCD.CLS lcd.str(string("Right ")) GoRight ir#OK : if Power == True LCD.CLS lcd.str(string(LcdLine1, "OKAY")) ' okay = enter GoStop ir#zero : ' numeric keys if Power == True LCD.CLS lcd.str(string("<0>")) other: LCD.CLS waitcnt((clkfreq / 1000) * 30 + cnt) waitcnt((clkfreq / 1000) * 30 + cnt) '==================================== 'PUB str(strAddr) ' repeat strsize(TXT) ' for each character in string ' (byte[strAddr++]) ' write the character PUB GoFwd outa [Led4..Led1] := Mot_Fwd ' outa [MotB2..MotA1] := Mot_Fwd ' '==================================== PUB GoBack outa [Led4..Led1] := MOT_Aft ' outa [MotB2..MotA1] := Mot_Aft ' '==================================== PUB GoRight ' If TurnType == 1 ' turn outa [Led4..Led1] := Mot_Rit ' outa [MotB2..MotA1] := Mot_Rit ' else ' pivot outa [Led4..Led1] := Mot_RoR ' outa [MotB2..MotA1] := Mot_RoR ' '==================================== PUB GoLeft If TurnType == 1 ' turn outa [Led4..Led1] := Mot_Lft ' outa [MotB2..MotA1] := Mot_Lft ' else ' pivot outa [Led4..Led1] := Mot_RoL outa [MotB2..MotA1] := Mot_RoL '==================================== PUB GoStop ' outa [PwmL..PwmL] := %00 ' hit brakes WaitMS(500) ' outa [LED8..LED1] := Mot_Off ' outa [MotB2..MotA1] := Mot_Off ' outa [PwmL..PwmL] := %11 ' release brakes '==================================== PUB Brakes(y) outa [PwmL..PwmL] := %00 ' hit brakes WaitMS(y) ' for this long outa [LED8..LED1] := Mot_Off ' outa [MotB2..MotA1] := Mot_Off ' outa [PwmL..PwmL] := %11 ' re-enable motors '==================================== PUB WaitMS(W) ' wait for W milliseconds W := W * MSec WaitCNT (W+cnt) '==================================== PUB LCD_Timer Repeat ' loop forever waitcnt(clkfreq + cnt) ' wait one second if byte[@LCD_Time] => 1 ' keep counting byte[@LCD_Time] -- ' down '==================================== PUB ProgramVersion lcd.str(string(lcdline1, "QBot")) lcd.str(string(lcdline2, "06/02/2022")) return '====================================
Thank you for the Flexprop link, Jon.
had troubles formatting code!
I've attached a dual PWM driver that I've used in pan/tilt controllers for video cameras. You might need to make changes for your driver chip, but the code will show you how to run fixed-frequency. variable duty-cycle PWM in a secondary Spin cog. This code uses the cog counter to generate pulse outputs. It works well.
You can do this with a serial input. I've attached a big project (that has other goodies you may find useful); this was a mini "Stranger Things" wall that could accept commands and spell out other words using a string of WS2811 Christmas bulbs. Note that his project was coded for a P1-powered HC-8 controller from EFX-TEK (I designed it for them). I used an HC-06 Bluetooth (BT4) module.
There are no such things as string variables in Spin. As in C, a string is an array of characters terminated with 0 (null). You can build a string inline with the string() function, but if that string will be used more than once or needs to be referenced in code, it's best to define it in a DAT section. The Stranger Things example. For methods that require strings, you'll pass a pointer to that string. @ provides the run-time address of an object; we use this with specified strings.
I've already been studying the dual_motor_ez code.
Nice piece. Works well.
But I had some problems with it too.
Not with your code.
With my little toy motors.
I'm using the Tamiya double motor gearbox.
They use a pair of cheapie permanent magnet motors.
I tried characterizing a couple of them and found out that they don't run at the same speeds.
And the difference is different at different speeds.
Which makes going straight a problem!
The robot only needs four speeds to run well.
Stop, ahead 1/4, 1/2, 3/4, and flank (in naval terms)
That translates pretty close to 1,2,3, and 4 inches per second.
Table tops are not unlimited space!
And I want to use masking tape to draw off mazes.
Anyway,
I characterized each motors to see what it takes in the way of PWM ticks to achieve a given RPM.
Put those numbers in a dual array (left and right) and see if it can go straight.
Turn Rates, I think, would be controlled by a different paradigm - mule train.
Gee little bit, Haw little bit = increase outside motor speed or decrease inside motor speed.
For line following or edge following.
I know you guys have done a ton of work on small robot projects.
I should probably sit down and read through it.
Just opened Pandora's box of Stranger Things.
It may take a while!
I gotta get better at shifting...
That scan_ttl_ins for reading a 74165...
Speed shifting a 16 speed gearbox!
cool
What did you use to **send **Bluetooth.
Not on the wall end, but the user end?
Computer?
Phone?
Another Propeller?
A phone would be ideal, but that would mean writing a phone app too?
Gee: Right
Gee-yup(or hup): Hard right.
Haw: Left.
Haw-yup (or hup): Hard left.
Geep (jipp): Onward/faster.
Whoe: Slow/stop/stand
Click-click: attention/listen/watch
Hald: stand still, head down (if human near).
On: pull fwd until there is no slack and stop.
Givit: pick foot up (pick feet)
Nawp: no/quit whatever you are doing/wrong/listen to me
Hawz (or Hawz-up): Rest after backing up a step to unload team from pulling the plow/wagon pull (unloaded rest position)
Whoops: Spilled my beer (ok, I made that one up)
We draft horse owners call these the “12 Commandments” of draft horse management.
Just sayin’
That code is unrolled so that it can run as fast as possible (no loop overhead). It's running in a background cog that has a 1ms loop which is convenient for timing. I also do red-green color mixing for a 2-lead R/G LED which gives me off, red, green, and yellow, with solid and blinking states. Turns out you can do quite a lot of work in 1ms in an 80MHz P1.
No, you can use a simple terminal app since the input to the program is just text (which makes it easy to test through the PST window, too). This code lets me select how the program is going to take input. Once the command parser is tested and running through PST, I switch to BT. Easy-peasy.
For the trade show where that display was setup I wrote a little Android app for EFX-TEK using MIT App Inventor. It's kind of clunky (it's a "blocks" environment), but it's a tool that works and is free. My understanding is that it now supports iPhones so I may look at it again having moved from Android to iPhone late last year.
That navigation module is going to be a LOT more interesting now.

@cavelamb
The day that I first used the Prop (late 2011), I also had a Bluetooth module and I immediately wanted to communicate with my Android phone and so I tried this:
https://rfobasic.miraheze.org/wiki/Main_Page
One of the sample programs is for Bluetooth. I had the phone talking to the Prop in minutes
I selected the Android platform as the HMI for my machine control systems. I now have them all over the place in some pretty grimy environments. This has proven to be a great way to go
Some examples:
On-board schematics:
Craig
Hey Mick,
Yeah. I've played with RFO BASIC a bit.

Wrote a Retro Dialer.
I'll have to go back and look a that again.
Craig
Okay, next dumb question please?
In the following I have commented where the difficulty lives.
Using IR remote control - the program needs to check for user input to turn the Power variable
on or off.
If Power is off, do not parse the remaining cases.
So, how to structure that - without the ugly mess in the listing -
i.e. having to test for Power on in each case?
It seems interrupting the Case structure is not allowed?
I fixed that problem right after posting the above...
Gilda Radner voice - "Nevermind"...
Just curious... Why are you restarting the ir code in a loop?
repeat ... ' other code If IRC_ret <> ir#NoNewCode ' we have a key code IRcode := IRC_ret ir.Start(IRCpin, @IRC_ret) ' set up for next code ' <== seems dubious
Shoudn't you just use something like:
ir.getSonyCode(IRCpin, @IRC_ret)
to get the most recent IR code? Seems odd to create a new cog in each loop of your main code. Am I missing something?
dgately
Yep.
You are missing the Sony IR driver that Tom Doyle wrote.
It's written to be restarted that way.
There is a "continuous" version somewhere.
But I haven't used it.
{{ IR_Remote_NewCog.spin Tom Doyle 2 March 2007 The IR_Remote.spin object receives and decodes keycodes from a Sony TV remote control. The procedure to read and decode is run in a separate cog. This eliminates the big problem of the mainline program hanging while waiting for a key to be pressed. When a keycode is received it is written into main memory (IRcode) by the cog. See IR_Remote.spin for more information. }} CON _CLKMODE = XTAL1 + PLL16X ' 80 Mhz clock _XINFREQ = 5_000_000 IRdetPin = 5 ' IR Receiver - Propeller Pin OBJ ir : "IR_Remote" pst : "Parallax Serial Terminal" num : "simple_numbers" VAR byte IRcode ' keycode from IR Receiver PUB Init | freq, index, cog, lcode if pst.start(9600) pst.str(string("IR Remote")) cog := ir.Start(IRdetPin, @IRcode) ' Propeller pin connected to the IR receiver, address of variable if cog > 0 repeat If IRcode <> ir#NoNewCode lcode := IRcode ir.Start(IRdetPin, @IRcode) ' set up for next code pst.str(string("Binary: ")) pst.str(num.bin(lcode, 7)) pst.str(string(" ")) pst.str(string("Decimal: ")) pst.str(num.dec(lcode)) pst.str(string(" ",13)) waitcnt((clkfreq / 1000) * 30 + cnt)
{{ IR_Remote_NewCog.spin Tom Doyle 2 March 2007 Panasonic IR Receiver - Parallax #350-00014 Receive and display codes sent from a Sony TV remote control. See "Infrared Decoding and Detection appnote" and "IR Remote for the Boe-Bot Book v1.1" on Parallax website for additional info on TV remotes. The procedure uses counter A to measure the pulse width of the signals received by the Panasonic IR Receiver. The procedure waits for a start pulse and then decodes the next 12 bits. The entire 12 bit result is returned. The lower 7 bits contain the actual key code. The upper 5 bits contain the device information (TV, VCR etc.) and are masked off for the display. Most TV Remotes send the code over and over again as long as the key is pressed. This allows auto repeat for TV operations like increasing volume. The volume continues to increase as long as you hold the 'volume up' key down. Even if the key is pressed for a very short time there is often more than one code sent. The best way to eliminate the auto key repeat is to look for an idle gap in the IR receiver output. There is a period of idle time (20-30 ms) between packets. The getSonyCode procedure will wait for an idle period controlled by the gapMin constant. This value can be adjusted to eliminate auto repeat while maintaining a fast response to a new keypress. If auto repeat is desired the indicated section of code at the start of the getSonyCode procedure can be commented out. The procedure sets a tolerance for the width of the start bit and the logic level 1 bit to allow for variation in the pulse widths sent out by different remotes. It is assumed that a bit is 0 if it is not a 1. The procedure to read the keycode ( getSonyCode ) is run in a separate cog. This allows the main program loop to continue without waiting for a key to be pressed. The getSonyCode procedure writes the NoNewCode value (255) into the keycode variable in main memory to indicate that no new keycode is available. When a keycode is received it writes the keycode into the main memory variable and terminates. With only 8 cogs available it seems to be a good idea to free up cogs rather than let them run forever. The main program can fire off the procedure if and when it is interested in a new keycode. }} CON _CLKMODE = XTAL1 + PLL16X ' 80 Mhz clock _XINFREQ = 5_000_000 NoNewCode = 255 ' indicates no new keycode received gapMin = 2000 ' minimum idle gap - adjust to eliminate auto repeat startBitMin = 2000 ' minimum length of start bit in us (2400 us reference) startBitMax = 2800 ' maximum length of start bit in us (2400 us reference) oneBitMin = 1000 ' minimum length of 1 (1200 us reference) oneBitMax = 1400 ' maximum length of 1 (1200 us reference) VAR byte cog long Stack[20] PUB Start(Pin, addrMainCode) : result {{ Pin - propeller pin connected to IR receiver addrMainCode - address of keycode variable in main memory }} stop byte[addrMainCode] := NoNewCode cog := cognew(getSonycode(Pin, addrMainCode), @Stack) + 1 result := cog PUB Stop {{ stop cog if in use }} if cog cogstop(cog~ -1) PUB getSonyCode(pin, addrMainCode) | irCode, index, pulseWidth, lockID {{ Decode the Sony TV Remote key code from pulses received by the IR receiver }} ' wait for idle period (ir receiver output = 1 for gapMin) ' comment out "auto repeat" code if auto key repeat is desired ' start of "auto repeat" code section dira[pin]~ index := 0 repeat if ina[Pin] == 1 index++ else index := 0 while index < gapMin ' end of "auto repeat" code section frqa := 1 ctra := 0 dira[pin]~ ' wait for a start pulse ( width > startBitMin and < startBitMax ) repeat ctra := (%10101 << 26 ) | (PIN) ' accumulate while A = 0 waitpne(0 << pin, |< Pin, 0) phsa:=0 ' zero width waitpeq(0 << pin, |< Pin, 0) ' start counting waitpne(0 << pin, |< Pin, 0) ' stop counting pulseWidth := phsa / (clkfreq / 1_000_000) + 1 while ((pulseWidth < startBitMin) OR (pulseWidth > startBitMax)) ' read in next 12 bits index := 0 irCode := 0 repeat ctra := (%10101 << 26 ) | (PIN) ' accumulate while A = 0 waitpne(0 << pin, |< Pin, 0) phsa:=0 ' zero width waitpeq(0 << pin, |< Pin, 0) ' start counting waitpne(0 << pin, |< Pin, 0) ' stop counting pulseWidth := phsa / (clkfreq / 1_000_000) + 1 if (pulseWidth > oneBitMin) AND (pulseWidth < oneBitMax) irCode := irCode + (1 << index) index++ while index < 11 irCode := irCode & $7f ' mask off upper 5 bits byte[addrMainCode] := irCode