Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
| |||||
Hand tools and fabrication machines | ||||||
![]() |
|
The first and second parts to this story.
So it's now offical, I'm in love with the ESP8266. My god these little fellas can really go like a cut snake on hot sand. So no surprise the the third installment (revolutions) will involve a nodemcu. Now, I have been practicing between drinks so I'm doing a little better coding wise.
So built in reliable WiFi - No problems. Stand alone configuration mode and AP wifi client - No problems. PWM yeah bring it on. In short everything you need to make the solar tracker that has an NTP client and RTC.
So at the end of the second part of the story I was left with a partial success. It all worked but like it just wasn't right. Cant put my finger on what it was but I felt it could be better. I had way better things to do but found myself porting the tracker code to the ESP8266 in the Arduino IDE. It actually took longer to work out the OLED display and wire the items up than it took to port the code.
So not to long after I started I had the smallest tracking computer I had ever made.
I'm being much more conscious of power budget this time, taking measurements at each step rather than at the end. I can also put this CPU to sleep if I wish, however not much point if I have enough power or if I need to keep it warm in winter. I'm being mindful that it needs to fit in my 7 AH power budget but this is at 24 V this time. So at present there is still lots of wiggle room.
I have gone for NTP over RTC for the time source. I loved the GPS but overkill and power hungry. The NTP code works great on the ESP8266, one of the most reliable bits, a warm thanks to the person who wrote that example :)
The user interface is much the same as the mega tracker but now has indicators for RTC battery. This interface can operate totally independently of any web services or infrastructure making it ideal for remote installation.
Next task mount it all up and into a the box.
This time I sort of checked out the layout a bit better before reaching for the drill. I think I might even have my 30% extra space.
This is the first time I've had to use a level shifter as the LED display was 5V only and the rest of the system was cool on 3.3. I actually soldered/sweated the level shifter to the stubs of the pins on the LED board to save wiring and make it more compact.
So now the one remaining task is work out the OTA updates and get it working with the on farm web server. Well that doesn't turn out to be so difficult. Turns out there is a great web page for this. With surprisingly few lines of code (about 5) you can add an OTA upload server, I shifted this to port 81 so the default page takes you to operations not update. I then just dynamically linked it at the bottom of the main web page as I am a lazy sod who cant be bothered typing :)
So yay for OTA - I don't have to start the power ladder and balance the laptop on my arm at heights while I update the program once the unit is installed.
I also worked out in another project that a "factory reset" for the eeprom data is a good idea, this can be seen on the web interface. Makes initial blank CPU configurations much easier. The I2C scanner is also a very cool tool for working out bus clashes or addressing issues.
Note that I had to change this (ht16k33.h) library, it did a "memcpy" in the constructor which seemed to cause my board to lock up ? Have included the modified one in the code area that works for me, your mileage may vary.
That's about it...All finished...
O yeah, Big thanks to those closest to me for their "special" understanding during these builds.
Update April 2019 - Deployment to South Tower
Finally, a forced break in the traffic. South tower suffered a jack failure, apparently the electric motors don't run long when filled with water. Anyhow jack replaced and finally a reason to deploy the ESP tracker that has been sitting on my desk for 6 months or more. First issue was the H-Switch seemed to saturate above 12V if using valves over 1020 for PWM? So fixed the code so it doesn't, as the FETS where half on and getting mighty hot. Second was I forgot that SSID is case sensitive, DUH. So after a day of patching code, connecting/rerouting cables and testing the box is finally running the south tower pending the sunrise test tomorrow. Oh yeah, this one is set to park in the west to keep water out of the jack!
The north tower is next and I'm also doing a truck load of re-write on the code so a another unit was born this weeked. Always good to have a second unit to test code on. I will upload the new code to github soon!
A week later and a few "false starts", it's all operating as designed. The IP address has settled to it's reservation and the NTP is doing a query every 24Hrs. It's interesting as you can see the clock drift during the day, only a couple of seconds but it's obvious why the east towers uncorrected clock moves subtly. It's really cool being able to look at the trackers user interface from my office workstation, don't know why it took me so long to get it to this point. Makes me want to return back to where it all started on the east tower and retro fit it with WiFi. Hmm I do have that Dual CPU Uno clone, I wonder ...
Update June 2019
There is now an update that support Alt//Az and Equatorial mounts up on GitHub. It's beta so comments are welcome. Still lots to do like finish the magnetometer calibration page and the wind parking but it's making progress. Thanks to all those who are building and helping with feedback on the program.
Have just combined first and third version output programs and eventually tested it on hardware to get the bugs out. Thanks for your patients I've been a bit busy the last few weeks and I did leave the code broken for a while.
There are actually 4 options for output now. PWM, L298 PWM , Relay Active High and Relay Active Low. You will have to buffer and connect as appropriate but these options should cover a wide range of elect to mech configurations.
ESP8266 Tracker Second Cut - OTA Updates working
C/C++This code is circa Sep 2018 much newer code available on GitHub Site
#include <ESP8266WiFi.h>
#include <WiFiUDP.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266httpUpdate.h>
//#include <DNSServer.h>
#include <TimeLib.h>
#include <Wire.h>
//#include <SPI.h>
#include <EEPROM.h>
#include <stdio.h>
#include <LSM303.h> // modified ... fixed a couple of bugs
#include <L3G.h>
#include <SFE_BMP180.h>
#include <TinyGPS.h>
#include "ht16k33.h"
#include "SSD1306.h"
#include "SH1106.h"
#include "SH1106Wire.h"
#include "ds3231.h"
#define BUFF_MAX 32
/* Display settings */
#define minRow 0 /* default = 0 */
#define maxRow 127 /* default = 127 */
#define minLine 0 /* default = 0 */
#define maxLine 63 /* default = 63 */
#define LineText 0
#define Line 12
const byte SETPMODE_PIN = D0 ;
const byte FLASH_BTN = D3 ; // GPIO 0 = FLASH BUTTON
const byte SCOPE_PIN = D3 ;
const byte FACTORY_RESET = D0 ;
const byte LED = BUILTIN_LED ; // = D4 ;
SSD1306 display(0x3c, 5, 4); // GPIO 5 = D1, GPIO 4 = D2
//SH1106Wire display(0x3c, 4, 5); // arse about ??? GPIO 5 = D1, GPIO 4 = D2
#define BUFF_MAX 32
#define PARK_EAST 1
#define PARK_WEST 2
#define PARK_NORTH 3
#define PARK_SOUTH 4
#define PARK_FLAT 5
#define MOTOR_DWELL 100
#define HT16K33_DSP_NOBLINK 0 // constants for the half arsed cheapo display
#define HT16K33_DSP_BLINK1HZ 4
#define HT16K33_DSP_BLINK2HZ 2
#define HT16K33_DSP_BLINK05HZ 6
const byte RELAY_XZ_PWM = D5; // PWM 1 Speed North / South Was the X- S relay Orange
const byte RELAY_YZ_PWM = D6; // PWM 2 Speed East / West Was the Y+ W relay Blue
const byte RELAY_YZ_DIR = D7; // DIR 2 Y+ Y- East / West Was the Y- E relay Yellow
const byte RELAY_XZ_DIR = D8; // DIR 1 X+ X- North / South Was the X+ N relay Brown
static bool hasSD = false;
static bool hasNet = false;
static bool hasGyro = false;
static bool hasRTC = false;
static bool hasPres = false ;
char dayarray[8] = {'S','M','T','W','T','F','S','E'} ;
char NodeName[16] ={"South_Tower\0"} ;
char nssid[20] ;
char npassword[20] ;
char timeServer[40] = {"au.pool.ntp.org\0"};
char buff[BUFF_MAX];
IPAddress MyIP(192,168,2,110) ;
IPAddress RCIP(192,168,2,255) ;
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
byte rtc_sec ;
byte rtc_min ;
byte rtc_hour ;
byte rtc_fert_hour ;
float rtc_temp ;
unsigned int localPort = 2390; // local port to listen for NTP UDP packets
unsigned int localPortCtrl = 8666; // local port to listen for Control UDP packets
unsigned int RemotePortCtrl = 8664; // local port to listen for Control UDP packets
L3G gyro;
LSM303 compass;
SFE_BMP180 pressure;
TinyGPS gps;
HT16K33 HT;
int motor_recycle_x = 0 ;
int motor_recycle_y = 0 ;
char trackername[18] ;
unsigned long gpschars ;
float heading ; // MODBUS MAP
struct ts tb; //
struct ts tn; //
struct ts td; //
struct ts tg; //
struct ts tc; //
float ha ;
float sunX ;
float sunrise ;
float sunset ;
int iNightShutdown ; //
int iMultiDrive ; // 69 do the axis drives run together
time_t setchiptime ; // 68 if set to non zero this will trigger a time set event
float zAng ; // 66
float xMul = 1.0 ; // 64
float yMul = 1.0 ; // 62
float zMul = 1.0 ; // 60
int iXYS = 0 ; // 59
int iSave = 0 ; // 58
int iDoSave = 0 ; // 57
int iGPSLock = 0 ; // 56
unsigned long fixage ; // 54
float xRoll = 0.0 ; // 52
float yRoll = 0.0 ; // 50
float zRoll = 0.0 ; // 48
float gT ; // 46 temp from sensor
float Pr ; // 44 presure sensor
float alt ; // 42 altitude from GPS
float T; // 40 temperature of board (if has RTC)
float xzTarget ; // 38 target for angles
float yzTarget ; // 36
float xzH ; // 34 hyserisis zone
float yzH ; // 32
float xzAng; // 30 current angles
float yzAng; // 28
float xzOffset; // 26 offset xz
float yzOffset; // 24 offset yz
float dyPark; // 22 parking position
float dxPark; // 20
float xMinVal ; // 18 Min and Max values X - N/S
float xMaxVal ; // 16
float yMinVal ; // 14 Y -- E/W
float yMaxVal ; // 12
float latitude; // 10
float longitude; // 8
int timezone; // 7
int iDayNight ; // 6
float solar_az_deg; // 4
float solar_el_deg; // 2
int iTrackMode ; // 1
int iMode ; // 0
int iPMode;
int iPWM_YZ ;
int iPWM_XZ ;
int iPowerUp = 0 ;
int iUseGPS = 0 ;
long lTimeZone ;
long lScanCtr = 0 ;
long lScanLast = 0 ;
time_t AutoOff_t ; // auto off until time > this date
bool bConfig = false ;
uint8_t rtc_status ;
bool bDoTimeUpdate = false ;
long lTimePrev ;
long lTimePrev2 ;
WiFiUDP ntpudp;
WiFiUDP ctrludp;
ESP8266WebServer server(80);
ESP8266WebServer OTAWebServer(81);
ESP8266HTTPUpdateServer OTAWebUpdater;
//DNSServer dnsServer;
void StopYZ(){
iPWM_YZ=0 ;
motor_recycle_y = MOTOR_DWELL ;
}
void StopXZ(){
iPWM_XZ=0 ;
motor_recycle_x = MOTOR_DWELL ;
}
void ActivateRelays(int iAllStop) {
if (motor_recycle_y > 0 ){
motor_recycle_y-- ;
}
if (motor_recycle_x > 0 ){
motor_recycle_x-- ;
}
if ( iAllStop == 0 ) {
StopYZ() ;
StopXZ() ;
} else {
if (( iPWM_YZ==0 ) && (motor_recycle_y == 0 )){
if ((( yzAng ) < ( yzTarget - yzH )) ) { // do Y ie E/W before N/S
digitalWrite(RELAY_YZ_DIR, LOW) ;
iPWM_YZ=2 ;
}
if ((( yzAng ) > ( yzTarget + yzH )) ) {
digitalWrite(RELAY_YZ_DIR, HIGH) ;
iPWM_YZ=2 ;
}
}
if ( iPWM_YZ>0 ){
if ((yzAng > yzTarget) && ( digitalRead(RELAY_YZ_DIR)==LOW )) {
StopYZ() ;
}
if ((yzAng < yzTarget) && ( digitalRead(RELAY_YZ_DIR)==HIGH )) {
StopYZ() ;
}
}
if (( iPWM_YZ==0) || ( iMultiDrive == 1 )) { // if finished on E/W you can do N/S or if we are doing multidrive
if (( iPWM_XZ==0 ) && (motor_recycle_x == 0 )){
if ((xzAng < ( xzTarget - xzH )) ) { // turn on if not in tolerance
digitalWrite(RELAY_XZ_DIR, LOW) ;
iPWM_XZ=2 ;
}
if ((xzAng > ( xzTarget + xzH )) ) { // turn on if not in tolerance
digitalWrite(RELAY_XZ_DIR, HIGH) ;
iPWM_XZ=2 ;
}
}
}else{
if ((iPWM_XZ>0 )){
StopXZ() ;
}
}
if ( iPWM_XZ>0 ){
if ((xzAng > xzTarget ) && ( digitalRead(RELAY_XZ_DIR)==LOW )) { // if on turn off
StopXZ() ;
}
if ((xzAng < xzTarget ) && ( digitalRead(RELAY_XZ_DIR)==HIGH )) { // if on turn off
StopXZ() ;
}
}
}
if (iPWM_XZ>0){
iPWM_XZ += 1 ;
}
if (iPWM_YZ>0){
iPWM_YZ += 1 ;
}
iPWM_XZ = constrain(iPWM_XZ,0,1023); // 254 in atmel - arduino land
iPWM_YZ = constrain(iPWM_YZ,0,1023); //
analogWrite(RELAY_XZ_PWM,iPWM_XZ);
analogWrite(RELAY_YZ_PWM,iPWM_YZ);
}
// Arduino doesnt have these to we define from a sandard libruary
float arcsin(float x) {
return (atan(x / sqrt(-x * x + 1)));
}
float arccos(float x) {
return (atan(x / sqrt(-x * x + 1)) + (2 * atan(1)));
}
// fractional orbital rotation in radians
float gama(struct ts *tm) {
return ((2 * PI / 365 ) * DayOfYear(tm->year , tm->mon , tm->mday , tm->hour , tm->min ));
}
// equation of rime
float eqTime(float g) {
return (229.18 * ( 0.000075 + ( 0.001868 * cos(g)) - (0.032077 * sin(g)) - (0.014615 * cos (2 * g)) - (0.040849 * sin(2 * g))));
}
// declination of sun in radians
float Decl(float g) {
return ( 0.006918 - (0.399912 * cos(g)) + (0.070257 * sin(g)) - (0.006758 * cos(2 * g)) + ( 0.000907 * sin(2 * g)) - ( 0.002697 * cos(3 * g)) + (0.00148 * sin(3 * g)) );
}
float TimeOffset(float longitude , struct ts *tm , int timezone ) {
float dTmp ;
dTmp = (-4.0 * longitude ) + (60 * timezone) - eqTime(gama(tm)) ;
return (dTmp);
}
float TrueSolarTime(float longitude , struct ts *tm , int timezone ) {
float dTmp ;
dTmp = ( 60.0 * tm->hour ) + (1.0 * tm->min) + (1.0 * tm->sec / 60) - TimeOffset(longitude, tm, timezone) ;
return (dTmp);
}
float HourAngle(float longitude , struct ts *tm , int timezone) {
float dTmp;
dTmp = (TrueSolarTime(longitude, tm, timezone) / 4 ) - 180 ; // 720 minutes is solar noon -- div 4 is 180
return (dTmp);
}
// Hour angle for sunrise and sunset only
float HA (float lat , struct ts *tm ) {
float latRad ;
latRad = lat * 2 * PI / 360 ;
return ( acos((cos(90.833 * PI / 180 ) / ( cos(latRad) * cos(Decl(gama(tm)))) - (tan(latRad) * tan(Decl(gama(tm)))))) / PI * 180 );
}
float Sunrise(float longitude , float lat , struct ts *tm , int timezone) {
return (720 - ( 4.0 * (longitude + HA(lat, tm))) + (60 * timezone) - eqTime(gama(tm)) ) ;
}
float Sunset(float longitude , float lat , struct ts *tm , int timezone) {
return (720 - ( 4.0 * (longitude - HA(lat, tm))) + (60 * timezone) - eqTime(gama(tm)) ) ;
}
float SNoon(float longitude , float lat , struct ts *tm , int timezone) {
return (720 - ( 4.0 * (longitude + (60 * timezone) - eqTime(gama(tm)))) ) ;
}
float SolarZenithRad(float longitude , float lat , struct ts *tm , int timezone) {
float latRad ;
float decRad ;
float HourAngleRad ;
float dTmp ;
latRad = lat * 2 * PI / 360 ;
decRad = Decl(gama(tm));
HourAngleRad = HourAngle (longitude , tm , timezone ) * PI / 180 ;
dTmp = acos((sin(latRad) * sin(decRad)) + (cos(latRad) * cos(decRad) * cos(HourAngleRad)));
return (dTmp) ;
}
float SolarElevationRad(float longitude , float lat , struct ts *tm , int timezone ) {
return ((PI / 2) - SolarZenithRad(longitude , lat , tm , timezone )) ;
}
float SolarAzimouthRad(float longitude , float lat , struct ts *tm , int timezone) {
float latRad ;
float decRad ;
float solarzenRad ;
float HourAngleRad ;
float dTmp ;
latRad = lat * 2 * PI / 360 ;
decRad = Decl(gama(tm));
solarzenRad = SolarZenithRad ( longitude , lat , tm , timezone ) ;
HourAngleRad = HourAngle (longitude , tm , timezone ) * PI / 180 ;
dTmp = acos(((sin(decRad) * cos(latRad)) - (cos(HourAngleRad) * cos(decRad) * sin(latRad))) / sin(solarzenRad)) ;
if ( HourAngleRad < 0 ) {
return (dTmp) ;
} else {
return ((2 * PI) - dTmp) ;
}
}
int NumberOK (float target) {
int tmp = 0 ;
tmp = isnan(target);
if ( tmp != 1 ) {
tmp = isinf(target);
}
return (tmp);
}
unsigned long sendCTRLpacket(IPAddress address){
int j ;
byte packetBuffer[50]; //buffer to hold outgoing packets
Serial.println("sending CTRL packet...");
memset(packetBuffer, 0, sizeof(packetBuffer[50])); // set all bytes in the buffer to 0
packetBuffer[0] = 0xff; // broadcast as all stations
packetBuffer[1] = 0xff; //
packetBuffer[2] = 0xff; //
packetBuffer[3] = 0xff; //
ctrludp.beginPacket(address, RemotePortCtrl); // Send control data to the remote port - Broadcast ???
ctrludp.write(packetBuffer, sizeof(packetBuffer[50]));
ctrludp.endPacket();
}
unsigned long sendNTPpacket(char* address)
{
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
Serial.println("sending NTP packet...");
memset(packetBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
ntpudp.beginPacket(address, 123); //NTP requests are to port 123
ntpudp.write(packetBuffer, NTP_PACKET_SIZE);
ntpudp.endPacket();
}
void FloatToModbusWords(float src_value , uint16_t * dest_lo , uint16_t * dest_hi ) {
uint16_t tempdata[2] ;
float *tf ;
tf = (float * )&tempdata[0] ;
*tf = src_value ;
*dest_lo = tempdata[1] ;
*dest_hi = tempdata[0] ;
}
float FloatFromModbusWords( uint16_t dest_lo , uint16_t dest_hi ) {
uint16_t tempdata[2] ;
float *tf ;
tf = (float * )&tempdata[0] ;
tempdata[1] = dest_lo ;
tempdata[0] = dest_hi ;
return (*tf) ;
}
float LoadFloatFromEEPROM(int address,float minval,float maxval, float defaultval){
float tmp ;
int i ;
byte *ba ;
ba =(byte *)&tmp ; // set the byte array to point at the long
for ( i = 0 ; i < 4 ; i++ ){
ba[i] = EEPROM.read((address*4)+i); // read the 4 bytes
}
if (( tmp < minval ) || ( tmp > maxval )) {
tmp = defaultval ;
for ( i = 0 ; i < 4 ; i++ ){
EEPROM.write((address*4)+i , ba[i] );
EEPROM.commit(); // save changes in one go ???
}
}
return(tmp);
}
int LoadIntFromEEPROM(int address,int minval,int maxval, int defaultval){
int dummy1 = 0 ; // belt and braces ... dont know which way the stack works
int tmp ;
int dummy2 = 0 ; // yep write this one as well ... maybe
int i ;
byte *ba ;
ba =(byte *)&tmp ; // set the byte array to point at the long
for ( i = 0 ; i < 4 ; i++ ){
ba[i] = EEPROM.read((address*4)+i); // read the 4 bytes
}
if (( tmp < minval ) || ( tmp > maxval )) {
tmp = defaultval ;
for ( i = 0 ; i < 4 ; i++ ){
EEPROM.write((address*4)+i , ba[i] );
EEPROM.commit(); // save changes in one go ???
}
}
return(tmp);
}
int LoadLongFromEEPROM(int address,long minval,long maxval, long defaultval){
long tmp ;
int i ;
byte *ba ;
ba =(byte *)&tmp ; // set the byte array to point at the long
for ( i = 0 ; i < 4 ; i++ ){
ba[i] = EEPROM.read((address*4)+i); // read the 4 bytes
}
if (( tmp < minval ) || ( tmp > maxval )) {
tmp = defaultval ;
for ( i = 0 ; i < 4 ; i++ ){
EEPROM.write((address*4)+i , ba[i] );
EEPROM.commit(); // save changes in one go ???
}
}
return(tmp);
}
time_t LoadTimeFromEEPROM(int address, time_t defaultval){
time_t tmp ;
int i ;
byte *ba ;
ba =(byte *)&tmp ; // set the byte array to point at the long
for ( i = 0 ; i < 4 ; i++ ){
ba[i] = EEPROM.read((address*4)+i); // read the 4 bytes
}
if ( year(tmp) < 2000 ) {
tmp = defaultval ;
for ( i = 0 ; i < 4 ; i++ ){
EEPROM.write((address*4)+i , ba[i] );
EEPROM.commit(); // save changes in one go ???
}
}
return(tmp);
}
void LoadCharFromEEPROM(int address , char * target , int targetsize ){
for ( int i = 0 ; i < targetsize ; i++ ){
target[i] = EEPROM.read((address*4)+i);
}
}
void SaveCharToEEPROM(int address , char * target , int targetsize ){
for ( int i = 0 ; i < targetsize ; i++ ){
EEPROM.write((address*4)+i,target[i]);
}
}
void LoadIPFromEEPROM(int address , IPAddress * target ){
for ( int i = 0 ; i < 4 ; i++ ){
target[i] = EEPROM.read((address*4)+i);
}
}
void SaveIPToEEPROM(int address , IPAddress * target ){
for ( int i = 0 ; i < 4 ; i++ ){
EEPROM.write((address*4)+i,target[i]);
}
}
byte LoadDayByteFromEEPROM(int address, int ofs){
byte tmp ;
ofs %= 4 ;
tmp = EEPROM.read((address*4)+ofs); // read the 4 bytes
return(tmp);
}
void SaveDayByteToEEPROM(int address, int ofs,byte val){
byte tmp ;
ofs %= 4 ;
EEPROM.write((address*4)+ofs,val); // read the 4 bytes
}
void SaveFloatToEEPROM(int address,float val){
float tmp ;
int i ;
byte *ba ;
tmp = val ;
ba =(byte *)&tmp ; // set the byte array to point at the long
for ( i = 0 ; i < 4 ; i++ ){
EEPROM.write((address*4)+i , ba[i] );
}
}
void SaveLongToEEPROM(int address,long val){
long tmp ;
int i ;
byte *ba ;
tmp = val ;
ba =(byte *)&tmp ; // set the byte array to point at the long
for ( i = 0 ; i < 4 ; i++ ){
EEPROM.write((address*4)+i , ba[i] );
}
}
void SaveTimeToEEPROM(int address,time_t val){
time_t tmp ;
int i ;
byte *ba ;
tmp = val ;
ba =(byte *)&tmp ; // set the byte array to point at the long
for ( i = 0 ; i < 4 ; i++ ){
EEPROM.write((address*4)+i , ba[i] );
}
}
void SaveIntToEEPROM(int address,int val){
int dummy1 = 0 ;
int tmp ;
int dummy2 = 0 ;
int i ;
byte *ba ;
tmp = val ;
ba =(byte *)&tmp ; // set the byte array to point at the long
for ( i = 0 ; i < 4 ; i++ ){
EEPROM.write((address*4)+i , ba[i] );
}
}
void LoadParamsFromEEPROM(bool bLoad){
if ( bLoad ) {
xzH = LoadFloatFromEEPROM(0,0.1,20.0,4.0); // hysterisis NS
yzH = LoadFloatFromEEPROM(1,0.1,20.0,4.0); // "" EW
dyPark = LoadFloatFromEEPROM(2,-70.0,50.0,0);
dxPark = LoadFloatFromEEPROM(3,-5.0,50.0,0.0);
xzOffset = LoadFloatFromEEPROM(4,-90.0,90.0,0); // NS
yzOffset = LoadFloatFromEEPROM(5,-90.0,90.0,0); // EW
xzTarget = LoadFloatFromEEPROM(6,-90.0,90.0,0); // NS
yzTarget = LoadFloatFromEEPROM(7,-90.0,90.0,0); // EW
xMinVal = LoadFloatFromEEPROM(8,-10.0,60.0,0.0); // NS
xMaxVal = LoadFloatFromEEPROM(9,-10.0,60.0,45);
yMinVal = LoadFloatFromEEPROM(10,-70.0,50.0,-65); // EW
yMaxVal = LoadFloatFromEEPROM(11,-70.0,50.0,45);
iTrackMode = LoadIntFromEEPROM(12,-1,4,0);
latitude = LoadFloatFromEEPROM(13,-90.0,90.0,-34.051219);
longitude = LoadFloatFromEEPROM(14,-180.0,180.0,142.013618);
timezone = LoadIntFromEEPROM(15,0,23,10);
xMul = LoadFloatFromEEPROM(16,-10,10,1);
yMul = LoadFloatFromEEPROM(17,-10,10,1);
zMul = LoadFloatFromEEPROM(18,-10,10,1);
iXYS = LoadIntFromEEPROM(19,0,1,0);
if ( xMul == 0.0 ) // zero is rubbish value so take 1.0 as the default
xMul = 1.0 ;
if ( yMul == 0.0 )
yMul = 1.0 ;
if ( zMul == 0.0 )
zMul = 1.0 ;
iNightShutdown = LoadIntFromEEPROM(20,0,1,1);
iMultiDrive = LoadIntFromEEPROM(21,0,1,0);
EEPROM.get((23 * sizeof(float)) , iUseGPS ) ;
if (digitalRead(FACTORY_RESET)== LOW) {
MyIP = IPAddress(192,168,42,1);
sprintf(trackername,"Most Excellent\0");
sprintf(nssid , "Configure\0") ;
sprintf(npassword, "\0");
}else{
EEPROM.get((22 * sizeof(float)) , MyIP );
if ((( MyIP[0] == 255 ) && ( MyIP[1] == 255 ))) {
MyIP = IPAddress(192,168,42,1);
}
EEPROM.get((30 * sizeof(float)) , trackername );
if ( String(trackername).length() < 2 ){
sprintf(trackername,"Most Excellent\0");
}
EEPROM.get((40 * sizeof(float)) , nssid );
if ( String(nssid).length() < 2 ) {
sprintf(nssid , "Configure\0") ;
sprintf(npassword, "\0");
}else{
EEPROM.get((45 * sizeof(float)) , npassword );
}
EEPROM.get((50 * sizeof(float)) , timeServer );
}
}else{
EEPROM.put( 0 , xzH );
EEPROM.put(0 + (1 * sizeof(float)) , yzH );
EEPROM.put(0 + (2 * sizeof(float)) , dyPark );
EEPROM.put(0 + (3 * sizeof(float)) , dxPark );
EEPROM.put(0 + (4 * sizeof(float)) , xzOffset );
EEPROM.put(0 + (5 * sizeof(float)) , yzOffset );
EEPROM.put(0 + (6 * sizeof(float)) , xzTarget );
EEPROM.put(0 + (7 * sizeof(float)) , yzTarget );
EEPROM.put(0 + (8 * sizeof(float)) , xMinVal );
EEPROM.put(0 + (9 * sizeof(float)) , xMaxVal );
EEPROM.put(0 + (10 * sizeof(float)) , yMinVal );
EEPROM.put(0 + (11 * sizeof(float)) , yMaxVal );
EEPROM.put(0 + (12 * sizeof(float)) , iTrackMode );
EEPROM.put(0 + (13 * sizeof(float)) , latitude );
EEPROM.put(0 + (14 * sizeof(float)) , longitude );
EEPROM.put(0 + (15 * sizeof(float)) , timezone );
EEPROM.put(0 + (16 * sizeof(float)) , xMul );
EEPROM.put(0 + (17 * sizeof(float)) , yMul );
EEPROM.put(0 + (18 * sizeof(float)) , zMul );
EEPROM.put(0 + (19 * sizeof(float)) , iXYS );
EEPROM.put(0 + (20 * sizeof(float)) , iNightShutdown );
EEPROM.put(0 + (21 * sizeof(float)) , iMultiDrive );
EEPROM.put(0 + (22 * sizeof(float)) , MyIP );
EEPROM.put(0 + (23 * sizeof(float)) , iUseGPS );
EEPROM.put(0 + (30 * sizeof(float)) , trackername);
EEPROM.put(0 + (40 * sizeof(float)) , nssid);
EEPROM.put(0 + (45 * sizeof(float)) , npassword);
EEPROM.put(0 + (50 * sizeof(float)) , timeServer);
EEPROM.commit(); // save changes in one go ???
}
}
unsigned long processNTPpacket(void){
int oldyear ;
oldyear = year() ;
ntpudp.read(packetBuffer, NTP_PACKET_SIZE); // the timestamp starts at byte 40 of the received packet and is four bytes, or two words, long. First, esxtract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); // combine the four bytes (two words) into a long integer
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
unsigned long secsSince1900 = highWord << 16 | lowWord; // this is NTP time (seconds since Jan 1 1900):
const unsigned long seventyYears = 2208988800UL; // now convert NTP time into everyday time: Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
unsigned long epoch = secsSince1900 - seventyYears + (timezone * SECS_PER_HOUR); // subtract seventy years:
setTime((time_t)epoch); // update the clock
Serial.print(F("Unix time = "));
Serial.println(epoch); // print Unix time:
tn.year = year(); // record the last NTP time set
tn.mon = month() ;
tn.mday = day();
tn.hour = hour();
tn.min = minute();
tn.sec = second();
if (( !hasRTC ) && ( oldyear < 2000 )){
tb = tn ;
}
}
int SetTimeFromGPS(){
byte hundredths ;
time_t chiptime ;
gps.crack_datetime((int *)&tg.year,(byte *)&tg.mon,(byte *) &tg.mday,(byte *) &tg.hour,(byte *) &tg.min,(byte *) &tg.sec , &hundredths, &fixage);
setTime((int)tg.hour,(int)tg.min,(int)tg.sec,(int)tg.mday,(int)tg.mon,(int)tg.year ) ; // set the internal RTC from last GPS time
chiptime = now() ; // get it back again
chiptime += (( timezone * SECS_PER_HOUR ) + ( fixage / 1000 )) ; // add the offset plus the fix age
setTime(chiptime); // set it again
if (hasRTC) {
tg.year = year();
tg.mon = month() ;
tg.mday = day();
tg.hour = hour() ;
tg.min = minute();
tg.sec = second();
DS3231_set(tg); //should also update this
}
return(0);
}
// ############################## SETUP #############################
void setup() {
int i , j = 0;
String host ;
pinMode(LED,OUTPUT); // builtin LED
pinMode(SETPMODE_PIN,INPUT_PULLUP);
display.init();
display.flipScreenVertically();
/* show start screen */
display.clear();
display.setFont(ArialMT_Plain_16);
display.drawString(0, 0, "ESP Solar");
display.drawString(0, 16, "Tracker");
display.setFont(ArialMT_Plain_10);
display.drawString(0, 40, "Copyright (c) 2019");
display.drawString(0, 50, "Dougal Plummer");
display.display();
compass.init();
compass.enableDefault();
compass.setTimeout(1000);
if (gyro.init()) {
gyro.enableDefault();
hasGyro = true ;
}
if (pressure.begin()){
Serial.println("BMP180 init success");
hasPres = true ;
}
// pinMode(FACTORY_RESET,INPUT_PULLUP);
pinMode(RELAY_XZ_DIR, OUTPUT); // Outputs for PWM motor control
pinMode(RELAY_XZ_PWM, OUTPUT); //
pinMode(RELAY_YZ_PWM, OUTPUT); //
pinMode(RELAY_YZ_DIR, OUTPUT); //
iPWM_YZ = 0 ;
iPWM_XZ = 0 ;
ActivateRelays(0); // call an all stop first
EEPROM.begin(1024);
LoadParamsFromEEPROM(true);
compass.m_min = (LSM303::vector<int16_t>) {-3848, -1822, -1551 }; // calibration figures are empirical
compass.m_max = (LSM303::vector<int16_t>) { +3353, +5127, +5300};
delay(1000);
if (iUseGPS==0){
Serial.begin(115200);
Serial.println("Warp Speed no GPS !");
}else{
Serial.begin(9600);
}
Serial.setDebugOutput(true);
Serial.println("Chip ID " + String(ESP.getChipId(), HEX));
Serial.println("Configuring WiFi...");
display.clear();
display.setFont(ArialMT_Plain_10);
display.drawString(0, 11, "Chip ID " + String(ESP.getChipId(), HEX) );
display.display();
sprintf(nssid,"*********\0"); // put you credentials in here
sprintf(npassword,"********\0");
if ((digitalRead(SETPMODE_PIN) == HIGH) ){
bConfig = true ;
IPAddress localIp(192, 168, 5 +(ESP.getChipId() & 0x7f ) , 1);
IPAddress MaskIp(255, 255, 255 , 0);
WiFi.softAPConfig(localIp,localIp,MaskIp);
WiFi.softAP(nssid); // configure mode no password
MyIP = WiFi.softAPIP();
Serial.print("Soft AP IP address: ");
Serial.println(MyIP);
display.drawString(0, 22, "Soft AP IP address: "+String(MyIP) );
display.display();
}else{
bConfig = false ; // are we in factory configuratin mode
Serial.println(String(nssid));
Serial.println(String(npassword));
display.drawString(0, 22, String(nssid) );
display.drawString(0, 33, String(npassword) );
display.display();
if ( npassword[0] == 0 ){
WiFi.begin((char*)nssid); // connect to unencrypted access point
}else{
WiFi.begin((char*)nssid, (char*)npassword); // connect to access point with encryption
}
while (( WiFi.status() != WL_CONNECTED ) && ( j < 20 )) {
j = j + 1 ;
delay(500);
Serial.print("+");
}
if ( j >= 20 ) {
bConfig = true ;
WiFi.disconnect();
IPAddress localIp(192, 168, 5 +(ESP.getChipId() & 0x7f ) , 1);
IPAddress MaskIp(255, 255, 255 , 0);
WiFi.softAPConfig(localIp,localIp,MaskIp);
WiFi.softAP(nssid); // configure mode no password
MyIP = WiFi.softAPIP();
Serial.print("Soft AP IP address: ");
Serial.println(MyIP);
display.drawString(0, 22, "Soft AP IP address: "+String(MyIP) );
display.display();
}else{
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
MyIP = WiFi.localIP() ;
Serial.println(MyIP) ;
display.drawString(0, 53, "IP "+String(MyIP) );
display.display();
hasNet = true ;
}
if (localPortCtrl == localPort ){ // bump the NTP port up if they ar the same
localPort++ ;
}
Serial.println("Starting UDP");
ntpudp.begin(localPort); // this is the recieve on NTP port
display.drawString(0, 44, "NTP UDP " );
display.display();
Serial.print("NTP Local UDP port: ");
Serial.println(ntpudp.localPort());
ctrludp.begin(localPortCtrl); // recieve on the control port
display.drawString(64, 44, "CTRL UDP " );
display.display();
Serial.print("Control Local UDP port: ");
Serial.println(ctrludp.localPort());
} // end of the normal setup
host = trackername ;
String(host).replace(" ","_");
String(host).toCharArray(buff,sizeof(buff));
if (MDNS.begin(buff)) {
MDNS.addService("http", "tcp", 80);
Serial.println("MDNS responder started");
Serial.print("You can now connect to http://");
Serial.print(host);
Serial.println(".local");
}
server.on("/", handleRoot);
server.on("/setup", handleRoot);
server.on("/scan", i2cScan);
server.on("/stime", handleRoot);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
// dnsServer.setTTL(300);
// dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure);
// dnsServer.start(53,"injector.local",myIP);
tc.mon = 0 ;
tc.wday = 0 ;
DS3231_init(DS3231_INTCN); // look for a rtc
DS3231_get(&tc);
rtc_status = DS3231_get_sreg();
if (((tc.mon < 1 )|| (tc.mon > 12 ))&& (tc.wday>8)){ // no rtc to load off
Serial.println("NO RTC ?");
}else{
setTime((int)tc.hour,(int)tc.min,(int)tc.sec,(int)tc.mday,(int)tc.mon,(int)tc.year ) ; // set the internal RTC
hasRTC = true ;
Serial.println("Has RTC ?");
rtc_temp = DS3231_get_treg();
DS3231_get(&tb);
}
rtc_min = minute();
rtc_sec = second();
HT.begin(0x00);
for (int led = 0; led < 127; led++) {
HT.clearLed(led);
}
HT.sendLed();
Serial.println("OTA startup");
OTAWebUpdater.setup(&OTAWebServer);
OTAWebServer.begin();
Serial.println("End of Setup");
}
// ############################## LOOP #############################
void loop() {
long lTime ;
long lRet ;
int i , j , k ;
float P;
float sunInc;
float sunAng;
float xzRatio;
float yzRatio;
float decl ;
float eqtime ;
float dTmp ;
float heading ;
float tst ;
float flat, flon;
unsigned short goodsent;
unsigned short failcs;
String msg ;
bool bSendCtrlPacket = false ;
server.handleClient();
OTAWebServer.handleClient();
lTime = millis() ;
compass.read(); // this reads all 6 channels
if (( compass.a.z != 0) && (!compass.timeoutOccurred() )) {
zAng = (float)compass.a.z ;
if (iXYS == 0 ){ // Proper Job make it configurable
xzRatio = (float)compass.a.x * xMul / abs(zAng) ; // Normal
yzRatio = (float)compass.a.y * yMul / abs(zAng) ;
}else{
xzRatio = (float)compass.a.y * xMul / abs(zAng) ; // Swapped
yzRatio = (float)compass.a.x * yMul / abs(zAng) ;
}
xzAng = ((float)atan(xzRatio) / PI * 180 ) + xzOffset ; // good old offsets or fudge factors
yzAng = ((float)atan(yzRatio) / PI * 180 ) + yzOffset ;
}else{ // try restarting the compass/accelerometer modual - cos he gone walkabout...
Wire.begin(); // reset the I2C
compass.init();
compass.enableDefault();
compass.setTimeout(1000); // BTW I fixed up the int / long issue in the time out function in the LM303 lib I was using
compass.m_min = (LSM303::vector<int16_t>) {-3848, -1822, -1551 }; // calibration figures are empirical (just whirl it around a bit and records the min max !!)
compass.m_max = (LSM303::vector<int16_t>) { +3353, +5127, +5300 };
}
// digitalWrite(SCOPE_PIN,!digitalRead(SCOPE_PIN)); // my scope says we are doing this loop at an unreasonable speed except when we do web stuff
// pwmtest++ ;
// if ( pwmtest > 1024 ) {
// pwmtest = 0 ;
// }
// analogWrite(SCOPE_PIN,pwmtest);
delay(1); // limmit to 1000 cyclces a second max
if (digitalRead(FLASH_BTN) == LOW) { // what to do if the button be pressed
}
lScanCtr++ ;
bSendCtrlPacket = false ;
if ( rtc_sec != second()){
// digitalWrite(LED,!digitalRead(LED)); // my scope says we are doing this loop at an unreasonable speed except when we do web stuff
rtc_sec = second() ;
display.clear();
// display.drawLine(minRow, 63, maxRow, 63);
display.setTextAlignment(TEXT_ALIGN_LEFT);
snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", year(), month(), day() , hour(), minute(), second());
display.drawString(0 , LineText, String(buff) );
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(128 , LineText, String(WiFi.RSSI()));
display.setTextAlignment(TEXT_ALIGN_LEFT);
// snprintf(buff, BUFF_MAX, "TX %d TY %d", year(), month() );
msg = "X " + String(xzAng,2) ;
display.drawString(0 , 11, msg ) ;
msg = "Y " + String(yzAng,2) ;
display.drawString(56 , 11, msg ) ;
msg = "TX " + String(xzTarget,2) ;
display.drawString(0 , 22, msg ) ;
msg = "TY " + String(yzTarget,2) ;
display.drawString(56 , 22, msg ) ;
msg = "DX " + String((xzAng-xzTarget),2) ;
display.drawString(0 , 33, msg ) ;
msg = "DY " + String((yzAng-yzTarget),2) ;
display.drawString(56 , 33, msg ) ;
display.setTextAlignment(TEXT_ALIGN_RIGHT);
msg = "" ;
if ( iPWM_YZ != 0 ) {
if (( digitalRead(RELAY_YZ_DIR) == LOW )) {
msg = "W" ;
}else{
msg = "E" ;
...
This file has been truncated, please download it to see its full contents.
/*
ht16k33.h - used to talk to the htk1633 chip to do things like turn on LEDs or scan keys
* Copyright: Peter Sjoberg <peters-alib AT techwiz.ca>
* License: GPLv3
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* History:
* 2015-10-04 Peter Sjoberg <peters-alib AT techwiz.ca>
* Created using https://www.arduino.cc/en/Hacking/LibraryTutorial and ht16k33 datasheet
* 2015-11-25 Peter Sjoberg <peters-alib AT techwiz DOT ca>
* first check in to github
* 2015-12-05 Peter Sjoberg <peters-alib AT techwiz.ca>
* moved displayram to public section
*/
#ifndef ht16k33_h
#define ht16k33_h
#include "Arduino.h"
class HT16K33
{
public:
typedef uint16_t KEYDATA[3];
typedef uint8_t DisplayRam_t[16];
DisplayRam_t displayRam;
HT16K33(); // the class itself
void begin(uint8_t address);
void end();
uint8_t sleep(); // stop oscillator to put the chip to sleep
uint8_t normal(); // wake up chip and start ocillator
uint8_t clearLed(uint8_t ledno); // 16x8 = 128 LEDs to turn on, 0-127
uint8_t setLed(uint8_t ledno); // 16x8 = 128 LEDs to turn on, 0-127
boolean getLed(uint8_t ledno,boolean Fresh=false); // check if a specific led is on(true) or off(false)
uint8_t setDisplayRaw(uint8_t pos, uint8_t val); // load byte "pos" with value "val"
uint8_t sendLed(); // send whatever led patter you set
uint8_t setLedNow(uint8_t ledno); //Set a single led and send led in one function
uint8_t clearLedNow(uint8_t ledno); //Clear a single led and send led in one function
uint8_t setBrightness(uint8_t level); // level 0-16, 0 means display off
uint8_t keyINTflag(); // INTerrupt flag value, set when a key is pressed
uint8_t keysPressed(); // report how many keys that are pressed, clear means report as if new
int8_t readKey(boolean clear=false); // read what key was pressed, Fresh=false to go from cache
void readKeyRaw(KEYDATA keydata,boolean Fresh=true); //read the raw key info, bitmapped info of all key(s) pressed
uint8_t setBlinkRate(uint8_t rate); // HT16K33_DSP_{NOBLINK,BLINK2HZ,BLINK1HZ,BLINK05HZ}
void displayOn();
void displayOff();
private:
void _updateKeyram();
uint8_t _i2c_write(uint8_t val);
uint8_t _i2c_write(uint8_t cmd,uint8_t *data,uint8_t size,boolean LSB=false);
uint8_t _i2c_read(uint8_t addr);
uint8_t _i2c_read(uint8_t addr,uint8_t *data,uint8_t size);
KEYDATA _keyram;
uint8_t _address;
};
#endif
/*
* ht16k33.cpp - used to talk to the htk1633 chip to do things like turn on LEDs or scan keys
* Copyright: Peter Sjoberg <peters-alib AT techwiz.ca>
* License: GPLv3
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* History:
* 2015-10-04 Peter Sjoberg <peters-alib AT techwiz.ca>
* Created using https://www.arduino.cc/en/Hacking/LibraryTutorial and ht16k33 datasheet
* 2015-11-25 Peter Sjoberg <peters-alib AT techwiz DOT ca>
* first check in to github
*
*
*
*
*
*/
#include "Arduino.h"
#include "ht16k33.h"
#include <Wire.h>
// "address" is base address 0-7 which becomes 11100xxx = E0-E7
#define BASEHTADDR 0x70
//Commands
#define HT16K33_DDAP B00000000 // Display data address pointer: 0000xxxx
#define HT16K33_SS B00100000 // System setup register
#define HT16K33_SS_STANDBY B00000000 // System setup - oscillator in standby mode
#define HT16K33_SS_NORMAL B00000001 // System setup - oscillator in normal mode
#define HT16K33_KDAP B01000000 // Key Address Data Pointer
#define HT16K33_IFAP B01100000 // Read status of INT flag
#define HT16K33_DSP B10000000 // Display setup
#define HT16K33_DSP_OFF B00000000 // Display setup - display off
#define HT16K33_DSP_ON B00000001 // Display setup - display on
#define HT16K33_DSP_NOBLINK B00000000 // Display setup - no blink
#define HT16K33_DSP_BLINK2HZ B00000010 // Display setup - 2hz blink
#define HT16K33_DSP_BLINK1HZ B00000100 // Display setup - 1hz blink
#define HT16K33_DSP_BLINK05HZ B00000110 // Display setup - 0.5hz blink
#define HT16K33_RIS B10100000 // ROW/INT Set
#define HT16K33_RIS_OUT B00000000 // Set INT as row driver output
#define HT16K33_RIS_INTL B00000001 // Set INT as int active low
#define HT16K33_RIS_INTH B00000011 // Set INT as int active high
#define HT16K33_DIM B11100000 // Dimming set
#define HT16K33_DIM_1 B00000000 // Dimming set - 1/16
#define HT16K33_DIM_2 B00000001 // Dimming set - 2/16
#define HT16K33_DIM_3 B00000010 // Dimming set - 3/16
#define HT16K33_DIM_4 B00000011 // Dimming set - 4/16
#define HT16K33_DIM_5 B00000100 // Dimming set - 5/16
#define HT16K33_DIM_6 B00000101 // Dimming set - 6/16
#define HT16K33_DIM_7 B00000110 // Dimming set - 7/16
#define HT16K33_DIM_8 B00000111 // Dimming set - 8/16
#define HT16K33_DIM_9 B00001000 // Dimming set - 9/16
#define HT16K33_DIM_10 B00001001 // Dimming set - 10/16
#define HT16K33_DIM_11 B00001010 // Dimming set - 11/16
#define HT16K33_DIM_12 B00001011 // Dimming set - 12/16
#define HT16K33_DIM_13 B00001100 // Dimming set - 13/16
#define HT16K33_DIM_14 B00001101 // Dimming set - 14/16
#define HT16K33_DIM_15 B00001110 // Dimming set - 15/16
#define HT16K33_DIM_16 B00001111 // Dimming set - 16/16
// Constructor
HT16K33::HT16K33(){
}
/****************************************************************/
// Setup the env
//
void HT16K33::begin(uint8_t address){
uint8_t i;
_address=address | BASEHTADDR;
Wire.begin();
_i2c_write(HT16K33_SS | HT16K33_SS_NORMAL); // Wakeup
_i2c_write(HT16K33_DSP | HT16K33_DSP_ON | HT16K33_DSP_NOBLINK); // Display on and no blinking
_i2c_write(HT16K33_RIS | HT16K33_RIS_OUT); // INT pin works as row output
_i2c_write(HT16K33_DIM | HT16K33_DIM_16); // Brightness set to max
//Clear all lights
for (i = 0 ; i < 16 ; i++){
displayRam[i] = 0 ;
}
// memcpy(displayRam,0,sizeof(displayRam)); // this was the problem ???
_i2c_write(HT16K33_DDAP, displayRam,sizeof(displayRam),true);
} // begin
/****************************************************************/
// internal function - Write a single byte
//
uint8_t HT16K33::_i2c_write(uint8_t val){
Wire.beginTransmission(_address);
Wire.write(val);
return Wire.endTransmission();
} // _i2c_write
/****************************************************************/
// internal function - Write several bytes
// "size" is amount of data to send excluding the first command byte
// if LSB is true then swap high and low byte to send LSB MSB
// NOTE: Don't send odd amount of data if using LSB, then it will send one to much
//
uint8_t HT16K33::_i2c_write(uint8_t cmd,uint8_t *data,uint8_t size,boolean LSB){
uint8_t i;
Wire.beginTransmission(_address);
Wire.write(cmd);
i=0;
while (i<size){
if (LSB){
Wire.write(data[i+1]);
Wire.write(data[i++]);
i++;
} else {
Wire.write(data[i++]);
}
}
return Wire.endTransmission(); // Send out the data
} // _i2c_write
/****************************************************************/
// internal function - read a byte from specific address (send one byte(address to read) and read a byte)
//
uint8_t HT16K33::_i2c_read(uint8_t addr){
_i2c_write(addr);
Wire.requestFrom(_address,(uint8_t) 1);
return Wire.read(); // read one byte
} // _i2c_read
/****************************************************************/
// read an array from specific address (send a byte and read several bytes back)
// return value is how many bytes that where really read
//
uint8_t HT16K33::_i2c_read(uint8_t addr,uint8_t *data,uint8_t size){
uint8_t i,retcnt,val;
_i2c_write(addr);
retcnt=Wire.requestFrom(_address, size);
i=0;
while(Wire.available() && i<size) // slave may send less than requested
{
data[i++] = Wire.read(); // receive a byte as character
}
return retcnt;
} // _i2c_read
/****************************************************************/
// Put the chip to sleep
//
uint8_t HT16K33::sleep(){
return _i2c_write(HT16K33_SS|HT16K33_SS_STANDBY); // Stop oscillator
} // sleep
/****************************************************************/
// Wake up the chip (after it been a sleep )
//
uint8_t HT16K33::normal(){
return _i2c_write(HT16K33_SS|HT16K33_SS_NORMAL); // Start oscillator
} // normal
/****************************************************************/
// Turn off one led but only in memory
// To do it on chip a call to "sendLed" is needed
//
uint8_t HT16K33::clearLed(uint8_t ledno){ // 16x8 = 128 LEDs to turn on, 0-127
if (ledno>=0 && ledno<128){
bitClear(displayRam[int(ledno/8)],(ledno % 8));
return 0;
} else {
return 1;
}
} // clearLed
/****************************************************************/
// Turn on one led but only in memory
// To do it on chip a call to "sendLed" is needed
//
uint8_t HT16K33::setLed(uint8_t ledno){ // 16x8 = 128 LEDs to turn on, 0-127
if (ledno>=0 && ledno<128){
bitSet(displayRam[int(ledno/8)],(ledno % 8));
return 0;
} else {
return 1;
}
} // setLed
/****************************************************************/
// check if a specific led is on(true) or off(false)
//
boolean HT16K33::getLed(uint8_t ledno,boolean Fresh){
// get the current state from chip
if (Fresh) {
_i2c_read(HT16K33_DDAP, displayRam,sizeof(displayRam));
}
if (ledno>=0 && ledno<128){
return bitRead(displayRam[int(ledno/8)],ledno % 8) != 0;
}
} // getLed
/****************************************************************/
uint8_t HT16K33::setDisplayRaw(uint8_t pos, uint8_t val) {
if (pos < sizeof(displayRam)) {
displayRam[pos] = val;
return 0;
} else {
return 1;
}
} // setDisplayRaw
/****************************************************************/
// Send the display ram info to chip - kind of commit all changes to the outside world
//
uint8_t HT16K33::sendLed(){
return _i2c_write(HT16K33_DDAP, displayRam,sizeof(displayRam));
} // sendLed
/****************************************************************/
// set a single LED and update NOW
//
uint8_t HT16K33::setLedNow(uint8_t ledno){
uint8_t rc;
rc=setLed(ledno);
if (rc==0){
return sendLed();
} else {
return rc;
}
} // setLedNow
/****************************************************************/
// clear a single LED and update NOW
//
uint8_t HT16K33::clearLedNow(uint8_t ledno){
uint8_t rc;
rc=clearLed(ledno);
if (rc==0){
return sendLed();
} else {
return rc;
}
} // clearLedNow
/****************************************************************/
// Change brightness of the whole display
// level 0-15, 0 means display off
//
uint8_t HT16K33::setBrightness(uint8_t level){
if (HT16K33_DIM_1>=0 && level <HT16K33_DIM_16){
return _i2c_write(HT16K33_DIM|level);
} else {
return 1;
}
} // setBrightness
/****************************************************************/
// Check the chips interrupt flag
// 0 if no new key is pressed
// !0 if some key is pressed and not yet read
//
uint8_t HT16K33::keyINTflag(){
return _i2c_read(HT16K33_IFAP);
} // keyINTflag
/****************************************************************/
// Check if any key is pressed
// returns how many keys that are currently pressed
//
//From http://stackoverflow.com/questions/109023/how-to-count-the-number-of-set-bits-in-a-32-bit-integer
#ifdef __GNUC__
uint16_t _popcount(uint16_t x) {
return __builtin_popcount(x);
}
#else
uint16_t _popcount(uint16_t i) {
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
#endif
uint8_t HT16K33::keysPressed(){
// Serial.println(_keyram[0]|_keyram[1]|_keyram[2],HEX);
return (_popcount(_keyram[0])+_popcount(_keyram[1])+_popcount(_keyram[2]));
} // keysPressed
/****************************************************************/
// Internal function - update cached key array
//
void HT16K33::_updateKeyram(){
uint8_t curkeyram[6];
_i2c_read(HT16K33_KDAP, curkeyram, 6);
_keyram[0]=curkeyram[1]<<8 | curkeyram[0]; // datasheet page 21, 41H is high 40H is low
_keyram[1]=curkeyram[3]<<8 | curkeyram[2]; // or LSB MSB
_keyram[2]=curkeyram[5]<<8 | curkeyram[4];
return;
} // _updateKeyram
/****************************************************************/
// return the key status
//
void HT16K33::readKeyRaw(HT16K33::KEYDATA keydata,boolean Fresh){
int8_t i;
// get the current state
if (Fresh) {_updateKeyram();}
for (i=0;i<3;i++){
keydata[i]=_keyram[i];
}
return;
} // readKeyRaw
/****************************************************************
* read the keys and return the key that changed state
* if more than one is pressed (compared to last scan)
* only one is returned, the first one found
* 0 means no key pressed.
* "1" means the key #1 is pressed
* "-1" means the key #1 is released
* "clear"=true means it will only look keys currently pressed down.
* this is so you can detect what key is still pressed down after
* several keys are pressed down and then all but one is released
* (without keeping track of up/down separately)
*
*Observations:
* As long as the key is pressed the keyram bit is set
* the flag is set when key is pressed down but then cleared at first
* read of key ram.
* When released the key corresponding bit is cleared but the flag is NOT set
* This means that the only way a key release can be detected is
* by only polling readKey and ignoring flag
*
*/
int8_t HT16K33::readKey(boolean clear){
static HT16K33::KEYDATA oldKeyData;
uint16_t diff;
uint8_t key;
int8_t i,j;
// save the current state
for (i=0;i<3;i++){
if (clear){
oldKeyData[i]=0;
} else {
oldKeyData[i]=_keyram[i];
}
}
_updateKeyram();
key=0; //the key that changed state
for (i=0;i<3;i++){
diff=_keyram[i] ^ oldKeyData[i]; //XOR old and new, any changed bit is set.
if ( diff !=0 ){ // something did change
for (j=0;j<13;j++){
key++;
if (((diff >> j) & 1) == 1){
if (((_keyram[i] >> j) & 1)==0){
return -key;
}else{
return key;
}
} // if keyram differs
} // for j in bits
} else {
key+=13;
} // if diff
} // for i
return 0; //apperently no new key was pressed - old might still be held down, pass clear=true to see it
} // readKey
/****************************************************************/
// Make the display blink
//
uint8_t HT16K33::setBlinkRate(uint8_t rate){
switch (rate) {
case HT16K33_DSP_NOBLINK:
case HT16K33_DSP_BLINK2HZ:
case HT16K33_DSP_BLINK1HZ:
case HT16K33_DSP_BLINK05HZ:
_i2c_write(HT16K33_DSP | HT16K33_DSP_ON |rate);
return 0;
;;
default:
return 1;
}
} //setBlinkRate
/****************************************************************/
// turn on the display
//
void HT16K33::displayOn(){
_i2c_write(HT16K33_DSP |HT16K33_DSP_ON);
} // displayOn
/****************************************************************/
// turn off the display
//
void HT16K33::displayOff(){
_i2c_write(HT16K33_DSP |HT16K33_DSP_OFF);
} // displayOff
Comments