PropBASIC update June 2017

in Propeller 1
I know it has been awhile, but I've been quite busy.
But now I am working on updating PropBASIC with a couple problems I've found.
So I am asking if anyone that uses PropBASIC has found any issues or problems that they can let me know about, I will see about getting them fixed.
Also, if there is a feature that you would like to see, let me know now while I have my head in the code.
One of the major changes is that the "*", "**", "*/", "/", and "//" operators now use subroutines instead of always being in-line code. That should save quite a bit of code space.
Anyway, please let me know ASAP so I can post this new version quickly. (Next week or so ???).
Bean
But now I am working on updating PropBASIC with a couple problems I've found.
So I am asking if anyone that uses PropBASIC has found any issues or problems that they can let me know about, I will see about getting them fixed.
Also, if there is a feature that you would like to see, let me know now while I have my head in the code.
One of the major changes is that the "*", "**", "*/", "/", and "//" operators now use subroutines instead of always being in-line code. That should save quite a bit of code space.
Anyway, please let me know ASAP so I can post this new version quickly. (Next week or so ???).
Bean
Comments
Hopefully some of the regular PropBASIC lovers will chime in. I'll also refer back to my code and give this some serious thought in the next 24 hours.
In the meantime, I'm posting a reminder to the PropBASIC thread, so all members subscribed will get notified.
Version 00.01.43 Changed SERIN so it doesn't wait for stop bit Optimize Divide and remainder operation [June 27,2014] Version 00.01.44 Made PAUSE a subroutine Made *, */, **, /, and // operator subroutine Allow FREQ with RCSLOW or RCFAST "ProcessFreq" Feb 3, 2016 Version 00.01.45 Changed A = x - A to generate neg A,A adds A,x Fixed two range errors Fixed __remainder was __temp1, but should be __temp4 now Mar 7, 2016 Fixed: I2C commands in LMM mode require minimum of 150 WAITCNT Jun 28,2017 Fixed: I2CSTART locks up if no pull-ups (should only try 10 times) Jun 29,2017
Bean
Dammit I typed a lot more but it vanished..will have to wait because I have to be somewhere.
I'll make a more ambitious suggestion - add a P2 code generation switch ?
There is this
http://forums.parallax.com/discussion/149659/propbasic-how-to-use-propbasic-in-simpleide
and some command line choices are here
http://forums.parallax.com/discussion/162749/solved-prop-1-loader-for-linux
http://onerobot.org/#ViewPort
Bean
More examples, and better reports are always good.
a) eg you could add the Reciprocal Frequency Counter (& others) to the examples dir, as well as the examples given in the PDF.
The examples I like the best are built-dir copies, so they include all report and list files, and downloadable files.
This allows users to verify against a known-good copy, and even download the example, before even starting the compiler.
That usually means something like this :
\examples\master\Blink\
\examples\user\Blink\
\examples\master\Ping\
\examples\user\Ping\
\examples\master\FreqCtr\
\examples\user\FreqCtr\
\master\ has the original build copies and user starts as a clone of that, but users are expected to modify/rename in these directories.
b) For better reports, I see it runs like this :
PropBasic Version 00.01.44 Jul 13, 2014 Finished Compile. 116 Lines Read, 228 Lines Generated, 0 Warnings, 0 Errors.
Build version is good, as are lines read, and Lines generated is OK, but I'd rather know how much of the CHIP was used.So a report of how much COG is consumed by CODE and VARs and how much HUB is used is more useful.
This info should appear on the screen, and also in the output file as a comment line.
c) Numeric values always help.
Example - DOCs have
I2SPEED Sets the clock speed for I2C operations.
I2CSPEED multipier
* “multiplier” may be a floating point value
A value of 2 would make I2C operations twice as fast as normal.
A value of 0.5 would make I2C operations half as fast as normal
but nowhere can I find the i2c clock speed in kHz. With parts supporting 100k, 400k, 1M, 3.4M info on actual i2c CLK is important.
Likewise, SERIN, SEROUT, SHIFTIN, SHIFTOUT all mention Baud or Speed, but give no limits on what valid range is supported ?
+1
IIRC we are also lacking SPI examples.
Captured Waveforms below, tuned for speed. CLK comes from IO, but it can have a ping-style trigger. As shown, receives 64b, then sends 64b.
Example would be 40 pins of IO expansion, and (spare) 24 bits of additional smarts each way.
5.5296MHz Ref clk can come from slave to sync. (example is 8MHz SCK as that's the variant I have )
I think the Prop code needed for Prop side of this is ~ 17 lines PASM - how much spare space is there in PropBASIC SPI area ?
Addit: I found this great thread
http://forums.parallax.com/discussion/155143/waitpxx-detailed-timing
which indicates Pin-Pin delays of 6~7 SysCLKS + 9.31ns Tco
That means whilst the software can service to a SPI Slave of SysCLK/16, the hardware just does not quite manage a timing budget...
If add WAIT to every clock edge, that does drops the clock peak, but is more tolerant, and does shrink the code & saves a wire.
Seems the 5MHz+ link speed needs to drop to something like < 80M/22 = 3.636MHz & 96M/22 = 4.363MHz MAX values.
That covers common values of 2.7648MHz & 3MHz & 4MHz
The aim is to enable the use of local vars in subs/funcs that can have the same name in other subs/funcs. Particularly handy when using libraries.
example:
RX_WAIT FUNC 1,3 TX_STR SUB 1,2 FUNC RX_WAIT idx VAR LONG val VAR LONG val = idx + 12 .....etc... SUB TX_STR idx VAR LONG val VAR LONG val = idx + 12 .....etc...
The PropBASIC compiler might do a pre-pass search of the code files (cogs, libs, etc..), and for each SUB or FUNC defined, then it might rename the variables in the actual SUB code to have a prefix with the SUB name. Thus solving any conflicts before the compiler does it's usual compiling tasks!
This pre-pass could be as simple as a textual search/replace. (Perhaps! I'm no Delphi expert!)
End result of this example (after this pre-pass) would be:
RX_WAIT FUNC 1,3 TX_STR SUB 1,2 FUNC RX_WAIT FUNC_RX_WAIT_idx VAR LONG FUNC_RX_WAIT_val VAR LONG FUNC_RX_WAIT_val = FUNC_RX_WAIT_idx + 12 .....etc... SUB TX_STR SUB_TX_STR_idx VAR LONG SUB_TX_STR_val VAR LONG SUB_TX_STR_val = SUB_TX_STR_idx + 12 .....etc...
With the local variable names made unique, the compiler should be able to compile the code as the user intended, without any conflicts or duplicate local var name errors.
This sort of naming convention is something I've used for a while to ensure zero conflicts, but it would still be handy (and certainly time saving) to have the compiler handle this automatically. I think the users original source code would be cleaner without having to manually add all the prefixes too, which makes sharing code easier!
One other thought- you'd probably need to process the LOAD-ed code libs in a similar way... Perhaps using the library filename as part of the variable name prefix.
example:
LOAD "delays_lib.pbas"
DELAY_MS SUB 1 DELAY_US SUB 1 SUB DELAY_MS delay VAR LONG delay = __param1 * 1000 PAUSE delay ENDSUB SUB DELAY_US delay VAR LONG delay = __param1 PAUSE delay ENDSUB
becomes:
DELAY_MS SUB 1 DELAY_US SUB 1 SUB DELAY_MS delays_lib_SUB_DELAY_MS_delay VAR LONG delays_lib_SUB_DELAY_MS_delay = __param1 * 1000 PAUSE delays_lib_SUB_DELAY_MS_delay ENDSUB SUB DELAY_US delays_lib_SUB_DELAY_US_delay VAR LONG delays_lib_SUB_DELAY_US_delay = __param1 PAUSE delays_lib_SUB_DELAY_US_delay ENDSUB
Something like that.
I did a quick check, and PropBASIC errors on the same name for SUB and FUNC anyway, (even if someone thought using the same name was a good idea.)
Missing for tightest code loops however, seem to be support for Carry Flag.
I'd suggest allowing
Pin = CY
CY = Pin
Plus Variant of SHR, SHL, that include C, and can manage 32 or 64b, or > 64b
Current SHR syntax is value1 = value2 SHR value3
but that makes it harder to support 64b - so Maybe new set of
ROR,RORC,ROL,ROLC can work with a param list.
RORC value1 'update CY and does 32b, with CY
RORC Value1,Value2 'update CY and does 64b with CY
Then serial code is something like this, which I think maps to 4 lines of PASM ?
SerLoop:
RORC Value1,Value2
TxPin = CY
DJNZ BitCtr,SerLoop
As mentioned, it would be good to add a definitive voice in the documentation regarding questions that have come up in the forum:
I2CSPEED ... what is "normal"? How does "normal" vary with clock settings? I recall seeing this in the forums, but did not take notes.
SHIFTIN(OUT) ... what does "speed" do and does it vary with clock settings? My notes are that it defaults to 50kHz and the practical limit is speed=20 for 1MHz. Exceeding the speed limit causes code to hang until cnt wraps around.
... valid range for "bits". My notes are that up to 32 bits can be written.
Those are examples of things that have tripped me up and taken time to work around, so it may be helpful to another new user.
Cheers!
I'm REALY HAPPY with propbasic- but sometimes an error is flagged- no by basic but by brads spin tool.
For example
do qclk=optclk 'get clock polarity if qclk <> 0 then same=sameval 'not zero reload counter else dec same 'endif loop until same=0
As you can see I commented out the endif and got this error
Propbasic says 0 warnings 0 errors but bst flags it up as an unresolved symbol.
Its no big deal- it figure it out eventually but thought I would mention it.
It would also be nice to know how many bytes are left for each cog used.
Nevertheless its great the way it is.
Dave
+1
.. another idea..
I see there is NOP, which is great..
I suggest making that slightly smarter with
NOP CountConst
So one line can insert 1(default),2,3,etc NOPs, and this should generate WAITCNT(or DJNZ?) when Count is large enough that WAITCNT is smaller than many NOP.
PAUSE operators are time based, this one is Cycles based.
Edit: A couple of minutes mucking around in the serout code generator required 3 code changes as remarked in attached code.
Unit _SERIAL; {$MODE Delphi} Interface // *************************************** Variable byte length mod // Uses GLOBAL; Uses GLOBAL, SysUtils; // *************************************** Procedure ProcessSerOut; Procedure ProcessSerIn; Implementation // *************************************** Variable byte length mod // Procedure ProcessSerOut; { SerOut Pin,Baud,Data } <- Original Code, just a comment Procedure ProcessSerOut; { SerOut Pin,Baud,Data,ByteSize } // Comment change // *************************************** { OT = Open drain, driven high } { ON = Open source, driven low } Var poDelayVar: PVarObj; lBaudDelay: LongInt; lBaudRate: Longint; sBaudRate, sBaudDelay: String[15]; sMode: Char; bOpenMode: Boolean; sNextCharLabel, sZeroCharLabel: String; iValError: Integer; Begin sNextCharLabel:=''; If g_bInternalClock Then Warning(c_iWarningInternalClock, 1); // *************************************** Variable byte length mod // If g_iCmdCnt = 6 Then <-Original Code If g_iCmdCnt = 8 Then // 8 accomodates additional comma and value for byte length param // *************************************** Begin If g_asCmdLine[3] <> ',' Then Error(c_iErrorCommaExpected, 3); If g_asCmdLine[5] <> ',' Then Error(c_iErrorCommaExpected, 5); If g_asCmdLine[7] <> ',' Then Error(c_iErrorCommaExpected, 7); // 4th parm comma check // temp1 = pin mask Case g_apoCmdVars[2]^.eGetType of e_Pin: Begin OutStr(' mov __temp1,'+g_apoCmdVars[2]^.sGetName); End; e_ShortConst: Begin OutStr(' mov __temp1,#1'); OutStr(' shl __temp1,#'+g_apoCmdVars[2]^.sGetName); End; e_LongConst, e_LongVar: Begin OutStr(' mov __temp1,#1'); If g_apoCmdVars[2]^.m_bVarIndex Then Begin If g_bLMM Then Begin OutStr(' mov __INDEX,#'+g_apoCmdVars[2]^.sGetName); OutStr(' add __INDEX,'+g_apoCmdVars[2]^.m_sIndex); OutStr(' jmp #_LMM_MOVS'); End Else Begin OutStr(' add '+g_apoCmdVars[2]^.m_sIndex+',#'+g_apoCmdVars[2]^.sGetName); OutStr(' movs $+2,'+g_apoCmdVars[2]^.m_sIndex); OutStr(' sub '+g_apoCmdVars[2]^.m_sIndex+',#'+g_apoCmdVars[2]^.sGetName); End; OutStr(' shl __temp1,0-0'); End Else Begin OutStr(' shl __temp1,'+g_apoCmdVars[2]^.sGetIndexName); End; End; Else Error(c_iErrorInvalidParameter, 2); End; // Case // temp2 = character If g_apoCmdVars[6]^.eGetType = e_ShortConst Then Begin OutStr(' mov __temp2,#'+g_apoCmdVars[6]^.sGetName); End Else If g_apoCmdVars[6]^.eGetType In [e_LongConst, e_LongVar] Then Begin If g_apoCmdVars[6]^.m_bVarIndex Then Begin If g_bLMM Then Begin OutStr(' mov __INDEX,#'+g_apoCmdVars[6]^.sGetName); OutStr(' add __INDEX,'+g_apoCmdVars[6]^.m_sIndex); OutStr(' jmp #_LMM_MOVS'); End Else Begin OutStr(' add '+g_apoCmdVars[6]^.m_sIndex+',#'+g_apoCmdVars[6]^.sGetName); OutStr(' movs $+2,'+g_apoCmdVars[6]^.m_sIndex); OutStr(' sub '+g_apoCmdVars[6]^.m_sIndex+',#'+g_apoCmdVars[6]^.sGetName); End; OutStr(' mov __temp2,0-0'); End Else OutStr(' mov __temp2,'+g_apoCmdVars[6]^.sGetIndexName); End Else If g_apoCmdVars[6]^.eGetType In [e_HubByte, e_DataLabel, e_ByteData] Then Begin ProcessHubAddrLit(6, '__temp5', 1); sNextCharLabel:=NewLabelStr; OutStr(sNextCharLabel); sZeroCharLabel:=NewLabelStr; OutStr(' rdbyte __temp2,__temp5 WZ'); OutStr(' adds __temp5,#1'); If g_bLMM Then OutStr(' IF_Z add __PC,#(('+sZeroCharLabel+'-$)*4)-4') Else OutStr(' IF_Z jmp #'+sZeroCharLabel); End Else Error(c_iErrorInvalidParameter, 6); OutStr(' or __temp2,#256'); // Stop bit is 1 OutStr(' shl __temp2,#1'); // Make room for start bit sBaudRate:=Upper(g_asCmdLine[4]); sMode:=UpCase(sBaudRate[1]); If sBaudRate <> '' Then Delete(sBaudRate, 1, 1); bOpenMode:=False; If sMode = 'O' Then Begin bOpenMode:=True; sMode:=UpCase(sBaudRate[1]); If sBaudRate <> '' Then Delete(sBaudRate, 1, 1); End; Val(sBaudRate, lBaudRate, iValError); Inc(iValError); // avoid lazarus warning lBaudDelay:=1000; If lBaudRate < 1 Then Error(c_iErrorInvalidParameter, 4) Else Begin lBaudDelay:=g_lFreq Div lBaudRate; If lBaudDelay < 10 Then Error(c_iErrorBaudRateTooHigh, 4); End; sBaudDelay:='_'+sBaudRate+'baud'; poDelayVar:=g_oDevice.pGetVarPtr(sBaudDelay); If poDelayVar = Nil Then poDelayVar:=g_oDevice.pAddLongConst(sBaudDelay, lBaudDelay, IntStr(lBaudDelay)); // ************************************ Variable byte length mod // OutStr(' mov __temp3,#10'); // Send 10 bits <- Original code OutStr(' mov __temp3,#'+(IntToStr(StrToInt(g_apoCmdVars[8]^.sGetName)+2))); // <- New code for variable byte length // ************************************ If sMode In ['T', 'N'] Then Begin // Normal serial output // Start Bit is a "0" bit. High for True, Low for iNverted OutStr(' mov __temp4,'+sBaudDelay); OutStr(' add __temp4,cnt'); If bOpenMode Then Begin // Setup dira and outa OutStr(' andn dira,__temp1'); // Make pin an input If sMode = 'N' Then OutStr(' or outa,__temp1') // Setup to drive high "ON" Else OutStr(' andn outa,__temp1'); // Setup to drive low "OT" End Else OutStr(' or dira,__temp1'); // Make pin output OutStr(NewLabelStr); OutStr(' shr __temp2,#1 WC'); If bOpenMode = False Then Begin If sMode = 'T' Then OutStr(' muxc outa,__temp1') Else OutStr(' muxnc outa,__temp1'); End Else Begin If sMode = 'N' Then OutStr(' muxc dira,__temp1') Else OutStr(' muxnc dira,__temp1'); End; OutStr(' waitcnt __temp4,'+poDelayVar^.sGetName); If g_bLMM Then Begin OutStr(' djnz __temp3,#_LMM_JUMP'); OutStr(' long @@@'+LabelStr); End Else OutStr(' djnz __temp3,#'+LabelStr); If sNextCharLabel <> '' Then Begin If g_bLMM Then Begin OutStr(' sub __PC,#(($-'+sNextCharLabel+')*4)+4'); // OutStr(' jmp #_LMM_JUMP'); // OutStr(' long '+sNextCharLabel+' * 4'); End Else OutStr(' jmp #'+sNextCharLabel); OutStr(sZeroCharLabel); End; g_bHandled:=True; End Else Error(c_iErrorInvalidParameter, 4); End Else Error(c_iErrorInvalidNumberOfParameters, 0); End;
Edit #2: I just noticed that the above code leaves the line idling low. Broke something else I guess! Disregard, looks OK on scope.
Some systems use 9 bits, and/or 2 stop bits, and we have done designs where > 2 stop bits, but exact timing were needed. (IIRC 17 or 19b frames)
With crystals (eg a Prop at each end) allowing for 32b serial makes very good sense.
(this will also be compatible with P2 UARTs