Signed numbers in a PID statement

edmunds

Senior Member
Dear all,

I have bitten the bullet and tried to go the signed numbers route on my line follower steering PID calculation. The bullet is bigger than it might look - I had to migrate to Shwindows to get #macro commands working and without them it would be even more confusing than the case statements I had difficulty to work through. Now it all looks beautiful and clear, but it does not work as expected and I cannot find the bug. Judging from the operation of the vehicle, it could be as little as one sign somewhere. I suspect it is the very beginning of the code, where I convert line_pos, which is a number between 1 and 7 for negative values, 8 and 9 for zero and 10 to 16 for positive values to ELine or error. I cannot figure out a solution, though.

For simplicity, I got rid of all the scaling as this introduced another layer of things I did not understand. Now I don't even need division, so it is multiplication only.

Here are my macro routines:
Code:
#macro MultiplySigned(word1,word2)
  sign = positive
  if word1 >= $8000 then
    sign = NOT sign
    word1 = -word1
  endif
  if word2 >= $8000 then
    sign = NOT sign
    word2 = -word2
  endif
  Result = word1 * word2
  if sign = negative then
    Result = -Result
  endif
#endmacro

#macro SignedICap(word1)
  if word1 >= $8000 then
    ICap = -ICap
    word1 = word1 MIN ICap
  else
    word1 = word1 MAX ICap
  endif
  Result = word1
#endmacro
Here is the PID calculation loop:
Code:
  KpELine = 20               'P coefficient/multiplier for line position error correction, 0.0x
  KiEline = 5                   'I coefficient for line error corrections, 0.00x
  KdEline = 40              'D coefficient for line error corrections, 0.0x 
  ICap = 100

  
  select case line_pos
  case < 8
    Eline = 8 - line_pos
    Eline = NOT Eline
  case > 9
    Eline = line_pos - 9
  else
    ELine = 0                                              'Set error to zero
    agr_error = 0                                        'Set accumulator to zero
    steer_input = 0                                     'Do not steer
  endselect
  MultiplySigned(ELine,KpELine)                     'Multiply error with P term coefficient
  PLine = Result                                    'Put calculation into P term
  agr_error = agr_error + ELine                     'Ad up error for I term
  MultiplySigned(agr_error,KiELine)                 'Multiply accumulated error with I term coefficient
  ILine = Result                                    'Put calculation into I term
  SignedICap(ILine)                                 'Cap signed ILine at ICap magnitude
  DLine = OldELine - ELine                          'Calculate error trend
  MultiplySigned(DLine,KdEline)                     'Multiply error trend with D term coefficient
  DLine = Result                                    'Put calculation into D term
  steer_input = PLine + ILine + DLine               'Add everything up into a steering input
  if steer_input bit 15 set then
    low DIR
    steer_input = NOT steer_input + 1
  else
    high DIR
  endif
  high EN                                            'Enable the device to steer, disable to save power and limit heat
  for Counter = 0 to steer_input         'Increase the number of loops to steer 'more' per cycle or decrease for 'less'
    pulsout STCK, 5000                       'About a minimum to get decent power
    pauseus 2500                                'pauseus is double the length of pulsout, so half of pulsout time for a square wave
  next
  low EN
  OldELine = ELine                              'Set the old error the current error
Thank you for your input,

Edmunds
 
Last edited:

Buzby

Senior Member
Hi edmunds. Now you are happy that eline is reliable you can put your pid in PE, put a tx instead of pulsout, then serin eline each time round the loop. So much easier to see what the code is doing in the simulator !

Cheers,

Buzby
 

stan74

Senior Member
Is this the bit about negative numbers Edmunds?
steer_input = PLine + ILine + DLine 'Add everything up into a steering input
if steer_input bit 15 set then................... is it negative?
low DIR
steer_input = NOT steer_input + 1..........make it positive and allow for zero?
else
high DIR
endif
 

hippy

Technical Support
Staff member
Your "ICap = -ICap" does not even pass syntax check when "Symbol ICap = 100" so I would guess that's not the actual code you are using.

I would actually use ...

Code:
#macro SignedICap(word1)
  if word1 >= $8000 then
    word1 = -word1 MAX ICap
    word1 = -word1
  else
    word1 = word1 MAX ICap
  endif
  Result = word1
#endmacro
There doesn't seem to be any need to set 'Result' nor move 'Result' back into the variable you have just applied the ICap to in the code.

Also; what are all the "NOT" commands doing in your PID calculation ? If you want to create a negative just use a minus sign ...

Code:
  select case line_pos
  case < 8
    Eline = 8 - line_pos
    Eline = -Eline
That will convert 0 to -8, 1 to -7, 7 to -1. Or you could just use ...

Code:
  select case line_pos
  case < 8
    Eline = -8 + line_pos
It's good to get into the habit of writing test code and running that in the simulator to check it does what you expect it to do. That's one of the best things about the simulator ...

Code:
w0 = 7
[b]w1 = -8 + w0[/b]
If w1 >= $8000 Then
  w1 = - w1
  SerTxd( "-", #w1, CR, LF )
Else
  SerTxd( "+", #w1, CR, LF )
End If
 

AllyCat

Senior Member
Hi,

To follow up my comment in the other thread about using conditional commands with twos complement numbers.

If you were trying to trap a potential overflow with something like: IF w1 > 255 THEN too_large, then that will fail with negative twos complement numbers (in PICaxe Basic or most other languages).

With most languages you'd use an ABS function (i.e. to convert any negative value to the same positive number) but with PICaxe you'd probably just use IF w1 > 255 AND w1 < $FF00 THEN too_large . However, an additional ABS subroutine or a macro would be easy to write and probably better practice.

Cheers, Alan.
 

edmunds

Senior Member
@hippy, I hope to develop the skill for your lean code one day :). In my defense, the ICap is just a misunderstanding. It does not compile if ICap is a constant, but the line ICap = 100 kind of demonstrates it is a variable and then it does compile. Using constants would be faster and smarter in the end and I have routines with them already written. However, I have reverted back to basics to debug what is going on.

Alan, I will look into the meaning of ABS. I have no experience with it from 'other languages' so lets see what I gather.

I have little progress for today. I still seem to get a response biased towards right hand side also after implementing fixes that people have pointed to above.

Simulator is a great tool and this is how I have arrived at the code in front of you - I added operations one by one following up with a test with one negative and one positive value. There was a point, however, where because of using #macros I could not follow what is going on anymore as the debugger does not dive into the code in macros. That is why I reverted back to the simpler version before posting to the forum. Its a pity Mac gets so little attention from RevEd these days if I may complain about one thing off topic :). Investing in building a piece of software like PE6 in tools that are not cross-platform is a pretty interesting decision, to say the least.

Edmunds
 

stan74

Senior Member
any use? I copied it.
two's complement | ones' complement | sign/magnitude
+---------------------+---------------------+---------------------+
5 | 0000 0000 0000 0101 | 0000 0000 0000 0101 | 0000 0000 0000 0101 |
-5 | 1111 1111 1111 1011 | 1111 1111 1111 1010 | 1000 0000 0000 0101 |
+---------------------+---------------------+---------------------+
In two's complement, you get a negative of a number by inverting all bits then adding 1.
In ones' complement, you get a negative of a number by inverting all bits.
In sign/magnitude, the top bit is the sign so you just invert that to get the negative.
 

Buzby

Senior Member
Hi edmunds,

I really think you should do more in the simulator.

The text below is the result of running your PID in the simulator for 11 scans of your code.
The first four are with the car 'on track' with line_pos = 8 .
The next four are with line_pos = 10, i.e the car is slightly 'off track'.
The last three are with the car back 'on track'.

line_pos = 8, Steps = -0
line_pos = 8, Steps = -0
line_pos = 8, Steps = -0
line_pos = 8, Steps = -0
line_pos = 10, Steps = -15
line_pos = 10, Steps = +30
line_pos = 10, Steps = +35
line_pos = 10, Steps = +40
line_pos = 8, Steps = +40
line_pos = 8, Steps = -0
line_pos = 8, Steps = -0


All looks OK for the first 'on track' scans, as the commands to the stepper are 0.
At the first 'off track' the stepper is commanded to run reverse 15 steps. This looks OK.
But after that it goes a bit haywire.

The next three 'off tracks' should be commanding 0, as the error hasn't changed, but your code drives the stepper 30 counts in the opposite direction, and continues driving 5 more counts each scan. The car is now turning tighter and tighter every scan.

When the car gets back 'on track' the code sends one more wrong command, then stops the stepper at whatever position it was last at.

The results should look more like this :

line_pos = 8, Steps = -0
line_pos = 8, Steps = -0
line_pos = 8, Steps = -0
line_pos = 8, Steps = -0
line_pos = 10, Steps = -15
line_pos = 10, Steps = 0
line_pos = 10, Steps = 0
line_pos = 10, Steps = 0
line_pos = 8, Steps = +15
line_pos = 8, Steps = -0
line_pos = 8, Steps = -0


In this case the car goes 'off track' and a correction is applied. This adjusts the steering to '-15'.
While the 'off track' error doesn't change, the steering stays at '-15'.
When the car gets back 'on track, the steering is returned to straight by applying an equal but opposite correction.

The effect of this on a 'real' car is that the car will snake along the track, always oversteering left and right, between line_pos 6 and 10.
This is basically what you would see with just a P controller, and is the first target you need to hit, as when you get this right the amplitude of the 'swimming' will be 1 or 2 'pixels', or whatever your smallest measure is. ( Personally I don't think you are going to need I, and even less need D. )

As both hippy and myself have said, the PE simulator is perfect for debugging this type of code.

Cheers,

Buzby
 

edmunds

Senior Member
Buzby,

Thank you for your time on my problem.

I realize simulator is a tool I'm yet to master, but trust me, I'm doing what I can :). Not always I manage to ask you a direct question, but some of the debug methods you suggest are just beyond my current knowledge. How did you get the algorithm to go through several different values? And while I'm at it, what do you mean with 'put a tx instead of pulsout, then serin eline each time round the loop'? What is tx? Do you mean something hardware or software?


Edmunds
 

edmunds

Senior Member
The next three 'off tracks' should be commanding 0, as the error hasn't changed, but your code drives the stepper 30 counts in the opposite direction, and continues driving 5 more counts each scan. The car is now turning tighter and tighter every scan.

When the car gets back 'on track' the code sends one more wrong command, then stops the stepper at whatever position it was last at.
While what it is doing does not seem correct, what you are suggesting it should does not seem correct either. If I'm steering and error is not changing, but is present, I should assume I'm not steering enough - either because of a turn or because I don't know how much is actually left/right or both. As long as I'm not at zero error, I should continue converging. Don't I?

Edmunds
 

Buzby

Senior Member
... If I'm steering and error is not changing, but is present, I should assume I'm not steering enough.
Not quite right, but not too wrong either.
If the error stays constant for quite a while it means either :

1, you are on a straight track, and going straight OK, but your 'mechanical zero position' is not actually zero. This is like a real car with a misaligned steering wheel. You could drive it straight, but you would have to have the steering wheel held at a constant angle all the time. ( This is where the 'I' part of PID comes in. It corrects for constant errors, and slowly pulls the two back in synch. )

2, you are going round a bend, and the radius is the same as your turning radius, so no problem !.

Your idea that "if there is an error then I must increase correction every scan" just means you have a huge re-correction to make when you get back on track. When you are driving on the motorway and need to change lanes you do it with a small fixed turn of the wheel, followed by a tapered return to 'straight' as you approach your final line.

Your hardware is the smallest line follower I've seen, and the Ackermann steering is unique in this field as far as I know. The most awkward part is definatley the stepper for steering, a servo would have been much easier to control. It may have been better to use a separate PICAXE to run the stepper as a 'virtual servo', but we have what we have !. ( There is a possibility that your single PICAXE could itself have some code to provide a 'virtual servo', which would make controlling it much easier and not slow down the loop, but it would depend on interrupts, and I've not studied your code close enough to see if that would be possible. )

I really want to see this work, it will be such a cool sight to see !.

Cheers,

Buzby
 

edmunds

Senior Member
1, you are on a straight track, and going straight OK, but your 'mechanical zero position' is not actually zero. This is like a real car with a misaligned steering wheel. You could drive it straight, but you would have to have the steering wheel held at a constant angle all the time. ( This is where the 'I' part of PID comes in. It corrects for constant errors, and slowly pulls the two back in synch. )
Except that is not a mechanical zero position, but an offset to the line that we see. So it is an absolute, not relative reference. As for the turn, I believe traveling parallel to the center of the line is not really optimal either. While I understand world is not perfect, the algorithm should reach for it as far as I understand.

Anyway, no worries, I'm confident this will work as I have code from a few days back that actually navigates a full circle on my test track (albeit with some wiggling) about every fifth time or so. Mainly dependent on the fact if the wiggle before a 90 degree turn happens to be to the inside or to the outside. I could cheat and draw some line with larger radius and have the car tracking reasonably well as we speak. I'm not comfortable with that, however, because this would not mimic the streets of model railroad layouts I would like to be able to navigate and also because I'm not in full control of why it is almost working yet :). It is not far off, though whatever it may seem.

I have thought about a separate picaxe, or actually STM32, because they come in 2x2.8mm package, but I have realized the only advantage to be gained per loop is equal to acquiring and analyzing the sensor data, which is minimal compared to issuing the steps for the stepper driver. Even with the steering co-processor of sorts, the main processor would have to wait for its counterpart to finish issuing the steps to the motor before sending in more. I am very confident in line readings now, so there is certainly no need to sample anything for averages, so I think the main processor would just be waiting and spending time on checking 'busy' pin from the slave. On top of that, this already is the second processor in the car. The main main processor is managing the main motor, lights, wireless charging, obstacle avoidance, infrared communication and to be implemented inertial navigation and BLE communication.

Thank you for your support,

Edmunds
 

AllyCat

Senior Member
Hi,

Your hardware is the smallest line follower I've seen, and the Ackermann steering is unique in this field as far as I know. The most awkward part is definatley the stepper for steering, a servo would have been much easier to control.
Yes indeed. My own project is planned to use differential steering (but still with stepper motors). I can see why edmunds must use a stepper for his steering and now that I've sussed how to drive them from a low cost 3-5v audio ic, I couldn't resist ordering some of these 3.5mm steppers. I guess they'll make my present 6mm steppers look like giants. ;)

Cheers, Alan.
 

AllyCat

Senior Member
Hi,

That's almost half the price I paid. :( And I now see the listing says:

"We will carefully test every item before shipment. The quality is guaranteed!"

Sorry edmunds for taking this thread off-topic. ;)

Cheers, Alan.
 

edmunds

Senior Member
Sorry edmunds for taking this thread off-topic. ;)
No problem, it must be some sort of karma of my threads.

I have used both of the steppers you are discussing on several model railroad layout projects and could share some of my experience, schematics and code, but not on this thread as I'm not done with my signed numbers, yet :)

Edmunds
 

edmunds

Senior Member
Hooray! [Or whatever you english say when pleased about something :)]

Dear all,

Below is a video of my little bugger following the line around the track at crawl speed. I have decided to get a good sleep for once tonight, so I will post the code after I clean it up and improve it at places I know it can be improved tomorrow or the day after depending on some other errands.

Here are the most important points that, when addressed, led me to this small victory:

1) D term or difference between previous error and new error must be the driving component of the equation. For the driving you see, P term coefficient is 52 and D term coefficient is 330. Until I tried that magnitude of difference, I was getting absolutely nowhere. I did not invent it however, but read on some forum on the Internet where somebody who had done it advised somebody like me to start at D value five-fold of P.

2) A university course on control of mobile robots teaches you the D term should work with OldError - Error. For some reason, this is wrong in real life or they count the error backwards from the target value. It should be Error - OldError, because only that can give you the negative D term to steer away from the line when still on that side of the line, but approaching it. If you know what I mean.

3) #macros parameters work in a totally different manner than I expected and the writeup I found about it online and in manual 2 did not explain this at all. I.e. if you pass w1 to a macros, but macros is set up to use w0 and performs operations on it, on exit, it will stuff the w0 into your w1. So basically, if you give macros a variable, you cannot get it back even if you used totally different variables in the macros. Thus, you have to first set w0 = w1 and only then go to macros. I have not properly thought if there is a way around it that does not involve the extra lines of assigning the variables every time a macros is called. The parameters idea seems useless this way. I was also surprised you cannot have a macros NameGoesHere() if you do not need any parameters to be passed to it.

4) Falling voltage of the battery has effect on both, the sensor reading and the speed the stepper executes its commands. Not taking this into account cost me a couple of hours of 'this just worked and does not anymore'. At one point, I got so carried away, the download did not work anymore - that is how low the poor battery had gone.

As said above, there are things to clean up and there is I term, which is currently zero. Some sources on PID tuning recommended starting with P term and then go to I term. Maybe this works for thermostats and such, but for ackermann line follower this is the way to loose your hair. Now, however, I can see how I term, if I can keep it sufficiently small per iteration, will actually make things better and could be a key to increasing speed of the car. Also, I need to get back to the calibration routine to calibrate more often - both because of changing battery voltage and changing light conditions. And anything that can be done for speed to measure and adjust more frequently.

Goes almost without saying - a great THANK YOU to everybody who helped the project to get this far! There is more to come ;)

Enjoy the video here.

Edmunds
 
Last edited:

rossko57

Senior Member
... the main processor would have to wait for its counterpart to finish issuing the steps to the motor before sending in more.
I think this is where the "virtual servo" idea comes in.
You would have code that analyses the opto data and sends a "desired steering angle" (equivalent of a servo position).
The steering handler code keeps track of its current position, and plods away stepping towards the desired position (or not stepping if it is already there).
But that need only hang things up for the time it takes to set up one step, while the step is mechanically taking place you can redo the opto stuff and maybe set a new desired steering angle.
Next iteration of steering handler compares new current position with (possibly changed) target, and sets up next step...
The advantage is the steering movement need not complete before the next check to see how things are going.


If a step time is not too dissimilar to opto-read-analayze execution time, it might all work as inline code.
Else it sounds like a job for an interrupt driven steering handler.

Does the steering have mechanical limits? You'd need a self-centre at startup, say gently stepping far too many steps (so you know it must be on one end stop) and then coming back half the step range (hopefully known?) and calling that "middle" - 128 or zero or whatever the maths needs. I know there are accuracy/repeatability issues here but it only needs to be roughly right, the feedback loop will sort it out.
 

lbenson

Senior Member
Sweet. Great to see it come together. How does it know not to take the turnoffs it encounters (noting the little wobble at the left turnoff on the right-hand track)?

3) Something sounds wrong there regarding your macro problem. We would have to see a code example.
 

edmunds

Senior Member
Sweet. Great to see it come together. How does it know not to take the turnoffs it encounters (noting the little wobble at the left turnoff on the right-hand track)?

3) Something sounds wrong there regarding your macro problem. We would have to see a code example.
Thanks. Yes, coming up, on a separate thread, I guess. I would like it sorted in my mind. But towards the night or tomorrow.

Edmunds
 

edmunds

Senior Member
Else it sounds like a job for an interrupt driven steering handler.

Does the steering have mechanical limits? You'd need a self-centre at startup, say gently stepping far too many steps (so you know it must be on one end stop) and then coming back half the step range (hopefully known?) and calling that "middle" - 128 or zero or whatever the maths needs. I know there are accuracy/repeatability issues here but it only needs to be roughly right, the feedback loop will sort it out.
Well, now I'm becoming intrigued. Please, elaborate, when you have time. Steering has mechanical limits and stepping 'gently' to one end is perfectly possible. What I do not understand is how do you make stepping 'in the background'. Or at least part of it, because that where the potential speed improvement would lie.

Thank you for your input,

Edmunds
 

Buzby

Senior Member
... What I do not understand is how do you make stepping 'in the background'.
Code:
' Init
SetInterrupt    ' Set timed interrupt to trigger every 5mS, or whatever.

' Main loop
do
   read_ccd   ' Read input from sensor
   calc_pid    ' Calculate required 'virtual_servo' position, RqVSpos
loop

' Interrupt subroutine
  Send_some_steps ' Drive stepper, pulse count dependent VSpos - RqVSpos  
  Update_VSpos      ' Adjust VSpos +/- the number of pulses sent 
return
Above is the basic skeleton of how background stepping could be implemented.

Although this would be simple to implement, it does need some understanding of the consequences on the rest of your code, specifically the CCD reading.

( Another option is to put the PID in the timed interrupt. In fact, a PID should preferably be called at fixed intervals, to ensure the I and D parts work smoothly. )

Cheers,

Buzby
 

AllyCat

Senior Member
Hi,

I think rossko is hinting at (timed) interrupts. That's certianly a possibility but I think we need some "numbers", so my "diversion" may not have been as off-topic as we thought:

I have used both of the steppers you are discussing on several model railroad layout projects and could share some of my experience, ..
Those steppers seem quite similar to your steering motor and my (initial) experience is that even the larger one needs the current to be switched (pulsed) every few ms (or faster?). That might be rather too fast (the proportion of time-consumed in the interrupt calls) even for your X2 running at 64 MHz. Also as Buzby implies, you may need to disable the interrupts while scanning the imaging sensor because it could affect the exposure (integration) time.

Cheers, Alan.
 

BESQUEUT

Senior Member
Hi edmunds,

I really think you should do more in the simulator.

...

In this case the car goes 'off track' and a correction is applied. This adjusts the steering to '-15'.
While the 'off track' error doesn't change, the steering stays at '-15'.
When the car gets back 'on track, the steering is returned to straight by applying an equal but opposite correction.

The effect of this on a 'real' car is that the car will snake along the track, always oversteering left and right, between line_pos 6 and 10.
This is basically what you would see with just a P controller, and is the first target you need to hit, as when you get this right the amplitude of the 'swimming' will be 1 or 2 'pixels', or whatever your smallest measure is. ( Personally I don't think you are going to need I, and even less need D. )

As both hippy and myself have said, the PE simulator is perfect for debugging this type of code.

Cheers,

Buzby
+1
If track is more than 1 pixel wide, you have to calculate the center point of the line (may be as fine as 1/2 pixel... so basic unit should be 1/2 pixel...)
If some pixels are white inside the dark line, this may be a little tricky...
 

AllyCat

Senior Member
Hi,

... calculate the center point of the line (may be as fine as 1/2 pixel... so basic unit should be 1/2 pixel...) If some pixels are white inside the dark line, this may be a little tricky...
IMHO there's not much risk of that, because the optical resolution is much lower than the digital resolution (photodiode/CCD/clock, etc.). I have some ideas how to get much better optical resolution for line-following applications like this, but that's for another thread. ;)

Cheers, Alan.
 

edmunds

Senior Member
If track is more than 1 pixel wide, you have to calculate the center point of the line (may be as fine as 1/2 pixel... so basic unit should be 1/2 pixel...)
I have been scratching my head all along about how to implement '1/2 pixel' idea. Or in between pixels, actually. Because my line_pos is a 16 bit value with ones representing the line and zeroes representing the rest, my middle is either non-existent or both, 8 or 9. Currently I assume error is zero for both, 8 and 9, which is sub-optimal, of course. I.e. I loose two potential pixels of resolution in the middle. Either I need to stuff the line in 15 pixels - from 128 (huh?) or find a way of treating the space between 8 and 9 :D.

Edmunds
 

Buzby

Senior Member
Hi edmunds,

Just a quick question, what is the physical width of your photo array ?

( I can't remember, did you post the part number somewhere ? )

Cheers,

Buzby
 

BESQUEUT

Senior Member
I have been scratching my head all along about how to implement '1/2 pixel' idea. Or in between pixels, actually. Because my line_pos is a 16 bit value with ones representing the line and zeroes representing the rest, my middle is either non-existent or both, 8 or 9. Currently I assume error is zero for both, 8 and 9, which is sub-optimal, of course. I.e. I loose two potential pixels of resolution in the middle. Either I need to stuff the line in 15 pixels - from 128 (huh?) or find a way of treating the space between 8 and 9 :D.

Edmunds
One way is to calculate the mean of dark pixels :
- For example, supposing a 128 pixels array is something like :
----------------BBBB-BBB-BBBBBBBB-BBBBBBB--------B-----------------------B---------------------------------------------------------------
There are 24 black pixels :
17, 18, 19, 20, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 40, 41, 50, 67
Sum is 762.
Mean is 31.75 witch can be considered as center of Black line,
Array center is obviously 64, so delta is 32.25

Note that, if mean is zero, you are offline...

Correction can be made proportional to delta.
Supposing servopos is 150 for straight, you can add (or substract) delta (or part of delta) for correction.
For exemple, for 1/2 delta steering :
servopos=166

if you are using a stepper, do not add correction at each loop !
- first loop : current servopos=150, needed servopos=166 ==> 16 steps
- second loop : current servopos=166, needed servopos=166 ==> 0 steps
- ...
- current servopos=166, needed servopos=158 ==> -6 steps


(numerical integration...)
 
Last edited:

hippy

Technical Support
Staff member
By imagining two pixels on the sides of the 128 physical pixels you have, you can use sets of 10 pixels to create a 13 bit value which represents the line position with a single bit set when centred -

Bit0 = -6
Bit1 = -5
:
Bit5 = -1
Bit6 = 0
Bit7 = +1
:
Bit12 = +6
Bit13 = unused
Bit14 = unused
Bit15 = Unused
 

AllyCat

Senior Member
Hi,

Just a quick question, what is the physical width of your photo array ? .. ( I can't remember, did you post the part number somewhere ? )
The diode array is 8mm long, as is the width of the line-scan (no optics). The Part number is TSL 1401 CCS form AMS. Quoted in this thread,

I've just received one from RS, but I'll post the "story" elsewhere to avoid going OT again. ;)

Cheers, Alan.
 

BESQUEUT

Senior Member
One way is to calculate the mean of dark pixels
If calculation is too long, you can use only even pixels :
18 20 22 24 26 28 30 32 36 38 40 50
sum is 364
mean is 30.33 , not so bad...

With odd pixels :
17 19 23 27 29 31 33 35 37 39 41 67
sum is 398
mean is 33.16

Note that if circuit is a 8, at a time all pixels will be black...
Mean will be 64, and you are going straight.

"Mean of black pixel" is an automatic vectorization algorithm.


The diode array is 8mm long, as is the width of the line-scan (no optics). .
If line is 2 mm width, 32 pixels are supposed to be black. It is important to know where is the center !
 
Last edited:

AllyCat

Senior Member
Hi,

I wasn't disputing that a centre-line calculation isn't desirable, only that (with the low optical resolution) it is not necessary to handle a half-pixel sensor resolution (30 um), nor single, isolated "bad" pixels (at least if the level-detection is working correctly). However, when calculating the brightness threshold, the width of the line should not be assumed constant. For example to handle branches/crossovers, and also the OP has previously said that he would (still) like to detect the line when one edge of the line is past the end of the sensor.

Cheers, Alan.
 

BESQUEUT

Senior Member
I wasn't disputing that a centre-line calculation isn't desirable, only that (with the low optical resolution) it is not necessary to handle a half-pixel sensor resolution (30 um)
I agree : some lens may add a forward seeing of the line, usefull if speed increase.
If line is 1 or 3 pixels wide it may be possible to drive center with no oscillation only by using 1/2 pixel calculation. But I agree : this is perfectionism.
Note that if a lens is used, this will reduce line width.

, nor single, isolated "bad" pixels (at least if the level-detection is working correctly). However, when calculating the brightness threshold, the width of the line should not be assumed constant. For example to handle branches/crossovers, and also the OP has previously said that he would (still) like to detect the line when one edge of the line is past the end of the sensor.

Cheers, Alan.
Black pixel averaging take care of all that.
If one edge of the line past the end of the sensor, the mean (IE center of line) will go either to zero or to 128,
and correction will be maximal.

You can have special action, for example reduce speed, if center is below 10 or past 118.
 

edmunds

Senior Member
Dear @Besquet,

Let me explain the current state of affairs a little bit.

To read the sensor, I'm using a lookup table. This helps the software to decide what to do with a reading of eight pixels = 1 byte of, say, %10001011. I tried managing that 'on the fly' and while the way I did it could be improved with marginal effect on speed, I do not want to go back that route. It was way slower than lookup from EEPROM. So I read every 8 pixels into a byte and read a value of 1 or 0 corresponding to the line data from EEPROM and store it as the corresponding bit of a word variable. So after the reading phase I get a line_state variable that contains 16 bits with clear, homogenous (no zeros between ones) image of the line. This is so reliable and fast, I would not like to touch that part at this point.

However, as described above, 16 bits do not really have a centre bit. It is either space between two bits that cannot be used for anything or two midmost bits. Same happens to the line if the exposure time happens to be such, that the line is an even number of bits wide. This gives a 1/8th of an error for the centre and 1/line width error for the line centre. Total error of over 1/8th of total range is possible then, which is probably about 1mm tops. It is possible, that this is the sole reason for the slight wiggling (difficult to see in the video, except on the branch-off crossing). Ideally, I would like to take care of this.

I think it is possible and there is no need for me to take everybody's time thinking aloud. I just need to sit with pen and paper in piece and quiet for some time :).


Thank you for your input,

Edmunds
 

edmunds

Senior Member
By imagining two pixels on the sides of the 128 physical pixels you have, you can use sets of 10 pixels to create a 13 bit value which represents the line position with a single bit set when centred -

Bit0 = -6
Bit1 = -5
:
Bit5 = -1
Bit6 = 0
Bit7 = +1
:
Bit12 = +6
Bit13 = unused
Bit14 = unused
Bit15 = Unused
Dear @hippy,

Thank you for your input, but I have no idea how to handle 10bit values in EEPROM efficiently. However, if I would discard 4 bits from each end, I would get 15 bits and probably could use the remaining bit as a flag of some sort (branching, off line, on line, line lost to left, line lost to right, line lost forward etc).


Thank you for your time,

Edmunds
 

edmunds

Senior Member
I have some ideas how to get much better optical resolution for line-following applications like this, but that's for another thread.
Dear Alan,

Thank you for your continuous input.
Maybe we should start threads 'Better Optical Resolution for Line-following Applications' and 'Driving Miniature Stepper Motors with Picaxe Microcontrollers'. :)


Edmunds
 

edmunds

Senior Member
Sweet. Great to see it come together. How does it know not to take the turnoffs it encounters (noting the little wobble at the left turnoff on the right-hand track)?

3) Something sounds wrong there regarding your macro problem. We would have to see a code example.
Please find the code below. Ready for pasting into PE6 and simulation.

Code:
#macro MultiplySigned(word1,word2)
	sign = positive
	if word1 >= $8000 then
		sign = NOT sign
		word1 = -word1
	endif
	if word2 >= $8000 then
		sign = NOT sign
		word2 = -word2
	endif
	Result = word1 * word2
	if sign = negative then
		Result = -Result
  endif
#endmacro

Symbol sign = bit0

Symbol word1 = w1
Symbol word2 = w2
Symbol Result = w3
Symbol temp = w4

Symbol ELine = w5
Symbol KpELine = w6

Symbol positive = 0
Symbol negative = 1

ELine = -70
KpELine = 20

MultiplySigned(ELine,KpELine)

if Result >= $8000 then
  temp = $FFFF - Result + 1
  sertxd ("Result = -", #temp, "; ")
else      
  sertxd ("Result = ", #Result, "; ")
endif
sertxd("ELine = ", #ELine, LF, CR)
It correctly executes multiplication, but ELine has changed its sign despite the fact I have not asked for it. Or so I believe :).

Code:
#macro MultiplySigned(word1,word2)
	sign = positive
	if word1 >= $8000 then
		sign = NOT sign
		word1 = -word1
	endif
	if word2 >= $8000 then
		sign = NOT sign
		word2 = -word2
	endif
	Result = word1 * word2
	if sign = negative then
		Result = -Result
  endif
#endmacro

Symbol sign = bit0

Symbol word1 = w1
Symbol word2 = w2
Symbol Result = w3
Symbol temp = w4

Symbol ELine = w5
Symbol KpELine = w6

Symbol positive = 0
Symbol negative = 1

ELine = -70
KpELine = 20

word1 = ELine : word2 = KpELine
MultiplySigned(word1,word2)

if Result >= $8000 then
  temp = $FFFF - Result + 1
  sertxd ("Result = -", #temp, "; ")
else      
  sertxd ("Result = ", #Result, "; ")
endif
if ELine >= $8000 then
  temp = $FFFF - ELine + 1
  sertxd ("ELine = -", #temp, "; ")
else      
  sertxd ("ELine = ", #ELine, "; ")
endif
Adding word1 = ELine : word2 = KpELine fixes the problem, but is weird and defeats the purpose of parameters.

The macros would compile, but not the call, unless a dummy variable name is put in the parenthesis.

Code:
#macro PulseSomePin()
  Pulsout B.0, 10
  pauseus 5
#endmacro


PulseSomePin()
If these can be explained, I would be most grateful.

Thank you,

Edmunds
 
Last edited:

edmunds

Senior Member
Just an update, before I call it a day.

I managed to increase the speed more than twofold to approx. 20 scale km/h by cleaning things up into macros instructions instead of gosubs and reducing safety overheads taking out pauses and making pulses shorter.

I think there is space for about that much more improvement remaining. Also, there must be some more juice to squeeze from the algorithm. I experimented a little bit with non-linear response - i.e. taking correction values from a lookup table, where they are progressively bigger depending on the error. Sort of exponentially. That made things much worse, but more testing is needed to see if this was because the direction of the exponent was wrong or because values were not scaled properly. More work it is.

If I can get to scale 50km/h, I need no more, this would be super cool. 30km/h would be acceptable, so I'm almost there before I call it a 'code snippet' :D.

I also used the method suggested by @hippy to measure the time of the loop. The worst case scenario (the greatest correction needed) takes about 60us. I checked many times. Can it be that fast?


Thank you for your inputs,

Edmunds
PS If somebody could help me solve the macros puzzle above, this would win me some loop time ;)
 
Top