Code optimisation ideas needed (again)

edmunds

Senior Member
Dear all,

Here is the last bit of code of my line-follower program that looks bulky compared to other pieces now. This is used to 'calibrate' the exposure time for photodiode array to different light conditions. As for testing, I'm running it only once at the start of the program and that is sufficient because lighting conditions do not change. In real life application, however, I would have to run it more often than that, so being able to run it as fast as possible is important.

One obvious optimisation is decreasing the number of pixels I read the value of - probably reading 16 pixels or every 8th pixel and then averaging those would be as good as averaging all 128. But I would have to run clock all 128 times anyway, so I'm not sure how to achieve any real speed improvements with that.

The second challenging area is the long select ... case statement with two's complement mathematics. I have a suspicion some OR binary operation could be quicker, but not there yet to figure it out. I will be working with this project tonight and some time tomorrow, so ideas and directions welcome, I will try them out and report back. And also post if come up with something myself while posting this :).

Code:
'Byte variables
Symbol pixel_data = b4            'Data from sensor [memory for testing]
Symbol pixel_mask = b5           'Decoded 8 pixel data
Symbol line_edge = b6             'Rightmost edge of the line
Symbol line_width = b7            'Width of the line in 16th of the total width
Symbol line_pos = b8               'Position of the line centre with 16 position resolution
Symbol counter2 = b9              'Just a counter, #2

'Word variables
Symbol Tint = w27                  'Integration time adjustable part
Symbol Vout = w26                 'Voltage on AO pin
Symbol EVout = w25               'Voltage on AO pin error, deviation from Vtarget
Symbol ELine = w24                'Line position error
Symbol Counter = w23            'Just a counter, #1
Symbol line_state = w22         'Sensor output, normalized to fit into 16bit word

'Constants
Symbol Vtarget = 198              'Target voltage on AO pin, two's complement, so actually 70
Symbol KpEVout = 30              'P coefficient/multiplier for Vout error correction
Symbol KpELine = 50               'P coefficient/multiplier for line position error correction, 10 means 1

Cal_exposure:
  do                                                     'Big calibration loop, exit only when Vout is good
    high SI : pulsout CLK, 10 : low SI    'Pulse SI to end the scan/start the next scan
    pwmout CLK, 1, 4                            'Clock out some fast pulses (8MHz @ 64MHz clock speed
    pauseus Tint                                   'Allow for sufficient integration time
    pwmout CLK, OFF                           'Stop the clock
    high SI : pulsout CLK, 10 : low SI    'Pulse SI to end the scan/start the next scan

    Vout = 0                                          'Make Vout zero for a new cycle
    for counter = 0 to 127                     'Read 128 pixels ...
    	readadc 10, pixel_data                  '... with 8 bit ADC ...
    	pulsout CLK, 10                             '... pulse the clock as fast as possible to read the next pixel ...
    	Vout = Vout + pixel_data               '... add to Vout for average calculation later on ...
    next counter                                    '... continue until done with all pixels
    Vout = Vout / 128                           'Average value first
    Vout = Vout max 127                      'Limit to 127, not interested in near saturation region
    EVout = Vtarget - Vout                    'Vtarget is expressed in two's complenet to get a signed value
  
    select case EVout                              'Go to normal from two's complement numbers
    case 128 : EVout = 0                         '128 means 0
    case < 128                                        'Means a negative number
    	EVout = 128 - EVout                        'Get the magnitude of error
    	Evout = EVout * KpEVout / 10          'Calculate correction signal
    	if Tint > EVout then                         'Prevet overflow below zero
        Tint = Tint - EVout                         'Correct integration time with the correction signal
    	else
    		Tint = 0
    		gosub Too_dark
    	endif
    case > 128                                        'Means a positive number
    	EVout = EVout - 128                        'Get the magnitude of error
    	Evout = EVout * KpEVout / 10          'Calculate correction signal
    	if Tint < 2600 then                          'Prevet overflow to values that give saturation - might have to correct if read_data can be speeded up.
    	  Tint = Tint + EVout                        'Correct integration time with the correction signal
    	else
    		Tint = 2600
    		gosub Too_bright
    	endif
    endselect
  loop until EVout < 3                            'Set error corridor here
return
Thank you for your time,

Edmunds
 

hippy

Technical Support
Staff member
Here is the last bit of code of my line-follower program that looks bulky compared to other pieces now.
Does it work ? If so then, other than it looking bulky, where is the need to optimise it ? Are you trying to make it look better, use less memory or execute faster ?
 

edmunds

Senior Member
Does it work ? If so then, other than it looking bulky, where is the need to optimise it ? Are you trying to make it look better, use less memory or execute faster ?
It does work. Execute faster. Sorry for not being clear.

Edmunds
 

hippy

Technical Support
Staff member
You can inline all your calculations ...

Code:
Vout = Vout / 128
Vout = Vout max 127
Code:
Vout = Vout / 128 max 127
--

Code:
EVout = 128 - EVout
Evout = EVout * KpEVout / 10
Code:
EVout = 128 - EVout * KpEVout / 10
--

Code:
EVout = EVout - 128
Evout = EVout * KpEVout / 10
Code:
EVout = EVout - 128 * KpEVout / 10
 

AllyCat

Senior Member
Hi,

I would have to run clock all 128 times anyway, so I'm not sure how to achieve any real speed improvements with that.
You could use the PWM as the clock, just as you do for the integration time. Start the PWM running at a suitable (experimentally determined) rate and then use a sequence of READADCs .. @ptrinc using either inline code (fastest) or a simple loop. The READADCs don't need to be synchronised to the clock and you can choose the PWM frequency to fit the 128 cycles into the number of samples that you want to read.

the long select ... case statement with two's complement mathematics.
Personally I wouldn't use negative numbers, what really needs to be done is to "scale up" or "scale down" the integration time, i.e. multiply by a value greater or less than unity. That's not difficult with an X2 if you consider 256 as representing "1" and use the */ operator. So if you want to scale up by +10% (i.e. by 282 / 256) you use */ 282 . Conversely, if a measured value is 10% too large then multiply by 256 / 282 by calculating 65535 / 282 and use that to */ the value to be scaled.

I guess there's some trial and error in the values in your algorithm, but the way I would calculate it is as follows:

If you are using a digital input to "slice" an analogue input then you need to know the corresponding analogue value. It's probably around 1.25 volts so if the ADC is referenced to 5 volts then it would correspond to about 64 with readadc (or 256 with readadc10).

Next, (to tolerate variable line widths) sort the analogue data into "white" and "black" groups and calculate each average (i.e. sum each group and divide by the number in each group). Then calculate the average of the white and black levels to give the present "threshold" level. Update the integration time (scaled down to a max of 255 if necessary) by */ the result of (65535 / present-threshold).

You may need to "trap" sensor saturation initially, but the algorithm above should automatically limit below 128 = white because black cannot be less than zero and the target average (threshold) is around 64.

Cheers, Alan.
 

lbenson

Senior Member
In practical terms, why would the count-the-bits method not work? If you know how wide the line is, once you find an edge (and you know which way you are scanning), you can stop scanning and get very close to the middle just by adding half of the line width.

Is it because you may have crossing (intersecting) lines? How long does it take to complete a scan? How far does the vehicle move in that time? What's the sharpest turn that's possible in that time; how far from the previous center can the vehicle have moved in that time? Can you speed up the processing without significant loss of accuracy by just processing every other bit (so, shortest possible clock, clock again, read and process)? Can you slow the vehicle down further and log and graph what you are reading?

I don't see how the vehicle can swerve off to the extent your video shows without there being an algorithm or timing problem.
 

inglewoodpete

Senior Member
You can save about 6 bytes and get a small improvement in execution speed by removing the superfluous decision point in your Case statement and replace it with "Else"
Code:
[color=Blue]case [/color][color=DarkCyan]> [/color][color=Navy]128            [/color][color=Green]'Means a positive number[/color]
 

hippy

Technical Support
Staff member
The problem we are trying to solve is quite simple; "Find an exposure time which, when used and the sensor is over a line, will have those pixels over the line digitally read as high, the rest as low".

It seems to me one wants to end up with an exposure time which maximises the over a line and otherwise difference, keeping the over above the Vih voltage and the low below Vil voltage.

I am not entirely convinced that reading the pixels as analogue and using an average of those readings can determine an exposure time to achieve the digital results expected.

Perhaps an explanation of the theory and algorithm is in order.
 

BESQUEUT

Senior Member
I don't see how the vehicle can swerve off to the extent your video shows without there being an algorithm or timing problem.
Or a problem with the stepper used for steering...
There is no reference point to know how to go straight ahead...
How is used the "correction signal" ?
 

edmunds

Senior Member
As for a popular request, I have just added a line position LED indicator on top of the car.

The project was a lot of fun, so I'm posting a few pictures here. I have a new video coming up to the other thread and while it proved what I already knew (i.e. the sensor is seeing the line correctly) the finer print is giving a lot of clues on what is wrong. On some of it Alan has overtaken me in the other thread, while I'm writing this one, but I will have to attend it properly later in the night. Need to run some errands now. Back to the LED indicator.

I was reasonably terrified of running 8 or even 16 wires to a matching number of LEDs directly from picaxe. Maybe you can imagine the pins are not so easy to get to and monitoring many more hair-thin wires than I already have for breakages is not what I'm after. My favourite I/O expander solution - I2C would introduce lots of complexity, pull-up resistors and having to get to pins that would really be hard. So I was looking in the old PCB drawers for a shift register of sorts and found 74HC138 which is a 3to8 signal encoder on one old PCB from something. Soldered it away and put the diodes right on top of it. Single current limiting resistor and decoupling cap was all the passives required. The most difficult part was a LED that has a pin on the other side than the rest, but after a few attempts it worked. Some small wires, epoxy on top and it is possible I have the 'smallest integrated home made 8bit LED indicator ever made' :D.

For the software part I ran the address pins to the matching A.0-A.2 pins on the picaxe and used a lookup table (again, my new concept :)) that is fed a line position variable and receives a 3bit code for the corresponding LED.

LED_Array_BOT.jpg LED_array_TOP.jpg

Edmunds
 
Top