Thursday, July 21, 2011

Netduino - RTC

I have bought, received and implemented my real time clock. I am using a class library I found from the forums. Currently I am just printing the time and date to the LCD along with the temperature, however I have run in to my first issue. The refresh rate on the screen seems to be about half a second, so when you clear the screen and rewrite it, you get this flickering affect, making it hard to read. I will post as I solve the issue.

I have managed to get the real time clock working now, however I am still plagued by the issue of the screen refresh.


MAIN

using System;
using System.Threading;
using System.Collections;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using CW.NETMF.Hardware;
using SecretLabs.NETMF.IO;
using netduino.helpers.Hardware;

namespace SerialGraphicLcd
{

    public class Program
    {

        static SGLcd disp = new SGLcd(SerialPorts.COM2);

        public static void Main()
        {
            var clock = new DS1307();
            // Set the clock to some arbitrary date / time
            clock.Set(new DateTime(2011, 7, 21, 22, 51, 20));
            clock.Halt(false);

            int maxT = 28;
            int minT = 27;
            float oldTemp = 0;
            OutputPort tled = new OutputPort(Pins.ONBOARD_LED, false);
            OutputPort rled = new OutputPort(Pins.GPIO_PIN_D7, false);
            OutputPort gled = new OutputPort(Pins.GPIO_PIN_D6, false);
            var oneWire = new OneWire(Pins.GPIO_PIN_D0); // Adjust the pin if necessary
            disp.BackLightDutyCycle(1);
            if (oneWire.Reset())
            {
                while (true)
                {
                    // DS18B20 Thermometer
                    oneWire.WriteByte(OneWire.SkipRom); // Address all devices
                    oneWire.WriteByte(DS18B20.ConvertT);
                    Thread.Sleep(750);                  // Wait Tconv (for default 12-bit resolution)

                    oneWire.Reset();
                    oneWire.WriteByte(OneWire.SkipRom);
                    oneWire.WriteByte(DS18B20.ReadScratchpad);

                    // Read just the temperature (2 bytes)
                    var tempLo = oneWire.ReadByte();
                    var tempHi = oneWire.ReadByte();
                    var temp = DS18B20.GetTemperature(tempLo, tempHi); // ((short)((tempHi << 8) | tempLo))/16F
                  
                    Debug.Print("New temp: " + temp.ToString() + " \u00b0" + "C    -    Old temp: " + oldTemp.ToString() + " \u00b0" + "C");

                    if (temp > (int)maxT | temp < (int)minT)
                    {
                        rled.Write(true);
                        gled.Write(false);
                    }
                    else
                    {
                        rled.Write(false);
                        gled.Write(true);
                    }

                        disp.ClearDisplay();
                        disp.GotoXY(2, 54);
                        disp.Write("Temp: " + temp.ToString() + " oC");  //\u00b0
                        disp.GotoXY(2, 63);
                        disp.Write(clock.Get().ToString());

                    oneWire.Reset();
                    Thread.Sleep(4250);
                    oldTemp = (float)temp;
                }
            }
        }
    }

}



RTC

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace netduino.helpers.Hardware
{
    ///

    /// This class implements a complete driver for the Dallas Semiconductors / Maxim DS1307 I2C real-time clock: http://pdfserv.maxim-ic.com/en/ds/DS1307.pdf
    ///

    public class DS1307 : IDisposable
    {
        [Flags]
        // Defines the frequency of the signal on the SQW interrupt pin on the clock when enabled
        public enum SQWFreq { SQW_1Hz, SQW_4kHz, SQW_8kHz, SQW_32kHz, SQW_OFF };

        [Flags]
        // Defines the logic level on the SQW pin when the frequency is disabled
        public enum SQWDisabledOutputControl { Zero, One };

        // Real time clock I2C address
        public const int DS1307_I2C_ADDRESS = 0x68;
        // I2C bus frequency for the clock
        public const int DS1307_I2C_CLOCK_RATE_KHZ = 100;

        // Allow 10ms timeouts on all I2C transactions
        public const int DS1307_I2C_TRANSACTION_TIMEOUT_MS = 10;

        // Start / End addresses of the date/time registers
        public const byte DS1307_RTC_START_ADDRESS = 0x00;
        public const byte DS1307_RTC_END_ADDRESS = 0x06;

        // Square wave frequency generator register address
        public const byte DS1307_SQUARE_WAVE_CTRL_REGISTER_ADDRESS = 0x07;

        // Start / End addresses of the user RAM registers
        public const byte DS1307_RAM_START_ADDRESS = 0x08;
        public const byte DS1307_RAM_END_ADDRESS = 0x3f;

        // Total size of the user RAM block
        public const byte DS1307_RAM_SIZE = 56;

        // Instance of the I2C clock
        I2CDevice clock;

        public DS1307()
        {
            clock = new I2CDevice(new I2CDevice.Configuration(DS1307_I2C_ADDRESS, DS1307_I2C_CLOCK_RATE_KHZ));
        }

        ///
        /// Gets the date / time in 24 hour format.
        ///

        /// A DateTime object
        public DateTime Get()
        {
            byte[] clockData = new byte[7];

            // Read time registers (7 bytes from DS1307_RTC_START_ADDRESS)
            var transaction = new I2CDevice.I2CTransaction[] {
                I2CDevice.CreateWriteTransaction(new byte[] {DS1307_RTC_START_ADDRESS}),
                I2CDevice.CreateReadTransaction(clockData)
            };

            if (clock.Execute(transaction, DS1307_I2C_TRANSACTION_TIMEOUT_MS) == 0)
            {
                throw new Exception("I2C transaction failed");
            }

            return new DateTime(
                BcdToDec(clockData[6]) + 2000, // year
                BcdToDec(clockData[5]), // month
                BcdToDec(clockData[4]), // day
                BcdToDec(clockData[2] & 0x3f), // hours over 24 hours
                BcdToDec(clockData[1]), // minutes
                BcdToDec(clockData[0] & 0x7f) // seconds
                );
        }

        ///
        /// Sets the time on the clock using the datetime object. Milliseconds are not used.
        ///

        /// A DateTime object used to set the clock
        public void Set(DateTime dt)
        {
            var transaction = new I2CDevice.I2CWriteTransaction[] {
                I2CDevice.CreateWriteTransaction(new byte[] {
                                  DS1307_RTC_START_ADDRESS,
                                  DecToBcd(dt.Second),
                                  DecToBcd(dt.Minute),
                                  DecToBcd(dt.Hour),
                                  DecToBcd((int)dt.DayOfWeek),
                                  DecToBcd(dt.Day),
                                  DecToBcd(dt.Month),
                                  DecToBcd(dt.Year - 2000)} )
            };

            if (clock.Execute(transaction, DS1307_I2C_TRANSACTION_TIMEOUT_MS) == 0)
            {
                throw new Exception("I2C write transaction failed");
            }
        }

        ///
        /// Enables / Disables the square wave generation function of the clock.
        /// Requires a pull-up resistor on the clock's SQW pin.
        ///

        /// Desired frequency or disabled
        /// Logical level of output pin when the frequency is disabled (zero by default)
        public void SetSquareWave(SQWFreq Freq, SQWDisabledOutputControl OutCtrl = SQWDisabledOutputControl.Zero)
        {
            byte SqwCtrlReg = (byte)OutCtrl;

            SqwCtrlReg <<= 3;   // bit 7 defines the square wave output level when disabled
            // bit 6 & 5 are unused

            if (Freq != SQWFreq.SQW_OFF)
            {
                SqwCtrlReg |= 1;
            }

            SqwCtrlReg <<= 4; // bit 4 defines if the oscillator generating the square wave frequency is on or off.
            // bit 3 & 2 are unused

            SqwCtrlReg |= (byte)Freq; // bit 1 & 0 define the frequency of the square wave

            WriteRegister(DS1307_SQUARE_WAVE_CTRL_REGISTER_ADDRESS, SqwCtrlReg);
        }

        ///
        /// Halts / Resumes the time-keeping function on the clock.
        /// Calling this function preserves the value of the seconds register.
        ///

        /// True: halt, False: resume
        public void Halt(bool halt)
        {
            var seconds = this[DS1307_RTC_START_ADDRESS];

            if (halt)
            {
                seconds |= 0x80; // Set bit 7
            }
            else
            {
                seconds &= 0x7f; // Reset bit 7
            }

            WriteRegister(DS1307_RTC_START_ADDRESS, seconds);
        }

        ///
        /// Writes to the clock's user RAM registers as a block
        ///

        /// A byte buffer of size DS1307_RAM_SIZE
        public void SetRAM(byte[] buffer)
        {
            if (buffer.Length != DS1307_RAM_SIZE)
            {
                throw new ArgumentOutOfRangeException("Invalid buffer length");
            }

            // Allocate a new buffer large enough to include the RAM start address byte and the payload
            var TrxBuffer = new byte[sizeof(byte) /*Address byte*/ + DS1307_RAM_SIZE];

            // Set the RAM start address
            TrxBuffer[0] = DS1307_RAM_START_ADDRESS;

            // Copy the user buffer after the address
            buffer.CopyTo(TrxBuffer, 1);

            // Write to the clock's RAM
            var transaction = new I2CDevice.I2CWriteTransaction[] { I2CDevice.CreateWriteTransaction(TrxBuffer) };

            if (clock.Execute(transaction, DS1307_I2C_TRANSACTION_TIMEOUT_MS) == 0)
            {
                throw new Exception("I2C write transaction failed");
            }
        }

        ///
        /// Reads the clock's user RAM registers as a block.
        ///

        /// A byte array of size DS1307_RAM_SIZE containing the user RAM data
        public byte[] GetRAM()
        {
            var ram = new byte[DS1307_RAM_SIZE];

            var transaction = new I2CDevice.I2CTransaction[] {
                    I2CDevice.CreateWriteTransaction(new byte[] {DS1307_RAM_START_ADDRESS}),
                    I2CDevice.CreateReadTransaction(ram)
                    };

            if (clock.Execute(transaction, DS1307_I2C_TRANSACTION_TIMEOUT_MS) == 0)
            {
                throw new Exception("I2C transaction failed");
            }

            return ram;
        }

        ///
        /// Reads an arbitrary RTC or RAM register
        ///

        /// Register address between 0x00 and 0x3f
        /// The value of the byte read at the address
        public byte this[byte address]
        {
            get
            {
                if (address > DS1307_RAM_END_ADDRESS)
                {
                    throw new ArgumentOutOfRangeException("Invalid register address");
                }

                var value = new byte[1];

                // Read the RAM register @ the address
                var transaction = new I2CDevice.I2CTransaction[] {
                    I2CDevice.CreateWriteTransaction(new byte[] {address}),
                    I2CDevice.CreateReadTransaction(value)
                    };

                if (clock.Execute(transaction, DS1307_I2C_TRANSACTION_TIMEOUT_MS) == 0)
                {
                    throw new Exception("I2C transaction failed");
                }

                return value[0];
            }
        }

        ///
        /// Writes an arbitrary RTC or RAM register
        ///

        /// Register address between 0x00 and 0x3f
        /// The value of the byte to write at that address
        public void WriteRegister(byte address, byte val)
        {
            if (address > DS1307_RAM_END_ADDRESS)
            {
                throw new ArgumentOutOfRangeException("Invalid register address");
            }

            var transaction = new I2CDevice.I2CWriteTransaction[] {
                I2CDevice.CreateWriteTransaction(new byte[] {address, (byte) val})
            };

            if (clock.Execute(transaction, DS1307_I2C_TRANSACTION_TIMEOUT_MS) == 0)
            {
                throw new Exception("I2C write transaction failed");
            }
        }

        ///
        /// Takes a Binary-Coded-Decimal value and returns it as an integer value
        ///

        /// BCD encoded value
        /// An integer value
        protected int BcdToDec(int val)
        {
            return ((val / 16 * 10) + (val % 16));
        }

        ///
        /// Takes a Decimal value and converts it into a Binary-Coded-Decimal value
        ///

        /// Value to be converted
        /// A BCD-encoded value
        protected byte DecToBcd(int val)
        {
            return (byte)((val / 10 * 16) + (val % 10));
        }

        ///
        /// Releases clock resources
        ///

        public void Dispose()
        {
            clock.Dispose();
        }
    }
}



LCD

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using System.IO.Ports;

namespace SerialGraphicLcd
{
    public sealed class SGLcd
    {

        public enum DisplayType { H12864, H240320 }

        public enum Status { On, Off }


        private readonly SerialPort _serialPort;

        private readonly DisplayType _displayType;


        public SGLcd(string portName)
            : this(portName, DisplayType.H12864)
        { }

        public SGLcd(string portName, DisplayType displayType)
        {
            // Defaults for SerialPort are the same as the settings for the LCD, but I'll set them explicitly
            _serialPort = new SerialPort(portName, 115200, Parity.None, 8, StopBits.One);

            _displayType = displayType;
        }

        public void DemoDisplay()
        {
            Write(new byte[] { 0x7C, 0x04 });
        }

        public void ClearDisplay()
        {
            Write(new byte[] { 0x7C, 0x00 });
        }

        // Reverse Mode
        public void Reverse()
        {
            Write(new byte[] { 0x7C, 0x12 });
        }

        //Splash Screen
        public void SplashScreen()
        {
            Write(new byte[] { 0x7C, 0x13 });
        }

        //Set Backlight Duty Cycle
        public void BackLightDutyCycle(byte dutycycle)
        {
            byte[] buffer = new byte[3];
            buffer[0] = 0x7c;
            buffer[1] = 0x02;
            buffer[2] = dutycycle;

            Write(buffer);
        }
        //Change Baud Rate
        /*
        “1” = 4800bps
        “2” = 9600bps
        “3” = 19,200bps
        “4” = 38,400bps
        “5” = 57,600bps
        “6” = 115,200bps
        */
        public void NewBaudRate(string baud)
        {
            byte[] temp = new byte[3];
            temp[0] = 0x7c;
            temp[1] = 0x07;
            temp[2] = (byte)baud[0];
            Write(temp);
        }


        //Set X or Y Coordinates
        public void GotoXY(byte x, byte y)
        {
            //set x first
            byte[] temp = new byte[3];
            temp[0] = 0x7c;
            temp[1] = 0x18;
            temp[2] = x;
            Write(temp);
            //set y
            temp[0] = 0x7c;
            temp[1] = 0x19;
            temp[2] = y;
            Write(temp);
        }

        //Set/Reset Pixel - x =0 to xmax, y = 0 to ymax, state = 0 or 1
        public void SetPixel(byte x, byte y, byte state)
        {
            byte[] temp = new byte[5];
            temp[0] = 0x7c;
            temp[1] = 0x10;
            temp[2] = x;
            temp[3] = y;
            temp[4] = state;
            Write(temp);
        }


        //Draw Line x1 y1 first coords, x2 y2 second coords, state 0 = erase 1 = draw
        public void DrawLine(byte x1, byte y1, byte x2, byte y2, byte state)
        {
            byte[] temp = new byte[7];
            temp[0] = 0x7c;
            temp[1] = 0x0c;
            temp[2] = x1;
            temp[3] = y1;
            temp[4] = x2;
            temp[5] = y2;
            temp[6] = state;
            Write(temp);
        }


        //Draw Circle
        public void DrawCircle(byte x, byte y, byte r, byte state)
        {
            byte[] temp = new byte[6];
            temp[0] = 0x7c;
            temp[1] = 0x03;
            temp[2] = x;
            temp[3] = y;
            temp[4] = r;
            temp[5] = state;
            Write(temp);
        }

        //Draw Box
        public void DrawBox(byte x1, byte y1, byte x2, byte y2, byte state)
        {
            byte[] temp = new byte[7];
            temp[0] = 0x7c;
            temp[1] = 0x0f;
            temp[2] = x1;
            temp[3] = y1;
            temp[4] = x2;
            temp[5] = y2;
            temp[6] = state;
            Write(temp);
        }

        //Erase Block
        public void EraseBlock(byte x1, byte y1, byte x2, byte y2)
        {
            byte[] temp = new byte[6];
            temp[0] = 0x7c;
            temp[1] = 0x05;
            temp[2] = x1;
            temp[3] = y1;
            temp[4] = x2;
            temp[5] = y2;

            Write(temp);

        }


        public void Open()
        {
            if (!_serialPort.IsOpen)
                _serialPort.Open();
        }



        public void Write(byte buffer)
        {
            Write(new[] { buffer });
        }

        public void Write(byte[] buffer)
        {
            Open();

            _serialPort.Write(buffer, 0, buffer.Length);
        }

        public void Write(char character)
        {
            Write((byte)character);
        }

        public void Write(string text)
        {
            byte[] buffer = new byte[text.Length];

            for (int i = 0; i < text.Length; i++)
            {
                buffer[i] = (byte)text[i];
            }

            Write(buffer);
        }
    }
}

Sunday, July 17, 2011

Netduino - Getting somewhere...

Since my last post I haven't had much time to do anything with my project, but yesterday I had a fantastically productive day. I decided to bypass the 6-7 volt requirement of the 5v regulator on the serial backpack for my LCD and to power the 5v pin directly from my netduino. This meant I could instantly start playing around with my LCD. I found I really useful class on the netduino forums: http://forums.netduino.com/index.php?/topic/687-driver-class-for-sparkfun-lcd-graphic-backpack/ and quickly managed to get temperatures on the screen.

Currently my project is reading temperatures, determining if it is with a range, then if it is in range a green led is on and when it goes out of range a red led comes on. Also the temperature is then printed to the display, see my code so far below:


using System;
using System.Threading;
using System.Collections;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using CW.NETMF.Hardware;
using SecretLabs.NETMF.IO;


namespace SerialGraphicLcd
{

    public class Program
    {

        static SGLcd disp = new SGLcd(SerialPorts.COM2);

        public static void Main()
        {
            int maxT = 28;
            int minT = 27;
            float oldTemp = 0;
            OutputPort tled = new OutputPort(Pins.ONBOARD_LED, false);
            OutputPort rled = new OutputPort(Pins.GPIO_PIN_D7, false);
            OutputPort gled = new OutputPort(Pins.GPIO_PIN_D6, false);
            var oneWire = new OneWire(Pins.GPIO_PIN_D0); // Adjust the pin if necessary
            disp.BackLightDutyCycle(1);
            if (oneWire.Reset())
            {
                while (true)
                {
                    // DS18B20 Thermometer
                    oneWire.WriteByte(OneWire.SkipRom); // Address all devices
                    oneWire.WriteByte(DS18B20.ConvertT);
                    Thread.Sleep(750);                  // Wait Tconv (for default 12-bit resolution)

                    oneWire.Reset();
                    oneWire.WriteByte(OneWire.SkipRom);
                    oneWire.WriteByte(DS18B20.ReadScratchpad);

                    // Read just the temperature (2 bytes)
                    var tempLo = oneWire.ReadByte();
                    var tempHi = oneWire.ReadByte();
                    var temp = DS18B20.GetTemperature(tempLo, tempHi); // ((short)((tempHi << 8) | tempLo))/16F
                   
                    Debug.Print("New temp: " + temp.ToString() + " \u00b0" + "C    -    Old temp: " + oldTemp.ToString() + " \u00b0" + "C");

                    if (temp > (int)maxT | temp < (int)minT)
                    {
                        rled.Write(true);
                        gled.Write(false);
                    }
                    else
                    {
                        rled.Write(false);
                        gled.Write(true);
                    }
                    if (oldTemp != (float)temp)
                    {
                        disp.ClearDisplay();
                        disp.GotoXY(2, 63);
                        disp.Write(temp.ToString() + " oC");  //\u00b0
                    }
                    oneWire.Reset();
                    Thread.Sleep(5000);
                    oldTemp = (float)temp;
                }
            }
        }
    }

}


My next aim is to get the data logged on to the Micro SD, in a format that can be used by the netduino and could also be used to plug in to a computer. The end idea for the SD is to give me more space for my code and also to store data, for example if the internet went down or the server that I will log my data on in the future, the netduino would have a backup on the SD. As each entry will be time stamped it can find the last entry and then reconcile the missing times. This data will then be graphically display in graphs etc to allow me to monitor fluctuations in parameters of the tank over time.

Update to my shopping list....

I have just order a real time clock board which has a DS1307 rtc IC on it, and 6 relays from Hong Kong. The relays are solid state and operate on a 3-32 volt switching current and can take up to 15amps @ 480v. I am going to build a board, perhaps using some diodes to isolate the relays completely and some transistors to switch them, with LEDs to let me know which relays are on. The great thing with the SSRs is I can do PWM on lights, essentially allowing me to dim any light, although I am eventually going to use LED lighting in all my tanks. The primary reason for the SSRs was just because they require a low voltage so I can power them directly from the netduino and they isolate the high current inside them. I will need to look at using a shift register to operate the relays as I don't want to use to many pins from the netduino. I have already had a little play with shift registers to control LEDs but only managed to daisy chain 2, the third seemed to operate inversely to other 2.

Thursday, July 7, 2011

Netduino - dimmer update

I have done a slight update to my dimmer code as I noticed that there was a little noise at the bottom end which led to the led flickering slightly rather than being completely off. I corrected this with a simple if less than or equal to 3% then set to 0%. I also looked at inverse usage of the pots reading, this is done by taking the pot value away from 100, as the range of the pot was set to 0 - 100.


using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace CIRC_08
{
    public class Program
    {

        static PWM pwm2 = new PWM(Pins.GPIO_PIN_D10);
        static AnalogInput potentiometer = new AnalogInput(Pins.GPIO_PIN_A0);
        static PWM pwm = new PWM(Pins.GPIO_PIN_D5);

        public static void Main()
        {
            int sensorValue = 0;
            potentiometer.SetRange(0, 100);

            while (true)
            {
                sensorValue = potentiometer.Read();
                if (sensorValue <= 3)
                {
                    sensorValue = 0;
                }
                Debug.Print("pot value = " + sensorValue.ToString());
                pwm.SetDutyCycle((uint)sensorValue);
                pwm2.SetDutyCycle(100-(uint)sensorValue);
            }
        }

    }
}

Wednesday, July 6, 2011

Netduino - project update

Since my last post I have been quite busy following basic tutorials (losely) and trying to achieve a few simple projects that may become part of my final project. This has been great for me to get to grips with c# and the .net framework and I am already starting to see similarities in between asp.net and the micro framework.

I have sucessfully managed to create a dimmer, initially this was a button triggered dimmer that turned a light on and off in a fading action. This has been refined with a more personal approach and I have managed to take reading from a pot and use this to affect the pwm port in order to dim the light. It's basic but it proves a point.


using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace CIRC_08
{
    public class Program
    {

        static OutputPort led = new OutputPort(Pins.GPIO_PIN_D13, false);
        static AnalogInput potentiometer = new AnalogInput(Pins.GPIO_PIN_A0);
        static PWM pwm = new PWM(Pins.GPIO_PIN_D5);
      
        public static void Main()
        {
            int sensorValue = 0;
            potentiometer.SetRange(0, 100);
                      
            while (true)
            {
                sensorValue = potentiometer.Read();
                Debug.Print("pot value = " + sensorValue.ToString());
                pwm.SetDutyCycle((uint)sensorValue);
            }
        }

    }
}


Finally I have also ordered some extra bits and pieces, I have 10 shift registers, a real time clock, a new temperature probe and a few other bits on their way. I am currently trying to figure out if I can bypass the 6-7 v vcc on the lcd display I have in order to run it straight off the netduino. The idea being I will play around with the screen a little and then see if I can print my various reading to it. After that it will be logging data to the sd card!