heliostat project


I’m trying to build a heliostat using an openmvcam. A heliostat is a device that tracks the sun and employs a mirror to reflect it to the same target through the day. Basically I will attach a Mirror to a 2 axis (pan/tilt) mount and let the camera take control over the servos. The camera will face the mirror and makes sure that the sun always stays in the center of the frame. So the camera in the same direction as the target.

So far I attached the openmvcam to a 2 axis gimbal mount (very cheap one I have to say)

I used a lamp for my model sun for now, and attached a Kodak Wratten #88A Filter to the lens. This is an infrared-pass sensor that will only let wavelengths above 800nm or so through. I thought this might make the image simpler.

The code I took from various threads and the blob detection example. (THANKS!)

import sensor, image, time

grey_threshold = (50, 255)
# You may need to tweak he above settings for tracking green things...
# Select an area in the Framebuffer to copy the color settings.

sensor.reset() # Initialize the camera sensor.
sensor.set_framesize(sensor.QQVGA) # use QQVGA for speed.
sensor.skip_frames(10) # Let new settings take affect.
sensor.set_whitebal(False) # turn this off.
clock = time.clock() # Tracks FPS.
sensor.set_vflip(True) #invert display camera is mounted upside down!

from pyb import Servo

#x_pos = 1200 # default
#y_pos = 1200 # default

x_min = 1000
x_max = 1800
y_max = 1800
y_min = 1000

x_gain = 1.00 # You have to tweak this value to stablize the control loop.
               # You also may need to invert the value if the system goes
               # in the wrong direction.
y_gain = -2.00 # You have to tweak this value to stablize the control loop.
               # You also may need to invert the value if the system goes
               # in the wrong direction.
xServo = Servo(1)
yServo = Servo(2)



while (True):
    img = sensor.snapshot()
    blobs = img.find_blobs([grey_threshold])
    if blobs:
        for b in blobs:

            img.draw_cross(b[5], b[6])

            x = b[5]
            y = b[6]

            x_error = x - (img.width()/2)
            y_error = y - (img.height()/2)

            x_pos += x_error * x_gain
            y_pos += y_error * y_gain

            # Clamp output between min and max
            if (x_pos > x_max):
                x_pos = x_max
            if (x_pos < x_min):
                x_pos = x_min

            # Clamp output between min and max
            if (y_pos > y_max):
                y_pos = y_max
            if (y_pos < y_min):
                y_pos = y_min


right now it will track a lamp perfectly ok in X dimension but if I activate Y (up and down) everything will crash and the mount moves violently. The cam actually gets dismounted from linux. If I manually set pulses in a test script the mount moves well in both directions. I don’t know if i am missing something. I’ve tried different y_gain variables but I don’t see any improvements.

Any help would be appreciated, even if you guys tell me to get a better servo mount :wink:

Hi luik,

Have you determined your servo mins and maxes? If so, then I’m guessing the PID control loop for the Y channel is inverted. So, please remove the “-” sign from the y_gain value and set the gain to like 0.1 and then increase it steadily until the system stabilizes and has the performance you want.

thanks for the quick answer.

Now i got y_gain at 0.6 and the cam basically turns away from the light. But the system is stable now. I don’t get it :confused:

edit: I think I got the mins and maxes right

Okay, then make the gain -0.6.

The code is doing a P control algorithm. So, you have to tune things carefully.

When values are way above what they should be then the P control loop is very violent.

ah I see. thank you again!

I have them both X and Y running independently running very good, but once I activate both of them there seems to be some discrepancy between them, as soon as I move the lamp in two directions (eg. up and right) at once → violent movement and crashes again. Do I have to fiddle a little more on the y and x_gains or might this be another problem?

Try lowering the gains more. They both interact with each other. Like, lower each to 0.1 and then up them from there.

Okey, I’ll do that. That will take a while to figure out, I guess.

could it also be that the voltage supplied by the board is to small for both servos? Might they run smoother if I would go and use an external 5v power source?

The OpenMV cam doesn’t supply voltage to the servos. So, I’m not sure how they are being powered right now. But, servos directly connected to the VIN of the board can cause brown outs. So, if possible please give the servos their own power supply that is separate from the Cam. Just make sure to share grounds.

okey, now that I use external 5V power it works. Not very smooth, but it runs very stable, no crashes anymore.

sorry, I guess those are real beginner’s mistakes

Alright, go back to playing with the P gains then. If you want to make the algorithm better look up the term PID control and search for example code.


I tried it out yesterday as the sun was out for a moment, but the system had a hard time telling the sun from lensflares and other reflections in the frame that where above the 250 Brightness minimum. I tried to address this problem by replacing the lens with a simple pinhole made from tin foil to get rid of lensflares, but I’m afraid other reflections might still cause problems. Now, is there any way to manipulate the exposure of the camera so extensively that only the sun will be visible (a drastic underexposure)? I’ve tried to improve simplicity by setting the sensor.set_contrast(+3) and sensor.set_brightness(-3) but it doesn’t seem to really help. I thought that the pinhole might also help this problem, but evidently the camera is very light sensitive and the exposure programme is designed to automatically average the exposure.

sensor.set_exposure_cntrl(False) returns AttributeError: ‘module’ object has no attribute ‘set_exposure_cntrl’

I just tried out exposure_ctrl and it works, but doesnt really help me either. (It says cntrl in the docs ) :slight_smile:

Hmm, so, I’m not quite sure how you can get rid of lens flare. That said, if you want to play with the exposure settings please use the register read and write commands to directly manipulate regs in the system. Don’t use the set_exposure_cntr. That function is just some left over cruft.

Here’s the camera datasheet: http://www.haoyuelectronics.com/Attachment/OV7725%20+%20AL422B(FIFO)%20Camera%20Module(V1.0)/OV7725%20datasheet(V1.2).pdf.

I have another document that has details on how to actually mess with exposure settings. However, I can’t post it online. So, contact me via email privately. kwagyeman @ you know what.


I think I found a solution to my problem: Instead of watching the mirror, the camera will now watch the target. This will get rid of the lensflare problem and also solve the exposure problem, because there shouldn’t be any other IR lightsources or reflexes in the area that is to be lit by the heliostat :slight_smile:

I will now proceed to write a fallback loop in case the system looses track of the sun. For better mechanics and overall smoothness, I ordered the SPT400 and matching Gearbox from Servociy. The new mirror will be cut out from mirror polished V2A steel.

Need anything from me? BTW. A new version of OpenMV IDE will be out soon.

Sounds great! Actually I have one more question. I copied my code to the board and tried to power the cam with the powersupply. The board just gave me the blue heartbeat, the programm did not start. The same happens if I use a Phone Usb charger. If I just plug it into the usb port of my computer the system starts working. What could I be missing?

Hi, can you verify that the script was copied to the board? The blue heart program should go away once you copy he script. See the tools - save script to board function.

The script should replace main.py BTW.

Make sure you remove the storage before disconnecting the cam (umount on Linux or “Safely Remove” on Windows).

I got it now, I thought the main script has to stay. sorry ^^