Tuesday, May 5, 2020

Arduino ESC Tester - Adding Fast PWM and OneShot125

The Arduino Servo/ESC Tester Series (Part 3)


Figure 1. Arduino and Tester Shield mounted in 3D printed enclosure.


In our first article in this series, we built a prototype Servo Tester using the Altronics MegaBox as our hardware platform. We followed this up with an article using a custom Arduino shield to make construction cheaper and easier.

As shown in Figure 1, we have created a 3D printed case for the shield version of our tester. You can download the STL files for the base, lid and LCD mount from Thingiverse. The LCD mounting bracket and hinges are connected using M3 bolts and nuts.

If we do another version of this shield, I would do a few things differently. These include:

  • Moving the group of 15 tester pin headers away from the left hand hinge, towards the centre of the PCB.
  • Moving the LCD connector towards the top of the PCB (or use a 90 degree connector) to allow the LCD mount to close like a lid.
  • Move the potentiometer a bit further towards the front of the PCB.
  • Break out the D13 LED onto the shield.
  • Adding four mounting holes to the PCB and use this as the lid to the box.

Our previous prototypes only provided PWM output at varying frequencies (50 Hz, 125 Hz and 250 Hz). In this article we will add PWM 490 Hz and the OneShot125 protocol to our tester. This will be used for testing ESC's which utilise this protocol.

PWM 490 Hz (Fast PWM)


In standard PWM the maximum pulse width is 2 ms, so the highest theoretical frequency using this control strategy is 500 Hz. To get pulse separation, the practical upper limit is 490 Hz.

Adding this protocol is very easy. In part 1 of our series we found that the simplest way to output PWM is to use the analogWrite(pin, dutyCycle) command. The ATmega328P PWM default frequency is 490 Hz for all PWM capable pins (3, 9, 10 and 11), with the exception of pins 5 and 6, whose default frequency is 980 Hz.

We tested this using our protocol analyser and found that a 50% duty cycle [analogWrite(9, 127)] provides PWM with a 1.016 ms pulse width and a frequency of 490 Hz.

For our existing shield sketch we may as well continue to use the register method of setting PWM frequency. The formula that we derived in Part 1 gives us the value for ICR1 at a particular PWM frequency:

ICR1 = 1MHz / fPWM = 1MHz / 490 = 2040

Setting the ICR1 register to this value should give us PWM at 490 Hz. Updating the sketch accordingly, we measured the output on our protocol analyser (Figure 2). We ended up with a PWM frequency very close to 490 Hz. The updated code will be provided at the end of the article.

Figure 2. Output for PWM 490 Hz

We also tried connecting the 9G Servo to see if it would work at this frequency. It did to a certain extent but there was a LOT of jitter. The servo is expecting a nominal frequency of 50 Hz so these results are not surprising.

What is an ESC?


Figure 3. An example of an ESC.

An Electronic Speed Control or ESC is an electronic circuit that controls and regulates the speed of an electric motor. ESC's come in many different packages, one version is shown in Figure 2.

ESC's contain a microcontroller which take an input (e.g. direction or speed from a flight controller) and convert this to the appropriate motor control output. The flight controller (or ESC tester in our case), communicates with the ESC using an ESC protocol. The available protocols depend on the ESC firmware being used. Some examples of ESC firmware are:

  • BLHeli ESC.
  • BLHeli_S ESC.
  • SimonK ESC.
  • Kiss.
  • BLHeli_32.

The ESC in Figure 3 is encapsulated in yellow heatshrink. The 3 x blue leads with bullet connectors, go to the brushless motor that is being controlled. With brushless motors, the speed of the motor is varied by adjusting the timing of pulses of current delivered to the windings of the motor. The ESC effectively creates three-phase AC power to deliver these pulses.

Figure 4. Example ESC control wiring.

In Figure 3, the thick red and black wires (ending in a male Deans T-plug) connect to the LiPo batteries and the 3 smaller wires (white, black and red) are for the ESC control (white) and to provide a regulated 5 VDC (red and black). Not all ESC's provide this regulated 5 VDC (also called a Battery Eliminator Circuit or BEC).

An example of how an ESC is used in a quadcopter design is shown in Figure 4. This design uses a quad ESC (i.e. four ESC's mounted on one PCB), controlled by a Beaglebone Blue flight controller.

The idea is that our ESC tester can be used in place of the flight controller, to allow us to test each part of the system separately.

What is OneShot?


The OneShot125 protocol consists of:
  • A single pulse (hence the name OneShot) with the new response required (this is different to PWM which is a stream of pulses);
  • Pulse width is between 125 µs (stop) and 250 µs (full power). This is where the 125 in the name comes from.
Oneshot comes in three varieties:
  • Oneshot125 (pulse width 125-250 μs, maximum frequency 4 kHz); 
  • Oneshot42 (pulse width 42-84 μs, maximum frequency 11.9 kHz); and 
  • Multishot (pulse width 5-25 μs, maximum frequency 40 kHz). 

OneShot125 was created by Flyduino and is supported by flight controllers which use Cleanflight, Betaflight, Raceflight and Kiss. From an ESC perspective you will need SimonK, BLHeli rev13, or KISS firmware.

OneShot42 was also developed by Flyduino as part of their KISS FC and ESC program. It is not widely supported at the moment.

Out of the three varities in the OneShot stable, Multishot is the fastest ESC protocol. To take advantage of this it requires a fast flight controller and ESC processor. It was developed by RaceFlight and again is not widely supported.

The Raceflight firmware and Multishot protocol were introduced by RS2K (RCGroups user). Raceflight is a fork (or branch) of Betaflight, meaning it used the original Betaflight code and was modified for the purpose of F4 flight controller targets.

How about DShot?


Compared to the protocols already considered, DShot is the only one which is truly digital. You could argue that PWM and OneShot are also digital since they use pulses with encoded data but the point is moot. A comparison of the theoretical data rates for OneShot and Dshot are shown in Figure 5. The advantage of DShot over OneShot is not so much about speed but reliability and flexibility. Moving to a fully digital protocol allows the introduction of error correction and bi-directional data flow.

Figure 5. DShot vs OneShot


A DShot digital message consists of 16 bits in three parts, throttle value (11 bits), telemetry request (1 bit) and the CRC checksum (4 bits). The telemetry request asks for data back from the ESC on models that support it. Transmission of telemetry data is done on a different wire. Any ESC that comes with BLHeli_S firmware should support DShot.

There are four types of DShot, differentiated by their speed of connection:

  • DShot150 – 150 kbits per second or 9375 Hz update frequency
  • DShot300 – 300 kbits per second or 18.75 kHz update frequency
  • DShot600 – 600 kbits per second or 37.5 kHz update frequency
  • DShot1200 – 1,200 kbits per second or 75 kHz update frequency

We will look at developing DShot capability for our ESC Tester in a subsequent article.

Is OneShot125 better than standard PWM?


In our original article in this series, we talked about how PWM is used to control a servo. TLDR - a 1 ms pulse width equates to stop and a 2 ms pulse width = full power. As mentioned above, the theoretical fastest refresh rate is 1 / 2ms = 500 Hz. In practice it is more like 490 Hz as you need some gap between the pulses. A consequence of this is that there is no point your flight controller running faster than 490 Hz since we can only update the motor control at this rate.

By reducing the pulse width band to 125 - 250 µs, OneShot125 is theoretically eight times faster than PWM 490 Hz. A consequence of this faster update speed is no jitter in your controls. Jitter is caused when your flight model updates quicker than your ESC can respond. With OneShot125, your flight controllers PID loop can update eight times faster than Fast PWM.

A comparison of Fast PWM and OneShot is shown in Figure 6.

Figure 6. Fast PWM vs OneShot


Programming OneShot125 for the Arduino UNO


Using our approach to date we would send continuous OneShot125 pulses in the 125 - 250 µs range (see Figure 6) as we have done for the previous PWM solutions, but that is not how OneShot operates. OneShot sends one pulse when the throttle information changes.

However, we are not writing flight controller software but an ESC tester. The ESC wont care if it gets repeated pulses with the same value. So for simplicity we will produce a stream of pulse at a frequency of about 1 kHz. The pulse width will be between 125 - 250 µs, based on the potentiometer position (simulating the throttle input).

Taking this approach allows us to reuse a lot of our exisiting code. If you need a more purist solution then you can use the OneShot125 code written for the Arduino Mega which is available on the Arduino forum.

Once again, the formula that we derived in Part 1 gives us the value for ICR1 at a particular PWM frequency:

ICR1 = 1MHz / fPWM = 1MHz / 1kHz = 1000

Setting the ICR1 register to this value should give us PWM at 1 kHz. The only other changes we need to make are to the minimum and maximum pulse widths. Register OCR1A can then be set to the required pulse width between 125 and 250.

Figure 7. OneShot125 with pulse width set to 193 µs


Connecting the output of our Servo/ESC tester to our protocol analyser provides the output shown in Figures 7 and 8. As expected we get a pulse width of 193 µs at a frequency of 1kHz.

Figure 8. Output on Protocol Analyser.

Adding OneShot42 or Multishot to our ESC Tester is trivial. You just need to change the minimum and maximum pulse width values and use the same approach as that used for OneShot125. I'm not going to add it to our code because I have no need for it.

You can download a copy of the updated sketch from the Reefwing Gist.

Thursday, March 26, 2020

Arduino Shield Multiprotocol ESC/Servo Tester

The Story So Far...




In a previous article we used the Altronics MegaBox to create a prototype multiprotocol servo/ESC tester. We will build on the techiques and code from that article to create a custom Arduino UNO shield which can be used to deliver the same functionality. As the UNO uses a different microprocessor to the Mega 2560, we may need to change some of the timer and register specific code (actually it turns out only minor changes are required - our PWM output D9 uses Timer 1, which is the same as D11 on the Mega).

The Arduino Shield


Using Fritzing we designed a simple 2 layer Arduino shield (Figure 1) to make it easier to test our hardware. We have made a few changes to our prototype. To save cost we are using a 10k potentiometer in place of the encoder (this saves one digital input as well). We have also replaced the LCD with a version that is controlled via I2C (saving another 5 digital inputs). PWM is being driven via D9. The board has five male header pins in the standard servo connection configuration. This allows you to control up to 4 servo/ESC's (current permitting) and still have an extra connection to view the output on an oscilloscope (for example).

Figure 1. ESC/Servo Tester Shield

As the shield will cover the reset button on the UNO, we have provided another one on the shield. An External Reset is generated by a low level on the RESET pin. In case the other pin connections are not obvious from Figure 1, here are the Arduino UNO pin defintions that you will need.
//PIN CONNECTIONS

//  POTENTIOMETER INPUT
const byte POT = A0;

//  PWM OUTPUT
const byte PWM = 9;

//  PUSH BUTTON INPUTS
const byte PB_DEC = 8;          // Decrement button
const byte PB_INC = 7;          // Increment button
const byte PB_SLT = 6;          // Select Button
const byte PB_BCK = 5;          // Back Button

//END PIN CONNECTIONS
Following testing we will design a 3D printed case for our tester. The LCD is a bit wider than the UNO PCB and we need to allow access to the buttons on the shield.



Parts List


I used the parts in Figure 2, there is nothing special required, but you need to ensure that the dimensions of the parts you select are correct for the PCB design. The part numbers and prices are from Jaycar (these are in AUD and subject to change).

Figure 2. Parts List

In addition to the parts listed above you will need an UNO (or an equivalent clone), a plugpack to power the UNO, the shield PCB, 4 x male to female Dupont cable (Figure 4) and an I2C capable 16 x 2 LCD (Figure 3).

Note that the DPDT switch isn't used in this version of the shield (a subsequent version will include a connection for external power for the servo/ESC's).

I2C Serial 16 x 2 LCD Module


Figure 3. I2C capable LCD 16 x 2


You can buy one of these LCD's online from places like Aliexpress, at the time of writing they were available for USD2.53 (again, this will change). The LCDs are usually attached to another module which performs the serial to parallel conversion using a Philips/NXP PCF8574T. You may have to solder this module on yourself (I did).

The four LCD connection pins (GND, 5V, SDA and SCL) line up with the corresponding connection on the PCB and you could straighten the pins and solder it in place but then you wouldn't have access to the push buttons on the shield or the contrast pot on the back of the LCD. A better option is to use some male to female Dupont cable (Figure 4).

Figure 4. Male to Female Dupont Cable

This link will allow you to download an I2C LCD Arduino Library, which you will need when we develop the firmware in the next section.

The Firmware


The ATmega328P has 6 PWM outputs, 2 are located on each timer/counter. Each timer has two Output Compare Registers (OCR) that control the PWM width for the timer's two outputs. The PWM capable pins are normally marked with a "~" on the UNO and we have replicated this on our shield (Figure 1). The relation between timers and PWM outputs is:

  • Timer 0 controls Pins 5 and 6;
  • Timer 1 controls Pins 9 and 10; and
  • Timer 2 controls Pins 11 and 3.

We are using D9 to deliver our PWM signal, this is on Timer 1 and the relevant OCR for this pin is OCR1A (refer to Figure 9 in the ATmega2560 article). The settings we put in place will effect D10 as well.

To assign D9 as an output, we need to know which Port it is mapped to (or just use pinMode). For the UNO:

  • Port B controls digital pin 8 to 13;
  • Port C controls the analog input pins; and
  • Port D controls digital pins 0 to 7.

From the Arduino pin mapping diagram (Figure 5), we see that digital input 9 (D9) is controlled by bit 1 of Port B's Data Direction Register (DDR). The DDR determines whether a pin is an input or an output (Figure 6).

Figure 5. ATmega168/328P Arduino Pin Mapping

Figure 6. Port B Data Direction Register Bits


Thus we set the DDR of Port B, bit 1 to assign D9 as an ouput:

DDRB |= 1 << DDB1;

Alternatively, you can just use:

pinMode(9, OUTPUT);

As with the ATmega2560, the 328P PWM is capable of running in 3 different modes: Fast PWM, Phase Corrected PWM and Phase and Frequency Phase Corrected PWM.

Every PWM square wave has a duty cycle. This is defined as:

Duty Cycle = [ON time / (ON time + OFF time) ] * 100 

We can manipulate the Output Compare Register (OCR) mentioned above, to adjust the PWM duty cycle. The OCR's (A and B) for Timer 1 are 16 bit registers. So for example, to get a 50% duty cycle, you set the OCR to half of the top counter value - referred to as "TOP" in the datasheet. The TOP value can be assigned to be one of the fixed values: 0xFFFF, 0x00FF, 0x01FF, or 0x03FF, or to the value stored in the OCRnA or ICRn Register. The assignment is dependent on the mode of operation.

Fast PWM using Registers


In Fast PWM mode, the timer repeatedly counts from 0 to 255. The output turns on when the timer is at 0, and turns off when the timer matches the output compare register. The higher the value in the output compare register, the higher the duty cycle.

To get hardware PWM working on a pin, you need to as a minimum:

  1. Set that pin as an output;
  2. Set the PWM mode and frequency using the Timer Counter Control Registers (TCCR); and
  3. Set the Output Compare Register to determine the pulse width / duty cycle.

So let's move on to steps 2 and 3. Firstly, there are two TCCR's (A and B). The control bits for TCCR1A are shown in Figure 7.


Figure 7. TCCR1 (A and B) Control Bits.

For fast PWM, we want non-inverting mode, thus from Figure 8, COM1A = 10 (BINARY).

Figure 8. Compare Output Mode Options, Fast PWM.

Figure 9. Waveform Generation Mode Bit Description.

Next we need to set the Waveform Generation Mode. We will use mode 5 (Fast PWM, 8 bit) which has a TOP = 0x00FF = 255. To get mode 5 (ref: Figure 9), we need to set the following bits within the TCCR's:

WGM10:3 = 0101

We will also set the Clock Select (CS) Bits (Figure 10) to set the prescaler to 1. The appropriate value for this is:

CS10:2 = 001

Figure 10. Clock Select Bits.

The Fast PWM frequency for the output can be calculated by the following equation:


The N variable represents the prescaler divider (1, 8, 64, 256, or 1024). For our settings:

fPWM = 16 Mhz / (1 * (1 + 255) = 16000000 / 256 = 62.5 kHz.

The register code to achieve the register operations explained above is as shown in Figure 11.
DDRB |= 1 << DDB1;                // Set D9 as an output pin
TCCR1A = 0;                              // Clear TCCR A & B bits
TCCR1B = 0;
TCCR1A = (1 << COM1A1) | (1 << WGM10);
TCCR1B = (1 << WGM12)  | (1 << CS10); 
OCR1A = 127;                             // 50% Duty Cycle (i.e. 127/255)
Figure 11. Fast PWM using Registers.

Measuring the results on our protocol analyser provides the expected outcome (Figure 12), 50% duty cycle, pulse width 8 µs and frequency 62.5 kHz. The pulse width is a product of the duty cycle and the frequency (as period is the inverse of frequency).

Figure 12. Fast PWM Output on D9.

Changing PWM Frequency using Registers


Coincidently, the PWM pin (D9) that we chose on the UNO uses the same timer as the D11 pin on the Mega2560, so apart from the pin output definitions the code for phase and frequency correct PWM is the same (Figure 13). The article on the ATmega2560 version explains why we selected the control register bits in Figure 13. It also explains more about how phase and frequency correct PWM differs from Fast PWM. Of course you can also refer to the ATmega328P data sheet.
DDRB |= 1 << DDB1;                 // Set D9 as an output pin
TCCR1A = 0;                              //  Clear TCCR A & B bits
TCCR1B = 0;
TCCR1A = (1 << COM1A1);                  //  Non-inverting output
TCCR1B = (1 << WGM13)  | (1 << CS11);    //  Phase & Freq correct PWM, prescaler N = 8
ICR1 = 20000;                            //  Freq = 50 Hz
OCR1A = 1500;                            //  pulse width = 1500 us;
Figure 13. Phase & Frequency Correct PWM on the UNO.

The default PWM mode, set by init() in the Arduino boiler plate code, for Timer 1 is mode 1 and we want mode 8 (PWM, Phase and Frequency Correct) - see Figure 9. In mode 8 we can adjust the PWM frequency using register ICR1. To get mode 8 we just set the WGM13 bit. We will use a non-inverting output as before so set the COM1A1 bit in TCCR1A. We set the prescaler (N) to 8, to do this, the CS11 bit in TCCR1B needs to be ON.

From the data sheet, the frequency formula for phase and frequency correct PWM is:


For our settings, the calculation is:

fPWM = 16 MHz / (2 * 8 * ICR1) = 16000000 / (2 * 8 * 20000) = 50 Hz

From this formula, it is apparent that we can adjust the PWM frequency by adjusting the value in register ICR1. It also explains why we use a prescaler of 8. The system clock is 16 MHz and to get the PWM frequency we divide by 2N. If we make 2N = 16 then the relationship between fPWM and ICR1 (i.e. TOP) is straight forward. Rearranging the formula:

ICR1 = 1MHz / fPWM

For a fPWM of 50 Hz, ICR1 = 1,000,000 / 50 = 20,000.

Figure 14. Phase & Frequency Correct PWM waveform at 50 Hz.

The results of the code in Figure 13 is shown in Figure 14. So for our tester we can adjust the pulse width using the value of register OCR1A and we can adjust the PWM frequency using ICR1.

Servo Tester Firmware


We previously used a rotary encoder to select menu options and adjust the PWM pulse width. This functionality is now provided by a 10k linear potentiometer (i.e. variable resistor).

The Arduino ATmega328P has a 10-bit ADC which means it can return 2¹⁰ (i.e. 0–1023) values. This is where the 1024 comes from in the equation from the data sheet:

ADC = (Vin * 1024)/Vref

If you want to convert the ADC value to a voltage use the following formula (as the hardware rounds down):

float voltage = ((float) rawADC + 0.5 ) / 1024.0 * Vref;

I have written a separate article on Programming the ATmega328P ADC Registers which goes into a lot more detail if you are interested but for our purposes we only care about the relative position of the potentiometer so we can just analogRead() the input connected to the middle pin of the potentiometer. The other two pins on the pot are connected to GND and 5V so moving the pot shaft should give us a value between 0 and 1023. The schematic looks like Figure 15.

Figure 15. 10K Potentiometer Connected to an UNO

To test this we can use the sketch shown in Figure 16. As expected, rotating the potentiometer shaft caused the analog input read to vary between 0 and 1023.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Set the LCD I2C address to 0x3F with 16 chars and a 2 line display
LiquidCrystal_I2C lcd(0x3F, 16, 2);

//PIN CONNECTIONS

//  POTENTIOMETER INPUT
const byte POT = A0;

//END PIN CONNECTIONS

void setup() {
  lcd.begin();                    // Initialize the LCD
  lcd.backlight();                // Turn on the backlight
}

void loop() {
  // Read A0
  int potValue = analogRead(POT);
  
  lcd.clear();
  lcd.print("Pot Value: ");
  lcd.setCursor(0, 1);
  lcd.print(potValue);
  delay(100);                   //  Reduce LCD flicker
}
Figure 16. Potentiometer Test Sketch.

To convert the potValue to a PWM pulse width, we can use the Arduino map() function. You can define the minimum and maximum pulse widths to whatever values you like. I used 800 and 2200.

pulseWidth = map(potValue, 0, 1023,  MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);

I have updated the UNO code to provide the same functionality as the Mega 2560 version. The full source listing of the Arduino code is available for download from the Reefwing Gist. In the articles following we will design a 3D case and add additional digital protocols.

Thursday, March 19, 2020

Loading 3D Models in SceneKit

COLLADA 3D Models


Figure 1. 3D Model of a Neuron.

The Mac has supported the visualization of COLLADA 3D models since 2009. If you created or downloaded a model this could be viewed in Finder and Preview. With the introduction of the 3D Framework, SceneKit, it is relatively simple to load and display a 3D model in iOS or OSX.

What is COLLADA?



COLLADA (COLLAborative Design Activity) is a file format for 3D applications. COLLADA documents are effectively XML files, usually identified with a .dae (digital asset exchange) filename extension. This file format has its own ISO standard (17506:2012).

So if you are choosing a 3D software application to create digital assets for your iOS app, make sure that it can export to the COLLADA (.dae) file format.

In addition to being able to use .dae files with SceneKit, many game engines also provide native support for COLLADA.

Getting a 3D Model


In order to import and display a 3D model we are going to need to have one! For this tutorial, I downloaded a model from clara.io. Clara.io has free online modelling tools plus free downloads from their library. Make sure that your model is in COLLADA format.

SceneKit


SceneKit is an iOS/OSX framework that allows you to build and manipulate 3D objects within a 3D scene. SceneKit has a high-performance rendering engine and a descriptive API for import, manipulation, and rendering of 3D assets.

SceneKit is based on the concept of nodes. Each 3D object that you want to render using SceneKit is a node. The SCNScene object is a container for the node hierarchy and global properties that together form a displayable 3D scene.

To build a 3D scene, you use nodes to create its structure, and then add lights and a camera to create the visible content. SceneKit implements content as a hierarchical tree structure of nodes, also known as scene graph. The rootNode object in a scene defines the coordinate system of the rendered 3D world.

The Code


Assuming you now have the 3D model which you want to display, drag it into the art.scnassets folder in Xcode. You can then load a 3D model in SceneKit using one line of code.

let scene = SCNScene(named: "art.scnassets/brain-simple.dae")!

If you select the iOS Game template when you create a new project in Xcode, then the remainder of the boiler plate code will do everything else. The only class you need to modify is GameViewController. I have moved things around a bit to make the functionality more obvious. The viewDidLoad function becomes:
var cameraNode: SCNNode!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // create a new scene
        let scene = SCNScene(named: "art.scnassets/brain-simple.dae")!
        
        // create and add a camera to the scene
        cameraNode = setupCamera(for: scene)
        
        // create and add a light to the scene
        setupLighting(for: scene)
     
        // Setup our scene view:
        setupSceneView(with: scene)
    }
The functions called from viewDidLoad are then:
func setupCamera(for scene: SCNScene!) -> SCNNode {
        // Create and add a camera to the scene:
        let cameraNode = SCNNode()
        
        cameraNode.camera = SCNCamera()
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
        scene.rootNode.addChildNode(cameraNode)

        return cameraNode
    }
    
    func setupLighting(for scene: SCNScene!) {
        // Create and add a light to the scene:
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)
        
        // Create and add an ambient light to the scene:
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)
    }
    
    func setupSceneView(with scene: SCNScene!) {
        // retrieve the SCNView
        let sceneView = self.view as! SCNView
        
        // set the scene to the view
        sceneView.scene = scene
        
        // allows the user to manipulate the camera
        sceneView.allowsCameraControl = true
        
        // show statistics such as fps and timing information
        sceneView.showsStatistics = true
        
        // configure the view
        sceneView.backgroundColor = UIColor.black
        
        // add a tap gesture recognizer
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        sceneView.addGestureRecognizer(tapGesture)
    }
That's it! Run the code and you can rotate, tilt and pan your 3D model.

Selecting the Material which Responds to a Tap


Figure 2. Xcode SceneKit Editor


In our setupSceneView() function we assign a tap gesture which turns the first material in our model red whenever we tap the screen. How do you know what the first material is?

Well, Xcode includes a SceneKit Editor. If you click on your 3D Model file (.dae) in the aert.scnassets folder, then it will appear in the SceneKit editor (Figure 2). Click on the image of the model and then click on the material inspector icon (top right of the editor), and you will see a list of materials available in the model. Mine includes four materials labelled default, red, green and blue. So in my case the first material is "default" which is a light grey colour in my model.

If we want one of the other materials to glow red when the screen is tapped then instead of the firstMaterial property, we can use its name. For example if I wanted the green material to glow red, then I would change this line in the handleTap() function as shown:

let material = result.node.geometry!.material(named: "green")!



Monday, March 9, 2020

Multiprotocol ESC/Servo Tester using the Altronics MegaBox


Introduction


My interests include robotics and drones. These inevitably involve using servos and ESC's (Electronic Speed Controllers) which I would like to be able to test before I attach the Flight Controller or microprocessor which is running the show. Hence my interest in developing an ESC/Servo Tester.

Figure 1. HJ Servo Tester

You can buy ESC/Servo Testers (e.g. Figure 1) but they are pretty basic with most only supplying PWM to drive your ESC or Servo. The HJ Servo Consistency Tester shown above allows you to change the PWM pulse width (800 ~ 2200 µs @ 50 Hz) and for digital servos, the frequency [50 Hz (20 ms), 125 Hz (8 ms) or 250 Hz (4 ms)]. Note that pulse width is determined by the product of the duty cycle and frequency (freq = 1 / period).

The connections to the HJ tester are not particularly obvious. The left hand column is for power (S at top if required, 5V and GND) and then the next 4 columns are for the servo or ESC following the same order (signal, 5V and GND at the bottom).

Figure 2. HJ Connected to Saleae Protocol Analyser

I connected the HJ Tester to my Saleae Protocol Analyser, with the pulse width set to 800 µs. The results are shown in Figures 2 and 3. Our measurements show that the HJ Tester is pretty accurate, pulse width is 0.7991 ms and the frequency is 50.09 Hz.

Figure 3. Single pulse for HJ Tester at 800 µs


The select button toggles between three test modes:

  1. Manual - pulse width is changed by the potentiometer on the right from 800 to 2200 µs
  2. Centre - pulse width set to 1.5 ms (i.e. servo centred)
  3. Sweep - pulse width sweeps automatically from 800 to 2200 µs. The potentiometer adjusts sweep speed in this mode.

The pulse width button toggles between the three frequencies 50 Hz, 125 Hz and 250 Hz.

This functionality is ok but for ESC's in particular, most of these are now controlled with some fancy digital protocol, and it would be nice to test them using this. In addition to the different varieties of PWM, ESC's can use oneshot, multishot and Dshot protocols (amongst others). The aim is to develop a Servo/ESC tester with these protocols available. In Part 1 we will put together our own version of the HJ Tester, then in subsequent parts we will add some common digital ESC protocols.

The Arduino MegaBox


K9670 Mega box for Arduino
Figure 4. Altronics MegaBox for Arduino


As I happen to have an Altronics MegaBox (Figure 4) available, I will use this for my initial prototype. It has most of what I need including a LCD, 4 switches and a rotary encoder. There is an Arduino Mega 2560 clone plugged into this box to provide the smarts. The encoder will be used to select modes and adjust the PWM duty cycle / motor speed.

Servo Tester Specification


We will start off with the code for a servo tester as this is the most straight forward. Most servos will move a set number of degrees based on the pulse width of the signal on the control line. Our test servo will be the common SG90.

Figure 5. SG90 Servo


The Jaycar SG90 Data Sheet only helps with the wiring details. We need the pulse width's required to drive the servo. We found a more useful Data Sheet at MicoPik. From this we can determine the following characteristics:
  • Weight: 9 g
  • Dimension: 22.2 x 11.8 x 31 mm approx.
  • Stall torque: 1.8 kgf·cm
  • Operating speed: 0.1 s/60 degree
  • Operating voltage: 4.8 V (~5V)
  • Dead band width: 10 µs
  • Temperature range: 0 ºC – 55 ºC 
  • Running current with 5V supply (no mechanical load) 220 ±50mA
  • Stall current with 5V supply (horn locked)                 650 ±80mA
  • Idle current with 5V supply                                         6 ±10mA

The SG90 expects a frequency of 50 Hz on the control line and the position it moves to depends on the pulse width of the signal. the servo will move to:
  • 0 degrees - the centre position with a 1.5 ms pulse;
  • + 90 degrees with a ~2 ms pulse; and
  • - 90 degrees with a  ~1 ms pulse.

Whether + or - 90 degrees is left or right will depend on how you mounted the servo. There is no point changing the frequency of the control pulses, unless the servo you are testing expects something different to 50 Hz. The servo moves at a set speed (0.1 s/60 degrees for the SG90), regardless of how quickly you change the pulses.

Figure 6. SG90 Wiring Details

For this version of the code, we will use the rotary encoder to adjust the pulse width and the four buttons will funcion as follows:

Button 1 - min pulse width (default 1000 µs @ 50 Hz)
Button 2 - max pulse width (default 2000 µs @ 50 Hz)
Button 3 - start auto sweep
Button 4 - stop auto sweep and centre (1500 µs @ 50 Hz)

Servo Tester PWM Approaches


The Arduino Mega 2560 has 15 pins which can be used for PWM output. The simplest way to output PWM on these pins is to use the analogWrite(pin, dutyCycle) command. The PWM default frequency is 490 Hz for all pins, with the exception of pin 13 and 4, whose frequency is 980 Hz.

We tested this using our protocol analyser and found that a 50% duty cycle [analogWrite(11, 127)] provides PWM with a 1.016 ms pulse width and a frequency of 490 Hz.

Figure 7. Default analogWrite PWM

Another option is to use the Arduino Servo library. In this library you select a value between 0 and 180 degrees to set the position of the servo. So at 90 degrees (centred) you would expect a pulse width of 1500 µs. Figure 8 shows what we measured using the Servo libray with the servo set to 90 degrees, a 1.475 ms pulse width and a frequency of 49.96 Hz. So again pretty close to what is expected.

Figure 8. Servo Library PWM set to 90 degrees


Alternatively, you can access the Atmel registers directly for finer control, this allows you to change the type, range and frequency of the pulse width modulation (PWM). It isn't possible to change the frequency using either of the two previous methods.

Changing Pulse Width / Duty Cycle Using Registers


We will use pin D11 (ATmega2560 physical pin 24, Port B bit 5) as this will work for an UNO or MEGA and is our next free pin. To set D11 as an output we can use:

DDRB |= 1 << DDB5;

This line of code sets the 5th bit of the DDRB register to 1 and is equivalent to pinMode(11, OUTPUT). DDRB is the data direction register for Port B.

Most of the register based PWM examples available are for the ATmega328P and our version of the MEGA uses the ATmega2560, so you need to make sure that you are using the correct registers and timers for your microprocessor.

The ATmega2560 has 15 pins which can be used for PWM output. Instead of using analogWrite(), we can manipulate the Output Compare Registers (OCR) directly to adjust the duty cycle. The OCR is a 16 bit register. So for example, to get a 50% duty cycle, you set the OCR to half of the top counter value - referred to as "TOP" in the datasheet. The TOP value can be assigned to be one of the fixed values: 0xFFFF, 0x00FF, 0x01FF, or 0x03FF, or to the value stored in the OCRnA or ICRn Register. The assignment is dependent on the mode of operation (Figure 10).

In the data sheet the Output Compare Registers are labelled OCRxA, OCRxB or OCRxC (where x is the timer number 0..5). The association between OCR's and pins for the 2560 is shown in Figure 9.

Pin Register (2560)                              Pin     Register (328P)
2 OCR3B
3 OCR3C                                            3         OCR2B
4 OCR4C
5 OCR3A                                            5         OCR0B
6 OCR4A                                            6         OCR0A
7 OCR4B
8 OCR4C
9 OCR2B                                             9        OCR1A
10 OCR2A                                             10      OCR1B
11 OCR1A                                             11      OCR2A
12 OCR1B
13 OCR0A
44 OCR5C
45 OCR5B
46 OCR5A

Figure 9. Arduino pins and associated Output Compare Register.

To adjust the PWM duty cycle, we can modify the appropriate Output Compare Register. For D11on the ATmega2560 the register is OCR1A.

OCR1A = 32767;

What happens next will be determined by the current mode of operation, which is set by a combination of the Waveform Generation mode and Compare Output mode bits. This is defined in Table 17-2 of the ATmega2560 data sheet (Figure 10).

The main PWM modes are "Fast PWM" and "Phase-correct PWM".

Assuming we haven't changed anything and we are in "Normal" mode, then TOP = 0xFFFF = 65535, and setting 0CR1A to 32767, should deliver PWM with a 50% duty cycle. Note that the data sheet states that "using the Output Compare to generate waveforms in Normal mode is not recommended, since this will occupy too much of the CPU time."

Testing this shows no PWM output. This is because you need to both enable the pin for output and set the PWM mode (i.e. set the COM bits) on that pin in order to get a result. So let's do that using Fast PWM.

Figure 10. ATmega2560 Modes of Operation

Fast PWM using Registers


In Fast PWM mode, the timer repeatedly counts from 0 to 255. The output turns on when the timer is at 0, and turns off when the timer matches the output compare register. The higher the value in the output compare register, the higher the duty cycle. To set the mode we want, we use the Timer / Counter Control Registers, TCCRnA and TCCRnB (where n = the timer number).

For D11 we need Timer 1 (this is explained in the next section). So we need to set the relevant bits in TCCR1A and TCCR1B. Note that this will effect pin D12 as well.


Figure 11. Timer 1 Counter Control Register Bits


We will use mode 5 (Fast PWM, 8 bit) which has a TOP = 0x00FF = 255. To get mode 5, we need to set the following bits within the TCCR's:

WGM10:3 = 0101

We will also set the Clock Select (CS) Bits to set the prescaler to 1. The appropriate value for this is:

CS10:2 = 001

The PWM frequency for the output can be calculated by the following equation (from the data sheet):

Where the N variable represents the prescaler divider (1, 8, 64, 256, or 1024).

For our settings: fPWM = 16 Mhz / (1 * (1 + 255) = 16000000 / 256 = 62.5 kHz.

The various Clock Select modes are shown in Table 17-6 of the data sheet (Figure 12).

Figure 12. Clock Select Bit Options

The final part of the puzzle is setting the Compare Output Mode for the relevant type of PWM. We need Table 17-4 from the data sheet for this (Figure 13). We want non-inverting mode and will set:

COM1A0:1 = 01

Figure 13. Compare Output Mode for Fast PWM


The register code to achieve this is as follows.
DDRB |= 1 << DDB5;                 // Set D11 as an output pin
TCCR1A = 0;                              // Clear TCCR A & B bits
TCCR1B = 0;
TCCR1A = (1 << COM1A1) | (1 << WGM10);
TCCR1B = (1 << WGM12)  | (1 << CS10); 
OCR1A = 127;                             // 50% Duty Cycle (i.e. 127/255)
Figure 14. Fast PWM using Registers

In the code above (Figure 14) you will notice that we clear the bits in the Timer / Counter Control Registers (TCCR1A and TCCR1B). This is required because if you are using the Arduino IDE, there is a hidden main function which calls setup() and loop(). More importantly from our perspective is the init() function which it calls first. The init() is defined in hardware/arduino/avr/cores/arduino/wiring.c. If you have a look at this file, you will see that amongst other things it sets up the TCCR's for Timers 0, 1 and 2. Thus unless we clear these registers we may get an unexpected result when we assign our bits.
int main(void)
{
    init();
#if defined(USBCON)
    USBDevice.attach();
#endif
    setup();
    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
    }
    return 0;
}
Figure 15. Arduino Main() Function

Measuring the results on our protocol analyser provides the expected outcome (Figure 16), 50% duty cycle, pulse width 8 µs and frequency 62.5 kHz. The pulse width is a product of the duty cycle and the frequency (as period is the inverse of frequency).

Figure 16. Fast PWM Output on D11.

Changing PWM Frequency using Registers


The ATmega328P has three timers known as Timer 0, Timer 1, and Timer 2. Timer 0 and Timer 2 are 8 bit timers, while Timer 1 is a 16 bit timer. Each timer has two output compare registers that control the PWM width for the timer's two outputs: when the timer reaches the compare register value, the corresponding output is toggled.

For comparison, the ATmega2560 has 2 x 8 bit and 4 x 16 bit timers. Although the registers and timers may change, the process of setting up PWM is similar for both the 328P and the 2560.

The difference between 8 bit and 16 bit timers is the resolution. 8 bits can represent 256 values (2^8) whereas 16 bits can represent 65536 values (2^16). The timer situation for the UNO's and MEGA's is summarised in Figure 17. These timer setups are done in the init() function mentioned earlier (Figure 15).
Timer 0:
Timer0 is a 8 bit timer.
For Arduino's, Timer0 is used for timer functions, delay(), millis() and micros().
Timer 1:
Timer1 is a 16 bit timer.
For Arduino's, the Servo library uses Timer1 in Uno's and Timer5 on Mega's.
Timer 2:
Timer2 is an 8 bit timer like Timer0.
In the Arduino work the tone() function uses Timer2.
Timer 3, Timer 4, and Timer 5:  
Timers 3,4, & 5 are only available on Mega's. These timers are all 16 bit.
Figure 17. Arduino UNO and Mega Timer Summary

Each of the timers has a prescaler that generates the timer clock by dividing the system clock by a prescale factor such as 1, 8, 64, 256, or 1024. The UNO and the MEGA both have a system clock speeds of 16MHz and the timer clock frequency will be the system clock frequency divided by the prescale factor.

The ATmega328P has 6 PWM capable outputs and three timers. The relation between timers and PWM outputs is:

  • Timer 0 controls Pins 5 and 6;
  • Timer 1 controls Pins 9 and 10; and
  • Timer 2 controls Pins 11 and 3.

Similarly for the ATmega2560:

  • Timer 0 controls Pins 4 and 13;
  • Timer 1 controls Pins 11 and 12;
  • Timer 2 controls Pins 9 and 10;
  • Timer 3 controls Pin 2, 3 and 5;
  • Timer 4 controls Pin 6, 7 and 8; and
  • Timer 5 controls Pin 46, 45 and 44.

As the two processors use different timers for different pins, our register code will not be the same for the UNO and MEGA. It would have been nice if they could have kept the first three timer pin allocations the same between chips. The allocation above is how we know that we need to use Timer 1 for pin D11 on the ATmega2560.

    // timers 1 and 2 are used for phase-correct hardware pwm
    // this is better for motors as it ensures an even waveform
    // note, however, that fast pwm mode can achieve a frequency of up
    // 8 MHz (with a 16 MHz clock) at 50% duty cycle
Figure 18. Comment from Arduino init()

Refering to the comment in the Arduino init() function (Figure 18) we note that phase-correct hardware pwm is better for motors (this is also mentioned in the data sheet), so lets use that. The dual-slope phase-correct operation gives a lower maximum operation frequency compared to the single-slope modes, but it is high enough for our purposes.

The default PWM mode set by init() for Timer 1 is mode 1 and we want mode 8 (PWM, Phase and Frequency Correct) - see Figure 10. In mode 8 we can adjust the PWM frequency using register ICR1. To get mode 8 we just set the WGM13 bit. We will use a non-inverting output as before so set the COM1A1 bit in TCCR1A.

For reasons that will become apparent below, we set the prescaler (N) to 8, to do this, the CS11 bit in TCCR1B needs to be set.

The trusty data sheet provides us with the frequency formula for phase and frequency correct PWM.


Where the N variable again represents the prescaler divider (1, 8, 64, 256, or 1024). For our settings, the calculation is:

fPWM = 16 MHz / (2 * 8 * ICR1) = 16000000 / (2 * 8 * 20000) = 50 Hz

From this formula, it is apparent that we can adjust the PWM frequency by adjusting the value in register ICR1. It also explains why we use a prescaler of 8. The system clock is 16 MHz and to get the PWM frequency we divide by 2N. If we make 2N = 16 then the relationship between fPWM and ICR1 (i.e. TOP) is straight forward. Rearranging the formula:

ICR1 = 1MHz / fPWM

For a fPWM of 50 Hz, ICR1 = 1,000,000 / 50 = 20,000.
DDRB |= 1 << DDB5;                 // Set D11 as an output pin
TCCR1A = 0;                              //  Clear TCCR A & B bits
TCCR1B = 0;
TCCR1A = (1 << COM1A1);                  //  Non-inverting output
TCCR1B = (1 << WGM13)  | (1 << CS11);    //  Phase & Freq correct PWM, prescaler N = 8
ICR1 = 20000;                            //  Freq = 50 Hz
OCR1A = 1500;                            //  pulse width = 1500 us;
Figure 19. Register Code Phase & Frequency Correct PWM at 50 Hz.

The results of the code in Figure 19 is shown in Figure 20. So for our tester we can adjust the pulse width using the value of register OCR1A and we can adjust the PWM frequency using ICR1A.

Figure 20. Phase & Frequency Correct PWM waveform at 50 Hz.

We implemented this functionality on the Mega and were able to select the required frequency and pulse width using the rotary encoder (Figures 21 and 22).

Figure 21. PWM 50 Hz and Pulse Width of 1755 µs 


Figure 22. Measured PWM waveform (setting 1755 µs @ 50 Hz)


The code for this partial implementation is available from the Reefwing Gist repository. In Part 2 of this series we will develop a custom Arduino shield to implement the servo testing functionality. In Part 3 we will add some common ESC digital protocols (e.g. oneshot).

Tuesday, September 24, 2019

Programming the ATmega328P ADC Registers

Programming the ATmega328P Registers


This is the second article that I have written regarding programming the ATmega328P registers. In the first article we looked at how we could blink an LED using the hardware registers and interrupts. Now we will examine the ADC and how we can use it to measure voltage on one of the Analogue Inputs (AI's) of the Arduino UNO. 

The Arduino IDE libraries make it trivial to read the voltage on an AI: 

int value = analogRead(A0); 

Using default settings, a return value of 0 would represent 0V, and a return value of 1023 (the maximum) would represent approximately 5V. By understanding what is going on behind the scenes you can go beyond this basic implementation and do things like:
  • Change the conversion speed via the pre-scaler;
  • Use different reference voltages;
  • Make ADC reading non-blocking;
  • Use an interrupt which returns when the value is read;
  • Read other things such as the internal chip temperature, GND and VCC;


ATmega328P Analogue to Digital Converter (ADC)


Figure 1. ATmega328P ADC Simplified Block Diagram.

The Arduino ATmega328P has a 10-bit ADC which means it can return 2^10 (i.e. 0 – 1023) values. This is where the 1024 comes from in the equation from the data sheet:

ADC =  (Vin*1024)/Vref

If you want to convert the ADC value to a voltage use the following formula (as the hardware rounds down):

float voltage = ((float) rawADC  + 0.5 ) / 1024.0 * Vref;

Select the ADC Voltage Range


In order to convert an analog voltage to a digital value on any ADC, the converter has to be provided with the range of voltages. The lower limit is always GND on the Arduino (i.e. 0V) but you can select one of three sources to serve as the high reference:
  • AREF - This is a separate pin on the microcontroller that can be used to provide any high reference voltage you wish to use as long as it’s in the range of 1.0V to VCC. On the Uno it’s wired to one of the black headers. 
  • AVCC - This pin on the microcontroller provides power to the ADC circuitry on the chip. On the Uno it is connected to VCC. This is the simplest option to use provided AVCC is connected to VCC. 
  • 1.1V - The microcontroller has an internal 1.1V reference voltage that can be used
Regardless of the source selected, the data sheet advises putting a capacitor between AREF and ground to smooth out the voltage on that pin. Table 24-3 (data sheet) is used to select which voltage reference you want to use. Note that if you use either of the internal voltages references (AVCC or 1.1V) then that voltage is connected to the AREF pin internally. In this situation, do not connect any other voltage sources to that pin or it will be shorted to the internal reference (and probably let out the magic smoke).


I would default to AVCC unless you had a reason to do otherwise. This will give you a nominal 0 - 5V range.

Note that all 8 AI’s on the UNO are connected to the same ADC (see Figure 1). So you can only sample one input at a time. That’s what table 24-4 (data sheet) and the ADMUX register is used for.


ADC Control Registers (ADCSRA and ADCSRB)


The ADC module of the ATmega328P has two control and status registers, ADCSRA and ADCSRB. For basic ADC operations only the bits in the ADMUX and ADCSRA register have to be modified.

Figure 2. ADMUX Register

Assuming we are using AVCC, from Table 24-3 we need REFS1 = 0 and REFS0 = 1. To get a 10 bit result, ADLAR = 1, and if we are using A0 as our input then from Table 24-4, MUX3-0 = 0000. Thus

ADMUX = 0b01000000

Figure 3. ADCSRA Register

The things we need to worry about for ADCSRA are:

Bit 7 - The ADEN bit enables the ADC module. Must be set to 1 to do any ADC operations.

Bit 6 - Setting the ADSC bit to a 1 initiates a single conversion. This bit will remain a 1 until the conversion is complete. If your program using the polling method to determine when the conversion is compete, it can test the state of this bit to determine when it can read the result of the conversion from the data registers. As long as this bit is a one, the data registers do not yet contain a valid result.

Bit 5 - ADATE: ADC Auto Trigger Enable. When this bit is written to one, Auto Triggering of the ADC is enabled. The ADC will start a conversion on a positive edge of the selected trigger signal. The trigger source is selected by setting the ADC Trigger Select bits, ADTS in ADCSRB. We don't want auto triggering so select 0 for this bit.

Bit 4 - ADIF: ADC Interrupt Flag. This bit is set when an ADC conversion completes and the Data Registers are updated. The ADC Conversion Complete Interrupt is executed if the ADIE bit and the I-bit in SREG are set. ADIF is cleared by hardware when executing the corresponding interrupt handling vector. Alternatively, ADIF is cleared by writing a logical one to the flag. Beware that if doing a Read-Modify-Write on ADCSRA, a pending interrupt can be disabled. This also
applies if the SBI and CBI instructions are used. Set ADIF to 0.

Bit 3 - Setting the ADIE bit to a 1 enables interrupts. An interrupt will be generated on the completion of a conversion. The interrupt vector name is “ADC_vect”.

Bits 2:0 - The ADPS2, ADPS1 and ADPS0 bits selects the pre-scaler divisor value. Those available are:

Figure 4. ADC Pre-Scaler Table

The ADC circuitry needs to have a clock signal provided to it in the range of 50kHz to 200kHz. The ATmega328P clock is too fast (16MHz on the Uno) so the chip includes an adjustable pre-scaler to divide the processor clock down to something usable. The processor clock speed is divided by the pre-scaler value to give an ADC clock speed. Using a lower pre-scaler will make the conversion faster but at the cost of accuracy.

The lowest usable value is a pre-scaler of 16 for the UNO. The analogRead library uses a pre-scaler of 128 in order to get maximum resolution. Unless you understand the consequences, stick with 128.

The value for ADCSRA is going to depend on what conversion approach you use (see below).

 ADC Data Register (ADCH and ADCL - high and low bytes)


The results of the conversion are stored in the two bytes of the Data Register. The most significant bits of the result are stored in ADCH and the least significant bits are in ADCL. For 10-bit conversion, the ADLAR bit in the ADMUX register should be zero.

Figure 5. ADC Data Registers

When using the 10-bit results (ADLAR=0), the results can be read into your program with:

int value = ADC; 

The ADC measures voltage by charging an internal 14 pF capacitor and then measures that voltage with successive approximations. The ADC takes 13 ADC clock cycles to perform a conversion, except the first time the ADC is enabled, at which point it takes 25 ADC cycles, due to the initialisation overhead. The ADC Sample and Hold takes approximately 12μs and the entire conversion process can take up to 260 μs (depending on the pre-scaler selected). So there are at least 3 ways you can approach this:
  1. Put a long enough delay in your while loop so you know the conversion is done. This is the least elegant method!
  2. Set the ADSC bit in ADCSRA to a one. This starts the first conversion. Then poll the ADSC bit in ADCSRA until it becomes zero and read the ADC value.
  3. Use the ADC interrupt and handle the reading in the associated ISR - ADC_vect()
I suggest using approaches 2 or 3.

Don’t forget that you need to set the ADSC bit in ADCSRA to one, every time you want to do a conversion.

Sample ADC Register Polling Code


To cement the information above, I will provide some sample code to read the ADC using registers. The first example uses the polling technique (approach 2 above).


A few notes about this code:
  • We need to use the standard Arduino loop() to allow serial events to be handled (i.e. the printing out of the result to the serial monitor).
  • C5:: The delay(500) is just there to enable us to see the printed result. It is not functionally required, it is the job of the polling (i.e. checking the ADSC bit) to ensure the result is ready.
  • With nothing connected to A0, the pin is floating and I was reading values of around 650 (but this could be anything due to electrical noise from the environment, or capacitively coupling with a nearby pin.). Connect A0 to GND and you should see 0, while connecting it to 5V should show 1023.


Sample ADC Register Interrupt Code


For our final example we will measure the ADC using interrupts.


Regarding the interrupt version of the code:
  • We have to enable interrupts by setting the ADIE bit of the ADCSRA register.
  • The sei() command is optional, setting ADIE is sufficient to enable this interrupt.
  • Note we try to do as little as possible in the ISR.
  • It is a little bit more complicated than the polling version, but not much.
  • As with the polling example if nothing is connected to A0, then the pin is floating and you will get a random result. Connect A0 to GND and you should see 0, while connecting it to 5V should show 1023.