Wednesday, February 12, 2014

RCArduino Downloads

Downloads available so far -

Illutron B,
Personal Lap Timer Part 1
Latest Personal Lap Timer
Serial Servos - 20 Servos from 4 Pins

projects are now uploaded to google docs.

To download zip files containing the projects, you will need to -

1) Select the project link below

2) In the window that opens look for the 'File' menu in the top left of the screen

3) Open the file menu and select 'Download', this should open a popup window asking you to confirm the download, you should see the name of the project zip file in this window, if so accept the download.

4) Unzip the folder inside the file and move it into your arduino projects folder. You should now be able to build the project.


IllutronBDemo0_2.zip
https://docs.google.com/file/d/0B8WjAvuO3LnIZUNLRkxwWTZKYzQ
Original project Post
http://rcarduino.blogspot.ae/2012/08/the-must-build-arduino-project-illutron.html



LapTimerBuildAlongPart1.zip
https://docs.google.com/file/d/0B8WjAvuO3LnIS18wb3QzMm1LM3c
Original Posts
http://rcarduino.blogspot.com/2012/07/lap-timer-build-along-part-one.html
http://rcarduino.blogspot.com/2012/11/lap-timer-part-5-buzzer-external-audio.html


LapTimerStandAloneComplexISRAudio.zip
https://docs.google.com/file/d/0B8WjAvuO3LnId1lCVGV3ZTJ0RzQ


Serial Servos
http://rcarduino.blogspot.ae/2012/10/arduino-serial-servos-20-servos-4-pins.html 
Download Here -
https://drive.google.com/file/d/0B8WjAvuO3LnISmRzWWg1OHpEOGs/edit?usp=sharing

More to come as and when I get asked for them.

As I am new to using google docs, please let me know if you successfully download the files.

Enjoy

Duane B.

Wednesday, July 24, 2013

PPM Output (Draft - Looking for testers)

If we have code capable of generating servo signals its very simple to adapt this to generate a PPM output instead.

In a previous post we looked at reading an incoming PPM signal and we saw how the PPM Signal describes the individual servo pulses -
http://rcarduino.blogspot.ae/2012/11/how-to-read-rc-receiver-ppm-stream.html

To generate the PPM Stream from a set of servo commands all we need to do is change our code to toggle a single pin instead of individual pins for each servo.


If we look at the point where the channel 1 pulse ends and the channel 2 pulse starts in the diagram above. The code needs to find which pin is associated with the current chanel, then set the pin low to end the pulse. It then finds the next channel, sets its pin high and then sets the timer compare register so that a interrupt routine will be called again when its time to end the new channels pulse and start the next channels pulse.

To output PPM its actually a lot simpler. We are always dealing with the same pin so all we need to do is set the pin high, set the compare register so that the interrupt is called again to when we need start the next channel pulse and then we immediately set the PPM pin low again and exit. Systems which expect a PPM Input are only interested in the rising edge of the PPM Pulse so once we have set it high (giving the external system the rising pulse edge it is looking for) we can immediately set it low again and get out.

Timers, Compare Registers, Service Routines

The following posts provide an indepth background to how the standard Arduino Servo library uses timers, compare registers and interrupt service routines to control upto 12 Servos. The RCArduinoFastLib uses the same approach with a number of optimizations including support for more servos, faster refresh rates, dual refresh rates and reading incoming PPM signals. The remainder of this post presents a small modification of the RCArduinoFastLib which will provide a PPM output instead of individual servo channels.

Background posts
Standard Arduino servo library overview
http://rcarduino.blogspot.ae/2012/01/can-i-control-more-than-x-servos-with.html

RCArduinoFastLib
http://rcarduino.blogspot.ae/2012/11/how-to-read-rc-channels-rcarduinofastlib.html
http://rcarduino.blogspot.ae/2012/11/how-to-read-rc-receiver-ppm-stream.html

The Modified RCArduinoFastLib with PPM Output
Looking for testers, PM me Duane B on the Arduino forum for a zip folder to build the library and test sketch.





Saturday, June 1, 2013

RC Arduino Traction Control - The Traction Alarm - Draft



In order to control traction we first need to detect when traction has failed. This post presents a hardware and software solution for efficiently detecting when traction is lost.

One of the challenges with detecting a loss of traction is that there is a lot happening. The RCArduino test vehicle generates two interrupts for each revolution of each wheel. With the four wheels rotating as much as 80 times per second, thats 2 * 4 * 80 = 640 interrupts per second. If we add two or three RC Channels we quickly approach 1000 interrupts per second.

It might sound like a lot to process, 1000 interrupts in a second, but the 16Mhz processor in your Arduino is able to get through 16,000 operations in each one thousandth of a second.



Bench Testing

 
Road Testing
More lights = Less traction. You can see the car light up on acceleration and the lights go one by one as traction is recovered.


If the processor is so fast, why do we need to focus on efficiency ?

The problem is that there will be times when all of our interrupts will occur at the same time. The Arduino is only able to process one interrupt at a time and so a queue will be formed. The interrupts within the queue are processed according to a fixed priority, not the time at which they occured.

The interrupts we are interested in are listed below according to the priority with which they are processed

INT0 - External Interrupt 0 - used to read RC Channels or wheel rotations
INT1 - External Interrupt 1- used to read RC Channels or wheel rotations
PCINT0 - Pin change interrupts - used to read RC Channels or wheel rotations
PCINT1 - Pin change interrupts - used to read RC Channels or wheel rotations
PCINT2 - Pin change interrupts - used to read RC Channels or wheel rotations
TIMER1COMPA - Used by the servo library to generate output pulses

If each type of interrupt takes 10 microseconds to process, we could have the signals we want to send to our ESC or steering servo (TIMER1COMPA in the table above) disrupted by upto 50 microseconds which can represent as much as a 10 percent error in the throttle and steering signals.



You can find a full list of the interrupt vectors and the order in which they are processed in the ATMega328 Datasheet under the second '11.4 Interrupt Vectors in ATmega328P' in the table 'Table 11-6. Reset and Interrupt Vectors in ATmega328'

You can find more background on clashing/queuing  interrupts in this post introducing the RCArduinoFastLib which includes an explanation of the diagram below -

Error introduced when interrupts collide

Reducing interrupt clashes in Arduino RC Projects -
http://rcarduino.blogspot.ae/2012/11/how-to-read-rc-channels-rcarduinofastlib.html


Pin Change Interrupts Vs Port Change Interrupts

The Arduino UNO, Leonardo, Mini and Micro have only two external interrupts, so how will we read four wheel sensors and two or more RC Channels ?

In the past the RCArduinoBlog has featured the pinchangeinterrupt library, this uses a capability of the ATMega chip in your Arduino to attach an interrupt to any of the 19 pins. The pinchangeint library adds higher level code to this low level capability in order to provide very similar functionality to the two external interrupts.

More here - http://rcarduino.blogspot.com/2012/03/need-more-interrupts-to-read-more.html

This convenience comes at a high cost in terms of performance but fortunately there is a simple trick we can use to access more interrupts without the performance hit of the pinchangeint library.

Introducing Port Change Interrupts  - A Simple Trick

If we limit ourselves to using just one pin from each of the Arduino ports we can eliminate all of the processing overhead normally required to determine which pin has caused the interrupt. This approach provides us with a total of five highly efficient interrupts, enough for four wheel sensors and a PPM Stream containing our RC Channel information.

Ok, but whats a port ?


The ATMega microprocessor in your Arduino has its pins arranged into ports, the following diagram shows how the Arduino software maps digital and analogue pins onto the ATMega 328 microprocessor ports. Similar diagrams are available for the ATMega 32u4 and ATMega 2560 used in the Leonardo and Mega.


The diagrams all show the pin function assigned by Arduino in red accompanied by black text which gives the ATMega port, pin number and any additional functions supported by the pin.

If we look at the Arduino pin labelled digital pin 2 we can see that nearest the pin, this is also labelled PD2, this tells us that the pin is pin number 2 on PORT D.

The ATMega 328 has three ports -

   PORTB (digital pin 8 to digital pin 13)
   PORTC (analog pin 0 to analog pin 5)
   PORTD (digital pin 0 to digital pin 7)

To create a port change interrupt we must select a single pin from a port. If we try to use more than one pin on a single port we will need additional code to determine which pin has changed, this will quickly use up our performance advantage.

As an example of the cost of having multiple pin change interrupts on a single port, the pinchangeint library is around 5 times less efficient than an external interrupt.

If we limit ourselves to this one pin per port approach we can have three additional interrupts with no lose in performance.

A Quick Introduction To Direct Port Manipulation and Registers



Have you ever seen code that looks like the following -

PORTB ^= (1<<5);

It might look like low level assembly but it isn't, its accessing the chip registers directly and there are two reasons why we would want to do this -

1) Performance - direct access is very fast, the code sample above could be 10 times faster than calling the equivalent digitalWrite(13,!digitalRead(13));


2) Configuration - Registers set up and control each of the chips modules, sometimes we will need to access a module which is not set up by default.


Registers can be thought of as a set of related switches which can be turned on and off to control some part of the chips behavior. Once you know the registers used by the modules your interested in, its as simple as programming the push button interface of a kitchen appliance.

Lets walk through the set up we need for the pin change interrupts used in the traction control project.

Step One - Turning on pin change interrupts
Pin change interrupts are turned on through the PCICR (Pin Change Interrupt Control Register). Keep in mind that this register is just a collection of on/off switches, in this register there are only three switches named PCIE2, PCIE1 and PCIE0. These three switches turn the pin change interrupt module on or off for PORTD, PORTC and PORTB respectively -

   PORTB (digital pin 8 to digital pin 13)
   PORTC (analog pin 0 to analog pin 5)
   PORTD (digital pin 0 to digital pin 7)


As our project will require pin change interrupts to be enabled on all three ports, we will need to turn the three switches on by setting the corresponding bit in the register to 1, it looks like this -

PCICR = (1<<PCIE2)|(1<<PCIE1)|(1<<PCIE0);

Each useable bit in every register is assigned a name in the datasheet, when we use the bit name in our code, it evaluates to the bit number in the register, not the bit value. To convert the bit number to the bit value we use the following notation

bit value = (1 << bit number)

We can combine values using the bitwise or operator '|' and thats how we arrived at the code above (and below) -

PCICR = (1<<PCIE2)|(1<<PCIE1)|(1<<PCIE0); // turn on PCIE2, PCIE1 and PCIE0 in the PCICR register


Step Two - Choosing our interrupt pins and linking them to a pin change interrupt 

We have discussed that there is a major performance benefit in enabling just one pin change interrupt on each port and this is easily done through setting the corresponding but in a mask register however before we do this we should return to the pin mapping diagram and take a closer look at the secondary functions of each pin.

The text between the brackets indicates the additional functions supported by each pin, for example analog input 0 has ADC0 and PCINT8 (analog to digital converter 0 and pin change interrupt 8).

We have two goals in selecting the pins -

1) Choose the pins so that we only enable one from each of the ports
2) Choose pins where we do not expect to use the secondary function later in our project - I might want to use SPI for data logging for example so must avoid these pins.

For now I have selected the following pins to enable our port change interrupts - 

  digital pin 4 (PCINT20,XCK,T0) // PD4, pin 4 on PORT D
  digital pin 8 (PCINT0,CLKO/ICP1) // PB0, pin 0 on PORT B
  analog input 0(ADC0,PCINT8) // PC0, pin 0 on PORT C
Now that we have our pins, we can use the mask registers associated with each port to attach the interrupt.
PCMSK0 = (1 << PCINT0 ); // enable pin change interrupts on PCINT0 contained in register PCMSK0
PCMSK1 = (1 << PCINT8); // enabled pin change interrupts on PCINT8 contained in register PCMSK1
PCMSK2 = (1 << PCINT20); // enabled pin change interrupts on PCINT20 contained in register PCMSK2



All of this information, including the chip modules, the registers associated with each module and the individual switches within the registers can be found in the datasheet. In this case we are dealing with PCICR and the PCMSKn registers found in sections 13.2.5 to 13.2.8 of the ATMega328 Datasheet.

http://www.arduino.cc/en/Reference/PortManipulation



The code -

#define IDLE_PERIOD 200

#define FLAG_FRONT_LEFT 1
#define FLAG_REAR_LEFT 2
#define FLAG_REAR_RIGHT 4
#define FLAG_FRONT_RIGHT 8


volatile uint16_t unPeriodFR;
volatile uint16_t unPeriodFL;
volatile uint16_t unPeriodRR;
volatile uint16_t unPeriodRL;

volatile uint8_t sFlags;

#define GREEN_INDICATOR 9
#define YELLOW_INDICATOR 10
#define RED_INDICATOR 11

void setup() {               
  pinMode(3,INPUT);
  pinMode(4,INPUT);
  pinMode(8,INPUT);
  pinMode(A0,INPUT);
  pinMode(6,OUTPUT);

  pinMode(GREEN_INDICATOR,OUTPUT);
  pinMode(YELLOW_INDICATOR,OUTPUT);
  pinMode(RED_INDICATOR,OUTPUT);

  // enable the external interrupt
  EIMSK = 2;  //1 - enable external interrupt 1
  EICRA = 12; //3 - enable interrupt on rising edge only
 
  // enable the pin change interrupts
  PCICR = _BV(PCIE0)|_BV(PCIE1)|_BV(PCIE2);
  PCMSK0 = 1;//PORT B
  PCMSK1 = 1;//PORT C
  PCMSK2 = 16;//PORT D

  TCCR1A = 0;
  TCCR1B=0x05;//Prescaler= 1024
 
  Serial.begin(9600); 
}

void loop() {
  static uint16_t unLocalPeriodFR; 
  static uint16_t unLocalPeriodFL;
  static uint16_t unLocalPeriodRR;
  static uint16_t unLocalPeriodRL;
 
  static uint32_t ulLastUpdate;
  static uint8_t sLocalFlags;     
  delay(100);

  // Take a temporary copy of the periods 
  uint8_t sreg = SREG;
  cli();

  unLocalPeriodFR = unPeriodFR;
  unLocalPeriodFL = unPeriodFL;
  unLocalPeriodRR = unPeriodFR;
  unLocalPeriodRL = unPeriodFL;
 
  sLocalFlags = sFlags & (FLAG_FRONT_RIGHT | FLAG_FRONT_LEFT);
  sFlags = 0;
 
  SREG = sreg;
 
  if(sLocalFlags)
  {
   ulLastUpdate = millis();
  
   uint16_t unTop = unLocalPeriodFR;
   
   if(unLocalPeriodFL > unTop)
   {
    unTop = unLocalPeriodFL;
   }
   if(unLocalPeriodRR > unTop)
   {
    unTop = unLocalPeriodRR;
   }
   if(unLocalPeriodRL > unTop)
   {
    unTop = unLocalPeriodRL;
   }
     
    uint16_t unBottom = unLocalPeriodFR;
    if(unLocalPeriodFL < unBottom)
    {
      unBottom = unLocalPeriodFL;
    }
    if(unLocalPeriodRR < unBottom)
    {
      unBottom = unLocalPeriodRR;
    }
    if(unLocalPeriodRL < unBottom)
    {
      unBottom = unLocalPeriodRL;
    }
     
    Serial.print(unTop);
    Serial.print(" ");
    Serial.print(unBottom);
    Serial.print(" ");
    Serial.println(unTop>>3);
   
    // if the difference is greater than 1/8th of top
    uint16_t unDifference = (unTop - unBottom);
    if(unDifference > (unTop >> 3))
    {
      tone(6,69);
    }
    else
    {
      noTone(6);
    }
   
    digitalWrite(GREEN_INDICATOR,unDifference > (unTop >> 3));  // difference > 1/8th of top
    digitalWrite(YELLOW_INDICATOR,unDifference > (unTop >> 2)); // difference > 1/4 of top
    digitalWrite(RED_INDICATOR,unDifference > (unTop >> 1));    // difference > 1/2 of top
  }
 
  if((millis() - ulLastUpdate) > IDLE_PERIOD)
  {
   noTone(6);
   digitalWrite(GREEN_INDICATOR,LOW);  // difference > 1/8th of top
   digitalWrite(YELLOW_INDICATOR,LOW); // difference > 1/4 of top
   digitalWrite(RED_INDICATOR,LOW);    // difference > 1/2 of top
  }
}

ISR(INT0_vect)
{

}

// WHEEL  ATMEGA pin, ports and masks
// FL     5           PORTD3    8 (also INT1)
// FR     6           PORTD4    16
// RR     8           PORTB0    1
// RL     A0          PORTC0    1

// WHEEL  ATMEGA pin, ports and masks
// FL     5           PORTD3    8 (also INT1)
ISR(INT1_vect)
{
  static uint16_t unLastTimer;
 
  if(PIND&8)
  {
    // debounce
    if((TCNT1-unLastTimer) > 100)
    {
      unPeriodFL = TCNT1-unLastTimer;
      // not strictly accurate, TCNT1 will have advanced between the previous line and this line,
      // however we are interested in the difference between our measurements which is not effected
      // by this constant error in each measurement unLastTimer = TCNT1;
      unLastTimer =TCNT1;
      sFlags |= FLAG_FRONT_LEFT;
    }
  }
}

// WHEEL  ATMEGA pin, ports and masks
// FR     6           PORTD4    16
ISR(PCINT2_vect)
{
  static uint16_t unLastTimer;
  if(PIND & 16)
  {
    // debounce
    if((TCNT1-unLastTimer) > 100)
    {
     unPeriodRL = TCNT1-unLastTimer;
     unLastTimer = TCNT1;
     sFlags |= FLAG_REAR_LEFT;
    }
  }  
}

// WHEEL  ATMEGA pin, ports and masks
// RR     8           PORTB0    1
ISR(PCINT0_vect)
{
  static uint16_t unLastTimer;
  if(PINB & 1)
  {
    // debounce
    if((TCNT1-unLastTimer) > 100)
    {
       unPeriodRR = TCNT1-unLastTimer;
      // not strictly accurate, TCNT1 will have advanced between the previous line and this line,
      // however we are interested in the difference between our measurements which is not effected
      // by this constant error in each measurement.
      unLastTimer = TCNT1;
      sFlags |= FLAG_REAR_RIGHT;
    }
  }
}

// WHEEL  ATMEGA pin, ports and masks
// RL     A0          PORTC0    1
ISR(PCINT1_vect)
{
  static uint16_t unLastTimer;
  if(PINC & 1)
  {
    // debounce
    if((TCNT1-unLastTimer) > 100)
    {
 
      unPeriodFR = TCNT1-unLastTimer;
      // not strictly accurate, TCNT1 will have advanced between the previous line and this line,
      // however we are interested in the difference between our measurements which is not effected
      // by this constant error in each measurement.
      unLastTimer = TCNT1;
      sFlags |= FLAG_FRONT_RIGHT;
    }
  }
}


Come back next time for track testing the control algorithms

Duane B (RCArduino)

Thursday, April 25, 2013

Reading RC Channels with Arduino Due

Update 27/04/2013

The following code was originally presented as a test sketch to demonstrate a glitch in the Arduino Due micros function which is being resolved. See the following post for details of the glitch and resolution.

http://arduino.cc/forum/index.php/topic,162787.0/topicseen.html

With this resolution in place the code presented below can be used to read 8 RC Channels and output them to a combination of upto 8 RC Servos or ESCs.

The code can be operated in two configurations -

1) Loop Back Test - Here 8 servo outputs are created and given fixed values from 1100 to 1800, these pulse values are output on pins 10 to 17 and can be read back in through the interrupts attached to pins 2 to 9. This is intended to give the user confidence that the code is able to read multiple RC Channels before moving to configuration 2 - Pass Through

2) Pass Through -  This is similar to 1) above however the input is now connected to the output such that a change in an incoming signal will cause a corresponding change in the servo output signal. To implement pass  through on any channel, simply remove the comment form the start of the servo.writeMicrosecond command for that channel for example

Change this -

  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel1.writeMicroseconds(unChannel1In);
      Serial.print(unChannel1In);
      Serial.print(",");
  }
to this -

  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      servoChannel1.writeMicroseconds(unChannel1In);
      Serial.print(unChannel1In);
      Serial.print(",");
  }




The code has been ported from an original project based on the Arduino UNO, follow the links in the comments for the background and detailed explanation.

// MultiChannels
//
// rcarduino.blogspot.com
//
// A simple approach for reading and writing eight RC Channels using Arduino Due interrupts
//
// See related posts -
// http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
// http://rcarduino.blogspot.co.uk/2012/03/need-more-interrupts-to-read-more.html
// http://rcarduino.blogspot.co.uk/2012/01/can-i-control-more-than-x-servos-with.html
//
// rcarduino.blogspot.com
//

#include "Servo.h"

// Assign your channel in pins
#define CHANNEL1_IN_PIN 2
#define CHANNEL2_IN_PIN 3
#define CHANNEL3_IN_PIN 4
#define CHANNEL4_IN_PIN 5
#define CHANNEL5_IN_PIN 6
#define CHANNEL6_IN_PIN 7
#define CHANNEL7_IN_PIN 8
#define CHANNEL8_IN_PIN 9

// Assign your channel out pins
#define CHANNEL1_OUT_PIN 10
#define CHANNEL2_OUT_PIN 11
#define CHANNEL3_OUT_PIN 12
#define CHANNEL4_OUT_PIN 13
#define CHANNEL5_OUT_PIN 14
#define CHANNEL6_OUT_PIN 15
#define CHANNEL7_OUT_PIN 16
#define CHANNEL8_OUT_PIN 17

// Servo objects generate the signals expected by Electronic Speed Controllers and Servos
// We will use the objects to output the signals we read in
// this example code provides a straight pass through of the signal with no custom processing
Servo servoChannel1;
Servo servoChannel2;
Servo servoChannel3;
Servo servoChannel4;
Servo servoChannel5;
Servo servoChannel6;
Servo servoChannel7;
Servo servoChannel8;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define CHANNEL1_FLAG 1
#define CHANNEL2_FLAG 2
#define CHANNEL3_FLAG 4
#define CHANNEL4_FLAG 8
#define CHANNEL5_FLAG 16
#define CHANNEL6_FLAG 32
#define CHANNEL7_FLAG 64
#define CHANNEL8_FLAG 128

// holds the update flags defined above
volatile uint32_t bUpdateFlagsShared;

// shared variables are updated by the ISR and read by loop.
// In loop we immediatley take local copies so that the ISR can keep ownership of the
// shared ones. To access these in loop
// we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on
// as quickly as possible, this ensures that we are always able to receive new signals
volatile uint32_t unChannel1InShared;
volatile uint32_t unChannel2InShared;
volatile uint32_t unChannel3InShared;
volatile uint32_t unChannel4InShared;
volatile uint32_t unChannel5InShared;
volatile uint32_t unChannel6InShared;
volatile uint32_t unChannel7InShared;
volatile uint32_t unChannel8InShared;

void setup()
{
  Serial.begin(115200);

  Serial.println("multiChannels");

  // attach servo objects, these will generate the correct
  // pulses for driving Electronic speed controllers, servos or other devices
  // designed to interface directly with RC Receivers
  servoChannel1.attach(CHANNEL1_OUT_PIN);
  servoChannel2.attach(CHANNEL2_OUT_PIN);
  servoChannel3.attach(CHANNEL3_OUT_PIN);
  servoChannel4.attach(CHANNEL4_OUT_PIN);
  servoChannel5.attach(CHANNEL5_OUT_PIN);
  servoChannel6.attach(CHANNEL6_OUT_PIN);
  servoChannel7.attach(CHANNEL7_OUT_PIN);
  servoChannel8.attach(CHANNEL8_OUT_PIN);

  // attach the interrupts used to read the channels
  attachInterrupt(CHANNEL1_IN_PIN, calcChannel1,CHANGE);
  attachInterrupt(CHANNEL2_IN_PIN, calcChannel2,CHANGE);
  attachInterrupt(CHANNEL3_IN_PIN, calcChannel3,CHANGE);
  attachInterrupt(CHANNEL4_IN_PIN, calcChannel4,CHANGE);
  attachInterrupt(CHANNEL5_IN_PIN, calcChannel5,CHANGE);
  attachInterrupt(CHANNEL6_IN_PIN, calcChannel6,CHANGE);
  attachInterrupt(CHANNEL7_IN_PIN, calcChannel7,CHANGE);
  attachInterrupt(CHANNEL8_IN_PIN, calcChannel8,CHANGE);

  // for loop back test only, lets set each channel to a known value
  servoChannel1.writeMicroseconds(1100);
  servoChannel2.writeMicroseconds(1200);
  servoChannel3.writeMicroseconds(1300);
  servoChannel4.writeMicroseconds(1400);
  servoChannel5.writeMicroseconds(1500);
  servoChannel6.writeMicroseconds(1600);
  servoChannel7.writeMicroseconds(1700);
  servoChannel8.writeMicroseconds(1800);
}

void loop()
{
  // create local variables to hold a local copies of the channel inputs
  // these are declared static so that thier values will be retained
  // between calls to loop.
  static uint32_t unChannel1In;
  static uint32_t unChannel2In;
  static uint32_t unChannel3In;
  static uint32_t unChannel4In;
  static uint32_t unChannel5In;
  static uint32_t unChannel6In;
  static uint32_t unChannel7In;
  static uint32_t unChannel8In;
 
  // local copy of update flags
  static uint32_t bUpdateFlags;

  // check shared update flags to see if any channels have a new signal
  if(bUpdateFlagsShared)
  {
    noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables

    // take a local copy of which channels were updated in case we need to use this in the rest of loop
    bUpdateFlags = bUpdateFlagsShared;
  
    // in the current code, the shared values are always populated
    // so we could copy them without testing the flags
    // however in the future this could change, so lets
    // only copy when the flags tell us we can.
  
    if(bUpdateFlags & CHANNEL1_FLAG)
    {
      unChannel1In = unChannel1InShared;
    }
  
    if(bUpdateFlags & CHANNEL2_FLAG)
    {
      unChannel2In = unChannel2InShared;
    }
  
    if(bUpdateFlags & CHANNEL3_FLAG)
    {
      unChannel3In = unChannel3InShared;
    }
   
    if(bUpdateFlags & CHANNEL4_FLAG)
    {
      unChannel4In = unChannel4InShared;
    }
  
    if(bUpdateFlags & CHANNEL5_FLAG)
    {
      unChannel5In = unChannel5InShared;
    }
  
    if(bUpdateFlags & CHANNEL6_FLAG)
    {
      unChannel6In = unChannel6InShared;
    }
   
    if(bUpdateFlags & CHANNEL7_FLAG)
    {
      unChannel7In = unChannel7InShared;
    }
  
    if(bUpdateFlags & CHANNEL8_FLAG)
    {
      unChannel8In = unChannel8InShared;
    }
    // clear shared copy of updated flags as we have already taken the updates
    // we still have a local copy if we need to use it in bUpdateFlags
    bUpdateFlagsShared = 0;
  
    interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
    // as soon as interrupts are back on, we can no longer use the shared copies, the interrupt
    // service routines own these and could update them at any time. During the update, the
    // shared copies may contain junk. Luckily we have our local copies to work with :-)
  }
 
  // do any processing from here onwards
  // only use the local values unChannel1, unChannel2, unChannel3, unChannel4, unChannel5, unChannel6, unChannel7, unChannel8
  // variables unChannel1InShared, unChannel2InShared, etc are always owned by the
  // the interrupt routines and should not be used in loop
 
  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel1.writeMicroseconds(unChannel1In);
      Serial.println();
      Serial.print(unChannel1In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL2_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel2.writeMicroseconds(unChannel2In);
      Serial.print(unChannel2In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL3_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel3.writeMicroseconds(unChannel3In);
      Serial.print(unChannel3In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL4_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel4.writeMicroseconds(unChannel4In);
    Serial.print(unChannel4In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL5_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel5.writeMicroseconds(unChannel5In);
    Serial.print(unChannel5In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL6_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel6.writeMicroseconds(unChannel6In);
    Serial.print(unChannel6In);
  }
 
  if(bUpdateFlags & CHANNEL7_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel7.writeMicroseconds(unChannel7In);
    Serial.print(unChannel7In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL8_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel8.writeMicroseconds(unChannel8In);
    Serial.print(unChannel8In);
  }
 
  bUpdateFlags = 0;
}

void calcChannel1()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL1_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel1InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL1_FLAG;
  }
}

void calcChannel2()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL2_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel2InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL2_FLAG;
  }
}

void calcChannel3()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL3_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel3InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL3_FLAG;
  }
}

void calcChannel4()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL4_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel4InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL4_FLAG;
  }
}

void calcChannel5()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL5_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel5InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL5_FLAG;
  }
}

void calcChannel6()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL6_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel6InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL6_FLAG;
  }
}

void calcChannel7()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL7_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel7InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL7_FLAG;
  }
}

void calcChannel8()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL8_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel8InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL8_FLAG;
  }
}


Wednesday, April 24, 2013

Problem reading RC Channels - The RCArduino Loop Back Test

Many people experience problems adding additional channels to the RCArduino test sketches or porting them to new devices, its usually down to mistakes in the way the additional channels are coded

This post presents an example of managing 8 input/output channels, its a very simple sketch which outputs 8 RC Channels and reads them right back in again.

The sketch can be used in three steps -

1) Initially the sketch outputs fixed values for the servos and reads them back in as a loop back test.

2) Once you are comfortable with the code, you can replace one or more of the loop back connections to start reading RC Channels. 

3) Once you are confident that the sketch is reading your channels correctly you can connect your servos and ESCs to the outputs and your done, 8 channels in, 8 channels out in three easy steps.


Step 1 - Loop back testing
To use the loop back test in step 1, connect pins 2 to 9 (the channel inputs) to pins 10 to 13 and A0 to A3 (the channel outputs).

Each channel is set to a fixed output from 1100 to 1800, if the code works on your board, your should see these values being output in the serial monitor.

Step 2 - RC Channel reading

Once you have this up and running, you can start swapping the connections to an RC receiver one by one. You should now see the channel values for the receiver channels updating in your serial monitor. If the values are within a range of around 1000 to 2000, your ready to move to step 3.

Step 3 - Full Control
To start outputting the values that you have read in, remove the comments from the servoName.writeMicroseconds functions to have full control of upto 8 servos/escs using 8 RC Receiver channels.

If it does not work, let me know which board you have, the code is easily adapted to work on any of the 8-bit Arduino boards. If you have a 32 bit Arduino Due, there is a dedicated post here -

http://rcarduino.blogspot.ae/2013/04/reading-rc-channels-with-arduino-due.html

For more performance and smoother output on the 8-bit Arduinos an RCArduinoFastLib versions will be added in separate in the coming days.

// RCArduino MultiChannel Loop back and servo ESC control for upto 8 RC channels
//
// rcarduino.blogspot.com
//
// A simple approach for reading three RC Channels using pin change interrupts
//
// See related posts -
// http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
// http://rcarduino.blogspot.co.uk/2012/03/need-more-interrupts-to-read-more.html
// http://rcarduino.blogspot.co.uk/2012/01/can-i-control-more-than-x-servos-with.html
//
// rcarduino.blogspot.com
//

// include the pinchangeint library - see the links in the related topics section above for details
#include <PinChangeInt.h>

#include <Servo.h>

// Assign your channel in pins
#define CHANNEL1_IN_PIN 2
#define CHANNEL2_IN_PIN 3
#define CHANNEL3_IN_PIN 4
#define CHANNEL4_IN_PIN 5
#define CHANNEL5_IN_PIN 6
#define CHANNEL6_IN_PIN 7
#define CHANNEL7_IN_PIN 8
#define CHANNEL8_IN_PIN 9

// Assign your channel out pins
#define CHANNEL1_OUT_PIN 10
#define CHANNEL2_OUT_PIN 11
#define CHANNEL3_OUT_PIN 12
#define CHANNEL4_OUT_PIN 13
#define CHANNEL5_OUT_PIN A0
#define CHANNEL6_OUT_PIN A1
#define CHANNEL7_OUT_PIN A2
#define CHANNEL8_OUT_PIN A3

// Servo objects generate the signals expected by Electronic Speed Controllers and Servos
// We will use the objects to output the signals we read in
// this example code provides a straight pass through of the signal with no custom processing
Servo servoChannel1;
Servo servoChannel2;
Servo servoChannel3;
Servo servoChannel4;
Servo servoChannel5;
Servo servoChannel6;
Servo servoChannel7;
Servo servoChannel8;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define CHANNEL1_FLAG 1
#define CHANNEL2_FLAG 2
#define CHANNEL3_FLAG 4
#define CHANNEL4_FLAG 8
#define CHANNEL5_FLAG 16
#define CHANNEL6_FLAG 32
#define CHANNEL7_FLAG 64
#define CHANNEL8_FLAG 128

// holds the update flags defined above
volatile uint8_t bUpdateFlagsShared;

// shared variables are updated by the ISR and read by loop.
// In loop we immediatley take local copies so that the ISR can keep ownership of the
// shared ones. To access these in loop
// we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on
// as quickly as possible, this ensures that we are always able to receive new signals
volatile uint16_t unChannel1InShared;
volatile uint16_t unChannel2InShared;
volatile uint16_t unChannel3InShared;
volatile uint16_t unChannel4InShared;
volatile uint16_t unChannel5InShared;
volatile uint16_t unChannel6InShared;
volatile uint16_t unChannel7InShared;
volatile uint16_t unChannel8InShared;

void setup()
{
  Serial.begin(115200);

  Serial.println("multiChannels");

  // attach servo objects, these will generate the correct
  // pulses for driving Electronic speed controllers, servos or other devices
  // designed to interface directly with RC Receivers
  servoChannel1.attach(CHANNEL1_OUT_PIN);
  servoChannel2.attach(CHANNEL2_OUT_PIN);
  servoChannel3.attach(CHANNEL3_OUT_PIN);
  servoChannel4.attach(CHANNEL4_OUT_PIN);
  servoChannel5.attach(CHANNEL5_OUT_PIN);
  servoChannel6.attach(CHANNEL6_OUT_PIN);
  servoChannel7.attach(CHANNEL7_OUT_PIN);
  servoChannel8.attach(CHANNEL8_OUT_PIN);

  // using the PinChangeInt library, attach the interrupts
  // used to read the channels
  PCintPort::attachInterrupt(CHANNEL1_IN_PIN, calcChannel1,CHANGE);
  PCintPort::attachInterrupt(CHANNEL2_IN_PIN, calcChannel2,CHANGE);
  PCintPort::attachInterrupt(CHANNEL3_IN_PIN, calcChannel3,CHANGE);
  PCintPort::attachInterrupt(CHANNEL4_IN_PIN, calcChannel4,CHANGE);
  PCintPort::attachInterrupt(CHANNEL5_IN_PIN, calcChannel5,CHANGE);
  PCintPort::attachInterrupt(CHANNEL6_IN_PIN, calcChannel6,CHANGE);
  PCintPort::attachInterrupt(CHANNEL7_IN_PIN, calcChannel7,CHANGE);
  PCintPort::attachInterrupt(CHANNEL8_IN_PIN, calcChannel8,CHANGE);

  // for loop back test only, lets set each channel to a known value
  servoChannel1.writeMicroseconds(1100);
  servoChannel2.writeMicroseconds(1200);
  servoChannel3.writeMicroseconds(1300);
  servoChannel4.writeMicroseconds(1400);
  servoChannel5.writeMicroseconds(1500);
  servoChannel6.writeMicroseconds(1600);
  servoChannel7.writeMicroseconds(1700);
  servoChannel8.writeMicroseconds(1800);
}

void loop()
{
  // create local variables to hold a local copies of the channel inputs
  // these are declared static so that thier values will be retained
  // between calls to loop.
  static uint16_t unChannel1In;
  static uint16_t unChannel2In;
  static uint16_t unChannel3In;
  static uint16_t unChannel4In;
  static uint16_t unChannel5In;
  static uint16_t unChannel6In;
  static uint16_t unChannel7In;
  static uint16_t unChannel8In;

  uint8_t bUpdateFlags = 0;
  // check shared update flags to see if any channels have a new signal
  // for nicely formatted serial output use this
  if(bUpdateFlagsShared == 0xFF)
  // for more responsive projects update any channels whenever a new signal is available using this
  // if(bUpdateFlagsShared)
  {
    noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables

    // take a local copy of which channels were updated in case we need to use this in the rest of loop
    bUpdateFlags = bUpdateFlagsShared;
  
    // in the current code, the shared values are always populated
    // so we could copy them without testing the flags
    // however in the future this could change, so lets
    // only copy when the flags tell us we can.
  

    if(bUpdateFlags & CHANNEL1_FLAG)
    {
      unChannel1In = unChannel1InShared;
    }
  
    if(bUpdateFlags & CHANNEL2_FLAG)
    {
      unChannel2In = unChannel2InShared;
    }
  
    if(bUpdateFlags & CHANNEL3_FLAG)
    {
      unChannel3In = unChannel3InShared;
    }

    if(bUpdateFlags & CHANNEL4_FLAG)
    {
      unChannel4In = unChannel4InShared;
    }
  
    if(bUpdateFlags & CHANNEL5_FLAG)
    {
      unChannel5In = unChannel5InShared;
    }
  
    if(bUpdateFlags & CHANNEL6_FLAG)
    {
      unChannel6In = unChannel6InShared;
    }
   
    if(bUpdateFlags & CHANNEL7_FLAG)
    {
      unChannel7In = unChannel7InShared;
    }
  
    if(bUpdateFlags & CHANNEL8_FLAG)
    {
      unChannel8In = unChannel8InShared;
    }
  
    // clear shared copy of updated flags as we have already taken the updates
    // we still have a local copy if we need to use it in bUpdateFlags
    bUpdateFlagsShared = 0;
  
    interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
    // as soon as interrupts are back on, we can no longer use the shared copies, the interrupt
    // service routines own these and could update them at any time. During the update, the
    // shared copies may contain junk. Luckily we have our local copies to work with :-)
  }
 
  // do any processing from here onwards
  // only use the local values unAuxIn, unThrottleIn and unSteeringIn, the shared
  // variables unAuxInShared, unThrottleInShared, unSteeringInShared are always owned by
  // the interrupt routines and should not be used in loop
  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel1.writeMicroseconds(unChannel1In);
      Serial.println("");
      Serial.print(bUpdateFlags);
      Serial.print(",");
      Serial.print(unChannel1In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL2_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel2.writeMicroseconds(unChannel2In);
      Serial.print(unChannel2In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL3_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel3.writeMicroseconds(unChannel3In);
      Serial.print(unChannel3In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL4_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel4.writeMicroseconds(unChannel4In);
      Serial.print(unChannel4In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL5_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel5.writeMicroseconds(unChannel5In);
      Serial.print(unChannel5In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL6_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel6.writeMicroseconds(unChannel6In);
      Serial.print(unChannel6In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL7_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel7.writeMicroseconds(unChannel7In);
      Serial.print(unChannel7In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL8_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel8.writeMicroseconds(unChannel8In);
      Serial.print(unChannel8In);
      Serial.print(",");
  }
}


void calcChannel1()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState) // this is equivalent to digitalRead(CHANNEL1_IN_PIN) but about 10 times faster
  {
    ulStart = micros();
  }
  else
  {
    unChannel1InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL1_FLAG;
  }
}

void calcChannel2()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel2InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL2_FLAG;
  }
}

void calcChannel3()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel3InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL3_FLAG;
  }
}

void calcChannel4()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel4InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL4_FLAG;
  }
}

void calcChannel5()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel5InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL5_FLAG;
  }
}

void calcChannel6()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel6InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL6_FLAG;
  }
}

void calcChannel7()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel7InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL7_FLAG;
  }
}

void calcChannel8()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel8InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL8_FLAG;
  }
}
Duane B


Thursday, April 11, 2013

The Problem ( and Solutions ) With Arduino Interrupts

If you have ever tried to port an Arduino project that uses interrupts from one board type to another, you have probably experienced frustration, this includes the sample code throughout RCArduino.


The following post examines the differences in interrupts between the popular boards, the Arduino UNO, Micro, Mega, Mini and Leonardo. Based on this information we can modify sketches to run on the full range of 8-bit Arduinos.

The 8-bitArduino boards are based on one of three related chips -

ATMega328 - UNO and Mini
ATMega32u4 - Leonardo and Micro
ATMega2560 - Mega

Each of these chips supports two types of interrupts -

1) External Interrupts
These are flexible easy to use interrupts which can be triggered by rising, falling or changing signals. The disadvantage is that there are a limited number available on each chip type.

If we want to access more interrupts we need to look at the next type -

2) Pin Change Interrupts
The underlying chip in your Arduino supports a second type of interrupt however these interrupts are not directly supported by Arduino and need to be accessed through an additional library.

Differences In External Interrupts

The external interrupts are associated with specific digital pins on each chip type, the following table taken from the attachInterrupt reference page lists the available external interrupts and the associated Arduino pin on each chip -


Boardint.0int.1int.2int.3int.4int.5
Uno, Ethernet,
Mini
23
Mega25602321201918
Leonardo, Micro3201


The Arduino team have hidden some of the differences between the ATMega328 and ATM2560 so that attaching INT0 using the attachInterrupt function will attach an interrupt to digital pin 2 on both chips even though on the Mega digital pin2 is actually INT4.

The same logic has not been carried across to the ATMega32u4 Based leonardo. Notice how int0 and int1 are actually reversed on the Leonardo, this will be a major trap for people who are porting code from the UNO.


Does the Leonardo have four external interrupts ?
While the Leonardo appears to have 4 external interrupts, int2 and int3 are attached to digital pins 0 and 1 which are almost always reserved for serial input/output. So yes there are four interrupts, but two of them are only available by disabling serial functionality.

Differences In Pin Change Interrupts
On the Arduino UNO, pin change interrupts can be used to enable interrupts on any of the Arduino PINs to give access to a total of 19 interrupts (13 digital pins and 6 Analog pins).

I initially assumed that this was also possible on the Mega, Micro and Leonardo as well. It isn't.

Pin change interrupts are supported on the following Leonardo/Micro pins - 8,9,10 and 11.

Pin change interrupts are supported on Arduino Mega pins 10,11,12,13,14,15 and analog pins 6 to 15

Interrupts and RCArduino
These differences between the Arduino platforms will have been responsible for some of the difficulty that users have had in porting RCArduino code to Minis, Micros, Leonardos and Megas.

The good news is that now we have a full understanding of the inconsistencies between the different devices there should be no problem in modifying the sample sketches to run on them.

If your having trouble with a sample sketch, get in touch, in the meantime I will be updating some of the sketches to work across multiple boards.

Stay tuned

Duane B

Tuesday, February 12, 2013

Arduino Notes, Frequencies and Scales plus a Bonus Filter.

A quick post with a file I use a lot to experiment with Audio projects.

The file contains the frequencies used to generate the midi notes 0 to 127 in your Arduino projects. The file doesn't quite provide the frequency, instead it provides the phase increment required to generate the frequency. If you are familiar with Direct Digital Synthesis you will know that this is what we use in our Auduino projects to generate sine and other interesting waveforms at a given frequency.

The Illutron B Uses Direct Digital Synthesis, there is an introduction to the technique here -
http://rcarduino.blogspot.com/2012/08/the-must-build-arduino-project-illutron.html

Saving Memory
The note tables file uses PROGMEM to efficiently store the frequency table out of the way in program memory -

http://arduino.cc/en/Reference/PROGMEM

In addition to the 128 midi notes, two scales are provided, the pentatonic blues scale that will be familiar to anyone who has built and Auduino and the equally widely used C Minor blues scale. To preserve memory, these scales are also stored in progmem. You can add your own scales by following the same pattern.

Accessing The Notes

To access a note you can use -

uint16_t unPhaseIncrement = getMidiNotePhaseIncrement(sNote);

where sNote is between 0 and 127

To access a note from one of the scales based on an analogInput you can use -

uint16_t unPhaseIncrement = getCMBluesPhaseIncrement(map(aInput,0,1024,0,CMBLUES_NOTES));

In the sample code above, we are using the map function to map an analog input value held in aInput the range of notes available in the C Minor Blues scale.

A Simple Test Synthesiser
At the end of this post is a very simple synth which uses the note tables and is based on the popular Auduino with one bonus feature. I have included the low pass filter from the Mozzi Arduino audio libraries. The Mozzi filter is better than the simple hardware filters I have tried and is very easy to add to a project.

In the test sketch the filter can be driven in two ways based on the position of an analog input. At the bottom end of the input range, the filter is controlled directly by the input, at the top end of the range, the filter is controlled by an oscillator, the frequency of the oscillator is  adjustable within this range.

To keep things simple, the test sketch uses the same hardware as an Auduino so if you want to have a play around with the mozzi filter or creating your own scales - load up and play away.

Mozzi

The Mozzi filter can be downloaded from http://sensorium.github.com/Mozzi/

The Mozzi sound samples are very good quality, the project implements a range of synthesizer building blocks and is something to take a closer look at.

Arduino Note Tables -

// Arduino Note Tables rcarduino.blogspot.com

#ifndef _NOTETABLES_
#define _NOTETABLES_

#include "avr/pgmspace.h"

#define MIDI_NOTES 128
// used to convert midi note numbers into the increments required to generate the note in the ISR
PROGMEM unsigned int midiNoteToWavePhaseIncrement[MIDI_NOTES] =
{
 66 // 0,8.18,66.98,66                C
,70 // 1,8.66,70.96,70
,75 // 2,9.18,75.18,75
,79 // 3,9.72,79.65,79
,84 // 4,10.30,84.38,84
,89 // 5,10.91,89.40,89
,94 // 6,11.56,94.72,94
,100 // 7,12.25,100.35,100
,106 // 8,12.98,106.32,106
,112 // 9,13.75,112.64,112
,119 // 10,14.57,119.34,119
,126 // 11,15.43,126.43,126
,133 // 12,16.35,133.95,133            C
,141 // 13,17.32,141.92,141
,150 // 14,18.35,150.35,150
,159 // 15,19.45,159.29,159
,168 // 16,20.60,168.77,168
,178 // 17,21.83,178.80,178
,189 // 18,23.12,189.43,189
,200 // 19,24.50,200.70,200
,212 // 20,25.96,212.63,212
,225 // 21,27.50,225.28,225
,238 // 22,29.14,238.67,238
,252 // 23,30.87,252.86,252
,267 // 24,32.70,267.90,267           C - lowest note used on rcarduino ribbon synth
,283 // 25,34.65,283.83,283
,300 // 26,36.71,300.71,300
,318 // 27,38.89,318.59,318
,337 // 28,41.20,337.53,337
,357 // 29,43.65,357.60,357
,378 // 30,46.25,378.87,378
,401 // 31,49.00,401.40,401
,425 // 32,51.91,425.27,425
,450 // 33,55.00,450.55,450
,477 // 34,58.27,477.34,477
,505 // 35,61.74,505.73,505
,535 // 36,65.41,535.80,535           C
,567 // 37,69.30,567.66,567
,601 // 38,73.42,601.42,601
,637 // 39,77.78,637.18,637
,675 // 40,82.41,675.07,675
,715 // 41,87.31,715.21,715
,757 // 42,92.50,757.74,757
,802 // 43,98.00,802.79,802
,850 // 44,103.83,850.53,850
,901 // 45,110.00,901.11,901
,954 // 46,116.54,954.69,954
,1011 // 47,123.47,1011.46,1011       C
,1071 // 48,130.81,1071.60,1071
,1135 // 49,138.59,1135.32,1135
,1202 // 50,146.83,1202.83,1202
,1274 // 51,155.56,1274.36,1274
,1350 // 52,164.81,1350.13,1350
,1430 // 53,174.61,1430.42,1430
,1515 // 54,185.00,1515.47,1515
,1605 // 55,196.00,1605.59,1605
,1701 // 56,207.65,1701.06,1701
,1802 // 57,220.00,1802.21,1802
,1909 // 58,233.08,1909.38,1909
,2022 // 59,246.94,2022.92,2022
,2143 // 60,261.63,2143.20,2143       C
,2270 // 61,277.18,2270.64,2270
,2405 // 62,293.66,2405.66,2405
,2548 // 63,311.13,2548.71,2548
,2700 // 64,329.63,2700.27,2700
,2860 // 65,349.23,2860.83,2860
,3030 // 66,369.99,3030.95,3030
,3211 // 67,392.00,3211.18,3211
,3402 // 68,415.30,3402.12,3402
,3604 // 69,440.00,3604.42,3604
,3818 // 70,466.16,3818.75,3818
,4045 // 71,493.88,4045.83,4045
,4286 // 72,523.25,4286.41,4286      C
,4541 // 73,554.37,4541.29,4541
,4811 // 74,587.33,4811.33,4811
,5097 // 75,622.25,5097.42,5097
,5400 // 76,659.26,5400.53,5400
,5721 // 77,698.46,5721.67,5721
,6061 // 78,739.99,6061.89,6061
,6422 // 79,783.99,6422.36,6422
,6804 // 80,830.61,6804.25,6804
,7208 // 81,880.00,7208.85,7208
,7637 // 82,932.33,7637.51,7637
,8091 // 83,987.77,8091.66,8091
,8572 // 84,1046.50,8572.82,8572     C
,9082 // 85,1108.73,9082.58,9082
,9622 // 86,1174.66,9622.66,9622
,10194 // 87,1244.51,10194.85,10194
,10801 // 88,1318.51,10801.07,10801
,11443 // 89,1396.91,11443.33,11443
,12123 // 90,1479.98,12123.79,12123
,12844 // 91,1567.98,12844.71,12844
,13608 // 92,1661.22,13608.50,13608
,14417 // 93,1760.00,14417.70,14417
,15275 // 94,1864.65,15275.02,15275
,16183 // 95,1975.53,16183.31,16183
,17145 // 96,2093.00,17145.63,17145     C
,18165 // 97,2217.46,18165.16,18165
,19245 // 98,2349.32,19245.31,19245
,20389 // 99,2489.01,20389.70,20389
,21602 // 100,2637.02,21602.14,21602
,22886 // 101,2793.83,22886.67,22886
,24247 // 102,2959.95,24247.58,24247
,25689 // 103,3135.96,25689.42,25689
,27216 // 104,3322.44,27216.99,27216
,28835 // 105,3520.00,28835.39,28835
,30550 // 106,3729.31,30550.04,30550
,32366 // 107,3951.06,32366.63,32366
,34291 // 108,4186.01,34291.26,34291    C
,36330 // 109,4434.92,36330.32,36330
,38490 // 110,4698.64,38490.65,38490
,40779 // 111,4978.03,40779.41,40779
,43204 // 112,5274.04,43204.25,43204
,45773 // 113,5587.65,45773.32,45773
,48495 // 114,5919.91,48495.14,48495
,51378 // 115,6271.92,51378.79,51378
,54433 // 116,6644.87,54433.96,54433
,57670 // 117,7040.00,57670.76,57670
,61100 // 118,7458.62,61100.07,61100
,64733 // 119,7902.13,64733.26,64733
,3046 // 120,8372.02,68582.53,3046      C
,7124 // 121,8869.84,72660.64,7124
,11445 // 122,9397.27,76981.30,11445
,16022 // 123,9956.06,81558.77,16022
,20872 // 124,10548.07,86408.50,20872
,26010 // 125,11175.30,91546.65,26010
,31454 // 126,11839.81,96990.28,31454
,31454 // 127,11839.81,96990.28,31454 // this is wrong, need to calculate correct value, even though at 8Khz its wrapping around on every tick
};

// Pentatonic scale
// C D E G A C
// to map to midi note

#define PENTATONIC_NOTES 54

// provides an index of pentatonic notes in the midi note table

PROGMEM unsigned char sPentatonicNotes[PENTATONIC_NOTES] =
{
  0,   2,  4,  7,  9,
  12, 14, 16, 19, 21,
  24, 26, 28, 31, 33,
  36, 38, 40, 43, 45,
  48, 50, 52, 55, 57,
  60, 62, 64, 67, 69,
  72, 74, 76, 79, 81,
  84, 86, 88, 91, 93,
  96, 98,100,103,105,
 108,110,112,115,117,
 120,122,124,127
};

// C Minor Blues scale
// C D# F F# G A# C
// to map to midi note

#define CMBLUES_NOTES 65

// provides an index of pentatonic notes in the midi note table

PROGMEM unsigned char sCMBluesNotes[CMBLUES_NOTES] =
{
  0,   3,  5,  6,  7, 10,  // 6
  12, 15, 17, 18, 19, 22,  // 12
  24, 27, 29, 30, 31, 34,  // 18
  36, 39, 41, 42, 43, 46,  // 24
  48, 51, 53, 54, 55, 58,  // 30
  60, 63, 65, 66, 67, 70,  // 36  
  72, 75, 77, 78, 79, 82,  // 42
  84, 87, 89, 90, 91, 94,  // 48
  96, 99,101,102,103,106,  // 54
 108,111,113,114,115,118,  // 60
 120,123,125,126,127       // 65
};


unsigned int getMidiNotePhaseIncrement(unsigned char sNote)
{
 if(sNote >= MIDI_NOTES)
 {
   sNote = (MIDI_NOTES - 1);
 }

 return pgm_read_word(midiNoteToWavePhaseIncrement + (sNote));
}
unsigned int getPentatonicPhaseIncrement(unsigned char sPentatonicNote)
{
 if(sPentatonicNote >= PENTATONIC_NOTES)
  sPentatonicNote = (PENTATONIC_NOTES - 1);

 uint8_t sMidiIndex = pgm_read_byte(sPentatonicNotes + sPentatonicNote);

 return pgm_read_word(midiNoteToWavePhaseIncrement + sMidiIndex);
}

unsigned int getCMBluesPhaseIncrement(unsigned char sCMBluesNote)
{
 if(sCMBluesNote >= CMBLUES_NOTES)
  sCMBluesNote = (CMBLUES_NOTES - 1);
 
 uint8_t sMidiIndex = pgm_read_byte(sCMBluesNotes + sCMBluesNote);
 return pgm_read_word(midiNoteToWavePhaseIncrement + sMidiIndex);
}

#endif

The very simple, very lo-fi synth

// Very Lo Fi synth with Mozzi filter rcarduino.blogspot.com

#include "NoteTables.h"
#include "lowpass.h"

#define PWM_OUT_REG OCR2B

#define KEY_COUNT 20
#define KEY_WIDTH (1024/KEY_COUNT)

class CAudio
{
  public:
   CAudio(){}
   static void begin(uint8_t bStopTimer0Interrupts)
   {
     // Setup timer 1
     TCCR1A=0x0;          // set the timer prescaler to 8 = 16/8 = 2MHz
     TCCR1B=0x02;          // set the timer prescaler to 8 = 16/8 = 2MHz
     TIMSK1 |= (1<<OCIE1A);   // Enable output compare match interrupt on OCR1A
 
     TCCR2A=0B10110011;                                    //-8 bit audio PWM
     //TCCR0A=0x83;          // Set timer waveform generation mode to FAST PWM, clear OC0A On match, set at bottom - OC0A = digital pin 6.
     TCCR2B=0x01;          // Set to clock frequency, no prescaler

     pinMode(3,OUTPUT);
    
     if(bStopTimer0Interrupts)
     {
      // stops timer0 which triggers interrupts for the millis and micros functions
      // if we stop the timer, we get better audio, but loose millis and micros.
      TIMSK0 &= (~((1 << OCIE0A)| (1 << OCIE0B)));
     }
   }
};


#define SAW 0
#define SQUARE 1
#define RAMP 2


class COscilator
{
public:
  void setWaveform(uint8_t sWaveform)
  {
    m_sWaveform = sWaveform;
  }
  void setPhaseIncrement(uint16_t unPhaseIncrement)
  {
    m_unPhaseIncrement = unPhaseIncrement;
  }
  uint8_t hasTriggered()
  {
    return m_unPhaseAccumulator < m_unPhaseIncrement;
  }
  void trigger()
  {
    m_unPhaseAccumulator = 0;
  }
  void updatePhase()
  {
    m_unPhaseAccumulator += m_unPhaseIncrement;
  }
  uint8_t getSample()
  {
    uint8_t sSample = m_unPhaseAccumulator>>8;
    switch(m_sWaveform)
    {
      case SQUARE:
       (sSample >= 127) ? sSample = 255 : sSample = 0;
      break;
      case RAMP:
       sSample = ~sSample;
      break;
    }
    return sSample;
  }
protected:
  uint16_t m_unPhaseIncrement;
  uint16_t m_unPhaseAccumulator;
  uint8_t m_sWaveform;
};

COscilator Osc1,Osc2,LFO,LFOFilter;

uint16_t getPhaseIncrement(uint16_t aInput,uint8_t sOffset)
{
  uint8_t sKey = aInput/KEY_WIDTH;

  // find where on the ribbon the key starts 
  uint16_t unKeyStart = sKey * KEY_WIDTH;
 
  // find where on the ribbon the key ends
  uint16_t unKeyEnd = (sKey+1) * KEY_WIDTH;
 
  // map where the current input sits between the key start and key end to a frequency that sits at the same point between
  // the frequency of the key and the frequency of the next key
 
  return map(aInput,unKeyStart,unKeyEnd,getMidiNotePhaseIncrement(sKey+sOffset),getMidiNotePhaseIncrement(sKey+1+sOffset));
}

LowPassFilter lpFilter;
uint16_t gFilter;

void setup()
{
  Serial.begin(9600);
  CAudio::begin(true); 

  Osc1.setWaveform(RAMP);
  Osc2.setWaveform(RAMP);
  LFO.setWaveform(SAW);
  LFOFilter.setWaveform(RAMP);
}

void loop()
{
  uint16_t aInput = analogRead(1);
  if(aInput >= 1021)
  {
    Osc1.setPhaseIncrement(0);
    Osc2.setPhaseIncrement(0);
    LFO.setPhaseIncrement(0);
    LFOFilter.trigger();
  }
  else
  {
    uint8_t sOffset = 20;//((analogRead(1) >> 3)/12)*12;
    uint16_t unPhaseIncrement = getCMBluesPhaseIncrement(map(aInput,0,1024,0,CMBLUES_NOTES));
    Osc1.setPhaseIncrement(analogRead(2)<<2);
    Osc2.setPhaseIncrement(analogRead(3)<<5);

    //lpFilter.setCutoffFreq(analogRead(4)>>2);
    lpFilter.setResonance(analogRead(5)>>2);
   
    LFO.setPhaseIncrement(unPhaseIncrement);   
   
    uint16_t unFilter = analogRead(4);
    if(unFilter < 512)
    {
     LFOFilter.setPhaseIncrement(0);
     unFilter>>=1;
    
     uint8_t sreg = SREG;
     cli();
     gFilter = unFilter;
     SREG = sreg;
    }
    else
    {
     uint8_t sreg = SREG;
     cli();
     gFilter = unFilter;
     SREG = sreg;
    
     LFOFilter.setPhaseIncrement((gFilter-512));
    }
  }
}



#define DELAY_LENGTH 1024

// iterate the grains and LFO
SIGNAL (TIMER1_COMPA_vect)
{
  static uint8_t sDelayBuffer[DELAY_LENGTH];
  static uint16_t sDelayIndex;

  sDelayIndex++;
  if(sDelayIndex >= DELAY_LENGTH)
  {
    sDelayIndex = 0;
  }
 
  OCR1A += 125;

  Osc1.updatePhase();
  Osc2.updatePhase();
  LFO.updatePhase();
  LFOFilter.updatePhase();
 
  if(LFO.hasTriggered())
  {
    Osc1.trigger();
    Osc2.trigger();
  }
   
  if(gFilter < 512)
  {
    lpFilter.setCutoffFreq(gFilter>>1);
  }
  else
  {
   lpFilter.setCutoffFreq(LFOFilter.getSample());
  }
 
  uint16_t nOutput = lpFilter.next((Osc1.getSample()>>1)+(Osc2.getSample()>>1));
  if(nOutput > 255)
   nOutput = 255;
  PWM_OUT_REG = nOutput;

  // not being mixed in to output but easy to add, see rcarduino.blogspot.com
  sDelayBuffer[sDelayIndex] = PWM_OUT_REG;
}




And don't forget the Mozzi filter 'lowpass.h' from https://github.com/sensorium/Mozzi

Duane B