Category Archives: PIC Programming

LED Controller – production version 0.1

The time has arrived to move the LED controller circuit from the bread board to a real board. It was to be my first experience with relatively delicate soldering of quite a few connections. I stuck the few components onto the board and started soldering the underside.

IMG_6574

I realised after a short while that I shouldn’t have placed cables on this side at all because the soldering will break if any pressure is applied to them. Also, after some cables were already soldered it because difficult to solder more of them, especially those connections that already lay under other cables. Anyway, most of the cables in the photo above connect the 7-segment controller with the 7-segment display.

On the left side of the photo you can see the two common ground an 5V lines that I connected to the board. This was very convenient.

I passed the rest of the cables on the upper side.

One of the nice features of the board is the old green connector that I salvaged from an old PC board. In this photo I connected LEDs for verification, but when I’ll mount it onto the quadcopter I’ll connect the cables attached to the LEDs. Getting it out of the original board was hard and attaching it to this board was even harder because its pins were wider than the holes in the board. I filed them for a long time and finally decided to hammer the piece (gently) onto the board. This worked.

IMG_6573

And here is the board in operation.

I had to protect the cables running on the bottom side so I prepared a frame from two pieces of this soft plastic material whose Enlish name I don’t know. I cut the frame to be smaller than the board so that it will hold the board in place.

IMG_6585

When I replaced the test LEDs with real high-intensity LEDS I found the following problems:

  1. The high-intensity LEDs are rather dim. I didn’t debug this problem yet but I can think of two possible causes. One is that I used resistors that are too strong (220 Ohm).
  2. I may connected the transistors incorrectly.
  3. The transistors are not sourcing enough current.

I will have to talk to my friend Tomer about this problem.

CDS – Installation and Operation

The electronic part of the CDS is relatively simple compared to its installation. I started by gluing the sensor box to the metal window frame with a 2-sided glue strip.

IMG_6562

Then I found a small reflector on an old bicycle and glued it with plaster at the exact position on the opposite wall.

Some time passed until I decided to finally connect the water pipes to my automatic irrigation system. I did that only when I discovered a small hole in one of the main pipes and had to take it apart and replace that section of the pipe. So I added a T-shaped connector and drew a line towards the window.

IMG_6563

This is how both ends of the system look like.

IMG_6559

And … it works perfectly!

One more video.

And here is how it looks from outside.

CDS – Cat Deterring System – Development

One of the cats living in our backyard has been bothering us for a long time. We love cats, but this one keeps sneaking into the house, meowing loudly even when it’s not hungry and trying to steal food on every occasion (even though we feed it outside). When we have a family meal we can’t open the kitchen window because it jumps inside and then tries to jump on the table.

Nothing helped until my daughter came up with the idea for a Cat Deterring System, or CDS in short. CDS is based on cat’s fear (or deep dislike) of being wet. The idea is to install a motion sensor across the window that opens an electric tap when the cat jumps on the ledge and sprays a could of water. Simple and cool.

I started by ordering this sensor on eBay. I checked the prices right now and they are very high. I managed to buy 2 of these for just 10 Euro each. They now sell for much higher prices – between 30 and 100 euros!

IMG_6578

I also ordered an electric solenoid valve running on 12V (as the sensor). These valves were rather expensive and I’m a bit surprised that I bought them just for scaring a cat away …

IMG_6577

 

These components were lying on my shelf for several months before I finally got down to develop the system. But at some point my wife threatened that she would do something drastic about the cat so I had no choice. I didn’t want to find out what that may be.

The most difficult part was to understand who the sensor works. Quite early in the process I realised that I need a reflector to position opposite the sensor. I also needed to create a connector for the sensor because it didn’t arrive with one. The sensor has 4 connections, two for ground and 12V input and output pins – one is high when the beam is reflected (no obstruction) and on is high when the beam is blocked. My friend Tomer helped me with the connectors – 4 pins salvaged from an old serial cable.

I hooked up a simple circuit in which the output of the sensor connects an old 12V relay that I had at home and the relay opens the valve. I needed the relay because the output from the sensor could not hold the valve open. Tomer said I should have used a transistor and that a relay would not work fast enough but in fact a relay is just perfect, and I had nothing to do with it.

So I set up a small test rig to try and get the sensor to work.

 

IMG_6456

 

After looking around for a reflector I found an old hologram which worked well. You can see it in the next picture. Unfortunately, the sensor is very sensitive and can detect the reflected beam only if the reflector is set just right. It took me a very long time to get it to work.

IMG_6457

 

The video below shows the system in operation.

The sensor has two lights, a green one that indicates that it is on and an orange one. The orange light comes on when the beam is reflected properly. It blinks when the beam is detected but not perfectly. It goes off when the beam is blocked.

 

LED Controller version 0.1

After many evenings and a long time of learning to use all the required resources of the PIC16F1825, here is the first version of the LED controller for my quadcopter. It requires some debugging but it works.

The video shows the test setup of the LED controller. The transmitter is on the right and the receiver is the small box between the transmitter and the breadboard.

The circuit on the breadboard consists of two PIC controllers, a 7-segment display, LEDs and some resistors. The left PIC controls the 7-segment and the one on the right controls executes the application.

When the video starts the 7 segment display shows a rotating segment for a split of a second. Then it enters the startup sequence for about 1 second. During this time the 7-segment displays  888 and all the LEDs light up. Then the 7-segment display shows the current mode and the LEDs enter this mode. The initial mode is 1 and all the LEDs are off.

Then I push the button that is associated to channel 6 upwards. This changes the PWM value on the channel and the modes start to change. There are 14 modes, so each mode corresponds to a range of PWM values and hence multiple presses are needed to switch to another mode. Notice how the LEDs change their behaviour when modes change.

There are still some bugs in the system. Notice how the number 154 appears on the display when I start pressing the button. This is because I configured the lower PWM value higher than the real minimum value. Also, the changing of modes is not smooth or predictable. This could be related to the non-linear configuration of the channel.

7-segment controller – Sequences and Serial protocol

A few days ago I finally started writing the LED controller code according to the specification of the init sequence that is described in another post here. One of the first tasks is to light up the LEDs and the display as a self-test on startup. Lighting up the display means that I should show the number ‘888’ that lights up all the segments (except for the decimal dot that I don’t use in this project). At this point I realised that the serial receiver part can receive only one byte – numbers up to 255. So I decided to spend a bit more time on the 7-segment controller and enhance it in two ways:

  1. Allow it to display sequences of patterns and not only numbers. The difference is this – when displaying a number the controller displays the same digits over and over again while a sequence shows a different pattern on every iteration through the main loop. The difference is a bit subtle because a number can be considered a sequence with just one state. Anyway, the youtube video below shows an example of a “sequence”
  2. Implement a serial input protocol by which it is possible to send commands to the controller. The protocol is explained below.

Example of a sequence

This youtube video shows an example of a sequence.

The serial protocol

The previous version of the controller listened on the serial port and expected to receive a single byte each time that it interpreted as an unsigned 1-byte number. The new version expects a “packet” with the following structure:

  • Type – 1 byte. Values are: 1 (1-byte number), 2 (2-byte number), 3 (4-byte number), 4 (sequence ID)
  • Length – 1 byte. The length of the value parameter. This is a forward looking field because right now the lengths of the values are completely determined by the type field. However, I am preparing for passing strings in the future.
  • Value – variable length according the the Length field. If the value is a number it is transmitted MSB first.
  • Delay – 1 byte. The delay is multiplied by 50 by the controller and is used for the time period in which digit is lit. The unit is the Timer0 rollover rate.
  • Stop byte – 1 byte – value 0xFF. This byte serves to indicate ‘end of packet’ and as a simple check sum. The controller verifies that it sees 0xFF at the expected end of the packet.

The code

#include <xc.h>

// CONFIG1
#pragma config FOSC = INTOSC // Oscillator Selection 
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON // Power-up Timer Enable (PWRT enabled)
#pragma config MCLRE = OFF // MCLR Pin Function Select 
#pragma config CP = ON // Flash Program Memory Code Protection 
#pragma config CPD = ON // Data Memory Code Protection 
#pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF // Clock Out Enable 
#pragma config IESO = ON // Internal/External Switchover 
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable 

// CONFIG2
#pragma config WRT = OFF // Flash Memory Self-Write Protection 
#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable 
#pragma config BORV = HI // Brown-out Reset Voltage Selection
#pragma config LVP = OFF // Low-Voltage Programming Enable 

#define STOP_BYTE 0xFF
#define BUFFER_SIZE 36
#define MAX_SEQUENCE_ID 1

// pin to segment assignment is:
// C0-C1-C2-C3-C4-C5-A4
// A -B -C -D -E -F -G

// Pin A1 used for Serial RX
// Pin A0 used for display pin 8 – digit 1 – ones
// Pin A2 used for display pin 9 – digit 2 – tens

// Serial input types
// 1 – 1 byte value
// 2 – 2 byte value
// 3 – 4 byte value
// 4 – sequence id

typedef struct {
    unsigned char *state;
    int length;
} Sequence;

int loopstate;
int overrun = 0;
unsigned char rxbuf[BUFFER_SIZE];
unsigned char rxoffset = 0;
unsigned char digit1;
unsigned char digit2;
unsigned char digit3;

The digits array maps each digit to its representation in 7-segments 

unsigned char digits[] = {
   0b01000000, // 0
    0b01111001, // 1
    0b00100100, // 2
    0b00110000, // 3
    0b00011001, // 4
    0b00010010, // 5
    0b00000010, // 6
    0b01111000, // 7
    0b00000000, // 8
   0b00011000 // 9
};

The sequences below describe special effects of lighting segments of the display. Each record (a triple) represents the state of the 3 digits. A zero (0) indicates that the corresponding segment it lit while a (1) indicates that the segment is off.

unsigned char state1[][3] = { // figure 8
   {0b01111110, 0b01111111, 0b01111111},
   {0b01111101, 0b01111111, 0b01111111},
   {0b01111011, 0b01111111, 0b01111111},
   {0b01111111, 0b01110111, 0b01111111},
   {0b01111111, 0b01111111, 0b01101111},
   {0b01111111, 0b01111111, 0b01011111},
   {0b01111111, 0b01111111, 0b01111110},
   {0b01111111, 0b01111111, 0b01111101},
   {0b01111111, 0b01111111, 0b01111011},
   {0b01111111, 0b01111111, 0b01110111},
   {0b01111111, 0b01111011, 0b01111111},
   {0b01111111, 0b01111101, 0b01111111},
   {0b01111111, 0b01111110, 0b01111111},
   {0b01111111, 0b01011111, 0b01111111},
   {0b01111111, 0b01101111, 0b01111111},
   {0b01110111, 0b01111111, 0b01111111},
   {0b01101111, 0b01111111, 0b01111111},
   {0b01011111, 0b01111111, 0b01111111}
};

unsigned char state2[][3] = { // run on edges
   {0b01111110, 0b01111111, 0b01111111},
   {0b01111111, 0b01111110, 0b01111111},
   {0b01111111, 0b01111111, 0b01111110},
   {0b01111111, 0b01111111, 0b01111101},
   {0b01111111, 0b01111111, 0b01111011},
   {0b01111111, 0b01111111, 0b01110111},
   {0b01111111, 0b01110111, 0b01111111},
   {0b01110111, 0b01111111, 0b01111111},
   {0b01101111, 0b01111111, 0b01111111},
   {0b01011111, 0b01111111, 0b01111111}
};

The sequence array describes the segments – a pointer to the array of triples and the length.

Sequence sequence[] = {
   {(unsigned char *) state1, 18},
   {(unsigned char *) state2, 10}
};

The following 5 variables control the loop that lights up segments.

// control variables with sensible initial values
int type = 4;
int state_index = 0;
int delay = 8;
unsigned int number = 256;
Sequence *seq = &(sequence[1]) ;

The set_digits function is called at the start of every “display” loop. It determines what should be displayed in the following iteration of the display loop. It could be a number where each display digit shows a digit of the number, or a different pattern of a sequence.
/****************************************
*
****************************************/
void set_digits()
{
   int remainder;
   unsigned char *s;

   if (type >= 1 && type <=3) { // number
      digit1 = digits[number / 100];
      remainder = number % 100;
      digit2 = digits[remainder / 10];
      digit3 = digits[remainder % 10];
   }
   else { // sequence
      s = &(seq->state[state_index]);
      digit1 = s[0];
      digit2 = s[1];
      digit3 = s[2];
      state_index += 3;
      if (state_index == seq->length * 3)
         state_index = 0;
      }
   }

}

The process_serial_input function is called by the ISR whenever it sees a stop byte (the value 0xFF). The stop value is a valid value therefore this function returns 0 to indicate that a full packet was processed and a -1 to indicate that the buffer does not contain a full packet and more bytes should be acquired.

/****************************************
*
****************************************/
int process_serial_input()
{
   int i;
   unsigned char ltype = 0;
   unsigned char len = 0;
   unsigned char ldelay = 0;
   unsigned int intvalue;
   unsigned char stopbyte = 0;
   unsigned char *buff = rxbuf;

   ltype = *buff++;
   len = *buff++;

   for (i = 0; i < len; i++) {
      intvalue = intvalue << 8;
      intvalue += *buff++;
   }
   ldelay = *buff++;
   stopbyte = *buff;

   // Check the input for errors
   if (buff[2 + len + 2] != STOP_BYTE)  // missing stop byte
      return -1;
   if (ltype > 4)
      return -1;
   if (ltype == 4 && intvalue > MAX_SEQUENCE_ID)
      return -1;

   type = ltype;
   delay = ldelay;
   if (ltype < 4)
      number = intvalue;
   else
      seq = &(sequence[intvalue]);

   return 0;
}

The interrupt service routine reads bytes from the serial connection and fills the input buffer.

/****************************************
*
****************************************/
void interrupt ISR()
{
   int rc;
   unsigned char invalue;

   if (PIR1bits.RCIF == 1) {
      if (FERR == 0) {
         invalue = RCREG;
         rxbuf[rxoffset] = invalue;
         rxoffset++;

         if (invalue == STOP_BYTE) {
            rc = process_serial_input();
            if (rc == 0)
               rxoffset = 0;
         }
     }

      if (OERR == 1) {
         overrun = 1;
         rxoffset = 0;
      }
      PIR1bits.RCIF = 0;
   }
}

The show_overrun_error displays a special pattern on the display to indicate an overrun error. No handling of this yet.

/****************************************
*
****************************************/
void show_overrun_error()
{
LATC0 = 0;
LATC1 = 1;
LATC2 = 1;
LATC3 = 1;
LATC4 = 1;
LATC5 = 1;
LATA4 = 1;
}

The show_number function displays  a digit.

/****************************************
*
****************************************/
void show_number(unsigned char number)
{
   LATC0 = ((number & 0x1) > 0) ? 1 : 0;
   LATC1 = ((number & 0x2) > 0) ? 1 : 0;
   LATC2 = ((number & 0x4) > 0) ? 1 : 0;
   LATC3 = ((number & 0x8) > 0) ? 1 : 0;
   LATC4 = ((number & 0x10) > 0) ? 1 : 0;
   LATC5 = ((number & 0x20) > 0) ? 1 : 0;
   LATA4 = ((number & 0x40) > 0) ? 1 : 0;
}

Main contains the initialisation and the display loop. It displays a single digit at any time. 

/****************************************
*
****************************************/
void main()
{
   int interval;

   OSCCON = 0xF0; // set internal osc to 32Mhz
   OPTION_REG = 0x08; // Prescaler not assigned to timer 0

   TRISC0 = 0;
   TRISC1 = 0;
   TRISC2 = 0;
   TRISC3 = 0;
   TRISC4 = 0;
   TRISC5 = 0;
   TRISA4 = 0;
   TRISA5 = 0;
   TRISA2 = 0;
   TRISA0 = 0;
   TRISA1 = 1;

   // configure the RX pin on A1
   RXDTSEL = 1;

   // configure EUSART BAUD rate: Fosc / 64(n+1)
   BRG16 = 0;
   BRGH = 0;

   // configure the EUSART receiver
   CREN = 1;
   SYNC = 0;
   SPEN = 1;

   // The analog bit must be cleared for RX to function
   ANSELAbits.ANSA1 = 0;

   loopstate = 0;

   // enabling EUSART rx interrupt
   PIE1bits.RCIE = 1;
   INTCONbits.PEIE = 1;
   INTCONbits.GIE = 1;

   while(1) {
      if (loopstate == 0) {
         interval = delay * 50;
         set_digits();
         LATA0 = 0;
         LATA2 = 1;
         LATA5 = 0;
         if (overrun == 1)
            show_overrun_error();
         else
            show_number(digit2);
      }
      if (loopstate == 1 * interval) {
         LATA0 = 1;
         LATA2 = 0;
         LATA5 = 0;
         if (overrun == 1)
            show_overrun_error();
         else
            show_number(digit3);
      }
      if (loopstate == 2 * interval) {
         LATA5 = 1;
         LATA2 = 0;
         LATA0 = 0;
         if (overrun == 1)
            show_overrun_error();
         else
            show_number(digit1);
      }
      if (loopstate >= 3 * interval)
         loopstate = 0;
      else
         loopstate++;
   };
}

An interesting bug

This code is not really debugged because of lack of time but it works. However, there is an interesting bug in the system that I don’t have time to fix. The problem is that every 256th number is shown as a zero (0).

I don’t know if the problem is in the controller, the protocol or the test app that send the numbers over the serial connection.

On second thought, this problem must be related to my use of the value 0xFF as a stop byte. I will have to research this a bit more.

7-segment display driver – improvements

As you could see in the video I of my 7-segment driver with serial input, the left most digit was flickering heavily. I decided to spend some time to improve the code. I did several variations of the program and finally settled on the following one.

In this new version I eliminated Timer0 and changed the timing of actions in the main loop. The flickering didn’t disappear completely but it became much much better.

Here is the new application.

unsigned char digits[] = {
0b01000000, // 0
0b01111001, // 1
0b00100100, // 2
0b00110000, // 3
0b00011001, // 4
0b00010010, // 5
0b00000010, // 6
0b01111000, // 7
0b00000000, // 8
0b00011000 // 9
};

/****************************************
*
****************************************/
void interrupt ISR()
{
  if (PIR1bits.RCIF == 1) {
    if (FERR == 0)
      adcvalue = RCREG;
    if (OERR == 1) {
      overrun = 1;
    }
    PIR1bits.RCIF = 0;
  }
}

/****************************************
*
****************************************/
void show_overrun_error()
{
  LATC0 = 0;
  LATC1 = 1;
  LATC2 = 1;
  LATC3 = 1;
  LATC4 = 1;
  LATC5 = 1;
  LATA4 = 1;
}

/****************************************
*
****************************************/
void show_number(unsigned char number)
{
  LATC0 = ((number & 0x1) > 0) ? 1 : 0;
  LATC1 = ((number & 0x2) > 0) ? 1 : 0;
  LATC2 = ((number & 0x4) > 0) ? 1 : 0;
  LATC3 = ((number & 0x8) > 0) ? 1 : 0;
  LATC4 = ((number & 0x10) > 0) ? 1 : 0;
  LATC5 = ((number & 0x20) > 0) ? 1 : 0;
  LATA4 = ((number & 0x40) > 0) ? 1 : 0;
}

/****************************************
*
****************************************/
void calculate_digits()
{
  static unsigned int value = 0;
  int remainder;

  if (value == adcvalue)
  return;

  if (adcvalue > 999)
    adcvalue = 999;
    digit1index = adcvalue / 100;
    remainder = adcvalue % 100;
    digit2index = remainder / 10;
    digit3index = remainder % 10;
    value = adcvalue;
}

/****************************************
*
****************************************/
void main()
{
  OSCCON = 0xF0; // set internal osc to 32Mhz
  OPTION_REG = 0x08; // Prescaler not assigned to timer 0

  TRISC0 = 0;
  TRISC1 = 0;
  TRISC2 = 0;
  TRISC3 = 0;
  TRISC4 = 0;
  TRISC5 = 0;
  TRISA4 = 0;
  TRISA5 = 0;
  TRISA2 = 0;
  TRISA0 = 0;
  TRISA1 = 1;

  // configure the RX pin on A1
  RXDTSEL = 1;

  // configure EUSART BAUD rate: Fosc / 64(n+1)
  BRG16 = 0;
  BRGH = 0;

  // configure the EUSART receiver
  CREN = 1;
  SYNC = 0;
  SPEN = 1;

  // The analog bit must be cleared for RX to function
  ANSELAbits.ANSA1 = 0;

  state = 0;
  adcvalue = 0;

  // enabling EUSART rx interrupt
  PIE1bits.RCIE = 1;
  INTCONbits.PEIE = 1;
  INTCONbits.GIE = 1;

  while(1) {
    switch (state) {
      case 0 * INTERVAL:
        LATA0 = 0;
        LATA2 = 1;
        LATA5 = 0;
        if (overrun == 1)
          show_overrun_error();
        else
          show_number(digits[digit2index]);
        break;
      case 1 * INTERVAL:
        LATA0 = 1;
        LATA2 = 0;
        LATA5 = 0;
        if (overrun == 1)
          show_overrun_error();
        else
           show_number(digits[digit3index]);
        break;
      case 2 * INTERVAL:
        LATA5 = 1;
        LATA2 = 0;
        LATA0 = 0;
        if (overrun == 1)
          show_overrun_error();
        else
          show_number(digits[digit1index]);
        break;
      case 3 * INTERVAL:
        calculate_digits();
        break;
     }
     if (state >= 3 * INTERVAL)
       state = 0;
     else
       state++;
  };
}

I found something strange while working on this code. My main development computer is a MAC mini but it does not recognise my fake PICKIT3. Therefore I normally develop on a virtual Windows machine that runs under VirtualBox on my MAC. This time I decided to run the IDE on the MAC and run only the IPE (the programmer) on the Windows. When I compiled the code above on the MAC and downloaded it with Windows, the application behaved very strangely. It would suddenly crash, or come to a halt at random points in time. Other variations of this program caused a read overflow very quickly. It was very strange and nothing I did fixed the problem. So I went back to my normal environment and ran exactly the same code on the Windows machine and it just worked.

I will be very glad if anyone could explain why this happens. I did compare all the compiler settings, especially the optimisation settings, between the two computers and they are identical.

 

 

 

PIC Serial Tx and Rx

The last step of preparation for some serious PIC programming is to learn how to use the serial interface of the PIC for reading (Rx) and writing (Tx). So I decided to enhance the 3-Digit 7-segment display to read the input value over the serial connection. To test it I wrote a simple serial transmitter that sends the numbers from 1 to 255 first counting upwards (1 to 255) and then downwards (255 to 1) over and over again. The 3-digit display shows these numbers that it reads from its serial port.

The transmitter

First some definitions for the direction of counting

#define UP 1
#define DOWN 2

Then the  timer 0 counter

int timer0count;

The ISR simply increments the timer 0 counter every time Timer0 rolls over

void interrupt ISR()
{
  if (INTCONbits.TMR0IF) {
    timer0count++;
    INTCONbits.TMR0IF = 0;
    TMR0 = 0;
  }
}

This is the main() function
void main()
{
  unsigned int value = 1;
  char direction = UP;

  OSCCON = 0xF0; // set internal osc to 32Mhz
  OPTION_REG = 0x08; // Prescaler not assigned to timer 0

  TRISA0 = 0;

The next line sets the TX function on pin A0. This is important because the default is to use A4 for Tx.

  // TX function on pin A0
  TXCKSEL = 1;

Baud rate configuration. The data sheet contains tables with the settings for common values.

  // configure EUSART BAUD rate: Fosc / 64(n+1)
  BRG16 = 0;
  BRGH = 0;

The following lines configure the EUSART (extended universal serial asynchronous receiver transmitter) for Tx

  // configure the EUSART receiver
  TXEN = 1;
  SYNC = 0;
  SPEN = 1;

Clearing the analog output bit for pin A0 that we will use for Tx

  // The analog bit must be cleared for RX to function
  ANSELAbits.ANSA0 = 0;

  timer0count = 0;

Enabling the interrupts – needed for Timer 0

  INTCON = 0xC0 | 0xE0;
  INTCONbits.GIE = 1;

  while(1) {

We wait for 2000 overruns for transmitting a number. This comes out around 16 times a second.
    if (timer0count < 2000)
      continue;

    timer0count = 0;

The TRMT bit indicates whether we can write a character to the transmit register. We can write if it is set.
    if (TRMT == 0)
      continue;

Transmitting a character is done by writing it to TXREG

    TXREG = value;

Then we increase of decrease the value.
    if (direction == UP)
      value += 1;
    else
      value -= 1;
    if (value == 254)
      direction = DOWN;
    if (value == 1)
      direction = UP;
  }
}

The receiver

We will not copy the 3-digit controller again, but only point out the differences.

First the updated ISR with the new section highlighted in bold. The new section reads a character from RCREG when the RCIF flag is raised.

void interrupt ISR()
{
  if (INTCONbits.TMR0IF) {
    timer0count++;
    INTCONbits.TMR0IF = 0;
    TMR0 = 0;
  }
  if (PIR1bits.RCIF == 1) {
    if (FERR == 0)
      adcvalue = RCREG;
    if (OERR == 1) {
      overrun = 1;
    }
  }
}

Next is a new function to indicate that there is an overrun error. An overrun error occurs when a character is received before all the characters have been read from the 2 character input buffer. I decided to indicate this condition by lighting one segment on each digit. This is done by the following function:

void show_overrun_error()
{
  LATC0 = 1;
  LATC1 = 0;
  LATC2 = 0;
  LATC3 = 0;
  LATC4 = 0;
  LATC5 = 0;
  LATA4 = 0;
}

There are some changes in the main function. First we enable the EUSART receive interrupt and enable the EUSART:

// enabling EUSART rx interrupt
PIE1bits.RCIE = 1;
INTCONbits.PEIE = 1;
INTCONbits.GIE = 1;

// configure EUSART BAUD rate: Fosc / 64(n+1)
BRG16 = 0;
BRGH = 0;

// configure the EUSART receiver
CREN = 1;
SYNC = 0;
SPEN = 1;

// The analog bit must be cleared for RX to function
ANSELAbits.ANSA1 = 0;

The last change is in the switch statement that shows the digit. Here we check for an overrun and display the overrun error or the digit:

if (overrun == 1)
  show_overrun_error();
else
  show_number(digits[digit1index]);

Demo

This is how it work. The 3-digit controller is not perfect yet and the digit (especially to left most one) are flickering. But it does show the numbers correctly and will be very helpful in debugging my applications.

PIC LED Controller – PWM capture

The LED controller for my quadcopter will be controlled by by the remote control on one of the PWM channels. The last step of preparation before writing the LED controller itself is to understand how to listen on a remote control (RC) channel and detect the PWM “value” that is transmitted on the channel.

I decided to connect my cheap Minima receiver to the circuit and write a small program to display the PWM value, that I’lll transmit to it, on my 3-digit, 7-segment display. The Minima is sold by Hobbyking.com here. It is compatible with my Hitec transmitter and the price is just 19.73 euros, which is cheap compared to the original Hitec Optima receiver.

I had to come up with a method for communicating between the two PIC16F1825 controllers because the application controller has to transmit the PWM value to the 7-segment controller. I thought of using the USART serial interface but I decided to use the DAC (Digital to Analog Conversion) feature of the PIC16F1825. In this mode, the chip can output 32 voltage levels on pin RA0 – the DACOUT pin. This pin is connected to input pin RA2 on the 7-segment controller. The 7-segment controller displays a number between 0 and 99 that corresponds to the voltage on its input pin so it will display a number that corresponds to the PWM value. I decided to use this method because accuracy is not important in this project.

Set up

The circuit is shown in the picture below.

PWMTest_Setup

The RC transmitter at the top is my Hitec Aurora 9. On the right side, outside the breadboard is the Minina receiver. Its red LED is on indicating that it is connected to the transmitter. The receiver is connected with 3 cables from its channel 3 output to the breadboard:

  • Red cable connected to +5V
  • Black cable connected to GND
  • White/Yellow cable – the signal cable (white on the Minima side and yellow on the breadboard side) connected to pin RA5 of the application controller.

There are 2 PIC16F1825 controllers on the board:

  • Application controller (top) – runs the PWM capture function
  • 7-Segment controller (bottom) – drives the 7-segment display.

Application controller

The application controller is connected to 4 LEDs that are not part of this project. They are there because I don’t want to take them out for every picture that I take of the breadboard and eventually they will be part of the LED controller.

The application controller is also connected to the PICKit3 data cable that connects to RA0, RA1, RA3 as I described in a previous post.

The two connections relevant to this project are RA5 – the PWM input, and RA0 – the output of the analog signal to the 7-Segment controller. RA0 functions as the DACOUT pin (Digital to Analog Converter Out).

7-Segment controller

The 7-segment controller is described in a previous post. It receives an input voltage, between 0V and +5V) on pin RA2 and displays a number between 0 and 99 that corresponds to the input. The input pin, RA2 is connected to the DACOUT pin on the application controller (RA0).

Code

The code is quite simple. I use the IOC (interrupt on change) feature that calls the ISR whenever a rising or falling edge is detected on an input pin. I also use Timer 1 to time the PWM duty cycle – the time the line is high.

The I use the DAC (digital to analog converter) to pass a value to the 7-segment display controller.

/*
* File: main.c
* Author: Eli Gurvitz – Quadcopter Blog
*
* Created on February 26, 2015, 10:59 PM
*/

#include <xc.h>

The rising and falling edge #defines are used in the ISR. The LOWPWM is the value of the lowest PWM reading. I found it by using the debugger.

#define LOWPWM 8800
#define RISING_EDGE 0
#define FALLING_EDGE 1

Skipping the settings of the configuration bytes which usually goes here.

unsigned int pwmvalue = 0;

/****************************************
*
****************************************/
void interrupt ISR()
{

The state variable indicates if we are dealing with a rising edge or a falling edge.

    static int state = RISING_EDGE;
    unsigned int newvalue;

    if (IOCAF5 == 1) {

If the ISR is called for a rising edge then we reset timer 1 and change the IOC setting to interrupt on a falling edge (the IOCAP and IOCAN registers).

        if (state == 0) { // rising edge
            TMR1L = 0;
            TMR1H = 0;
            IOCAP5 = 0;
            IOCAN5 = 1;
            state = FALLING_EDGE;
        }
        else { // falling edge

If the ISR is called for a falling edge then we read the value of timer 1. If the value is different from the previous value then we update the PWM value. Then we set IOC to be called on a rising edge.
            newvalue = (TMR1H << 8) + TMR1L;
            if (newvalue != pwmvalue) {
                pwmvalue = newvalue;
            }
            state = RISING_EDGE;
            IOCAP5 = 1;
            IOCAN5 = 0;
        }
        IOCAF5 = 0;
    }
}

/****************************************
*
****************************************/
void main()
{
    int i;
    int j;

    OSCCON = 0xF0; // set internal osc to 32Mhz

Timer1 settings

    TMR1ON = 1;
    TMR1GE = 0;

Setting RA0 for output and RA5 for input.

    TRISA0 = 0;
    TRISA5 = 1;

Setting the IOC for pin 5. The initial setting is for detecting a rising edge.

    IOCAP5 = 1;
    IOCAN5 = 0;
    IOCAF5 = 0;
    IOCIE = 1;

GIE must be enabled in order to receive the IOC interrupts.

    GIE = 1;

DAC settings – enable DAC and enable the DACOUT pin.

    DACEN = 1;
    DACOE = 1;

Main loop – I decided to update the 7-segment display only periodically (on a count to 10000). This

    while(1) {
        if (i > 10000) {

Initially, I tried to calculate the DACOUT value (which is set by writing to the 5 LSBits of DACCON1) by a simple function (see below) but it didn’t work. Therefore I compare the PWM value to each possible value from the lowest  (LOWPWM) and up to 32 increments (of 200 in this case) and set DACOUT to the index of the increment. This is good enough for the purpose of this sample.

            for (j = 0; j < 32; j++) {
                if (pwmvalue < (LOWPWM + (j * 200))) {
                    DACCON1 = j;
                    break;
                }
            }
            i = 0;
        }
        i++;
    };
}

Running

 Issues that I still need to understand

  1. I tried using the PWM capture feature of the PIC16F1825 but it didn’t work. It never stopped in the ISR (Interrupt Service Routine).
  2. At first I tried to calculate the output value in the following way but it never worked. The DACOUT value varied between 3 and 8 and never corresponded to the value of the function described below.
    1. Divide the PWM input range into 32 groups. Each group has a size of (PWMHIGH – PWMLOW) / 32
    2. Normalise the PWM input by subtracting the min value from it.
    3. Divide the normalised PWM input by the group size.
    4. Or, in other words: DACOUT = (pwmvalue – PWMLOW) / group size = (pwmvalue-PWMLOW) * 32 / (PWMHIGH – PWMLOW).

 

PIC LED Controller – 7-Segment driver

While working on the LED controller I decided that it would be nice if the blinking interval (the length of time a LED is on or off) will be controlled by a potentiometer. Then I remembered that I bought a few 3-digit 7-segment displays (see footnote) and it would be even nicer if I’ll show the pot setting on the display. It will add some color to the LED controller. I also bought some new PIC16F1825 controllers with 14 pins which are perfectly suited for the job. So I had everything ready and to my surprise it took me much longer to get it working than I anticipated.

I had to learn a few new things to make this work:

  • Using the ADC (Analog to digital feature of the PIC16F1826)
  • Connecting the display to power and to the controller
  • Driving the 7-segment display.

First of all, here is the final system. Notice how the numbers on the display change as I turn to pot with the screwdriver.

I’ll start by describing the wiring of the circuit.

Wiring

See Important update to the wiring scheme below

The image below shows the wiring of the potentiometer (marked), the PIC16F1825 controller and the display.

Wiring

Wiring

The VDD and VSS pins are connected to the power via the two grey wires at the right side.

The two wires marked “control” – a purple one connected to RA0 and a brown one connected to RA1 – control which digit is “displayed”, or more correctly – powered up. This display is a common anode display. This means that all the segments of a digit are connected to a single positive pin. There are three such pins, one for each digit. In order to light up a segment, the digit’s control pin must be positive and the segment’s pin must be negative. In order to display two or three digits at the same time, the program must switch the control pins on and off rapidly. In this project I’m using only two digits – to display numbers in the range 0 – 99.

Pin RA2 is connected to the potentiometer.

The yellow and orange wires connect each of the 7 segments to a pin on the controller according to the table below. The table shows the assignment of the controller’s pins to segments (segments are marked by the letters A – G) and the segments that must be lit to display each digit. For example, to display the number one, segments B and C must be on. This table is actually good for a common cathode display – I made it before I realized that I have a common anode display. So you’ll see that in the code the bits are negated.

7-segment-table

 

Pin RA4 is connected to segment G.

The problems

I had to take care of several issues in the code and I’ll explain how each one is handled. The main problems were:

  • The display is of the common anode type, meaning that to light a segment, its pin must be low (0)
  • The display has only 11 pins to control 3 digits, each with 8 segments (the 8th is the decimal dot). This means that the controller needs to turn them on and off in a round-robin way.
  • The pot is completely not stable – it sends different readings continuously. I had to “smooth” out the constantly changing readings

The initial, naive implementation was really dumb and had all the mistakes possible in such small program. When I ran it all the segments on both digits were flickering wildly and one of the digits was brighter than the other. The problems were:

  • I assumed that a positive voltage on a pin will light a segment but my display was of the common anode type, so a pin must actually be low to light a segment. The lighted segments didn’t resemble any number of course.
  • I understood that I have to switch each digit on and off, but the code first switched on digit 1, then switched it off and right after that, in the same function, switched digit 2 on and only after that changed the lit segments to show digit 2. This implementation really had all the possible errors and it caused one digit to be bright and the second to be dim.
  • I didn’t remember that the pot keeps sending different readings all the time, so the digits never stabilised (and of course, I couldn’t make out any digit because of the confusion with common anode vs. common cathode).

Solutions that didn’t work

I first thought that the controller can’t source enough current to light up the digits, so I connected the control pins to the 5V input via a transistor. This of course made no difference.

I realised that the pot is fluctuating wildly so I installed a low-pass filter – a resistor and capacitor in series. This was an interesting exercise and I played with various combinations of resistors and capacitors and watched the effects. This help a little but not much. I understood finally that a low pass filter is not a solution to the problem because even if it smooths out the input a little bit, some readings will pass through and make the display flicker.

So I started simplifying the circuit and the code and trying to isolate problems – first light up a single hard-coded digit, then two digits and then connect it to the pot.

Solutions that did help

I read the display’s data sheet to the end and found that the duty cycle of each digit is 1/10 and the actual time that it must be lit was 0.1 millisecond. So I understood that the correct way to drive the display was the following:

  1. Turn digit 1 on
  2. Wait 0.1 ms
  3. Turn digit 1 off
  4. Wait 0.1 ms
  5. Turn digit 2 on
  6. Wait 0.1 ms
  7. Turn digit 2 off
  8. Wait 0.9 ms

I did a few more changes:

  1. I fixed the segment table to match a common anode display
  2. I added code that discarded pot readings if they were within 10 points from the previous reading.
  3. The main change – driving the segments correctly.

I will highlight these areas in the code.

The code

We will describe the code in section. I skipped the configuration byte definition in order to save space.

#define INTERVAL1 4
#define INTERVAL2 40

Interval 1 is more or less 0.1 millisecond – it determines the time that  a digit is lit.
Interval 2 is the interval when both digits are off. Since the duty cycle is 1/10 interval 2 is ten times interval 1.

unsigned int adcvalue; // The input value from the pot
int timer0count;       // counts timer 0 interrupts
int state;             // state – moves the system between states.

The states are:

  •  0 – turn digit 1 on
  •  1 – turn digit 1 off
  •  2 – turn digit 2 on
  •  3 – turn digit 2 off
  •  4 – read the value on ADC

each state lasts interval 1 ticks

  • 5 – wait interval 2

// pin to segment assignment is:
// C0-C1-C2-C3-C4-C5-A4
// A -B -C -D -E -F -G
// Pin A2 used for ADC
// Pin A0 used for display pin 8 – digit 1 – ones
// Pin A1 used for display pin 9 – digit 2 – tens

unsigned char digits[] = {
0b01000000, // 0 

  0b01111001, // 1
  0b00100100, // 2
  0b00110000, // 3
  0b00011001, // 4
  0b00010010, // 5
  0b00000010, // 6
  0b01111000, // 7 
  0b00000000, // 8
  0b00011000  // 9
};

Note that in the digits array above a segment is lit if its value is 0. Hence the value for the number 8 is all zeros.

Next is the interrupt service routine – it increments the time tick counter and reads the pot value from the A2 pin.

void interrupt ISR()
{
  if (INTCONbits.TMR0IF) {
    timer0count++;
    INTCONbits.TMR0IF = 0;
    TMR0 = 0;
  }

  if (PIR1bits.ADIF) {
    adcvalue = (short) ADRES;
    PIR1bits.ADIF = 0;
    ADCON0bits.GO = 1;
  }
}

The show_number function sets the individual segments on or off according to their setting in the digits array above.

void show_number(unsigned char number)
{
  LATC0 = ((number & 0x1) > 0) ? 1 : 0;
  LATC1 = ((number & 0x2) > 0) ? 1 : 0;
  LATC2 = ((number & 0x4) > 0) ? 1 : 0;
  LATC3 = ((number & 0x8) > 0) ? 1 : 0;
  LATC4 = ((number & 0x10) > 0) ? 1 : 0;
  LATC5 = ((number & 0x20) > 0) ? 1 : 0;
  LATA4 = ((number & 0x40) > 0) ? 1 : 0;
}

And now to the main function. I will skip the set up code of the input/output directions of pins and the register settings for Analog-to-digital converter (ADC) and the timer 0 interrupt counter.

state = 0;
timer0count = 0;

adcvalue = 0;
previous_value = 0;
while(1)  {
  if (state == 5) {
    if (timer0count >= INTERVAL2) {
    timer0count = 0;
    state = 0;
  }
  else 
    continue;
  }
  if (timer0count < INTERVAL1) {
    continue;
  }

  switch (state) {
    case 0:
      LATA0 = 0;
      LATA1 = 1;
      show_number(digits[digit1index]);
      break;
    case 1:
LATA1 = 0;
       break;
    case 2:
      LATA0 = 1;
      LATA1 = 0;
      show_number(digits[digit2index]);
      break;
    case 3:
      LATA0 = 0;
      break;
    case 4:
      if (adcvalue > 990)
        adcvalue = 990;
      if (adcvalue < 10)
        adcvalue = 10;
      if (adcvalue >= previous_value)
        delta = adcvalue – previous_value;
      else
delta = previous_value – adcvalue;
if (delta > 10) {

        value = adcvalue / 10;
        previous_value = adcvalue;
      }
      digit1index = value / 10;
      digit2index = value % 10;
      ADCON0bits.GO = 1;
      break;
    }
    state++;
    timer0count = 0;
};

Updated wiring scheme

My good friend Tomer pointed out to me that I must add resistors to protect the chip from running at maximum current output from the chip. The wiring above is identical to short-circuit between the output chips and it will lead to damaging the chip.

So I added 220 Ohm resistors on all the segment pins to limit the current to around 22mA (5 / 220 according to Ohm’s law. Here is the updated wiring photo.

7-segment-updated

 

========

Data sheet for the 7-segment display can be found here. They are manufactured by Wayjun and cost me $2.55 for 10 units on Ali Express.

PIC LED Controller – Stage 2

After completing stage 1 and reviewing it with my friends I decided that it needs the following changes and additions – hence stage 2:

  • Manage 4 LEDs and not only two as in the code of stage 1
  • Allow the app to change the state of each LED to OFF, ON or BLINKING while the app is running. This is a fundamental feature for the full LED controller because the user will be changing the state of the LEDs by the remote control.
  • Allow the app to set the intensity of each LED
  • Change the  LED Controller API functions to start with LC in order to avoid namespace collisions with the client app (although I realise that this is for a tiny PIC and not a desktop).

In short, I wanted my application code (main.c) to look like this:

[file]http://www.qcptr.com/code-snippets/led-controller-stage-2/main.c[/file]

The file starts with the regular setting of the configuration words and then defines an array of “state” structures:

 

struct {
  LEDState state;
  short blinkCount;
  short intensity;
} states[] = {
  {LEDON, 0, 10},
  {LEDBLINKING, 1, 5},
  {LEDBLINKING, 3, 15},
  {LEDOFF, 0, 15},
  {LEDON, 0, 15},
  {LEDBLINKING, 4, 10}
};

 

Each state consists of an action – Off, On or Blinking. If the state is “blinking” then the second member is the number of blinks and the third member is intensity – a number between 1 and 15 where 15 is brightest and 1 is the least bright.

The array contains six different states and LED 1 (the rightmost LED will cycle through these states.

The main function itself sets up LEDConfig structs, sets the initial states for each LED (note that LED 2 – the second from right) is OFF throughout and enters the main loop. In the main loop LED 2 will remain off. LEDs 3, and 4 will blink at a fixed rate and LED 1 will move through the states above.

This is how it looks like:

The header file now contains some #ifdefs that allow it to compile and run on an iOS device (more about that later). The LEDConfig structure was changed from stage 1 and the functions are prefixed with LC.

Here is the header file.

[file]http://www.qcptr.com/code-snippets/led-controller-stage-2/ledcontrol.h[/file]

The LED controller C file is here:

[file]http://www.qcptr.com/code-snippets/led-controller-stage-2/ledcontrol.c[/file]

A word on the (free) XC8 compiler

 It seems that the free XC8 compiler is extremely inefficient in transforming the C code to assembly. I noticed that dereferencing a member in a struct requires countless assembly commands. For example, the command (from main.c):

946 ;main.c: 56: short b = states[index].blinkCount;

maps to the following assembly code:

947   02F3   3005   movlw 5
948   02F4   00A2   movwf ??_set_state
949   02F5   0822   movf ??_set_state,w
950   02F6   00F1   movwf ___bmul@multiplicand
951   02F7   0820   movf set_state@index,w
952   02F8   23D5   fcall ___bmul
953   02F9   3E01   addlw 1
954   02FA   3EA0   addlw _states& (0+255)
955   02FB   0086   movwf 6
956   02FC   0187   clrf 7
957   02FD   3F40   moviw [0]fsr1
958   02FE   0020   movlb 0 ; select bank0
959   02FF   00A4   movwf set_state@b
960   0300   3F41   moviw [1]fsr1
961   0301   00A5   movwf set_state@b+1

When the compiler finishes it gives me the following statistics:

Memory Summary:
Program space used      3FEh ( 1022) of 800h words ( 49.9%)
Data space used         7Eh  ( 126) of 80h bytes ( 98.4%)
EEPROM space used       0h   ( 0) of 100h bytes ( 0.0%)
Data stack space used   0h   ( 0) of 1h byte ( 0.0%)
Configuration bits used 2h   ( 2) of 2h words (100.0%)
ID Location space used  0h   ( 0) of 4h bytes ( 0.0%)

This means that not much is left for the rest of my controller code on this chip. The compiler also says that if I’ll buy the PRO version it will shrink the code by 40% and I will save 408 words.

Other areas of inefficiency in my code are the modulo-6 operation (index = (index + 1) % 6) and all the function calls that are an inherent part of the “pseudo object oriented programming” that I use here

Despite of all this inefficiency, I think it is worth writing in C because it it much more readable (to most of us) and allows us to give the program some structure. I think it is possible to overcome the memory capacity problem in several ways:

  1. Use a larger chip – the price difference is probably negligible
  2. Write some parts of the code in efficient assembly (will take me ages)
  3. Buy the PRO version of XC8
  4. Buy another compiler for the PIC processor. I found one for $50.
  5. Check out the GPUTILS – Gnu PIC utilities – I’m going to do this right after I finish this post.

The next steps towards the final LED controller are:

  1. Add a potentiometer that will control the length of the blink of the LEDs
  2. Learn how to receive a PWM signal into the PIC
  3. Revisit the product requirements and modify (I have some ideas for modifications already)
  4. Write a good specification of the init and initial configuration stage. This is not simple as it sounds.
  5. Implement the init and configuration stage
  6. Define the states and update the main function
  7. Test on the breadboard with regular LEDs – add the transistors to the circuit for driving the LEDs
  8. Build the real circuit with the high-power LEDs that I ordered
  9. Build a few more kits and try to sell them