Variable pin assignment using PWMDUTY & 20M2

agroom

New Member
I'm trying to write a program that takes inputs from 4 potentiometers, converts it to a PWM duty cycle and outputs it to one of 4 motors. The pot-motor is a static assignment, so say Pot1 directly controls Motor1. I'm early in the software design phase and trying to optimize my code since I know I'll probably be using most of the program storage.

I want to write a single read pot/assign pwm duty routine that can can work for any of the pot/motor pairs. I have it essentially done, but am getting tripped up on the PWMDUTY command (pwmduty oMotor, iSpeed). In the compile error window, the little carrot is under the apostrophe between the pin and duty cycle.

I know I can just write 4 different IF/THEN statements, which I was previously doing, but I'd like to shorten/optimize this.

Code:
'---[I/O PIN DEFINITIONS]---
SYMBOL Motor1	= c.3	 'PWM1 output pin
SYMBOL Motor2	= b.1  'PWM2 output pin
SYMBOL Motor3	= c.2  'PWM3 output pin
SYMBOL Motor4 	= c.5  'PWM4 output pin
SYMBOL Pot1	= b.0  'Pot1 input pin
SYMBOL Pot2	= b.3  'Pot2 input pin
SYMBOL Pot3	= b.2  'Pot3 input pin
SYMBOL Pot4	= b.0  'Pot4 input pin
SYMBOL Pot5	= c.1  'Pot5 input pin

'---[VARIABLE DEFINITIONS]---
SYMBOL cSpeed1	        = b2	'Motor1 current speed
SYMBOL cSpeed2	        = b3	'Motor2 current speed
SYMBOL cSpeed3	        = b4	'Motor3 current speed
SYMBOL cSpeed4	        = b5	'Motor4 current speed
SYMBOL iPot		= b6	'Temp input pot pin
SYMBOL oMotor	        = b7 	'Temp output motor pin
SYMBOL iSpeed	        = w4	'ADC input

'---[CONSTANT DEFINITIONS]---
SYMBOL period 	= 199 'PWM period, calculated for 20kHz
SYMBOL dutyMult	= 50	 'PWM duty cycle multiplier for speed control
SYMBOL dutyMin	= 300	 'PWM minimum duty cycle

'Initialize frequency, setup I/O pins and PWM output.
setup:

   setfreq M16
   dirsb = %00000010
   dirsc = %00101101
   
   pwmout pwmDiv4, Motor1, period, 0
   pwmout pwmDiv4, Motor2, period, 0
   pwmout pwmDiv4, Motor3, period, 0
   pwmout pwmDiv4, Motor4, period, 0
   
main:

   iPot = Pot1
   oMotor = Motor1
   gosub readInput
   
   iPot = Pot2
   oMotor = Motor2
   gosub readInput
   
   iPot = Pot3
   oMotor = Motor3
   gosub readInput
   
   iPot = Pot4
   oMotor = Motor4
   gosub readInput

   goto main

readInput:

   readadc iPot, iSpeed
   gosub calcSpeed
   if iSpeed <> cSpeed1 then
      cSpeed1 = iSpeed
      if iSpeed <> 0 then
         iSpeed = iSpeed * dutyMult + dutyMin
      endif
      pwmduty oMotor, iSpeed
   endif
   return

calcSpeed:

   iSpeed = iSpeed + 25
   iSpeed = iSpeed * 1 / 26
   return
 

geoff07

Senior Member
Carrot? Carat! (^)

The manual does say that 'pin' is a constant, not a constant or variable. Why the interpreter sees them as different isn't clear but this may be the issue. Which is a pity because your approach is a good one and allowed in many languages. You might code your main loop more like this:
Code:
do
   for channel = 1 to 4
      select channel
          case 1:oMotor=Motor1: readadc Pot1, iSpeed :gosub calcspeeds:pwmduty Motor1, iSpeed
          case 2 etc.
      endselect    
    next
loop
This puts readInput inline and saves a call/return. Calcspeeds is meant to include the pwmduty calculation in the same call.
This avoids the problem that you are having and saves some of the assignments. I don't know what the overall effect on length is.
It must be some program to use all the storage in today's chips.
 

agroom

New Member
Carrot? Carat! (^)
haha, yes sorry...rush job posting this :)

The manual does say that 'pin' is a constant, not a constant or variable. Why the interpreter sees them as different isn't clear but this may be the issue. Which is a pity because your approach is a good one and allowed in many languages.
I know :( And the odd part is that the PotX variables don't give me an error, so I'm wondering if it doesn't have to do specifically with the PWMDUTY command.

You might code your main loop more like this. This puts readInput inline and saves a call/return. Calcspeeds is meant to include the pwmduty calculation in the same call. This avoids the problem that you are having and saves some of the assignments.
I like what you've done here! The one thing you omitted is that I check to see if the input speed is the same as the current speed. For the majority of the time, the motor speeds won't be changing, so doing all the calculations every time only to output the same speed again is kind of wasteful. But I can add that into the select statement:

Code:
   for channel = 1 to 4
      select channel
         case 1:readadc Pot1,iSpeed:IF iSpeed <> cSpeed1 THEN gosub calcspeed:pwmduty Motor1,iSpeed
         case 2:readadc Pot1,iSpeed:IF iSpeed <> cSpeed2 THEN gosub calcspeed:pwmduty Motor1,iSpeed
         case 3:readadc Pot1,iSpeed:IF iSpeed <> cSpeed3 THEN gosub calcspeed:pwmduty Motor1,iSpeed
         case 4:readadc Pot4,iSpeed:IF iSpeed <> cSpeed4 THEN gosub calcspeed:pwmduty Motor4,iSpeed
      endselect  
   next
For this you wouldn't need the oMotor = MotorX either since it's not being passed to a sub and PWM is called in the same line. I'm also surprised with myself why I didn't put the pwmduty calculation in the same call as calcSpeed. Too easy to overlook these things :)

It must be some program to use all the storage in today's chips.
Well...maybe I'm being a bit presumptuous, but the code I posted was just the part that was directly related to the motor control. I don't have that much more written yet, but before I tried this change with what I have so far used 462/2048 bytes, or about 23%. After, it only took up 232/2048 or 11%, and my best guess is I have about 1/3 to 1/4 of it done. The full code for the readInput sub is longer than what I posted. Even using the select statement and adding in the rest of the code, I'm back up to 421 bytes.

Per a small blurb in the Programming Techniques on the website, it also says serout uses up a lot of memory and much of my program is displaying outputs and menus to an LCD screen via serout. So I'm just trying to be as clean as I can along the way.

I don't know what the overall effect on length is.
Well, it's not the 232 I was down to, but it looks A LOT cleaner! I know there must be a way to do this, and the issue must involve the PWMDUTY command, but I think it's enough for me to just move on anyway :)

Thanks for the help!
 

westaust55

Moderator
For each of the case statements, I believe that you will need an ENDIF command to signify the termiantion of the IF...THEN structure.

for example:

case 1:readadc Pot1,iSpeed : IF iSpeed <> cSpeed1 THEN : gosub calcspeed : pwmduty Motor1,iSpeed : ENDIF

for cases 2 and 3 don't forget to correct the POT and Motor numbers from 1 to 2 and 3 respectively
case 2:readadc Pot1,iSpeed:IF iSpeed <> cSpeed2 THEN gosub calcspeed:pwmduty Motor1,iSpeed
 

geoff07

Senior Member
You could put the IF check inside the subroutine. Slightly longer execution if the check fails, but less code.

Is this app something where a state-machine approach would work? With state machines, once the basic structure is in place, the code expands only very slowly as you add functionality. I have a state-machine structure that I use on most Picaxe projects that you could use. Chesck out my blog entry if you are interested.

If you have lots of text you could put some in eeprom, or preprogram into the display eeprom, it is I agree a space hound.
 

agroom

New Member
Is this app something where a state-machine approach would work? With state machines, once the basic structure is in place, the code expands only very slowly as you add functionality. I have a state-machine structure that I use on most Picaxe projects that you could use. Chesck out my blog entry if you are interested.
Never heard of state-machine before, but reading your blog post about it, this is how I 'typically' try to code. Just determine the few different operating modes, create subroutines for them, then just add in a little bit of code to manage when to enact those states. I've got an undergrad in CIS, and although I never learned BASIC and we never did embedded systems, this was pretty common practice, both in physical device state control or windows application development.

My main focus now is getting usable code to test and troubleshoot my hardware. But not being to address the PWMDUTY directly is going to throw a kink into things.

If you have lots of text you could put some in eeprom, or preprogram into the display eeprom, it is I agree a space hound.
EEPROM is mostly spoken for, though how much I'm not sure. Primarily I'm going to use it for saving motor states. I think the 20M2 has 256 bytes of EEPROM. With 4 motors and speeds 0-10 each, I assume I'll need 2 bytes to store all 4 motor states since 4 bits can hold 1 speed. Prob a more efficient way tho too. I don't think it needs 128 saved states, but I'd like to do ~50. I plan on storing all the static menus/screens in the display EEPROM, , but I still need to make all the serout commands, which I guess take up a lot of memory. I did a quick check, and a single serout command takes between 5-8 bytes, and that's w/o sending text, just commands to the LCD screen like what position to start at. However, serout w/ 16 characters takes 30! So I'll be able to shave off about 80 bytes just programming that into the LCD's memory.
 

hippy

Ex-Staff (retired)
My main focus now is getting usable code to test and troubleshoot my hardware. But not being to address the PWMDUTY directly is going to throw a kink into things.
I am not sure where you have got to now, or if things have changed, but your original code concept could be used as is by simply changing your 'readInput:' routine line ...

pwmduty oMotor, iSpeed

To ...

select case oMotor
case Motor1 : pwmduty Motor1, iSpeed
case Motor2 : pwmduty Motor2, iSpeed
case Motor3 : pwmduty Motor3, iSpeed
case Motor4 : pwmduty Motor4, iSpeed
end select
 

agroom

New Member
I LOVE I! From an outside perspective it looks kinda odd though :) This brought me down to 425 bytes, but I was able to add another piece of functionality too and still reduce the program size. It also lets me create a sub dedicated to updating the motor speeds w/o having to check the potentiometers first too.

Code:
'---[I/O PIN DEFINITIONS]---
SYMBOL Motor1	= b.1	'Motor1 PWM output pin
SYMBOL Motor2	= c.2 'Motor2 PWM output pin
SYMBOL Motor3	= c.5 'Motor3 PWM output pin
SYMBOL Motor4 	= c.3 'Motor4 PWM output pin

SYMBOL Pot1	= b.0 'Motor1 pot ADC input pin
SYMBOL Pot2	= b.3 'Motor2 pot ADC input pin
SYMBOL Pot3	= b.6 'Motor3 pot ADC input pin
SYMBOL Pot4	= c.7 'Motor4 pot ADC input pin
SYMBOL Sel		= c.1 'Selector pot ADC input pin
SYMBOL LCD	= c.0 'LCD serial out
SYMBOL Laser	= c.4 'Laser on output pin


'---[VARIABLE DEFINITIONS]---

SYMBOL cSpeed1	= b4	'Motor1 current speed
SYMBOL cSpeed2	= b5	'Motor2 current speed
SYMBOL cSpeed3	= b6	'Motor3 current speed
SYMBOL cSpeed4	= b7	'Motor4 current speed
SYMBOL tMotor	= b8	'Temp output motor
SYMBOL iPot	= b9	'Temp input pot
SYMBOL cSpeed	= b10	'Temp current speed
SYMBOL LCDloc	= b11 'Temp LCD display locatino
SYMBOL oMotor	= b12 'Temp output motor
SYMBOL iSpeed	= w13	'ADC speed input

'---[CONSTANT DEFINITIONS]---
SYMBOL dSpeed1	= 131	'Speed1 LCD screen position
SYMBOL dSpeed2	= 139	'Speed2 LCD screen position
SYMBOL dSpeed3	= 195	'Speed3 LCD screen position
SYMBOL dSpeed4	= 203 'Speed4 LCD screen position
SYMBOL period 	= 199	'PWM period (calculated for 5kHz)
SYMBOL dutyStep= 50	'PWM duty cycle speed step amount
SYMBOL dutyMin	= 300	'PWM minimum duty cycle
SYMBOL kStart    = 450 'Duty cycle needed to start motors

'---[INITIALIZE VARIABLES]---
cSpeed1 		= 0
cSpeed2 		= 0
cSpeed3 		= 0
cSpeed4 		= 0
iSpeed		= 0


'Initialize frequency, setup I/O pins and PWM output.
setup:

   setfreq M16		'16MHz	'
   dirsb = %00000010	'Setup portB I/O pins
   dirsc = %00101101	'Setup portC I/O pins
   
   pwmout pwmDiv4, Motor1, period, 0
   pwmout pwmDiv4, Motor2, period, 0
   pwmout pwmDiv4, Motor3, period, 0
   pwmout pwmDiv4, Motor4, period, 0
   
   
'Initialize LCD screen and display starup text
lcdInit:

   pause 500				'Pause for LCD panel startup
   serout LCD,N2400_16,(254,1)	'Clear Screen
   pause 30					'Wait for screen to clear
   serout LCD,N2400_16,(254,128)	'Move to start of first line
   serout LCD,N2400_16,("M1:0    M2:0    ")
   serout LCD,N2400_16,(254,192)
   serout LCD,N2400_16,("M3:0    M4:0    ")

   high Laser


'Main routine
main:

   gosub manualMode
   goto main


manualMode:
 
   cSpeed = cSpeed1
   iPot   = Pot1
   oMotor = Motor1
   LCDloc = dSpeed1
   gosub checkInput
   cSpeed1 = cSpeed
   
   cSpeed = cSpeed2
   iPot   = Pot2
   oMotor = Motor2
   LCDloc = dSpeed2
   gosub checkInput
   cSpeed2 = cSpeed
   
   cSpeed = cSpeed3
   iPot   = Pot3
   oMotor = Motor3
   LCDloc = dSpeed3
   gosub checkInput
   cSpeed3 = cSpeed
   
   cSpeed = cSpeed4
   iPot   = Pot4
   oMotor = Motor4
   LCDloc = dSpeed4
   gosub checkInput
   cSpeed4 = cSpeed
   
   goto manualMode	   
   

checkInput:
   
   readadc iPot, iSpeed
   iSpeed = iSpeed + 25 / 26
   if iSpeed <> cSpeed then
      cSpeed = iSpeed
      if iSpeed <> 0 then
         iSpeed = iSpeed * dutyStep + dutyMin 
      endif
      gosub setSpeed
      gosub displaySpeed
   endif
   pause 25
   return
   

setSpeed:

   select case oMotor
      case Motor1: pwmduty Motor1, iSpeed
      case Motor2: pwmduty Motor2, iSpeed
      case Motor3: pwmduty Motor3, iSpeed
      case Motor4: pwmduty Motor4, iSpeed
   end select
   return
 
displaySpeed:

   serout LCD,N2400_16,(254,LCDloc)
   serout LCD,N2400_16,("    ")
   serout LCD,N2400_16,(254,LCDloc)
   serout LCD,N2400_16,(#cSpeed)
   return
There's still some amount of setup to call the setSpeed sub, but I like this way much better.

I've been reading up on arrays, and the manual says scratchpad is the best place to create these; however, the M2 parts don't have that feature. Are the RAM variables b0-bx contiguous? Arrays would be ideal here for the setup routine. if I put all the motorx, potx, cspeedx, etc. variables in order I could just setup a single for loop to cycle though the memory address. Gotta take off now, but I think I might try this out.
 

hippy

Ex-Staff (retired)
The byte variables are all contiguous in RAM, and there's more RAM after the variables which can be used as an array.
 

agroom

New Member
The byte variables are all contiguous in RAM, and there's more RAM after the variables which can be used as an array.
I'd been thinking on this last night and it might work with some maneuvering. There's more setup, but I think if it works, I'll end up with a lot more flexibility. I didn't realize there were more bytes after the b0-b27 bytes! This will work great :) So I've been reading more on the variables and this must be the "storage" variables. I haven't been using PICAXE much, so this area was kind of confusing, but I think I'm getting it now.

I'm still a bit confused how the 'bptr' and '@bptr' commands are used though, and maybe just need some clarity. It sounds like they can only be used to address the bx variables and/or up to 256 of the 512 total variable/storage bytes. So for example, to access b0, I would first write: bptr = 0, then if I wanted b1 to contain the value of b0, I would use b1 = @bptr. Likewise, I could access storage register 28 by: bptr = 28, then b1 = @bptr. Then the storage variables 256-511 require the peek/poke commands.

Here are the 4 motor and potentiometer I/O pins:
Code:
SYMBOL PinMotor1  = b.1 'Motor1 PWM output pin
SYMBOL PinMotor2  = c.2 'Motor2 PWM output pin
SYMBOL PinMotor3  = c.5 'Motor3 PWM output pin
SYMBOL PinMotor4  = c.3 'Motor4 PWM output pin

SYMBOL PinPot1	  = b.0 'Motor1 pot ADC input pin
SYMBOL PinPot2	  = b.3 'Motor2 pot ADC input pin
SYMBOL PinPot3	  = b.6 'Motor3 pot ADC input pin
SYMBOL PinPot4	  = c.7 'Motor4 pot ADC input pin
I don't really know the best method to go about using the additional variables. My thought is to create a constant for each of the 1-4 motor, pot, lcd and speed values that contain the register address. Or maybe just one that contains the starting address. For now, I'll do them individually and delete/modify it later if it's not necessary (I don't really think it is).
Code:
SYMBOL Motor1	= 28
SYMBOL Pot1	= 29
SYMBOL LCDLoc1	= 30	
SYMBOL Speed1	= 31

SYMBOL Motor2	= 32
SYMBOL Pot2	= 33
SYMBOL LCDLoc2	= 34
SYMBOL Speed2	= 35

SYMBOL Motor3	= 36
SYMBOL Pot3	= 37
SYMBOL LCDLoc3	= 38
SYMBOL Speed3	= 39

SYMBOL Motor4 	= 40
SYMBOL Pot4	= 41
SYMBOL LCDLoc4	= 42
SYMBOL Speed4	= 43
I also need the temp variables to hold which motor, pot, speed and lcd location I have currently selected along with the ADC input word (using a word so I don't overflow). I'll also need a variable for my for loop.
Code:
SYMBOL cMotor	= b0 'Temp output motor
SYMBOL cPot      = b1 'Temp input pot
SYMBOL cSpeed	= b2 'Temp current speed
SYMBOL cLCDloc	= b3 'Temp LCD display location
SYMBOL iSpeed   = W2 'ADC speed input
SYMBOL cntr      = b6 'For loop counter
And finally I need to do some assignments
Code:
POKE Motor1, pinMotor1
POKE Motor2, pinMotor2
POKE Motor3, pinMotor3
POKE Motor4, pinMotor4

POKE Pot1, pinPot1
POKE Pot2, pinPot2
POKE Pot3, pinPot3
POKE Pot4, pinPot4

POKE Speed1, 0
POKE Speed2, 0
POKE Speed3, 0
POKE Speed4, 0

POKE LCDLoc1, 131 'Speed1 LCD screen position
POKE LCDLoc2, 139 'Speed2 LCD screen position
POKE LCDLoc3, 195 'Speed3 LCD screen position
POKE LCDLoc4, 203 'Speed4 LCD screen position
Okay, so onto the loop I'm hoping to achieve:

Code:
main:

   gosub manualMode ' only mode I have written so far
   goto main


manualMode:
 	
   bptr = 27     'Start the pointer at storage location - 1
   for cntr = 0 to 3
      cMotor  = @bptrinc
      cPot    = @bptrinc
      cLCDLoc = @bptrinc
      cSpeed  = @bptrinc
      gosub checkInput
      @bptr = cSpeed   'Write new speed back to storage byte
   next cntr
   
   goto manualMode	   
   

checkInput:
   
   readadc cPot, iSpeed
   iSpeed = iSpeed + 25 / 26
   if iSpeed <> cSpeed then
      cSpeed = iSpeed
      if iSpeed <> 0 then
         iSpeed = iSpeed * dutyStep + dutyMin 
      endif
      gosub setSpeed
      gosub displaySpeed
   endif
   pause 25
   return
   

setSpeed:

   select case cMotor
      case Motor1: pwmduty pinMotor1, iSpeed
      case Motor2: pwmduty pinMotor2, iSpeed
      case Motor3: pwmduty pinMotor3, iSpeed
      case Motor4: pwmduty pinMotor4, iSpeed
   end select
   return
 
displaySpeed:

   serout LCD,N2400_16,(254,cLCDloc)
   serout LCD,N2400_16,("    ")
   serout LCD,N2400_16,(254,cLCDloc)
   serout LCD,N2400_16,(#cSpeed)
   return
I'm not sure if I used the @bptr and @bptrinc correctly. I don't know if @bptrinc reads then increments or increments then reads, though I assumed the latter since I think it would make the most sense.

I compiled with no errors (well, few syntax issues initially) and it did reduce the size again down to 400 bytes! I realize that ultimately doing it this way is probably not necessary, but I'm honestly more intrigued about learning how to do this now than anything :) But it does set me up for way more efficient code down the road too. The 2k size I had initially thought prob won't be an issue now either.

I'm greatly thankful for everyone's help and patience!
 

hippy

Ex-Staff (retired)
@bptrinc is read/write then increment, so -

cSpeed = @bptrinc
gosub checkInput
@bptr = cSpeed 'Write new speed back to storage byte

Should probably be -

cSpeed = @bptr
gosub checkInput
@bptrinc = cSpeed 'Write new speed back to storage byte

You can write short bits of code and run them through the simulator to check that what happens is what you expect. You seemed to have grasped it all okay other than when inc ( and dec ) occurs.
 

agroom

New Member
@bptrinc is read/write then increment, so -
Ah, yeah...never thought of that, nice catch! Simulator is great :) Used it often to simulate my ADC inputs. Never thought of just creating small snipits though, good advice.

[EDIT] Just realized, in that cause I should also start bptr at 28 too.
 

agroom

New Member
Or, I believe, at/past 256 with the appropriate "real" M2 chips. But AFAIK (> 255) not supported in the simulator (but peek/poke are)?
Simulator does show all 512 general and storage bytes as well as 256 EEPROM and scratchpad (though not supported in the M2 series). Though you might be referring to being able to directly manipulate the variables during simulation, which it allows you to change b0-b13 if viewing by byte and w0-w13 by word.

I think I got this all cleared up though. This is by far the largest program I've written in several years, so I can't vouch for its efficiency/elegance, but it produces a working output :) Of the 4 'modes', I have 2 written and was already up to 1700 bytes. I hadn't used any of the custom save messages from the AXE-133's 18M2 chip though, and I'd say close to 50% of the program size comes from serial out commands printing characters to the LCD screen. It would be much easier if I could set variables = "text".

I ended up just rewriting (mostly just adding to) the driver code so that most of the display handling was done on the LCD controller. Since I'm not using the spare pins, I hijacked the (255,x) command and am using it to send custom commands. So instead of doing 4 serout in my main program (Move line 1, print 16 char, move line 2, print 16 char), I have the 18M2 do all that so my main program just has a single serout command.

So far I've managed to get down to around 1200 bytes. I could probably even better, though I think that's enough to finish my last 2 modes. They're mostly just back-end processing with almost no screen outputs.
 
Top