Flickering on 16 channel expansion

Expansion modules and attachments
Post Reply
User avatar
joshlawless
Posts: 138
Joined: Thu May 23, 2013 2:52 pm

Flickering on 16 channel expansion

Post by joshlawless »

I thought my 16 channel board was working fine, because I was able to cycle the lights through each channel, and confirmed that they would accept dimming levels from 0 to 4095. But when I actually hooked them up over the tank, and tried to simply run them at a fixed level, I get constant flickering.

I'm confident it's not a wiring problem, because I tried to fix it by uploading revised code and rebooting the RA+. Every time I upload new code, the flickering stops for the duration of the upload. As soon as the loop() is running, though, the channels flicker several times per second.

I've simplified the code down to just this function in the loop()

Code: Select all

for (int i=0;i<8;i++)
     {
       CustomExpansion(0x41, i, 1023);
     }
with this function definition:

Code: Select all

void CustomExpansion(byte id, byte channel, int level)
{
  Wire.beginTransmission(id);
  Wire.write(0);
  Wire.write(0xa1);
  Wire.endTransmission();
  Wire.beginTransmission(id);
  Wire.write(0x8+(4*channel));
  Wire.write(level&0xff);
  Wire.write(level>>8);
  Wire.endTransmission();
}
Comparing that to the definition for SIXTEENChExpansion() in RA_PWM.cpp (libraries 1.1.1), I can't see a difference (other than the Transmission address, which is set by the jumper, and must be correct for the lights to even turn on):

Code: Select all

void RA_PWMClass::SIXTEENChExpansion(byte channel, int data)
{
    // the data is in int, so just send that int to the module
    if (channel<SIXTEENCH_PWM_EXPANSION_CHANNELS) SIXTEENChExpansionChannel[channel]=data;
    Wire.beginTransmission(I2CPWM_16CH_PCA9685);
    Wire.write(0);
    Wire.write(0xa1);
    Wire.endTransmission();
    Wire.beginTransmission(I2CPWM_16CH_PCA9685);
    Wire.write(0x8+(4*channel));
    Wire.write(data&0xff);
    Wire.write(data>>8);
    Wire.endTransmission();
}
Nothing else in the code should have any impact that I can see:

Code: Select all

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <PAR.h>
#include <ReefAngel.h>

////// Place global variable code below here

// NW = Neutral White; WW = Warm White; RD = Red; CY = Cyan; CB = Cool Blue; RB = Royal Blue; IN = Indigo; VI = Violet
// -E = East; -W = West; -N = North; -S = South (compass points of tank)
// LED Array order      {NW-E, NW-W, NW-N, NW-S,   WW-E, WW-W, WW-N, WW-S,   RD-E, RD-W, RD-N, RD-S,   CY-E, CY-W, CY-N, CY-S,   CB-E, CB-W, CB-N, CB-S,   RB-E, RB-W, RB-N, RB-S,   IN-E, IN-W, IN-N, IN-S,   VI-E, VI-W, VI-N, VI-S}
byte LEDBoardAdd [32] = {0x41, 0x41, 0x42, 0x42,   0x41, 0x41, 0x42, 0x42,   0x41, 0x41, 0x42, 0x42,   0x41, 0x41, 0x42, 0x42,   0x41, 0x41, 0x42, 0x42,   0x41, 0x41, 0x42, 0x42,   0x41, 0x41, 0x42, 0x42,   0x41, 0x41, 0x42, 0x42};
byte LEDBoardChn [32] = {   0,    8,    0,    8,      1,    9,    1,    9,      2,   10,    2,   10,      3,   11,    3,   11,      4,   12,    4,   12,      5,   13,    5,   13,      6,   14,    6,   14,      7,   15,    7,   15};
int  LEDDelayOn  [32] = { -30,   30,    0,    0,    -30,   30,    0,    0,    -45,   45,    0,    0,    -30,   30,    0,    0,    -30,   30,    0,    0,    -30,   30,    0,    0,    -30,   30,    0,    0,    -30,   30,    0,    0};
int  LEDDelayOff [32] = {   0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0};
int  LEDStartPct [32] = {   0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0};
int  LEDEndPct   [32] = { 100,  100,  100,  100,    100,  100,  100,  100,    100,  100,  100,  100,    100,  100,  100,  100,    100,  100,  100,  100,    100,  100,  100,  100,    100,  100,  100,  100,    100,  100,  100,  100};
byte LEDRampRate [32] = { 120,  120,  120,  120,    120,  120,  120,  120,    120,  120,  120,  120,    120,  120,  120,  120,    120,  120,  120,  120,    120,  120,  120,  120,    120,  120,  120,  120,    120,  120,  120,  100};
int  LEDDefault  [32] = {   0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0,      0,    0,    0,    0};

int a=0;                                 // variable just needed for test code
int b=0;                                 // variable just needed for test code
int c=0;                                 // variable just needed for test code
byte previouschannel=0;                  // variable just needed for test code


////// Place global variable code above here


void setup()
{
    ReefAngel.Init();                        // This must be the first line -- initializes the controller
    ReefAngel.Use2014Screen();               // Let's use 2014 Screen 
    ReefAngel.AddSalinityExpansion();        // Salinity Expansion Module
    ReefAngel.AddORPExpansion();             // ORP Expansion Module
    ReefAngel.AddWaterLevelExpansion();      // Water Level Expansion Module
    ReefAngel.AddExtraTempProbes();          // Accommodate 4th temperature probe in parallel     
     
// Ports toggled in Feeding Mode

    ReefAngel.FeedingModePorts = Port3Bit;
    ReefAngel.FeedingModePortsE[0] =  Port4Bit;

// Ports toggled in Water Change Mode

    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit;
    ReefAngel.WaterChangePortsE[0] =  Port4Bit;

// Ports toggled when Lights On / Off menu entry selected

    ReefAngel.LightsOnPorts = 0;
    ReefAngel.LightsOnPortsE[0] = 0;

// Ports turned off when Overheat temperature exceeded

    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    ReefAngel.OverheatShutoffPortsE[0] = 0;

// Use T1 probe (in display tank) for temp functions and T3 probe (in skimmer section) for overheat functions

    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T3_PROBE;

// Feeeding and Water Change mode speed

    ReefAngel.DCPump.FeedingSpeed=0;
    ReefAngel.DCPump.WaterChangeSpeed=0;

// Ports that are always on

    ReefAngel.Relay.On( Port5 );       // Air Pump
    ReefAngel.Relay.On( Port6 );       // Filter manifold pump
    ReefAngel.Relay.On( Box1_Port1 );  // Circulation pump
    ReefAngel.Relay.On( Box1_Port2 );  // Diverter
    ReefAngel.Relay.On( Box1_Port5 );  // return pump L
    ReefAngel.Relay.On( Box1_Port6 );  // return pump R


////// Place additional initialization code below here

    ReefAngel.CustomLabels[0]="Heater1";  
    ReefAngel.CustomLabels[1]="Heater2";  
    ReefAngel.CustomLabels[2]="Skimmer";  
    ReefAngel.CustomLabels[3]="Neck Wipe";  
    ReefAngel.CustomLabels[4]="Air Pump";  
    ReefAngel.CustomLabels[5]="Filters";  
    ReefAngel.CustomLabels[6]="Refuge Lt";  
    ReefAngel.CustomLabels[7]="Ozone";  
    ReefAngel.CustomLabels[8]="Diverter";
    ReefAngel.CustomLabels[9]="Circulate";  
    ReefAngel.CustomLabels[10]="Kalk Stir";  
    ReefAngel.CustomLabels[11]="ATO  Pump";  
    ReefAngel.CustomLabels[12]="Return L";      
    ReefAngel.CustomLabels[13]="Return R";  
    ReefAngel.CustomLabels[14]="Heater3";  


////// Place additional initialization code above here
}

void loop()
{

////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                //
//                                          TEMPERATURE                                           //
//                                                                                                //
////////////////////////////////////////////////////////////////////////////////////////////////////
                                                                                                  //
  if ( ReefAngel.Params.Temp[T1_PROBE] < 775) {ReefAngel.Relay.On(Port1);}                        //
  if ( ReefAngel.Params.Temp[T1_PROBE] > 780) {ReefAngel.Relay.Off(Port1);}                       //
                                                                                                  //
  if ( ReefAngel.Params.Temp[T2_PROBE] < 775) {ReefAngel.Relay.On(Port2);}                        //
  if ( ReefAngel.Params.Temp[T2_PROBE] > 780) {ReefAngel.Relay.Off(Port2);}                       //
                                                                                                  //
  if ( ReefAngel.Params.Temp[T3_PROBE] < 775) {ReefAngel.Relay.On(Box1_Port7);}                   //
  if ( ReefAngel.Params.Temp[T3_PROBE] > 780) {ReefAngel.Relay.Off(Box1_Port7);}                  //
                                                                                                  //
  ReefAngel.CustomVar[7] = ReefAngel.Params.Temp[T4_PROBE];                                       // Puts hood temperature in C7
                                                                                                  //
////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                //
//                                            SKIMMER                                             //
//                                                                                                //
////////////////////////////////////////////////////////////////////////////////////////////////////
                                                                                                  //
  ReefAngel.Relay.DelayedOn( Port3 );                                                             // Skimmer is delayed to allow water level in skimmer section to adjust following return pump activation
  ReefAngel.DosingPumpRepeat1( Port4 );                                                           // Skimmer neck wipe is handled by internal memory location
  if(!ReefAngel.HighATO.IsActive()) {ReefAngel.Relay.Override(Port3,0);}                          // Skimmate collector sensor is connected to HighATO (closest to centerline of controller body)
                                                                                                  //
////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                //
//                                            LIGHTING                                            //
//                                                                                                //
////////////////////////////////////////////////////////////////////////////////////////////////////
                                                                                                  //
  ReefAngel.MoonLights( Port7 );                                                                  // The refugium light is on an opposite photoperiod, handled by internal memory location
                                                                                                  //
                                                                                                  //
 for (int i=0;i<8;i++)                                                                            // This code will apply the LED array content (per channel max and min and delays, etc.) to the PWM HD slope. 
     {                                                                                            // 
       CustomExpansion(0x41, i, 1023);                                                            //
     }                                                                                            //
// for (int i=0;i<32;i++)                                                                           // This code will apply the LED array content (per channel max and min and delays, etc.) to the PWM HD slope. 
//     {                                                                                            // 
//       CustomExpansion(LEDBoardAdd[i], LEDBoardChn[i], OffsetPWMSMoothRamp(LEDDelayOn[i],         // 
//       LEDDelayOff[i], LEDStartPct[i], LEDEndPct[i], LEDRampRate[i], LEDDefault[i]));             //
//     }                                                                                            // 
                                                                                                  // 
                                                                                                  // Test code to blink each channel in sequence (according to LED array sequence)
//  CustomExpansion(LEDBoardAdd[now()%32], LEDBoardChn[now()%32], 1023);                            // sends full on (12-bit) signal to current channel, current channel advancing from 0 to 31 every second
//  previouschannel = now()%32-1;                                                                   // calculates value of previously powered on channel
//  if (previouschannel == -1) {previouschannel = 31;}                                              // corrects value of previously powered on channel if current channel is 0
//  CustomExpansion(LEDBoardAdd[previouschannel], LEDBoardChn[previouschannel], 0);                 // sends full off signal to previous channel
                                                                                                  //
////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                //
//                                              ATO                                               //
//                                                                                                //
////////////////////////////////////////////////////////////////////////////////////////////////////
                                                                                                  //
// ReefAngel.DosingPumpRepeat2( Box1_Port3 );                                                     // Kalk stirrer is handled by internal memory location
// ReefAngel.WaterLevelATO( Box1_Port4, 300, 22, 60 );                                            // Inputs are ATO Pump Relay, Timeout (max seconds pump can run), 
                                                                                                  // low water level to turn on pump, high water level to turn off pump
                                                                                                  //
////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                //
//                                          RETURN PUMP                                           //
//                                                                                                //
////////////////////////////////////////////////////////////////////////////////////////////////////
                                                                                                  //
  ReefAngel.DCPump.UseMemory = false;                                                             // Seems necessary to set threshold
  ReefAngel.DCPump.Threshold = 70;                                                                // Minimum return pump value is 50%
  ReefAngel.DCPump.SetMode(Sine,100,240);                                                         // Return pumps oscillate back and forth from Threshold to 100 over 240 seconds
  ReefAngel.DCPump.DaylightChannel = Sync;                                                        // Daylight channel handles right/left (determine) pump
  ReefAngel.DCPump.ActinicChannel = AntiSync ;                                                    // Actinic channel handles left/right (determine) pump
                                                                                                  //
////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                //
//                                             OZONE                                              //
//                                                                                                //
////////////////////////////////////////////////////////////////////////////////////////////////////
                                                                                                  //
  if (ReefAngel.Params.ORP>450) ReefAngel.Relay.Off(Port8);                                       // If ORP > 450mV, then turn off ozone generator
  if (ReefAngel.Params.ORP==0) ReefAngel.Relay.Off(Port8);                                        // If ORP =  0mV (e.g., probe disconnected), then turn off ozone generator
  if (ReefAngel.Params.ORP<400 && ReefAngel.Params.ORP>0) ReefAngel.Relay.On(Port8);              // If ORP < 400mV, then turn on ozone generator (unless ORP=0, indicating missing probe)
                                                                                                  //
////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                //
//                                            NETWORK                                             //
//                                                                                                //
////////////////////////////////////////////////////////////////////////////////////////////////////
                                                                                                  //
  ReefAngel.Portal( "joshlawless" );                                                              // This line feeds info to the reefangel.com portal
  ReefAngel.DDNS( "reef" );                                                                       // Your DDNS is joshlawless-reef.myreefangel.com
  ReefAngel.ShowInterface();                                                                      // This should always be the last line
}

void CustomExpansion(byte id, byte channel, int level)  // this function is necessary to permit the 16-channel board to functionet
{
  Wire.beginTransmission(id);
  Wire.write(0);
  Wire.write(0xa1);
  Wire.endTransmission();                               // try to remove this and the next line
  Wire.beginTransmission(id);                           //try to remove this and the previous line
  Wire.write(0x8+(4*channel));
  Wire.write(level&0xff);
  Wire.write(level>>8);
  Wire.endTransmission();
}

//  this function calculates the LED channel levels based on a time-offset

int OffsetPWMSMoothRamp(int OnMinutesLate, int OffMinutesLate, int StartPercent, int EndPercent, byte SlopeLength, int OldValue)
{
  int onTime=NumMins(InternalMemory.StdLightsOnHour_read(),InternalMemory.StdLightsOnMinute_read())+OnMinutesLate;
  int offTime=NumMins(InternalMemory.StdLightsOffHour_read(),InternalMemory.StdLightsOffMinute_read())+OffMinutesLate;
  PWMSmoothRampHighRes(onTime/60,onTime%60,offTime/60,offTime%60,StartPercent,EndPercent,SlopeLength,OldValue);   
}
Any help would be greatly appreciated -- I don't want my fish to get a seizure!


--EDIT--

If I leave it running for more than a few seconds, it eventually confuses the expansion board so much that all the lights turn off. If I remove power from the expansion board, then power it up again, the lights do their disco dance for a while longer before turning off. But if I don't power-cycle the expansion board, even rebooting the controller doesn't get the lights to turn back on.

I tried uploading just this simple test code:

Code: Select all

#include <Salinity.h>
#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <rtc_clock.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <ReefAngel.h>

void setup()
{
  ReefAngel.Init();    

}

void loop()
{
  for (int a=0;a<8;a++)
  {
    CustomExpansion(0x41,a,0);
  }
  CustomExpansion(0x41,now()%8,100);
  
  ReefAngel.ShowInterface();
}

void CustomExpansion(byte id, byte channel, byte percentage)
{
  Wire.beginTransmission(id);
  Wire.write(0);
  Wire.write(0xa1);
  Wire.endTransmission();
  int newdata=(int)(percentage*40.95);
  Wire.beginTransmission(id);
  Wire.write(0x8+(4*channel));
  Wire.write(newdata&0xff);
  Wire.write(newdata>>8);
  Wire.endTransmission();
}
And while it will cycle through the lights indefinitely, for the duration each light is on, it flickers at least two or three times.

If I unplug the USB cable from the expansion board while one of the light channels is on, it stays on without flickering.

I've tried replacing the USB cable with a different one, replacing the 12v power supply to the expansion board with a different one, connecting the USB cable directly to the relay box (bypassing the expansion hub), power cycling the entire controller system, with no luck.

Really hoping there isn't something wrong with the board, but given how it essentially locks up after a few seconds, I'm suspicious it may not be a coding problem.
Last edited by joshlawless on Thu Jun 04, 2015 2:35 pm, edited 1 time in total.
User avatar
joshlawless
Posts: 138
Joined: Thu May 23, 2013 2:52 pm

Re: Flickering on 16 channel expansion

Post by joshlawless »

Thinking it might be a hardware problem, I disconnected all the wires from my expansion board and wired up my other expansion board, moving its jumper to make it address 0x41. Sadly, the same flickering problem persisted. Guess it's not a hardware issue?
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Flickering on 16 channel expansion

Post by rimai »

If you look closely into the code, we implement a crc to only send data when it is changed.
Also, there is an initialization section that needs to be sent to the module.

Code: Select all

void RA_PWMClass::ExpansionWrite()
{
	byte thiscrc=0;
	for ( byte a = 0; a < PWM_EXPANSION_CHANNELS; a++ )
		thiscrc+=GetChannelValueRaw(a)*(a+1);
	if (millis()%60000<200) lastcrc=-1;
	if (lastcrc!=thiscrc || millis()<5000)
	{
		lastcrc=thiscrc;
		// setup PCA9685 for data receive
		// we need this to make sure it will work if connected ofter controller is booted, so we need to send it all the time.
		Wire.beginTransmission(I2CPWM_PCA9685);
		Wire.write(0);
		Wire.write(0xa1);
		Wire.endTransmission();
		for ( byte a = 0; a < PWM_EXPANSION_CHANNELS; a++ )
		{
			Expansion(a,GetChannelValueRaw(a));
		}
	}
}
Maybe you ought to implement the same thing.
Roberto.
User avatar
joshlawless
Posts: 138
Joined: Thu May 23, 2013 2:52 pm

Re: Flickering on 16 channel expansion

Post by joshlawless »

Roberto,

Thank you for the helpful suggestion! I was able to code it so only changed data is pushed to the expansion board:

In the global variables:

Code: Select all

int FirstExpansionData [16];          // To keep track of the levels of the channels on the first board (address 0x41)
int SecondExpansionData [16];     // To keep track of the levels of the channels on the second board (address 0x42)
The revised custom function:

Code: Select all

void CustomExpansion(byte id, byte channel, int level)  // this function is necessary to permit the 16-channel board to functionet
{
  if ((id==0x41 && level!=FirstExpansionData[channel]) || (id==0x42 && level!=SecondExpansionData[channel]))
  {
  Wire.beginTransmission(id);
  Wire.write(0);
  Wire.write(0xa1);
  Wire.endTransmission(); 
  Wire.beginTransmission(id);
  Wire.write(0x8+(4*channel));
  Wire.write(level&0xff);
  Wire.write(level>>8);
  Wire.endTransmission();
  if (id==0x41) FirstExpansionData[channel]=level;
  if (id==0x42) SecondExpansionData[channel]=level;
  }
}
I'm sure there's a way to store all 32 levels in a single array, e.g.,

Code: Select all

if (level!=ExpansionData[channel+((id-41)*16)]) 
But I'm hesitant to tinker now that I've got the lights on steady.

If I understood what was happening in the CRC function, maybe I'd realize I was missing something else. But I think it's just calculating a unique value based on the levels of all the channels, and then determining if the currently desired levels would result in a different unique value, and only writing if that's the case. I'm unsure of what the millis() function does, but I'm guessing that it allows the value to be written, even if it's the same as the current value, so long as enough time has passed.
User avatar
joshlawless
Posts: 138
Joined: Thu May 23, 2013 2:52 pm

Re: Flickering on 16 channel expansion

Post by joshlawless »

Am I right to understand that this line:

if (millis()%60000<200) lastcrc=-1;

Forces the expansion module to write the currently desired value at least once a minute (e.g., 0.2 seconds out of every minute)

And that this line

if (lastcrc!=thiscrc || millis()<5000)

Forces the expansion module to write the currently desired value if it is either (a) different from the last written data or (b) when the Reef Angel first boots up

?
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Flickering on 16 channel expansion

Post by rimai »

Yeap :)
Roberto.
User avatar
joshlawless
Posts: 138
Joined: Thu May 23, 2013 2:52 pm

Re: Flickering on 16 channel expansion

Post by joshlawless »

Hooray, I'm learning! :D
User avatar
joshlawless
Posts: 138
Joined: Thu May 23, 2013 2:52 pm

Re: Flickering on 16 channel expansion

Post by joshlawless »

Well, I didn't have as much luck with the CRC as written as I would have liked. The first five seconds after boot up (mills()<5000) were flickering so bad, it would sometimes crash the 16 channel dimming board (especially if I rebooted a few times within a short while, like when testing code).

With these two lines in my global variable code:

Code: Select all

int  LEDLevel     [32] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int  LEDLastWrite [32] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
these lines in my initialization code:

Code: Select all

    ReefAngel.CustomVar[0]=16;
    ReefAngel.CustomVar[1]=16;
    ReefAngel.CustomVar[2]=16;
    ReefAngel.CustomVar[3]=8;
    ReefAngel.CustomVar[4]=4;
    ReefAngel.CustomVar[5]=8;
    ReefAngel.CustomVar[6]=4;
    ReefAngel.CustomVar[7]=2;
and this function definition:

Code: Select all

void CustomExpansion(byte id, byte channel, int level)
{
  if (level!=LEDLevel[channel+((id-0x41)*16)] || (millis()%60000<(channel+1)*100 && millis()%60000>channel*100))
  {
  Wire.beginTransmission(id);
  Wire.write(0);
  Wire.write(0xa1);
  Wire.endTransmission();                               // try to remove this and the next line
  Wire.beginTransmission(id);                           //try to remove this and the previous line
  Wire.write(0x8+(4*channel));
  Wire.write(level&0xff);
  Wire.write(level>>8);
  Wire.endTransmission();
  LEDLevel[channel+((id-0x41)*16)]=level;
  LEDLastWrite[channel+((id-0x41)*16)]=millis();
  }
}
I've been able to eliminate the flickering. Unfortunately, every once in a while, all the channels turn off for no apparent reason, for about half a second or so.

If anyone can see a reason why that's happening due to anything in the code, I'd be grateful for an explanation.


FYI, the "id" byte in the function is either 0x41 or 0x42, based on the two I2C addresses for the custom boards Roberto made. The expression [channel+((id-0x41)*16)] is designed to allow both sixteen channel boards to store values in a single 32-entry array. The "(millis()%60000<(channel+1)*100 && millis()%60000>channel*100)" is intended to time shift each channel's (or, actually, each pair of channel's) forced updates into separate 0.1 second long time spans.
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Flickering on 16 channel expansion

Post by rimai »

May I ask you to use the current 16ch code within the libraries and see if that works?
You would only be able to control one module (0x41), but it would let us know more details on what is going on.
Roberto.
User avatar
joshlawless
Posts: 138
Joined: Thu May 23, 2013 2:52 pm

Re: Flickering on 16 channel expansion

Post by joshlawless »

I'll give that a try tonight, as I only have one of the boards wired up anyways.
AlanM
Posts: 263
Joined: Wed Jan 01, 2014 7:26 am

Re: Flickering on 16 channel expansion

Post by AlanM »

For what it's worth, I added most of the 16ch and higher resolution code in the libraries and have been using it for a good part of a year and it seems to work fine for me, but if it's finicky with other situations I'd like to know. If the usage of it is confusing please post up here and I'll try to help.

Some usage notes for the 16 channel stuff and 12-bit high-res functions are at:

https://github.com/reefangel/Libraries/ ... _Notes.txt
User avatar
joshlawless
Posts: 138
Joined: Thu May 23, 2013 2:52 pm

Re: Flickering on 16 channel expansion

Post by joshlawless »

Alan, thanks for the great work on the code.

I thought I would have to use custom functions to accommodate the two 16-channel boards I'm setting up, so I'm trying to replicate some of your functionality in my own INO. I don't know that there's anyone else who's going to ever use two of these 16-channel devices, so I'm guessing that introducing standard support for two is unlikely. If it were introduced, I'm guessing it would look something like (in Globals.h):

#define THIRTYTWOCH_PWM_EXPANSION_CHANNELS 16
...
#ifdef THIRTYTWOCHPWMEXPANSION
#define TTPWMbit 32
#else
#define TTPWMbit 0
#endif // THIRTYTWOCHPWMEXPANSION

etc., with a dedicated I2C of 0x42 in the PWM libraries. I'm still new enough with Arduino code not to have confidence in putting this together, but am fairly sure at the rate I'm having to learn, I could implement something like that in a dev branch eventually.

In any event, I tried to replicate the functionality of the CRC check in the function I posted (checking to see if the value to be pushed to the 16ChPWM board is different, or if enough time has passed to force an update anyways). I couldn't get the millis()%60000<200 condition to work for me, as trying to push 8 channels of 12-bit updates as many times as possible during those 0.2 seconds was causing the LEDs to flicker every second. Hence my effort to separate the forced updates into different time bands by channel.

With all the other stuff going on with my unit, I'm not convinced that there's anything left to fix in my lighting code -- it seems that something on the bus is causing trouble with my expansion relay, and that could be the same underlying problem that's causing the occasional blink in (every channel, simultaneously of) my LEDs. Still, if you see a problem with how I've written it (or think that separating the forced updates into different time bands is a bad idea), I'd love to hear your thoughts.
AlanM
Posts: 263
Joined: Wed Jan 01, 2014 7:26 am

Re: Flickering on 16 channel expansion

Post by AlanM »

OK. I can think about how I'd try to support another 16 channels. Might be possible to just try to set a definition in the .ino for the number of PWM expansion channels instead of hardcoding it and change 16 to the memory location for number of channels everywhere it is used in an array and then on the write do a modular divide to get the actual channel number on the correct expansion board since you have them on two different I2C channels.

I don't know much about the millis() part of the code. I just reused it. I do know that you don't have to constantly send a value. The PCA9685 chipset in the 16 channel dimmer remembers the previous value even if you're not sending it stuff, even if the USB signal cable is disconnected, so be careful to just send values when they actually change. Surely they're not changing very quickly.

Does it work if you just use one 16ch board set to 0x41 address and with the current dev branch? Here's what I'm doing with my lights, by the way inside my loop. On my 16ch I have 2 channels of Jebao pumps, 6 channels of light colors, and one PWM fan channel. I set the maximums in memory locations that the standard dimming module would normally use and read them. I can change them with the android app by poking new values into those locations if I want or even do it with the browser with /mb commands.

Code: Select all

 ReefAngel.PWM.SIXTEENChannelPWMSigmoid(2,0,InternalMemory.PWMSlopeEnd0_read()); // Violet channel, startpwm, endpwm, offsets
  ReefAngel.PWM.SIXTEENChannelPWMSigmoid(3,0,InternalMemory.PWMSlopeEnd1_read()); // Royal Blue channel, startpwm, endpwm, offsets
  ReefAngel.PWM.SIXTEENChannelPWMSigmoid(4,0,InternalMemory.PWMSlopeEnd2_read()); // White channel, startpwm, endpwm, offsets
  ReefAngel.PWM.SIXTEENChannelPWMSigmoid(5,0,InternalMemory.PWMSlopeEnd3_read()); // Cool Blue channel, startpwm, endpwm, offsets
  ReefAngel.PWM.SIXTEENChannelPWMSigmoid(6,0,InternalMemory.PWMSlopeEnd4_read(),-60,-60); // Deep Red channel, startpwm, endpwm, offsets
  ReefAngel.PWM.SIXTEENChannelPWMSigmoid(7,0,InternalMemory.PWMSlopeEnd5_read(),-60,-60); // Cyan channel, startpwm, endpwm, offsets
  ReefAngel.PWM.SIXTEENChannelPWMSigmoid(8,0,85); // Fan channel, silent up to 75 or so.
User avatar
joshlawless
Posts: 138
Joined: Thu May 23, 2013 2:52 pm

Re: Flickering on 16 channel expansion

Post by joshlawless »

Hey, I just realized something.

I've declared my LEDLastWrite [32] array as a type int.

But millis() returns an unsigned long.

Probably isn't good to store a 32-bit unsigned long value in a 16-bit integer array, and then try to do math with it.

Since it's after midnight, I'll go work it out tomorrow.
User avatar
joshlawless
Posts: 138
Joined: Thu May 23, 2013 2:52 pm

Re: Flickering on 16 channel expansion

Post by joshlawless »

So regarding the library functions for writing to the 16 channel expansion, I think the occasional flicker I see is a result of trying to send too much data to the PCA9685 at the same time.

As I understand it, this block of code:

Code: Select all

void RA_PWMClass::SIXTEENChExpansionWrite()
{
	byte thiscrc=0;
	for ( byte a = 0; a < SIXTEENCH_PWM_EXPANSION_CHANNELS; a++ )
		thiscrc+=Get16ChannelValueRaw(a)*(a+1);
	if (millis()%60000<200) lastcrc=-1;
	if (lastcrc!=thiscrc || millis()<5000)
	{
		lastcrc=thiscrc;
		// setup PCA9685 for data receive
		// we need this to make sure it will work if connected ofter controller is booted, so we need to send it all the time.
		Wire.beginTransmission(I2CPWM_16CH_PCA9685);
		Wire.write(0);
		Wire.write(0xa1);
		Wire.endTransmission();
		for ( byte a = 0; a < SIXTEENCH_PWM_EXPANSION_CHANNELS; a++ )
		{
			SIXTEENChExpansion(a,Get16ChannelValueRaw(a));
		}
	}
}
first generates a CRC value "thiscrc" by summing the product of each channel's raw value (0 to 4095) and it's channel number plus 1 (to address the 0-indexing of the first channel). That CRC value is later used to determine whether there has been a change since the last write (in the "lastcrc!=thiscrc evaluation).

Similarly, the code also checks to see whether the system clock is within the first 0.2 seconds of every 60 seconds (per the millis()%60000<200 evaluation), and if so, resets the "thiscrc" value (ensuring it will be different from the "lastcrc" value to force a write).

Finally, the code checks to see whether the system has booted up in the last 5 seconds (per the millis()<5000 condition), and if so, forces a write.

When any of the foregoing conditions are met, the system forces an actual transmission to the chip for EVERY channel, (per the "for ( byte a = 0; a < SIXTEENCH_PWM_EXPANSION_CHANNELS; a++ )" language).

Even with only 8 channels connected, I still occasionally see a flicker that looks just like the flickers I got when writing too often without a CRC. Similarly, the lights flicker in the first few seconds after boot up, as the system sends lighting commands over and over and over for the first five seconds of the system operation.

What if, instead, the code were to determine on a channel by channel basis whether the channel data is different, or if enough time since _this particular channel_ was written has passed, before undergoing a write. And if instead of forcing writes for the first five seconds, it forced one write upon bootup (perhaps by waiting until millis() exceeded 1000 or 2000, to ensure any necessary prerequiste action has been taken if any are required).

I'm struggling to see how, given the Refresh() call of SIXTEENChExpansionWrite() which calls SIXTEENChExpansion() while passing a variable that calls Get16ChannelValueRaw() ... that's a lot for my newbie mind to iterate through. But conceptually, does it make sense to send data to all channels if only one has changed? And if the occasional flicker is caused by the millis()%60000<200 condition, in which all channels are refreshed (over and over and over as many times as possible in 0.2 seconds), does it make sense to separate the writes into different time slots?
AlanM
Posts: 263
Joined: Wed Jan 01, 2014 7:26 am

Re: Flickering on 16 channel expansion

Post by AlanM »

I think the millis()%60000<200 condition is not intended to refresh them really fast. I think it's intended to make sure that they don't refresh every single time that SIXTEENChExpansionWrite() is called.

Roberto can weigh in here, but I think the function makes it so that every 60000 milliseconds (once per minute) it gets written to whether or not anything has changed. Then there's the function that forces a write within the first 5 seconds and also if the CRC has changed.

My guess is that the CRC is calculated by adding up all channels to see if any of them has changed because it is slightly less computationally expensive than checking every channel, but I bet you could take that "for" loop and only write the ones that changed.

Lemme play with it a bit and give you a code snippet to paste in to RA_PWM.h and RA_PWM.cpp. Actually, I'll make a branch on my code on github that you can pull from if you have access there.
AlanM
Posts: 263
Joined: Wed Jan 01, 2014 7:26 am

Re: Flickering on 16 channel expansion

Post by AlanM »

Here ya go:

https://github.com/amunter-nist/Librari ... f4269714d5

So remove the pink lines and insert the green ones in your "Arduino/libraries/RA_PWM" directory files and try it out. I made this branch from Roberto's dev branch.
Post Reply