Author Archives: egurvitz

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.

 

 

 

LED Controller – Planning

After learning how to use the various features of the PIC16F1825 controller that I need for building and writing the LED controller it is time for some planning.

First the HW. Following is a schematic diagram that I prepared with the Freeware version of the Eagle application for Mac. Here are the details.

Screen Shot 2015-05-25 at 2.16.42 AM

It is an excellent app and I thank my friend Tomer for telling me about it. The freeware version is limited but completely suitable for my current educational purposes. I will even allow me to design a board if I choose to do so – the freeware version is limited to 10cm by 8 cm boards and this size is enough for my LED controller.

The application is a bit non intuitive, especially with the MAC single-key mouse, but after a bit of practice and watching this video, I managed to draw the whole schematic in about 1.5 hours.

So, here is my schematic diagram.

led-controller-schematic-v1

It looks a bit complicated but it is not. It consists of the following components:

[table id=4 /]

Most of the wiring in this diagram are for the 7-segment display. I think that the rest is self explanatory to some extent and will be clarified in the next posts when I start writing the SW.

I’m aware that this schematic is probably “on the face” as we say in Hebrew, which means very poor and I’m sure I will improve as we go.

 

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.

Quadcopter 1: Preparing to fly

I’m publishing this post a bit out of order as I already published the post about flying because it was simpler and shorter. However, lets not get hung up on small details. So here I explain the last stages of preparation before flying.

The stages are:
  1. Pair the receiver to the transmitter
  2. ESC calibration
  3. Naza calibration
  4. Attach the propellors  – they must be completely parallel to the ground
  5. Hold it by hand to  see that it responds correctly to RC commands (if it is a small model). Alternatively, you can ask someone to help as I’ll explain.
  6. Find an empty field large enough to fly without crashing into something, or worse, some spectators
All the steps from 1 to 5 should be carried out with the propellors NOT attached to the engines.

 Pairing the receiver to the transmitter

Usually most transmitters and receivers have buttons that place them in pairing mode. Follow the procedure for your devices. I found that my Hitec transmitter works with the Hitec Optima receiver (obviously) and with the cheap Minima receiver.

ESC Calibration

The purpose of ESC calibration is to set the throttle range onto the ESCs. Follow these steps to do that:

  1. Connect the signal cable of the ESC (its color is usually white) to the throttle channel of the receiver. This is usually channel 3 and the signal connector is the upper one.
  2. Turn the transmitter on
  3. Push the throttle to the top most position
  4. Connect the quadcopter power. The receiver should come on
  5. The engine should beep twice
  6. Within 2 seconds move the throttle to the bottom position
  7. The engine should beep 3 times and the ESC should reset itself
  8. Move the throttle up and verify the engine starts. Note the direction in which the engine rotates – clockwise (CW) or counter clockwise (CCW)

The ESC programming instructions are usually the same for all ESCs because they all run the same SimonK firmware. The instructions for my 4-in-1 ESC can be found here.

Note that when the engine starts it should beep several times corresponding to the number of cells in the battery. If one or more engines beep a wrong count then these engines should be programmed. The programming instructions can be found in the ESC manual.

After all engines are calibrated connect the ESC signal cables to the correct engine ports on the Naza controller.

Naza calibration

Naza calibration is guided by the Naza configuration application DJI Naza-M V2 Assistant that runs on Windows and MAC. Here is a screenshot of it’s first screen.

naza first screen

I will not repeat here the information and instructions listed in the Naza-M quick start manual. I will only point out some important points that might be overlooked.

It is important to do all the configuration steps when calibrating the Naza for the first time especially the IMU calibration in the Tools window.

I recommend to do the Naza compass calibration as well. This procedure is described here.

Attaching the propellors

Note that there are two clockwise propellors (CW) and two counter-clockwise propelloers (CCW). The propellors must be attached so that the two CW and the two CCW propellors are at the edges of the diagonals.

You must also ensure that the engines rotate the right way, i.e. the CW propellor should rotate CW and the CCW propellor should rotate CCW. If an engine turns the wrong way then disconnect two of the engine’s three power cables and swap them, meaning that each should be connected to the other lead coming from the engine. This will reverse the engine’s direction.

Hold the quadcopter by hand and run a dry test

This is a risky step and I suggest to do it only if you are a cool headed grown up guy/girl and your model is small enough to hold it with one hand. If the model is large then you should ask someone to help and hold it above his head.

So, hold the quadcopter tightly, arm the system and bring the throttle up until all propellors spin. Be careful not to let go.

Now move the remote control sticks and verify that the quadcopter responds correctly.

Next release the sticks and let them return to their center position. Then hold the quadcopter and tilt it to each side. You should feel the quadcopter resist as it tries to stabilize itself.

Flying:

Before flying you should perform the following checklist:
  1. All screws are tight. Especially those that connect the engines to the frame
  2. The battery is attached securely
  3. The propellors are screwed on tightly
  4. The propellors are parallel to the ground
  5. The indicator lights are in their normal state

First flights 

My first flights were catastrophic. I crashed the quadcopter many times and broke many propellors and some engines. So here are some tips to get you started:

Be patient – its takes time for your fingers to learn the controls and respond quickly and correctly.

Start flying in normal mode – be aware where the “forward” direction is and stand behind the quadcopter.

Start with simple flights – lift of and land, lift off, move one meter to each direction and land. And so on …

Don’t start flying in strong winds.

Try to fly the quadcopter circles. I think that if you succeed (in normal flight mode) then you are doing nice progress.

LED Controller – init sequence

Goals

When the controller starts it performs the init sequence. The goals of the init sequence are:

  1. Verify that all LEDs are in working order
  2. Read min/max values from NVRAM
  3. Allow the operator to set the min and max range of the PWM signal
  4. Start normal operation – read the PWM value and set the LEDs

Sequence

The following sections describe the steps of the init sequence. The steps are divided into functional areas.

LED verification

On start up all LEDs should flash for 1.0 second:

  1. Turn all LEDs on with highest intensity (LED State = LEDON)
  2. Enable Timer0
  3. Wait 1000 ms
  4. Disable Timer0
  5. Turn all LEDs off (LED State = LEDOFF)

Load PWM range from NVRAM

  1. Read the min and max values from NVRAM into global variables
  2. If min > max then move to Settings mode
  3. If min > 1023 then move to Settings mode
  4. If max > 1024 then move to Settings mode
  5. If min and max values are sensible then check if settings mode is enabled.

 Check if settings mode is enabled

To enter the settings mode the user should press the push button (SPST switch) during the LED verification stage (1 second).

 Check if the switch is pressed

  1. If the switch is pressed then enter settings mode
  2. If the switch is not pressed then move to normal operation

 Settings mode

Settigs mode allows the user to set the range (max – min) of the PWM value for the LED controller channel

To set the mode the user should move the PWM switch all the way up, wait one second and then all the way down and wait one second.Turn LED 1 on

  1. Start capturing the PWM value.
  2. Enable Timer0
  3. Set counter to 0
  4. While value n+1 is greater than value n set counter to 0
  5. If value n+1 == value n, then check the counter.
  6. If the counter value is 1 secod then:
    1. Store the PWM value as max in NVRAM
    2. Store the PWM value as max in the variable
    3. Turn LED 1 off
    4. Turn LED 2 on
  7. While value n+1 == MAX do nothing
  8. While value n+1 < value n then:
    1. Set counter to 0
  9. If value n+1 == value n then check the counter
  10. If the counter value is 1 second then:
    1. Turn LED2 off
    2. Store the PWM value as min in NVRAM
    3. Store the PWM value as min in the variable
    4. Move to normal operation.

LED Controller Requirements

Introduction

The LED Controller (LC) for quadcopters controls up to 4 high-power LEDs that can be attached to the quadcopter and can be used to make it visible in the dark and/or indicate the status and condition of the quadcopter.

The LC supports multiple configurations of LEDs as described below. It allows the operator to select configurations via the remote control over a dedicated channel.

The blinking interval (the length of time that the LEDs are on or off) can be set by a potentiometer.

The LC may include an optional 7-segment display that shows the LC state.

Hardware

The LC consists of the following HW components:

Component Purpose
PIC16F1825 Application processor – runs the main LC application
PIC16F1826 Optional 7-segment display controller
7-segment display Optional. Displays:

  • the blink interval
  • the PWM min and max values while in settings mode
  • the configuration number (see below) while the user is changing the mode
10K Potentiometer Defines the “blink interval” the amount of time a LED stays on when blinking
Push button – SPST Forces the LC into settings mode
4 Transistors For switching current to the LEDs. One for each LED.
4 High power LEDs

LED Configurations

The LC supports the following configurations:

ID Name Description
1 All off All LEDs are off
2 All on All LEDs stay on permanently
3 All blinking – 1 – High intensity  All LEDs blinking in a fixed rate – on and off
 4  All blinking – 1 – Low intensity  All LEDs blinking in a fixed rate – on and off
 5  All blinking – 2 – High intensity  All LEDs blinking twice and then a break
 6  All blinking – 2 – Low intensity  All LEDs blinking twice and then a break
 7 All blinking – 3 – High intensity  All LEDs blinking three times and then a break
 8 All blinking – 3 – Low intensity  All LEDs blinking three times and then a break
 9 LEDS 1, 2 on and 3, 4 off  
10 LEDs 3, 4 on and 1, 2 off  
11 LEDs 1, 2 blinking 1 – High intensity LEDs 3, 4 are off. LEDs 1 and 2 are blinking in a fixed rate
12 LEDs 1, 2 blinking 1 – Low intensity LEDs 3, 4 are off. LEDs 1 and 2 are blinking in a fixed rate
13 LEDs 1, 2 blinking 2 – High  intensity LEDs 3, 4 are off. LEDs 1 and 2 are blinking twice and then a break
14 LEDs 1, 2 blinking 2 – Low  intensity LEDs 3, 4 are off. LEDs 1 and 2 are blinking twice and then a break
15 LEDs 1, 2 blinking 3 – High  intensity LEDs 3, 4 are off. LEDs 1 and 2 are blinking three times and then a break
16 LEDs 1, 2 blinking 3 – Low  intensity LEDs 3, 4 are off. LEDs 1 and 2 are blinking three times and then a break
17 LEDs 3, 4 blinking – 1 – High intensity LEDs 1, 2 are off. LEDs 3 and 4 are blinking at a fixed rate
18 LEDs 3, 4 blinking – 1 – Low intensity LEDs 1, 2 are off. LEDs 3 and 4 are blinking at a fixed rate
19 LEDs 3, 4 blinking – 2 – High intensity LEDs 1, 2 are off. LEDs 3 and 4 are blinking twice and then a break
20 LEDs 3, 4 blinking – 2 – Low intensity LEDs 1, 2 are off. LEDs 3 and 4 are blinking twice and then a break
21 LEDs 3, 4 blinking – 3 – High intensity LEDs 1, 2 are off. LEDs 3 and 4 are blinking three times and then a break
22 LEDs 3, 4 blinking – 3 – Low intensity LEDs 1, 2 are off. LEDs 3 and 4 are blinking three times and then a break.

Main use cases

The main use cases of the system are:

1. Initialization

When the LC starts it enters the initialization sequence. The init sequence is described in a separate post.

2. Normal operation

In normal operation the LC drives the LEDs according to the currently selected configuration. The LC also monitors the following inputs:

a. Potentiometer – the potentiometer value determines the blink interval – the time that LEDs, in blinking mode, are on and off. In the “blinking 2” and “blinking 3” modes, the potentiometer value determines also the amount of time that the LED is off between blinks. This amount of time is twice as long as the blinking interval. When the value of the potentiometer changes the LC performs the “Setting the blink interval” use case.

b. RC input. If the PWM value on the RC channel changes then the LC performs the “Changing the LED configuration” use case.

During normal operation the 7-segment display, if present, shows  the ID of the current LED configuration.

3. Setting the blink interval

When the blink interval is changed (i.e. the potentiometer value is changed) and if the 7-segment display is present, then the LC displays the new value of the potentiometer for 1 second. After 1 second the display returns to showing the configuration ID.

4. Changing the LED configuration

When the PWM value changes, the LC moves to the newly selected LED configuration. The LC also displays the current configuration ID on the 7-segment display, if present.