Here is my first attempt at making a video of this all working:
I tried to power the motors using the motor driver on the flip side, I got weird results until I unplugged the ICSP header. After that everything worked as expected except when the power supply was turned off for some reason the motor drivers stopped working.
I have a problem of not having enough pins broken out.
For the VEML7700, I downloaded the following Adafruit libraries:
https://github.com/adafruit/Adafruit_VEML7700
https://github.com/adafruit/Adafruit_BusIO
For some reason I also needed to include the SPI.h file otherwise it would not compile.
Adding the VEML7700 code somehow makes the original code no longer work… I should now try with a vanilla arduino just to make sure the test code works.
The VOUT voltage sensing appears to work. This is a nice option because it means that just by taking the integral of the VOUT we can see if the solar panel position is getting more light (i.e. no need for multiple light sensors if we’re willing to spend some energy on motors in the spirit of trail and error sensing).
I have noticed that LDO is not stable at 2.2V but dips to below 2V. I’m not sure how related this is to the power supply being connected to the LTC3105.
I am working on a new minimal iteration of the sun seeking functionality.
Here is another 3D model I’m working on. The idea is that it cannot destroy itself by accident (it can turn all the way around in both axes and not break anything). It is also independent of whatever surface it is placed on. The bearing at the center allows everything to rotate freely.
Here are some sketches of me trying to figure out how to model the rotary thing:
The resin prints were pretty disastrous… The bearing didn’t fit and neither did the motors or the central axis pin! After some filing and hot gluing things fit a bit better.
Here is the full assembly. It’s extremely inelegant. Here are some issues:
-stiff wires.
-the weight of things…Everything is straining the central axis pin, and the main mast supporting the solar panel board is not being held straight up.
-The tilt arm is hitting the rotary gear, it needs to be smaller.
-everything is incredibly fragile and finicky.
Replacing the mast with just a simple arm is less inelegant:
Of course now the machine can destroy itself by just turning into the board hits something…It also needs a better solution for the solar panel being attached to the board somehow that can be easily removed for programming.
The main axis which fits into the bearing is still problematic.
This beautiful object was made by Mr. Paulo Salvagione at Pier 9. This would make a better base for the rotating part of this project I think. It could be made so that it fits the motor very snugly with no play.
I’ve decided to make my life easier and to go towards a more obvious solution for the next iteration. It will at least give me a chance to see how the balance of the object behaves over the course of a day. I’ve printed this version before in PLA but never in resin.
I’m taking the offset of .1mm all around to make the motors fit snugly.
Here is the Arduino code I have developed so far:
/*
*/
const int pgood = 3;
const int cap_voltage = A2;
const int delay_time = 100;
const int motor_time = 500;
float cap_reading_0;
float cap_reading_1;
float cap_reading_2;
float cap_reading_3;
float cap_reading_4;
float cap_reading_average;
int integral_0;
int integral_1;
int integral_2;
int integral_3;
int integral_4;
int integral_total;
int current_reading;
int last_reading;
// the setup function runs once when you press reset or power the board
void setup() {
// motor driver pins
pinMode(A4, OUTPUT);
pinMode(A5, OUTPUT);
pinMode(A0, OUTPUT);
pinMode(7, OUTPUT);
pinMode(cap_voltage, INPUT); //
pinMode(pgood, INPUT); //
}
// the loop function runs over and over again forever
void loop()
{
if (pgood==HIGH)
{
evaluate_east_west();
evaluate_north_south();
}
}
int evaluate_east_west()
{
last_reading = integral();
clockwise();
delay(motor_time);
current_reading = integral();
if(last_reading>current_reading) // move back if less sun in new position
{
counter_clockwise();
delay(motor_time);
}
last_reading = integral();
counter_clockwise();
delay(motor_time);
current_reading = integral();
if(last_reading>current_reading) // move back if less sun in new position
{
clockwise();
delay(motor_time);
}
}
int evaluate_north_south()
{
last_reading = integral();
tilt_up();
delay(motor_time);
current_reading = integral();
if(last_reading>current_reading) // move back if less sun in new position
{
tilt_down();
delay(motor_time);
}
last_reading = integral();
tilt_down();
delay(motor_time);
current_reading = integral();
if(last_reading>current_reading) // move back if less sun in new position
{
tilt_up();
delay(motor_time);
}
}
int cap_voltage_read()
{
cap_reading_0 = analogRead(cap_voltage);
delay(delay_time);
cap_reading_1 = analogRead(cap_voltage);
delay(delay_time);
cap_reading_2 = analogRead(cap_voltage);
delay(delay_time);
cap_reading_3 = analogRead(cap_voltage);
delay(delay_time);
cap_reading_4 = analogRead(cap_voltage);
delay(delay_time);
return cap_reading_average = (cap_reading_0 + cap_reading_1 + cap_reading_2 + cap_reading_3 + cap_reading_4) / 5;
}
int integral()
{
integral_0 = (int)cap_voltage_read(); // convert to int
delay(delay_time);
integral_1 = (int)cap_voltage_read(); // convert to int
delay(delay_time);
integral_2 = (int)cap_voltage_read(); // convert to int
delay(delay_time);
integral_3 = (int)cap_voltage_read(); // convert to int
delay(delay_time);
integral_4 = (int)cap_voltage_read(); // convert to int
delay(delay_time);
return integral_total = integral_0 + integral_1 + integral_2 + integral_3 + integral_4;
}
int clockwise()
{
digitalWrite(A5, HIGH); //
digitalWrite(A4, LOW); //
digitalWrite(A0, LOW); //
digitalWrite(7, LOW); //
}
int counter_clockwise()
{
digitalWrite(A5, LOW); //
digitalWrite(A4, HIGH); //
digitalWrite(A0, LOW); //
digitalWrite(7, LOW); //
}
int tilt_up()
{
digitalWrite(A5, LOW); //
digitalWrite(A4, LOW); //
digitalWrite(A0, HIGH); //
digitalWrite(7, LOW); //
}
int tilt_down()
{
digitalWrite(A5, LOW); //
digitalWrite(A4, LOW); //
digitalWrite(A0, LOW); //
digitalWrite(7, HIGH); //
}
The current issue I’m having is with PGOOD which is for some reason hovering around 1V instead of 2.2V. When I remove the PGOOD test things appear to work.
Here is the resin print, it’s not bad! I think I will turn the top arm outwards so that no matter what happens the machine won’t destroy itself (at least during the testing phase).
And here is with the board attached:
And here is a video of the thing rotating:
PGOOD is not working so I’m just making the code test the voltage of the capacitor when it wakes up. I’ve also added a motor_stop() function because I forgot to stop the motors turning during the time consuming voltage checking functions. This appears to work.
void loop()
{
if(((int)cap_voltage_read()) > 410)
// this is 1/3 – 20K/10K R divider of 2.7V out of a 2.2V LDO scale, mapped into 1023
{
evaluate_east_west();
evaluate_north_south();
}
}
I was noticing that there was considerable leakage from the capacitor without serious sun. I decided this was probably from having the motor driver sleep pin connected to PGOOD. I have disconnected PGOOD from the micro-controller, and now used that pin to turn on and off the motor sleep. If this doesn’t solve the problem the other candidate is the leakage from the resistor divider which could be replaced by a mosfet as per the JeeLabs zero current measuring post. This would require another pin from the microchip and a bit more space on my already crammed board…
(The next version of this board may need to be manufactured at a board house, not being able to easily make two sided boards is becoming very frustrating. I also want to use the fancy RF connectors and antennae I ordered.)
Hunting for the cause of the leaking current. The voltage seems to stabilize at 1.29V, not sure what that tells us though..
I’m using our super fancy Keithley DMM7510 7 1/2 Digit Multimeter. Check it out:
I added sleep mode to the code and now once the voltage hits 2.6V it stays essentially stable. (The motor driver uses 1.6-2.5uA @ 5V, the atmega 328p 1uA @ 3V, and the 3V/30K voltage divider draws 100uA.)
Already mentioned blog posts from Jeelabs on measuring battery voltage:
https://jeelabs.org/2013/05/15/what-if-we-want-to-know-the-battery-state/
https://jeelabs.org/2013/05/16/measuring-the-battery-without-draining-it/
https://jeelabs.org/2013/05/17/zero-powe-battery-measurement/
I’m wondering if I want to make things easier and have four mini solar panels and two comparators like this (http://www.ti.com/lit/ds/sbos589/sbos589.pdf) so that only two pins are required to determine which direction to move in. I could also possibly use the BEAM solarbotics “suspended bicore” solution (http://www.beam-online.com/Robots/Circuits/circuits.html#Phototropic%20Bicore).
There is a flaw in my system, if suddenly the sun comes out at the perfect time, the robot can think that turning away from the sun led to finding more sun whereas in fact it was just the sun that changed. I could solve this by having the robot do multiple experiments before committing to actually changing its angle.
I should also include in the code a moment where the machine decides if the difference between the past and current reading is sufficiently great to necessitate moving.
I have another problem. There is some give in the gearmotors, therefore they do not turn the same amount when they switch directions. This is bad but it’s critical when combined with the code not working very well. This is more an issue for the tilt motor, so if I could replace this with an earlier design rotating a metal rod and using a mast to take the weight of the board perhaps that would solve my problem.
I’m hoping I wouldn’t need these: https://www.pololu.com/product/4761…
Just realized my code is not doing what I want it to do. The integral() function is absolute, it is adding bigger and bigger numbers instead of looking at the difference between relative values…Fixed this.
I find the machine spends a lot of time moving back and forth between the same positions. It is rarely deciding that a new orientation is better than an old. I should get the radio set up so I can see the values that are being sent.
I also need to replace the tilt mechanism with one that holds the weight of the board like the mast/rotation system I tried earlier. I should also do east/west rotation before moving on to doing both axes at once.
Did a quick test, analogRead() has 10 bit resolution, giving a value between 0-1023. The resolution is barely good enough for taking a reading every 10 seconds and seeing a dropping voltage value in the cap. I think I should be taking measurements every few minutes to be able to detect a difference in charging.
I think I need to turn the radio off for long delays, otherwise it doesn’t work. I’m trying radio.sleep().
I also realized it would make far more sense to sense the solar panel voltage rather than the capacitor voltage to sense the solar aspect. However this would add another input pin…
*I think I might need to update the below code to include the floor() function which stops arduino from rounding? https://www.nongnu.org/avr-libc/user-manual/group__avr__math.html#ga0f0bf9ac2651b80846a9d9d89bd4cb85
Here is the far from optimal code so far:
// Include the RFM69 and SPI libraries:
#include <SPI.h>
#include <avr/wdt.h>
#include <RFM69.h>
#include <avr/sleep.h>
#include “LowPower.h”
// Addresses for this node. CHANGE THESE FOR EACH NODE!
#define NETWORKID 0 // Must be the same for all nodes
#define MYNODEID 2 // My node ID
#define TONODEID 1 // Destination node ID
// RFM69 frequency, uncomment the frequency of your module:
//#define FREQUENCY RF69_433MHZ
#define FREQUENCY RF69_915MHZ
// AES encryption (or not):
#define ENCRYPTKEY “TOPSECRETPASSWRD” // Use the same 16-byte key on all nodes
// Use ACKnowledge when sending messages (or not):
#define USEACK true // Request ACKs or not
// Packet sent/received indicator LED (optional):
#define LED A3 // LED positive pin
#define PGOOD 3 // PGOOD
#define MOSFET 9//
// Create a library object for our RFM69HCW module:
const int cap_voltage = A2;
const int delay_time = 100; //30 x delay_time (?) is how long it takes to take an integral of cap voltage
const int long_delay_time = 7000; //30 x delay_time (?) is how long it takes to take an integral of cap voltage
double T;
const int motor_sleep = 3;
const int motor_time = 200;
int current_reading;
int last_reading;
RFM69 radio;
void setup()
{
// Open a serial port so we can send keystrokes to the module:
pinMode(LED,OUTPUT);
digitalWrite(LED,LOW);
pinMode(MOSFET,OUTPUT); //mosfet
// pinMode(PGOOD, INPUT); // PGOOD
// motor driver pins
pinMode(A4, OUTPUT);
pinMode(A5, OUTPUT);
pinMode(A0, OUTPUT);
pinMode(7, OUTPUT);
pinMode(motor_sleep, OUTPUT);
pinMode(cap_voltage, INPUT); //
}
void loop()
{
wdt_enable(WDTO_8S);
digitalWrite(MOSFET, LOW); // turn off MOSFET
if(((int)cap_voltage_read()) > 410)
{
digitalWrite(motor_sleep, LOW); // sleep motor driver
Radio_Send(); //includes init and mosfet turn on
digitalWrite(MOSFET, LOW); // turn off radio
T = integral();
Radio_Send();
digitalWrite(MOSFET, LOW); // turn off MOSFET
evaluate_east_west();
}
else
{
digitalWrite(motor_sleep, LOW); // sleep motor driver
digitalWrite(MOSFET, LOW); // turn off MOSFET
sleep(); // sleep microchip
}
}
int evaluate_east_west()
{
last_reading = integral();
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
motors_off(); //also sleeps driver
wait_long();
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
clockwise();
delay(motor_time);
motors_off();
current_reading = integral();
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
if(last_reading>=current_reading) // move back if less sun in new position
{
counter_clockwise();
delay(motor_time);
motors_off();
}
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
wait_long();
last_reading = integral();
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
counter_clockwise();
delay(motor_time);
motors_off();
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
delay(long_delay_time);
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
current_reading = integral();
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
if(last_reading>=current_reading) // move back if less sun in new position
{
clockwise();
delay(motor_time);
motors_off();
}
digitalWrite(motor_sleep, LOW);
}
int Radio_Send()
{
char Tstr[10];
digitalWrite(MOSFET, HIGH); // turn on MOSFET
delay(100);
radio.initialize(FREQUENCY, MYNODEID, NETWORKID);
radio.setHighPower(); // Always use this for RFM69HCW
// Turn on encryption if desired:
radio.encrypt(ENCRYPTKEY);
char buffer[50];
dtostrf(T, 5,5, Tstr);
static int sendlength = strlen(buffer);
sprintf(buffer, ” T:%s”, Tstr);
radio.sendWithRetry(TONODEID, buffer, sendlength);
Blink(LED,100);
}
int integral()
{
int integral_0;
int integral_1;
//int integral_total;
int integral_total_1;
integral_0 = (int)cap_voltage_read(); // convert to int
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
wait_long();
wait_long();
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
integral_1 = (int)cap_voltage_read(); // convert to int
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
integral_total_1 = integral_1 – integral_0;
return integral_total_1;
}
int cap_voltage_read()
{
float cap_reading_0;
float cap_reading_1;
float cap_reading_2;
float cap_reading_3;
float cap_reading_4;
float cap_reading_average;
cap_reading_0 = analogRead(cap_voltage);
delay(delay_time);
cap_reading_1 = analogRead(cap_voltage);
delay(delay_time);
cap_reading_2 = analogRead(cap_voltage);
delay(delay_time);
cap_reading_3 = analogRead(cap_voltage);
delay(delay_time);
cap_reading_4 = analogRead(cap_voltage);
delay(delay_time);
return cap_reading_average = (cap_reading_0 + cap_reading_1 + cap_reading_2 + cap_reading_3 + cap_reading_4) / 5;
}
void Blink(byte PIN, int DELAY_MS)
// Blink an LED for a given number of ms
{
digitalWrite(PIN,HIGH);
delay(DELAY_MS);
digitalWrite(PIN,LOW);
}
void sleep(void)
{
digitalWrite(MOSFET, LOW); // turn on MOSFET
set_sleep_mode(SLEEP_MODE_PWR_DOWN); //select PWR DOWN, the most power savings
sleep_enable(); //set SE bit
sei(); // enable global interrupts
sleep_cpu(); //actually sleep
sleep_disable(); //code reaches this point after interrupt
}
int motors_off()
{
digitalWrite(motor_sleep, LOW); //put driver to sleep
digitalWrite(A5, LOW); //
digitalWrite(A4, LOW); //
digitalWrite(A0, LOW); //
digitalWrite(7, LOW); //
}
int clockwise()
{
digitalWrite(motor_sleep, HIGH); //wake up driver
digitalWrite(A5, HIGH); //
digitalWrite(A4, LOW); //
digitalWrite(A0, LOW); //
digitalWrite(7, LOW); //
}
int counter_clockwise()
{
digitalWrite(motor_sleep, HIGH); //wake up driver
digitalWrite(A5, LOW); //
digitalWrite(A4, HIGH); //
digitalWrite(A0, LOW); //
digitalWrite(7, LOW); //
}
int tilt_up()
{
digitalWrite(motor_sleep, HIGH); //wake up driver
digitalWrite(A5, LOW); //
digitalWrite(A4, LOW); //
digitalWrite(A0, HIGH); //
digitalWrite(7, LOW); //
}
int tilt_down()
{
digitalWrite(motor_sleep, HIGH); //wake up driver
digitalWrite(A5, LOW); //
digitalWrite(A4, LOW); //
digitalWrite(A0, LOW); //
digitalWrite(7, HIGH); //
}
int wait_long()
{
for(int i=0;i<10;i++)
{
wdt_reset(); // do this before 8 seconds has elapsed so we don’t reset
digitalWrite(motor_sleep, LOW); //put driver to sleep
digitalWrite(MOSFET, LOW); // turn off radio MOSFET
LowPower.idle(SLEEP_4S, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_OFF,
SPI_OFF, USART0_OFF, TWI_OFF);
}
}