Running MCP3208.spin continously in a cog
Erlend
Posts: 612
I want to structure my program such that in Main there are a number of global variables that hold I/O input values. Most of them are acquired by objects running in separate cogs, eg quad encoder, RTC, IR sensor, and ADC MCP3208. Therefore, I also want to have the MCP3208 continously running in a cog, do specific scaling of each input, and - a guess using LONGMOVE - the values in engineering units to the global values in Main. It could also - as I progress - be coded to do all sorts of things like monitoring levels, raise flags, sound warnings, etc.
e.g. PUB Main could do something like this:
IF gTemperature > 32
LCD("Human detected")
I am not good at assembly, and I need advice how to do this. Maybe it can even be done in the spin part of the objects code.
I guess there is no point in posting the obex code since it is part of the standard library.
Erlend
e.g. PUB Main could do something like this:
IF gTemperature > 32
LCD("Human detected")
I am not good at assembly, and I need advice how to do this. Maybe it can even be done in the spin part of the objects code.
I guess there is no point in posting the obex code since it is part of the standard library.
Erlend

Comments
the MCP3208.spin starts one cog with a PASM-driver to read-out the ADC whenever you call method "In" or method "Average"
To update variables continuosly you just start another cog with a method that has an infinite loop that is doing
PUB DeliverADCValues repeat MyGlobalVar1 := ADC.In(1) MyGlobalVar2 := ADC.Average(2,50) etcVariables can be updated this way across cogs. variables of the VAR-section are accessible from all methods in the same *.SPIN-file
where the varables are defined. It doesn't matter which cog is executing the method.
here is a demo how this works
CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 VAR long CogStack1[ 20] long CogStack2[ 20] long MyTestVar OBJ debug : "FullDuplexSerial" PUB Main 'the FIRST PUB-Method inside a spinfile is ALWAYS the startpoint where the program starts to run debug.start(31, 30, 0, 115200) MyTestVar := 100 debug.str(string("Start MyTestVar=")) debug.dec(MyTestVar) debug.Tx(13) 'all methods and all cognew-commands are in the SAME file as the variables 'in this case you can access variables simply by the variable-name cognew(M1,@CogStack1) cognew(M2,@CogStack2) repeat waitcnt(clkfreq + cnt) debug.str(string("MyTestVar=")) debug.dec(MyTestVar) debug.Tx(13) PUB M1 repeat waitcnt(ClkFreq * 3 + cnt) MyTestVar := 1 PUB M2 repeat waitcnt(ClkFreq * 5 + cnt) MyTestVar := 2best regards
Stefan
ps: please don't duplicate post.
So do I understand it correctly that your code start two new cogs to run M1 and M2, and since they are defined in the top level spin code, the adresses of the variables are passed along to the new cogs? -makes sense to me. And, in your first example the deliverADCvalues is defined in and object code to be a repeat-forever reading of ADC channels, so that in the main code I can do
OBJ
adc: "deliverADCvalues"
PUB M1
adc.start(gValue1, gValue2,gValue3,..gValue8)
- is this correct?
Thanks, I will read your code and see if it makes me wiser. Reading and writing to the HUB is new to me though, and I am not sure I want to go there yet. I still believe that somehow using LONGMOVE from within an object running in an other cog is what I need. I am thinking something like adc_read_forever.start(@gValue1, gValue2...) early in my Main code.
But how to do that?
pri mcp3208(p_analog) | ch, mux, level ' launch with cognew() outa[ADC_CS] := 1 ' output high dira[ADC_CS] := 1 outa[ADC_CLK] := 0 ' output low dira[ADC_CLK] := 1 repeat ' run forever repeat ch from 0 to 7 ' loop through channels outa[ADC_CS] := 0 ' activate adc ' output mux bits, MSBFIRST dira[ADC_DIO] := 1 ' dio is output mux := (%11000 + ch) << (32-5) ' single-ended mode repeat 5 ' send mux bits outa[ADC_DIO] := (mux <-= 1) & 1 ' output a bit outa[ADC_CLK] := 1 ' clock the bit outa[ADC_CLK] := 0 ' input data bits, MSBPOST dira[ADC_DIO] := 0 ' dio is input level := 0 ' clear work var repeat 13 ' null + 12 data bits outa[ADC_CLK] := 1 ' clock a bit outa[ADC_CLK] := 0 level := (level << 1) | ina[ADC_DIO] ' input data bit outa[ADC_CS] := 1 ' de-activate adc long[p_analog][ch] := level & $FFF ' update results arrayThe parameter required is a pointer (the hub address of) the array (or group of eight longs) to store the readings. This method can be launched like this:
You also need to define an array for the stack. As this method doesn't call anything outside itself and only has one parameter and three locals, 16 longs should be enough.
* Follow-up: I did a little timing test and on an 80MHz system that loop that reads all eight channels and reports them to the hub takes about 5.6ms.
It is from my forthcoming book for beginners.
You can run this program as a stand alone.
The read routine can be called as a method from outside this Object
Much more will be in my book for beginners.
{{ 022 PASM Read4Pots.spin Program to read 4 pots Installs in separate cog. Reads resistances to 12 bits appears in P_Val global variables 15 Dec 2011 By Harprit Sandhu }} CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 bits=12 pots=4 VAR long P_Val[4] 'there will be 4 variables starting at P_Val[0] long pot_sub 'subscript variable for identifying potentiometers OBJ fds : "FullDuplexSerial" PUB Main|clear 'clear is a local variable here fds.start(31,30,0,115200) 'start console at 115200 for debug output Read4Pots (@P_Val) 'start new cog at "generate", read var to P_Val waitcnt(clkfreq/60 +cnt) dira[0..1]~~ 'set pin directions to outputs fds.tx(16) 'clear screen repeat 'endless loop pot_sub:=0 'reset id of pot to be printed fds.tx($1) 'home to 0,0 repeat pots 'loop to display data fds.bin(P_val[pot_sub], bits) 'print to the PST in binary fds.str(string(" ")) 'spaces to erase old data overflow fds.dec(P_val[pot_sub]) 'print value as decimal value fds.str(string(" ")) 'spaces to erase old data overflow fds.dec(pot_sub) 'print value as decimal value fds.str(string(" ")) 'spaces to erase old data overflow fds.tx($d) 'new line pot_sub:=(pot_sub)+1 'increment display counter waitcnt(clkfreq/60+cnt) 'flicker free wait clear:=clear+1 'increment counter. This routine clears up screen if clear>10 'decision point by erasing extra lines of bot fds.tx(16) 'clear screen of PST display every 10 loops clear:=0 'reset counter '----------------------------------end of first Cog----------------------------------- PUB Read4Pots(value) cognew(@Read_Pots, value) '----------------------------------end of second Cog----------------------------------- DAT org 0 'sets the starting point in Cog Read_Pots 'first set up all the counters and addresses needed mov pots_read, #4 'number of pots to read [also change data] mov pot_id, #0 'first pot read is pot 0 mov bits_read, #12 'bit resolution of 3208 IC, can be less mov mem, par 'get address of mem for PAR mov dira, set_dira 'sets direction of the prop pins Next_pot or outa , chs_Bit 'makes Chip select high andn outa, chs_Bit 'makes chip select low andn outa , clk_bit 'ANDN it with the Clock Bit to make low or outa , din_Bit 'makes the Din high call #Tog_clk 'toggle clock line hi-low to read data or outa , din_Bit 'makes the Din high call #Tog_Clk 'toggle clock line hi-low to read data mov temp2, pot_id 'we will read three bits from this pot call #get_third_bit 'get bit if_nz jmp #its_one 'jump to set it as needed jmp #its_zero 'jump to set it as needed its_one call #Set_next_bit1 'setting bit subroutine call jmp #continue1 'go to next bit its_zero call #Set_next_bit0 'setting bit subroutine call continue1 call #get_second_bit 'get bit if_nz jmp #its_one1 'jump to set it as needed jmp #its_zero1 'jump to set it as needed its_one1 call #Set_next_bit1 'setting bit subroutine call jmp #continue2 'go to next bit its_zero1 call #Set_next_bit0 'setting bit subroutine call continue2 call #get_first_bit 'get bit if_nz jmp #its_one2 'jump to set it as needed jmp #its_zero2 'jump to set it as needed its_one2 call #Set_next_bit1 'setting bit subroutine call jmp #continue3 'go to next bit its_zero2 call #Set_next_bit0 'setting bit subroutine call continue3 andn outa , din_Bit 'makes Din low call #Tog_Clk 'toggle clock line hi-low to read data call #Tog_Clk 'toggle clock line hi-low to read data mov Data_read, #0 'clear register we will read data into mov bit_count, bits_read 'counter for number of bits we will read read_bit mov temp, ina 'read in what is in all the input lines andn temp, mask26 wz 'mask off except Dout line. Set Z flag shl Data_read, #1 'shift register left 1 bit for next bit if_nz add Data_read, #1 'if value + add 1 to data register call #Tog_Clk 'toggle clock get next bit ready in Dout sub bit_count, #1 wz 'decr the "bits read" counter. Set Z flag if_nz jmp #read_bit 'go up do it again if counter not yet 0 wrlong Data_read, mem 'write it in PAR to share it as P.Val add mem, #4 'add 4 to hub address for next long add pot_id, #1 'so we can look at next pot mov temp2, pot_id 'recall what pot we are reading sub temp2, pots_Read wz 'check if it is how many we want to read if_nz jmp #Next_pot 'if it is not 0 go up and read next pot jmp #Read_pots 'go back beginning and do all pots again 'subroutines used Set_next_bit0 andn outa , din_Bit 'makes Din low in 000 for line call #Tog_Clk 'toggle clock line hi-low to read data Set_next_bit0_ret ret 'return from this subroutine Set_next_bit1 or outa , din_Bit 'makes Din high in 000 for line call #Tog_Clk 'toggle clock line hi-low to read data Set_next_bit1_ret ret 'return from this subroutine Tog_Clk nop 'nop to settle signals or outa, clk_bit 'make clock bit high nop 'nop to settle signals andn outa, clk_bit 'make clock bit low nop 'nop to settle signals Tog_Clk_ret ret 'return from this subroutine Get_first_bit mov temp2, pot_id 'get current pot number andn temp2, mask0 wz 'get last bit Get_first_bit_ret ret 'return Get_second_bit mov temp2, pot_id 'get current pot number shr temp2, #1 'shift right 1 bit to get second bit andn temp2, mask0 wz 'return Get_second_bit_ret ret Get_third_bit mov temp2, pot_id 'get current pot number shr temp2, #2 'shift right 2 bits to get third bit andn temp2, mask0 wz 'return Get_third_bit_ret ret Set_dira long 001011_00000000_00000000_00001111 'Set dira register Chs_Bit long 000001_00000000_00000000_00000000 'Chip select bit 24 Din_Bit long 000010_00000000_00000000_00000000 'Data in bit 25 Dout_Bit long 000100_00000000_00000000_00000000 'Data out bit 26 Clk_Bit long 001000_00000000_00000000_00000000 'Clock bit 27 mask26 long 111011_11111111_11111111_11111111 'Mask to read Dout bit 26 mask0 long 111111_11111111_11111111_11111110 'Mask to read bit 0 mem res 1 'Par location temp res 1 'temporary storage variable, misc temp2 res 1 'temporary storage variable, misc bit_count res 1 'temporary storage variable, read bit counter Data_Read res 1 'temporary storage variable, data being read pot_id res 1 'current pot id number pots_read res 1 'total number of pots to be read. bits_Read res 1 'resolution of data read, 12 bits maxHarprit
Think I will manage now. Thanks to all of you!
Btw, all AI's are slow-changing, so speed is not an issue.
I'll save you the trouble (see attached) as I pulled that example from an object I use. Now... I am of the opinion that when you create an object it should be very general-purpose -- don't put specifics in it for one program that will just get in the way of another. Also, an array is simply a block of variables. My friend uses this object in a professional pan/tilt/dolley camera controller. In the variables section of his program we have this:
We instantiate the adc object like this:
As you can see, I'm telling it how many channels to read and pointing to the first variable in a list; I use those names in the program.
What about if I extend your object with something like PRI (1) EU_eTape, (2) EU_lm34, (3)EU_dsspd300, etc. Make it into a (almost) general purpose object with conversion of raw adc into engineering units? To control which channel gets what kind of conversion, the adc.startx() could pass 8 parameters that specifies conversion method for each channel, e.g. adc.startx(ADC_CS, ADC_CLK, ADC_DIO, 8, @firstvalue, 1, 2, 2, 1, 3, 3, -1, -1) - and in this instance the two last channels gets no conversion, with parameter= -1. Obviously these parameters can be packed into one array too.
I appreciate the help, and also all advice on 'coding ethics'.
The compiler organizes RAM by type, starting with longs, then words, then bytes. Within a type (e.g., longs) the variables are put in the order they appear in your listing.
You're free to do anything you like, of course, but if you decide to republish your modifications please take my name off of the listing. There's nothing in it that I feel the need to have credit for, but I don't like my name on listings that I would not have produced myself. If you want the listing to say "based on code by Jon McPhalen" then that is fine.
Another idea you could use for your new object: instead of passing so many parameters you could past the address of a DAT block that has your conversion values (I'm still not clear on what you're doing with that). Let's say, for example, you just want multiply by some value. You could express those values in 0.001 units and do something like this at the end of the adc loop:
After cleaning-up the newly read level, it grabs the adjustment factor from the table (p_multiplier is a pointer to it), then divides by 1000 to get back to whole units. By doing it this way you still have a general-purpose object and the program (you or whomever you share it with) can adjust the behavior by pointing to their own conversion table; building a table into the object is not a good idea in my opinion.
ADCbit resolution
Scaling: positve means multiply, negative means divide
Offset: in scaled units
Linearisation amount log e: negative means inverse
Linerarisation amount log 10: negative means inverse
Averaging period: positive means in n samples, negative means in n mS
Noise removal filter: percent of outlier samples removed from average
FloorL: minimum value to be allowed
FloorH: maximum level to be allowed
Time tagging y/n - seconds since yr 2000 ( will be put in msw)
-have I forgot anything?