RC model boat, class project

edmunds

Senior Member
#1
Hi all,

My latest picaxe challenge below ;).

I'm working on a class project we built with kids this year, where the outcome is a little radio controlled fishing boat. We built a number of boats and even had a competition for the school year-end in the local pond. Now, I want to sort out known problems and improve some things for the next year.

One such improvement is adding backwards 'gears' for the ship. The code below works as expected in forward 'gears' - you can select a speed and steer left and right. However, if you select backwards 'gear', as soon as you move the rudder (inputs C.3;C.4) the pwm is thrown over to forward motor output, while keeping the duty cycle.

I was suspecting some timer incompatibility, so I looked at SFRs of 14M2, but after trying them out (see the code) I can see nothing wrong. Any ideas?

Code:
'Ship control

#picaxe 14m2
#no_data

'Flags
Symbol dirflag = bit0

'Byte variables
Symbol RudderPos = b4
Symbol Counter = b5

'Word variables
Symbol Speed = w13
Symbol MainMotorDuty = w12

'Constants
Symbol SingleBtnDelay = 300
Symbol DoubleBtnDelay = 500
Symbol RudderRightMaxPos = 140
Symbol RudderLeftMaxPos = 80
Symbol ServoFullMoveDelay = 300
Symbol ServoStep = 10
Symbol MainMotorPeriod = 100
Symbol MainMotorStep = 20
Symbol MaxSpeed = 9                          '~3.0V motor voltage
Symbol MinSpeed = 1                          '0.0V motor voltage
Symbol SpeedStep = 1
Symbol DirCtrlVal = 4
Symbol SpeedSlope = 10

init:
  input B.1            'Go forward BTN
  input B.2            'Go reverse BTN
  input C.3            'Steer left BTN
  input C.4            'Steer right BTN
  
  output B.3           'Navigation lights, red and grn
  output B.4           'Steering servo
  output B.5           'Front and rear mast lights, white
  output C.0           'Interior light and high beam
  output C.1           'Main engine PWM signal B
  output C.2           'Main engine PWM signal A

  low B.5 : low B.3 : low C.0 'Initialise all lights to OFF
  
  RudderPos = 110
  peeksfr %10111110, b1
  b0 = b1 | %10000000
  pwmout pwmdiv64, B.4, 220, RudderPos         'Initialise servo, set rudder to centre position, trimmer pot solution required
  pokesfr %10111110, b0   ;Bank 5 => 101; location 01E => 11110
  speed = DirCtrlVal   'corresponds to zero
  
main:
  peeksfr %10111110, b2
  sertxd ("CCPTMRS: ",#b2,CR,LF)
  sertxd ("CCPTMRS: ",#bit23,#bit22,#bit21,#bit20,#bit19,#bit18,#bit17,#bit16,CR,LF)
  if pinC.4 = 0 then                                                'If steer right button pressed ...
  	pause SingleBtnDelay                                          '... ebounce the signal ...
  	if pinC.3 = 0 then                                               '... and check if this is not a two button command
  		pause DoubleBtnDelay                                      'In case it is, debounce buttons enough and ... 
  		toggle B.5                                                         '... toggle NAV lights ...
      toggle B.3
;  		pause DoubleBtnDelay                                      '... debounce some more
  	elseif RudderPos > RudderLeftMaxPos then        '... back to single button command steer right, limit max value ...
  	  RudderPos = RudderPos - ServoStep                 '... and move rudder servo a little
      peeksfr %10111110, b1
      b0 = b1 | %10000000
  	  pwmout pwmdiv64, B.4, 220, RudderPos
      pokesfr %10111110, b0   ;Bank 5 => 101; location 01E => 11110
  	endif
  	pause SingleBtnDelay                                          '... ebounce the signal ...
  endif
  if pinC.3 = 1 then                                                 'Same as above, but for the steer left button
  	pause SingleBtnDelay                                          '... ebounce the signal ...
  	if pinC.4 = 1 then
  		pause DoubleBtnDelay
  		toggle B.5
      toggle B.3
;  		pause DoubleBtnDelay
  	elseif RudderPos < RudderRightMaxPos then
  		RudderPos = RudderPos + ServoStep
      peeksfr %10111110, b1
      b0 = b1 | %10000000
  	  pwmout pwmdiv64, B.4, 220, RudderPos
      pokesfr %10111110, b0   ;Bank 5 => 101; location 01E => 11110
  	endif
  	pause SingleBtnDelay
  endif

  if pinB.1 = 1 then             'forward
  	pause SingleBtnDelay
  	pause SingleBtnDelay
  	if pinB.2 = 1 then
  		pause DoubleBtnDelay
  		toggle C.0
  		pause DoubleBtnDelay
    endif
  	if Speed < MaxSpeed then
 		  Speed = Speed + SpeedStep
    endif
    sertxd ("Speed: ",#Speed,CR,LF)
  	if Speed >= DirCtrlVal then
      Select case Speed
      case 4
    sertxd ("At 1st 4!",CR,LF)
        hpwm OFF
      case 5
        MainMotorDuty = 0
        for Counter = 1 to SpeedSlope
          MainMotorDuty = MainMotorDuty + 10;150
    		  hpwm pwmdiv64, pwmsingle, pwmHHHH, %0001, MainMotorPeriod, MainMotorDuty
          pause 200
        next Counter
      case 6
        MainMotorDuty = 180;180
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0001, MainMotorPeriod, MainMotorDuty
      case 7
        MainMotorDuty = 220;220
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0001, MainMotorPeriod, MainMotorDuty
      case 8
        MainMotorDuty = 270;270
  		  hpwm pwmdiv64, pwmsingle, pwmHHHH, %0001, MainMotorPeriod, MainMotorDuty
      case 9
        MainMotorDuty = 330;330
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0001, MainMotorPeriod, MainMotorDuty
      endselect
    else
      Select case Speed
      case 1
        MainMotorDuty = 220;220
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0010, MainMotorPeriod, MainMotorDuty
      case 2
        MainMotorDuty = 180;180
        hpwm pwmdiv64, pwmsingle, pwmHHHH, %0010, MainMotorPeriod, MainMotorDuty
      case 3
        MainMotorDuty = 150;150
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0010, MainMotorPeriod, MainMotorDuty
      case 4
    sertxd ("At 2nd 4!",CR,LF)
        hpwm OFF
      endselect
  	endif
  endif

  if pinB.2 = 1 then
  	pause SingleBtnDelay
  	pause SingleBtnDelay
  	if pinB.1 = 1 then
  		pause DoubleBtnDelay
  		toggle C.0
  		pause DoubleBtnDelay
    endif
  	if Speed > MinSpeed then
  		Speed = Speed - SpeedStep
  	endif
    sertxd ("Speed: ",#Speed,CR,LF)
  	if Speed >= DirCtrlVal then
      Select case Speed
      case 4
    sertxd ("At 3rd 4!",CR,LF)
        hpwm OFF
      case 5
        MainMotorDuty = 150;150
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0001, MainMotorPeriod, MainMotorDuty
      case 6
        MainMotorDuty = 180;180
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0001, MainMotorPeriod, MainMotorDuty
      case 7
        MainMotorDuty = 220;220
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0001, MainMotorPeriod, MainMotorDuty
      case 8
        MainMotorDuty = 270;270
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0001, MainMotorPeriod, MainMotorDuty
      case 9
        MainMotorDuty = 330;330
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0001, MainMotorPeriod, MainMotorDuty
      endselect
  	else
      Select case Speed
      case 1
        MainMotorDuty = 220;220
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0010, MainMotorPeriod, MainMotorDuty
      case 2
        MainMotorDuty = 180;180
    		hpwm pwmdiv64, pwmsingle, pwmHHHH, %0010, MainMotorPeriod, MainMotorDuty
      case 3
        MainMotorDuty = 0
        for Counter = 1 to SpeedSlope
          MainMotorDuty = MainMotorDuty + 10;150
    		  hpwm pwmdiv64, pwmsingle, pwmHHHH, %0010, MainMotorPeriod, MainMotorDuty
          pause 200
        next Counter
      case 4
    sertxd ("At 4th 4!",CR,LF)
        hpwm OFF
      endselect
  	endif
  endif
goto main

Thank you for your time,

Edmunds
 

hippy

Technical Support
Staff member
#2
if you select backwards 'gear', as soon as you move the rudder (inputs C.3;C.4) the pwm is thrown over to forward motor output, while keeping the duty cycle.
Not sure exactly what you mean by "the pwm is thrown over to forward motor output" and there's too much code to get an understanding of with a quick glance.

Perhaps if you could describe what is happening and what should be happening that may make it easier to diagnose or debug, give a clue where to look.

The only thing I can guess is that if you are using 0 to 100 for 0-100% forward and want something like 0 to -100 for 0-100% backwards, you may have to invert the duty in the reverse case.
 

edmunds

Senior Member
#3
Not sure exactly what you mean by "the pwm is thrown over to forward motor output" and there's too much code to get an understanding of with a quick glance.
Ok, fair enough, will try.


There is a transmitter. Cheap 433mhz transmitter, HT12E encoder and 4 buttons. On pressing a button, this bursts out [address+] four bits corresponding to which buttons were pressed. The buttons in my case are forward, backward, left and right.

There is a receiver. Cheap 433mhz receiver, HT12D decoder, picaxe14m2, cheap motor controller - basically a LD239-like thing, some transistors to drive higher current leds and some more. The RF receiver receives the 4 bit code, HT12D decodes it to 4 digital parallel outputs and feeds into picaxe. Picaxe figures out which buttons are pressed and makes the ship go forward, backward, left or right and coordinates some aux functions if two buttons are pressed at the same time.

The remote control needs to be as proportional as is possible with four discrete buttons. Initial press on forward, should not blast the ship away with 100% power, but, say, 15% of the max power available. Same for the rudder - steer left a little, some more, even more, and a lot. Pressing backwards or right now, should move the power down a notch and move the rudder back one step. I hope this makes sense.

The forward motion of the ship works - you can add speed by pressing forward button and make it slower by pressing backwards button. Also, while traveling forward, steering works perfectly fine - implemented with pwmout command, not a servo command. The forward motor motion is implemented with hpwm command in single mode with %0001. Backwards should be %0010. And it is. With all the speed increase and decrease - no problem. As long as you do not touch the left-right buttons.

As soon as left or right rudder buttons are used while the main engine is running backwards [%0010], it is thrown to forward motion [%0001] with the same duty as was set for backward direction. I.e. in case

Code:
        [color=Blue]hpwm pwmdiv64[/color][color=Black], [/color][color=Blue]pwmsingle[/color][color=Black], [/color][color=Blue]pwmHHHH[/color][color=Black], [/color][color=Red]%0010[/color][color=Black], 100,[/color] [color=Red]180[/color]
was apparently working correctly, on a steering command it is like a following command would be executed right after the rudder position change

Code:
        [color=Blue]hpwm pwmdiv64[/color][color=Black], [/color][color=Blue]pwmsingle[/color][color=Black], [/color][color=Blue]pwmHHHH[/color][color=Black], [/color][color=Red]%0001[/color][color=Black], 100,[/color] [color=Red]180[/color]
.

I have replicated the problem with LEDs on AXE091 dev board and as I wrote above, I have tried to remedy timer problems, but could not see anything wrong with it.

I hope this makes more sense,

Edmunds
 
Last edited:

hippy

Technical Support
Staff member
#4
Thanks. Your big picture description makes sense but doesn't entirely help a lot regarding how the code may be misbehaving.

If it's doing the wrong thing it's most likely because the code has incorrectly got to the point where the code tells it to do the wrong thing. The problem is determining how it got there and the why behind that.

It's trying to determine what the flow of code is which is the difficult part for me.

Are inputs active low or active high ?

Your speed control SELECT-CASE commands only seem to be executed when inputs are high, not continually; I'm not sure why. Why so many SELECT-CASE for speed control ? Is it simply executing the wrong one ?

What's the PEEKSFR/POKESFR on %10111110 doing ? Could that be screwing with something ?

What do the outputs you toggle do ?

Do your SERTXD commands reveal something which is different to what it should be when you press the button and things go wrong ? Have you tried adding more SERTXD to trace exactly where the code is going when you push the button and it goes wrong ?
 

hippy

Technical Support
Staff member
#5
Having looked at the code, tried simulating, I am still struggling with it. I think the best course would be to refactor what you have, split it into at least three parts.

Have one subroutine which updates desired speed, direction and rudder angle from the buttons, another subroutine which translates speed, direction and rudder angle to what should actually be output.

Have each of those with their own variables ( desired and current ) with a third subroutine which gets executed between the two which converts desired speed, direction and rudder angle from the first routine, to what the output routine should output.

Initially that middle routine can just copy desired to current, can later be improved to make change smoother.

That way you can check that the button handler is producing correct desired values and the output routine is outputting how things should be for the input it's given. That makes it easier to tell where the issue lies. If both work individually then both should work together.

As you are using an M2 it would be possible to make those three subroutines separate tasks which should make things even simpler.
 

edmunds

Senior Member
#6
Thanks, Hippy!

My clumsy code was not intended for efficiency analysis. Or not yet, at least :).

Anyway, you are right, lets take a step back. Or several.

Can you help me understand why this works (as in there is a pwm output on C.2 AND B.4 after pressing a button connected to C.3)

Code:
[color=Navy]#picaxe [/color][color=Black]14m2[/color]
[color=Navy]#no_data[/color]

[color=Green][PLAIN]'Connect motor driver [or LEDs] to C.1 and C.2[/PLAIN]
'Connect servo to B.4

'Start the main engine backwards[/color]
[color=Blue]hpwm pwmdiv64[/color][color=Black], [/color][color=Blue]pwmsingle[/color][color=Black], [/color][color=Blue]pwmHHHH[/color][color=Black], [/color][color=Red]%0001[/color][color=Black], [/color][color=Navy]100[/color][color=Black], [/color][color=Navy]150[/color]


[color=Green]'Move the rudder on button press[/color]
[color=Blue]do
  if [/color][color=Purple]pinC.3 [/color][color=DarkCyan]= [/color][color=Navy]1 [/color][color=Blue]then
    pause [/color][color=Navy]300              [/color][color=Green]'debounce the button
    [/color][color=Blue]pwmout pwmdiv64[/color][color=Black], [/color][color=Blue]B.4[/color][color=Black], [/color][color=Navy]220[/color][color=Black], [/color][color=Navy]110
  [/color][color=Blue]endif
loop[/color]
, but this does not (as in there is pwm output on B.4, but it dissapears on C.1 after pressing a button connected to C.3)?

Code:
[color=Navy]#picaxe [/color][color=Black]14m2[/color]
[color=Navy]#no_data[/color]

[color=Green][PLAIN]'Connect motor driver [or LEDs] to C.1 and C.2[/PLAIN]
'Connect servo to B.4

'Start the main engine backwards[/color]
[color=Blue]hpwm pwmdiv64[/color][color=Black], [/color][color=Blue]pwmsingle[/color][color=Black], [/color][color=Blue]pwmHHHH[/color][color=Black], [/color][color=Red]%0010[/color][color=Black], [/color][color=Navy]100[/color][color=Black], [/color][color=Navy]150[/color]


[color=Green]'Move the rudder on button press[/color]
[color=Blue]do
  if [/color][color=Purple]pinC.3 [/color][color=DarkCyan]= [/color][color=Navy]1 [/color][color=Blue]then
    pause [/color][color=Navy]300              [/color][color=Green]'debounce the button
    [/color][color=Blue]pwmout pwmdiv64[/color][color=Black], [/color][color=Blue]B.4[/color][color=Black], [/color][color=Navy]220[/color][color=Black], [/color][color=Navy]110
  [/color][color=Blue]endif
loop[/color]
Thank you,

Edmunds
 
Last edited:

hippy

Technical Support
Staff member
#7
Can you help me understand why this works ... but this does not
Not entirely but I do note that in PICAXE Manual 1 and in the online command descriptions for HPWM for Single Mode it says -

Supported : 20X2, 28X1, 28X2, 28X2-3V, 40X2, 40X2-3V

Not Supported: 14M, 14M2, 20M2, 28X2-5V, 40X1, 40X2-5V

http://www.picaxe.com/BASIC-Commands/Advanced-IO-Interfacing/hpwm

It may be that you are trying to do something which the 14M2 simply doesn't provide for. I would have to investigate further but suspect that, as it doesn't work when it otherwise would be expected to, that is the issue.

Added: The 14M2 supports four PWMOUT channels; C.0, C.2, B.2 and B.4 so you may be able to move your C.1 motor connection to C.0 and replace the HPWM with appropriate PWMOUT commands.
 
Last edited:

hippy

Technical Support
Staff member
#8
When I was trying to refactr your code I arrived at this for the motor speed control ...

Code:
OutputSpeed:
  Select Case nowSpeed
    Case 9 : mtrBits = %0001 : mtrDuty = 330
    Case 8 : mtrBits = %0001 : mtrDuty = 270
    Case 7 : mtrBits = %0001 : mtrDuty = 220
    Case 6 : mtrBits = %0001 : mtrDuty = 180
    Case 5 : mtrBits = %0001 : mtrDuty = 150
    Case 4 :                   mtrDuty = 0
    Case 3 : mtrBits = %0010 : mtrDuty = 150
    Case 2 : mtrBits = %0010 : mtrDuty = 180
    Case 1 : mtrBits = %0010 : mtrDuty = 220
  End Select
  If mtrDuty = 0 Then
    HPwm OFF
  Else
    HPwm PWMDIV64, PWMSINGLE, PWMHHHH, mtrBits, PWMPERIOD, mtrDuty
  End If
  Return
You could probably keep the SELECT-CASE the same then update the bottom part, which will use C.2/C.0 rather than C.2/C.1, untested ...

Code:
 If mtrDuty = 0 Then
    PwmOut PWMDIV64, C.2, PWMPERIOD, 0
    PwmOut PWMDIV64, C.0, PWMPERIOD, 0
  ElseIf mtrBits = %0001 Then
    PwmOut PWMDIV64, C.0, PWMPERIOD, 0
    PwmOut PWMDIV64, C.2, PWMPERIOD, mtrDuty
  Else
    PwmOut PWMDIV64, C.2, PWMPERIOD, 0
    PwmOut PWMDIV64, C.0, PWMPERIOD, mtrDuty
  End If
 

edmunds

Senior Member
#9
Not entirely but I do note that in PICAXE Manual 1 and in the online command descriptions for HPWM for Single Mode it says -

Supported : 20X2, 28X1, 28X2, 28X2-3V, 40X2, 40X2-3V

Not Supported: 14M, 14M2, 20M2, 28X2-5V, 40X1, 40X2-5V

http://www.picaxe.com/BASIC-Commands/Advanced-IO-Interfacing/hpwm
Thanks.

Brr. I do remember checking the suitability of 14M2 and if it would support hpwm at all. But I must have missed the fine print. Such is life. I have quite a few PCBs made, so I have five options to choose from:

1. Forget the reverse [just not cool :)]
2. Bridge and cut traces on PCBs [sort of tricky for students, but also not useless to practice]
3. Re-do the PCBs [how to re-use the ones I have?]
4. Use half-bridge mode (C.2=A; C.1=B) [was giving a lot more noise on the power supply than single mode, but I now have separate power supplies; testing needed]
5. Trick picaxe into correctly operating hpwm on C.1 [just for fun, try to start (e)ccp modules without picaxe commands, using SFRs only]

Thank you for your code solutions, too. No doubt, a much better looking program than the original :).

Edmunds
 
Top