Flip, Flap, Flop! – Solari Fix.

About 13 years ago, before this web site, and back when I used to be called boznz.com, I put out a simple project to reverse engineer some soft-flap display units (brand-name Solari). My clients CEO had bought 20 of them at a charity auction when they were upgrading the flight information boards at Auckland Airport (18 worked!).

I did a rough write up at the time that got featured on hack-a-day and I preserved the basics of the original web page here.

A Sad, Broken, Display 🙁

It was happily sitting pride of place in the cafeteria of my clients 15-Valley winery for those 13 years providing real-time updates to the grape tonnage received, and the occasional funny message, in its own retro-spectacular way. This was until a few weeks ago, when it failed to wake up at the start of vintage.

Actually I was quite surprised it lasted that long given how old and fragile the individual units are, how quick and dirty the whole project was engineered, and how bad an environment it was in. So, as things were ticking over nicely and problems were minor, and ignoring my usual warranty rules, I got the guys to uninstall it and throw it in the back of my dodgy electric pickup for the bumpy 20KM journey back to my workshop.

After an age with an air compressor and vacuum cleaner, and with the unit fully dismantled in front on me on the bench now, the memories flooded back. I had just returned to electronic engineering in from a 15 year hiatus in IT and network management and my electronics lab at that point consisted of an old radio shack soldering iron, a fluke multimeter, a 5Mhz analog scope, a newly acquired EasyPIC 4 development kit from Mikroelectronika with MikroPascal for PIC and various out of date through-hole components and breadboards, that managed to survive shipping from the UK. All of course in my spare bedroom, so when they guys at Indevin dropped the box of displays on my desk and asked me to “make it work” I was all fired up to give it all a try. I managed to get something knocked-up and working, good enough, at least that a carpenter could mount it all in a neat cabinet and hide the warts.

Fast forward 13 years..

With the unit in pieces I immediately saw the problem, the control board, and four of the units had pretty severe water damage. I say water, but it was more likely beer, wine or a soft drink as it had eaten away at some of the PCB’s and mechanics. But 14 of the display units looked good. The problem was the control board needed replacing and as this was the only time I ever used the MikroPascal compiler for PIC in anger, so it was going to be a new design.

I remember at the time I thought MikroPascal was quite cool, and it was dead simple, solving me the problem of re-learning C. Alas it was a short love affair as my next client insisted I use C and from that point on, so well I just went with the money!

Anyway, armed with the original pascal source code I quickly knocked out a replacement control board and 140 line of (XC8) C which would replicate the functionality. While in the re-design phase, changes were made to convert the input from ethernet to RS485, so the weighs could be directly updated from their SCADA Interface. I reused the original transformer, cables and 14 of the remaining displays and got it all working again, but while it was in pieces I thought I would capture some nicer pictures.

Some Technical Info for the future.

The Control board mainly consists of power-supplies. The two raw 18VAC supplies going directly out to the slave devices. And two DC supplies, a 12VDC 3A which supplies the solari slave devices, and a 5VDC 3A which supplies the control logic and the slave daughter boards. The 3A for the 5V is totally overkill, 20 slaves plus the control board draw a tenth of that, but it always good if you can re-use the same components to reduce the pick-n-place component count. The only other real change, apart from the obvious fact we are now using a serial RS485 data input instead of Ethernet, is that the front panel indicator/reset switch actually powers off the 12VDC and 5VDC supplies giving a harder reset. I also re-used another PIC16F1782 from my pile, which now wont go into landfill when I die 🙂

The one thing of note in the code below is that I have extended the delay between sending packets out to the individual devices; This is to reduce the demand on the 12VDC and 18VAC supplies as the slaves only draw significant power when enabled and ‘flapping’. Moving the delay from 100mS to 500mS will space the demand out, and keep everything less stressed but will take 8 seconds to send out the message and so about 12 seconds to display all 16 digits (Yes I got two more of the damaged ones working!)

I did not make any changes to the daughter board or its original pascal code. I saw a few minor improvements I could have made but did not see anything that really justified messing with them.

// SOLARI CONTROLLER Using PIC16F1782 https://rodyne.com/?p=2169

#include <xc.h>

#pragma config FOSC     = INTOSC    // Use internal 1% Oscillator should be good enough
#pragma config CLKOUTEN = OFF      
#pragma config FCMEN    = ON       	// osc fail monitor on
#pragma config MCLRE    = ON        // Used as reset button not IO
#pragma config WDTE     = ON     	// Watchdog timer ON
#pragma config STVREN   = ON        // reset on stack overflow ON
#pragma config BOREN    = ON        // reset on stack low voltage

#define BUS_DAT(state)  LATAbits.LATA0 = state // 1 = ON, 0 = OFF
#define BUS_CLK(state)  LATAbits.LATA2 = state // 1 = ON, 0 = OFF
#define ACT_LED(state)  LATBbits.LATB3 = state // 1 = ON, 0 = OFF (Activity LED)

#define _XTAL_FREQ 16000000 // req for the __delay_ms() function to work accurately
#define REC_RST 0
#define REC_ERR 1
#define REC_OK  2

const char ascii[128] = // Map ASCII to valid Solari character positions, invalid positions return 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,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,12,0,13,2,11,10,9,8,7,6,5,4,3,0,0,0,0,0,0,0,39,38,37,36,
    35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,0,0,0,0,1,0,39,38,37,36,
    35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,0,0,0,0,0
};

volatile uint8_t  RxBufPtr, RxTimeout, DataReady;
volatile uint8_t  SerBuf[32]; // 0..31 max message size is the number of slaves + cr

void __interrupt() ProcessINT() // UART RXD interrupt (ON RC7) and TIMER0
{
    if(RCIF) // character received
    {    
        SerBuf[RxBufPtr] = RCREG;
        RxTimeout=250; // start data timeout
        if(RCREG==0x13 && RxBufPtr>5)
        {
           RxTimeout=0; // turn off the timeout
           DataReady=REC_OK; // flag data ready
        }
        if(RxBufPtr<25) RxBufPtr++; else DataReady=REC_ERR; // data overrun
        RCIF=0; // flag RX interrupt processed
    }
    if(TMR0IF) // 1mS interrupt
    {
        if(RxTimeout>0) RxTimeout--;  // decrement to 0
        if(RxTimeout==1 || RxBufPtr>20) DataReady=REC_ERR; // flag receive data time out or too many devices being written too
        TMR0IF=0; // flag TMR0 interrupt processed        
    }        
}

void delay_uS(uint8_t us) // not going to be very accurate due to lots of factors, approx is fine though
{
	// note overhead of approx 2uS for procedure call. Accomodate if necessary
	while(us--)
	{
		// 16 x NOP. adjust for "more accuracy"
		asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
		asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
	}	
}

void main()
{
    char device, databit;
    uint16_t mask, data;

    OSCCON = 0b01111010; // 16Mhz Internal OSC
    WDTCON = 0b00010101; // WDT ON timeout 4 seconds inactivity
    
    // I/O Ports
    TRISA   = 0b00000000; // Port A Direction reg All outputs
    ANSELA  = 0b00000000; // Port A Analog config No analog
    LATA    = 0b00000000; // Port A Initial state All OFF
    TRISB   = 0b00000000; // Port B All output
    ANSELB  = 0b00000000; // Port B All digital
    LATB    = 0b00000000; // all off
    TRISC   = 0b10000000; // Port C All outputs except RC7 which is RXD
    LATC    = 0b00000000; //     

    ACT_LED(1); 
    BUS_CLK(1);
    BUS_DAT(0);                
	
    BRG16 = 0; // Set up UART 9600/8/N/1 Async (CREN=1, SYNC=0, SPEN=1)
    SPBRG = 25;        
    SPEN  = 1;
    RCIE  = 1;     // enable UART1 interrupt
    PEIE  = 1;     // Enable Peripheral interrupt    
    
    OPTION_REG = 0b10000011; // Set up TIMER0 for 1mS interrupt 16M/4/16/256 = 976Hz
    TMR0IE     = 1;          // enable timer 0 interrupt

    RxBufPtr=REC_RST;

    __delay_ms(1000);

    ACT_LED(0);    

    GIE = 1; //Restore interrupts    
        
    while(1)
    {        
        while(!DataReady) // wait here for data packet to be received (or timeout!)
        {    
            CLRWDT();
            if(RxBufPtr>0) ACT_LED(1); else ACT_LED(0); // briefly flash LED when serial data being received
        }            
        
        // Get to here we received a packet of data or receival time out 
                
        if(DataReady==REC_OK) // Got DATA + CR
        {            
			GIE=0; // interrupts off, not required for 
            // devices have addresses starting at 0 no need to check how many if sender wants to waste time sending wrong number of bits let them
            for(device=0; device<RxBufPtr; device++) 
            {
                data = (ascii[SerBuf[device]]*256) + device;
                mask = 0x0001;
                
                for(databit=0; databit<16; databit++) // clock out the string to the solaris
                {
                    CLRWDT();
                    BUS_CLK(0);
                    delay_us(10);
                    if(data & mask) BUS_DAT(1); else BUS_DAT(0);
                    delay_us(10);
                    BUS_CLK(1);
                    mask = mask << 1;
                    delay_us(10);                    
                }    
                __delay_ms(500); // big delay between chars so they dont all go off at the same time 
            }            
        }

        CREN=0;             // clear any rec errors (if any)
        RxBufPtr=0;         // clear receive buffer
        DataReady=REC_RST;  // clear data ready        
        CREN=1;
		GIE=1;				// interrupts back on
    }
}

A better view of the PCB is here. The only error I had was I added a pull-up resistor R13 in the original version 1 board on the TPS5430 ENA to Vin when it needs to float to enable and grounded to sleep, the design I have now removed this and works fine.

This entry was posted in C Programming, Electronics, Indevin, Repair, Soft-Flap Display, solari and tagged , , . Bookmark the permalink.