Skip to content

Using Mapyx Quo to Create Virtual Field-trips with Google Earth

This post describes an approach I have adopted to create virtual field trips using Mapyx Quo as a starting point to produce interactive annotated virtual field-trips. The initial impetus was to look at the geology around Millers Dale, specifically at the outcrops of the Millers Dale Lava Beds. Details of this trip will follow in the next post.

Quo is reasonably good but the KML exports it produces loose some of the folder-like structure that can be used inside Quo. It alsi has the very annoying feature of inserting timings into all “tracks”, no matter whether theses are logged from a GPS or inserted using a mouse or tablet inside Quo. Tracks are the best way to define areas and linear features for visualisation in Google Earth but timings and associated waypoints just make for clutter. Furthermore, the colours set for tracks and waypoints in Quo is lost on export.

My solution was to write some software (for MS Windows) to:

  1. tidy up the KML export, removing unwanted aspects and putting back some folder structure
  2. generate a javascript configuration file to drive a web page that uses the Google Earth Plugin (API)

The resulting KML may be used with the desktop Google Earth or from a web page (with #2 as the intended case).

The source code is available on GitHub, with an MIT licence. At some point I will produce an installer (sooner if someone asks). The source code should be consulted for specifics that are omitted from this post; there are sufficient comments for non-readers of C#.Net.

There are a number of assumptions about how Quo will be used, and how the software (Quo2GE) will be used, and this post is really an aide memoir for myself.

Organising Waypoints and Tracks in Quo

In Quo, create three groups (or two groups and make the first two be layers within the same group), and as content as follows:

  1. A group for points of interest.
    • Within this, create waypoint sets. For each set, specify a different “Point Name Pattern” consisting of a number of alpha-numeric characters followed by “-%N”. The characters comprise a prefix that will be used in Quo2GE to group the waypoints (Quo destroys its own group/layer/waypoint-set structure on export to KML). Note that the “-” sign MUST be present.
    • Any routes will be ignored by Quo2GE.
    • Quo2GE assumes three types of waypoint sets, which can be assigned ad hoc: sites to visit, landmarks, and places that make good viewpoints. These are treated slightly differently by Quo2GE, see below, with implications: for landmarks, add the name of the landmark to the “Note” field for each waypoint; for viewpoints, a place to look at from the viewpoint may be specified by adding to the “Note” field a “@” character followed by the name of any other point of interest, followed by a space character if it is not at the end of the “Note”.
  2. A group containing the walking/driving routes and maybe waypoints at key turns.
    • The waypoints will be ignored by Quo2GE but may be useful if a GPS is used in the field.
    • Several routes may be added, for example to break an over-long route into more walkable units or to separate walking and driving routes. Distinguish the routes by prefix, changing the default “Track 1” etc to something more meaningful. Note: for routes, use a space character (rather than “-” as before) to separate the prefix from the index number. Use different prefixes to allow different groups of 1 or more tracks to be separated in Google Earth. Otherwise use the same prefix.
  3. A group containing geological/geographical features designated using the Quo “track” tool.
    • Use layers to organise the features by type, e.g. a layer for geological faults.
    • Within each layer name the tracks with a prefix followed by a space and then an index number. e.g. “Fault 1”, “Fault 2” etc. The prefix is used by Quo2GE to group the features and to allow different colours to be used for each group.
    • Waypoints will be ignored by Quo2GE.
Example of Quo structure.

Example of Quo structure.

Export each of these as a separate KML file (right-click on the group or layer and “Export to…” (file), selecting Google Earth (kml) as the file type).

Quo2GE should work if any of the above groups/layers are omitted.

Using Quo2GE

CaptureQuo2GE

Use “Set Output Folder” to locate the KML files exported from Quo. The same folder will be used for the output of Quo2GE.

Load each of the exported KML files using the appropriate tab. The list on the left (with check-boxes) should enumerate all of the prefixes used for waypoint or track names in Quo. Use the checkboxes to de-select any that you do not wish to see in Google Earth.

For each prefix, the colour to be used in Google Earth may be changed, as may the title (which is initially set to the prefix). The title will be shown in the web page to allow groups of waypoints or tracks to be shown or hidden. They also appear as folder names in Google Earth Desktop.

For points of interest, there are three types that can be assigned to each prefix-set. The points are handled differently in Quo2GE and in the web page:

  1. Points of type “Site” are intended for places a person would visit. In the virtual tour, users can jump to have an aerial view of any of these. The points are shown in Google Earth with the waypoint name, e.g. WPT-002.
  2. Points of type “Landmark” are intended as key landmarks (!). They are shown in Google Earth with the waypoint Note (e.g. “Hammerton Hill”).
  3. Points of type “Viewpoint” are intended as places a person would view their surrounding from (in real life and in the virtual tour). In the virtual tour, users can choose to go to these places; the Google Earth view changes to a near-ground-level view looking north, or towards any point specified in the Quo waypoint Note using “@”.

Set the Main Title and click “Export”.

Using the Output

Several files will be created in the chosen output folder. Those starting “clean” are tidied-up versions of the KML files exported from Quo but without any organisation into folders, application of styles according to Quo2GE. The file allSets.kml has been produced from the cleaned KML and does contain the folders and styling. Use this with  Google Earth desktop.

Use on the web requires use of allSets.kml, gm.html and config.js. hg.html reads config.js to locate the kml file to be used and to set the controls that appear in the web page to allow the view to be manipulated.

Steps:

  • edit config.js so that the kmlHref variable contains the full URL to the ultimate location of the allSets.kml file.NOTE: the kml must be on a web server for the Google Earth plugin to be able to use it, although you can run gm.html from your local machine.
  • add text, change styling etc for gm.html according to your taste.
  • copy all three files to the web space.
  • enter the URL for the gm.html file in your web browser…. it should all appear!

As an alternative, which may be useful if you have several virtual field trips is to place the gm.html file in one folder and to create sub-folders for each trip, which contain the kml and js files. In this case, tell gm.html the name of the sub-folder (e.g. “trip1”) by adding it to the end of the URL, after a “?” (e.g. http://www.test.net/trips/gm.html?trip1).

Change Wheel Calculator for Warco 918 Lathe

Here is a spreadsheet to calculate all the thread pitches possible on a Warco 918 lathe using the change wheel set provided with the machine :  XLSX (newer versions of Excel) and XLS (Excel 97 and 2003, also works in LibreOffice/OpenOffice).

The cells are all locked except D4, which should be set to “y” or “n” depending on whether the 120/127 tooth gearing will be in use (see the plate attached to the lathe). Note that there are three worksheets for mm/rev, inch/rev and tpi.

Use should be self-evident, but in case not, here is an example.

Looking at the “mm per rev” sheet:

  • Put “y” in cell D4 because that gives results in nice round numbers
  • Assume you want 1.5 mm/rev
  • See this appears in four cells: I13, I14, F19 and C24
  • Choose F16 (on a whim, or maybe you already had a 36 tooth wheel in position b)
  • Change-wheels (where a and b are as on the plate attached to the lathe) to use are: a=45 tooth and b=36 tooth
  • The gearbox should be set to no. 4
  • Note that the Warco lathe-plate gives the C24 configuration.

If unsure, check you understand it by choosing a few of the pitches given on the lathe-plate and confirming that the change-wheels and gearbox settings you determine from the spreadsheet match the “right answer”.

Warning: I might have made a mistake so always try out on a piece of scrap and measure the result.

 

Making a Trepanning Tool – Notes and Plans

I’m planning on cutting some gear wheels and, having struggled with using a hole-saw to cut blanks, decided a trepanning tool was probably the answer.

The tool is only meant for cutting 1/8″ thick stock. Tested on brass and aluminium (may not be up to the job with steel). Use low speeds and if you don’t know what you are doing, don’t do it!

The Finished Trepanning Tool

The dark colour of the main body is due to a (failed) attempt to case harden the pilot; it is just uncleaned scale from from heating. I actually found the setup to be sufficiently rigid that the pilot didn’t take any load with 1/8″ brass and the narrow cutting tool shown below in the bottom right of the picture.

 

Trepanning Tool (click for larger size)

Trepanning Tool (click for larger size)

The Plans

These are not proper dimensioned drawings in an engineering sense, and were produced as part of the design process, but they should be sufficient. Stock used was steel 1″ dia bar and 3/8″ square.

Trepanning Tool Assembly (click to open full-size image)

Trepanning Tool Assembly (click to open full-size image)

Also available as a DXF File.

Brief Construction Notes

Arbor

Cut 1″ dia stock to slightly over length

3 jaw chuck :-

  • face off (light cuts), centre drill and support with tailstock centre
  • turn down 1/2″ shank
  • brighten up about 1/4″ of 1″ dia body (used later for concentricity setting with DTI)
  • cross-drill, widening progressively to 7/16″ (ideally finish to size with reamer)
  • drill and tap M5 (and shorten M5 cap screw to match)

4-jaw chuck :-

  • hold using 1/2″ shank, adjust to near zero runout with DTI
  • turn pilot (should be concentric with shank)

Cutter Arm

3/8″ square stock

4-jaw chuck :-

  • face off both ends (low overhang from chuck)
  • centre-drill second end, loosen 2 adjacent jaws, withdraw from chuck and support on centre, retightening the jaws to same setting
  • turn to 7/16″ dia to fit arbor

Pillar drill :-

  • drill and ream 3/16″ for cutting tool
  • drill 1/8″ for end of slot
  • drill for clamp bolt (M4 tapping drill size)
  • cut 1/32″ slot
  • drill clear for clamp bolt (up to slot)
  • thread M4 for clamp bolt

The Cutting Tool

Grind a 3/32″ or 1/16″ wide cutting point. Give it plenty of side clearance because the tool will be making an arc. Tighter arcs => more side clearance will be needed at the expense of a weaker tool.

Since round tool steel is used, the cutter can be rotated to adjust the in-use clearance a little to compensate for slightly uneven grinding, and changes in cutting radius, but I suppose the cutting face should be fairly close to lying on a radial line.

AVR Dragon Problem: stopped working, not recognised by USB

OK… so my Dragon just stopped working. Plug it in, the status LED just flashes amber and Windows (XP) does not detect it. Re-install the drivers – no, that isn’t it. (BTW, the instructions in the helpfile that comes with Atmel Studio 6 are wrong). Try another PC or using a motherboard USB socket rather than the hub – no, wrong again. Try a different USB cable – nope.

The answer, it seems is to use a USB hub but to do so with the hub’s separate power adapter. After that, I was able to dispense with the power adapter and the Dragon kept working, even after unplugging/re-connecting.

Wierd! There are a number of suggestions on the web that the Dragon draws a heavy current on startup,  so I guess it got into “a bit of a state.”

 

 

Debugging Arduino using debugWire (+ Atmel Studio and an AVR Dragon)

This post is motivated by the lack of information about using debugWire with an Arduino (or similar) on the web. There are quite a few brief comments on the forums and people asking how to do it but nothing really good, with the possible exception of a post by Steve Cogswell which refers to an old version of Arduino and an old version of Atmel Studio.

This really notes for myself for next time, when I’m bound to have forgotten the recipe, but I hope someone else will find it useful. The previous post describes how to get started with using the Atmel Studio simulator, which is a good introduction to the activities required during on-chip debugging (OCD) without needing to fiddle quite so much and without wondering whether the hardware is playing nicely.

What Can You Do with debugWire?

Inspect memory, single-step through code, set break-points (at which execution will stop), inspect registers and input/output port values etc. i.e. as for the simulator, but for the real hardware.

Pre-requisites

This assumes you have:

  • an Arduino Uno
  • Atmel Studio 6 (Windows only but you could try using it from inside VirtualBox if you are a linux or Mac user – I’ve had a good experience of VirtualBox on Ubuntu, although I’ve not tried Atmel Studio )
  • an AVR Dragon (a fairly cheap USB programmer/on-chip-debugger) and a 6-way cable to connect it.

I also recommend you get the ATMega328 data sheet.

Preparing the Hardware

The ATMega328 data sheet says:

• Capacitors connected to the RESET pin must be disconnected when using debugWire.
• All external reset sources must be disconnected.

If you check the Arduino Schematic, you will see that there IS a 100nF capacitor connected to RESET. This is used to cause a reset to be triggered when communication is initiated via the USB. This is normally useful since a reset is needed to prompt the bootloader on the Arduino to receive compiled code in the upload process.

In the older Arduinos you had to remove the capacitor (as per Steve Cogswell’s post) but on the Uno there is a bit of circuit board track specially prepared for cutting and re-soldering later if need be. If you leave it cut then it will be necessary to manually press the reset button just as the Arduino IDE (or avrdude) tries to start an upload.

RESET EN marks the place you need to cut (between the two solder pads)

RESET EN marks the place you need to cut (between the two solder pads)

Preparing the Software

This is basically just a case of compiling the code for the Arduino in Atmel Studio as I’ve previously described. It is NOT necessary to upload the compiled code using avrdude because Atmel Studio will do this for us via debugWire.

Preparing the Hardware

Connect the Dragon ISP header to the Arduino ISP header. Note that pin1 connects to pin1 on the other device, i.e. MISO connects to MISO. Both Dragon and Ardiuno are separately powered through their USB connectors and the Dragon senses the Arduino’s power supply voltage through the 6-way connector.

Select the Dragon as the tool to use

Select the Dragon as the tool to use

Open the device programmer using Tools|Device Programming menu or: program button.

Make sure Tool, Device and Interface are correct then click "Apply" to connect.

Make sure Tool, Device and Interface are correct then click “Apply”.

You can also “Read” the device signature to confirm that the physical device matches the one chosen in Atmel Studio. This will happen automatically later on anyway and Atmel Studio will block you if it isn’t right.

Warning… and Being Prepared

As described in Steve Cogswell’s post, it is possible to make a non-permanent mess of the ATMega328. It may be necessary to use the “HV” programming on the AVR Dragon if the ATMega gets stuck in debugWire mode and it may be necessary to re-install the boot-loader. These are not difficult if you have an AVR Dragon (or presumably other programmers too).

It can be useful if you need to recover from mishap, and is generally a good plan, I think, to get the fuse settings BEFORE messing about with debugWire and before fiddling with them. All you need to convert a factory fresh ATMega328 into something usable on an Arduino board is to set the fuses correctly and to program the boot loader.

Use "Copy to clipboard" and save the fuse settings somewhere save. Do the same for the Lock bits.

Use “Copy to clipboard” and save the fuse settings somewhere save. Do the same for the Lock bits.

The debugWire Cycle

This is the sequence of operations I have found works reliably. Try this with “Blink” (if you’ve carried on from the simulation example, remember to replace the delay() lines). NB: step 7 is important to get your chip back into its normal state.

  1. Program the DWEN fuse: make sure the box is ticked then click “Program”.
  2. Cycle the power on the Arduino by temporarily disconnecting the USB connector. At this point, you can probably no longer use the ISP interface.
  3. Set debugWire as the interface (see screenshot below)
  4. Click “Start Debugging and Break” or hit Alt+F5 or set a break-point then “Start Debugging” (F5). Note: this uploads the program too.
  5. Set break-points, single-step etc. Use the “IO View” to manually change the value of output pins on the Arduino, yes, the real ones!
  6. Stop debugging (Ctrl+Shift+F5), change the code and return to step 4.
  7. Menu “Debug|Disable debugWIRE and close”. This menu item is only accessible when debugging so you may need to use F5 again. The DWEN flag will now be reset and you will be able to use the ISP interface again.
Set the interface to debugWIRE

Set the interface to debugWIRE (step 3)

Now compare the fuses and lock-bits to the previously saved settings. They should be identical. debugWIRE should also have preserved the bootloader so it should be possible to go back to the Arduino IDE; you only need to remember to press the Arduino reset button just as the IDE announces it is uploading in order that the bootloader executes.

Simulating Arduino Code using Atmel Studio 6

The next post will look at on-chip-debugging (OCD) but it is worth using the Atmel Studio Simulator first. Using the simulator isn’t practical unless the input-output is pretty simple because it doesn’t simulate attached devices or real-time events. But even so, you can fiddle with input/output at the bit level and follow the way variables in the code change as it executes. This is MUCH smarter than using Serial.println() because once you’ve mastered Atmel Studio, you can save plenty of time. And poking about with the simulator helps to make sense of what is going on inside code such as “int led=13” or “digitalWrite(12, HIGH)”. All clean fun, and you don’t need any hardware other than a PC.

This starts off as just a case of compiling the code in Atmel Studio so you can set break-points or step line-by-line through the code to see what happens. See an earlier post of mine for how to get an Arduno sketch to become an Atmel Studio 6 project or just download the Atmel Studio project to match the following.

device

First make sure you have selected the right target device

choose_simulator

Then choose the simulator as the device

I have the “device and debugger toolbar” permanently enabled: device and debugger toolbar.

Tracing Input/Output

I’m going to use “blink” with delay() commented out because I have also found that use of delay() causes problems. Real time stuff doesn’t make sense in a step-through simulator anyway.

Set a breakpoint by clicking in the margin

Set a break-point by clicking in the margin

Now just hit F5 (or the green “play” icon) , watch the compile and wait for the break-point line to become highlighted in yellow. Now make sure the “IO View” is open. Use F10 to step through lines or the “Step Over” icon. If you F11/”Step Into”, you will end up inside the code that implements digitalWrite().

IO View showing PORTB inputs/outputs, which includes Arduino pin 13 as "bit 5"

IO View showing PORTB inputs/outputs, which includes Arduino pin 13 as “bit 5”. DDRB is set because pin13 is defined as an output. The bottom row is the output. PINB is the input value, which just follows the output.

Step through and watch bit 5 change, with nice highlighting in red. You can also click on it to change the value manually. I tend to use keyboard shortcuts but you might prefer the toolbar: step toolbar (watch, step-into, step-over and step-out). Step-Out is handy if you accidentally Step-Into the code for digitalWrite(), for example.

It should be an easy step to modify blink to read pin11 and write the value to pin12. Which bits of PORTB are these? Click on the appropriate bit in the PINB row to set the input value as high or low and step through to see it propagate to the output. Does it work if pin11 is set as an output (DDRB is set for that bit)?

Watching CPU Clock Cycles

Open the Processor view and watch the “Cycle Counter” as you step through. Changes in this indicate how many CPU clock cycles were needed to execute the line(s) of code. You can over-type the value to zero. At 16MHz clock, each cycle is 1/16,000,000s long so it how long does it take for the two digitalWrite()s in blink to execute?

Processor View is a little cryptic except for Cycle Counter

Processor View is a little cryptic except for Cycle Counter

 Watching Variables

Declare a boolean called “in” and read pin 11 into “in” before doing a digitalWrite(12, in). Now select the text “in” and add a “QuickWatch” (Shift F9 or click the glasses icon). Step through as before, manually changing bit 3 (pin 11) in “IO View”. NB: you can manually change values in the “Watch 1” tab too.

The result of a QuickWatch of both "led" and "in", which has just changed to 1

The result of a QuickWatch of both “led” and “in”, which has just changed to 1

OK… that was too simple but… Now find the “Memory 1” view (try Debug|Windows menu) and select “Memory: data IRAM”. Step through again and see if you can see which memory byte stores the value of “in”. Hint: in my case it was address 0x01df. Now add a QuickWatch for “led”, find the memory location and check its value is 0x0d (i.e. hexadecimal for 13 in decimal). See what happens in the Memory view if you change the value of “led” in Watch 1 to be -1 or 512. It should be clear that “led” uses two bytes of storage (check the Arduino reference and it says “Integers …  store a 2 byte value. This yields a range of -32,768 to 32,767”).

 

Rial’s Dungeon – an Open Source Tile-turning Game

This game is played on a table with bits of paper, counters and dice. The rules are quite simple and it is intended for playing with children. There are monsters, treasure, potions, traps, locked doors, chests… the usual stuff.

The Rials Dungeon v1 zip file download contains:

  • rules (MS Word docx)
  • the playing board (9 printed pages to stick onto a board backing) (MS Word docx)
  • sets of tiles to print out and guillotine (MS Word docx), which are laid out on the playing board and revealed as play proceeds
  • sets of cards (MS Word pptx) to print out and guillotine, which relate to contents of chests, chance events, useful items and treasure

There is no nice artwork and the rules get revised each time we play.

All content in the ZIP is licensed using CC BY-SA 2.0 UK because I want anyone who improves it to share their improvements back. Please link back to this post so I can find improvements and variations.

AVR Timer1 Dead Time Generator Example (ATtiny85)

The ATtiny 25/45/85 datasheet has an intriguing section about the “dead time generator” that I found a little confusing. A little practical example helped me to understand it. The code and logic analyser trace (made using the same analyser and client mentioned in previous posts) appear below. This is just an example to understand how it works. Real applications seem to be principally brushless DC motor control (pdf).

The setup below uses both A and B compare registers with the same compare value and applies some dead time to the B output so that the effect can be easily seen. I was also tempted to play around with the C compare register which sets the value at which the counter resets. Read the code comments for more…

Timer1 with Dead Time
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//set the top count give whole number percentage duty cycles
const unsigned char top = 99;
//40% duty cycle if top=99
const unsigned char compare = 39;
//prescale CLK/8, 8Mz clock and div8 prescale -> 1MHz tick -> appropx 10kHz output with top=99
const unsigned char prescaleTimer = (1<<CS12);
//prescale CLK/4.
const unsigned char prescaleDead = (1<<DTPS11);// div 8 = (1<<DTPS11) | (1<<DTPS10)
// with CLK/4 prescale and 8MHz clock the dead time is 0.5uS per LSB.
// Dead time is delay to rising edge of signal
const unsigned char deadHigh = 0x0F; //8uS dead time for OCR1B. Max 0x0F
const unsigned char deadLow = 0x08; //4uS dead time for /OCR1B
 
int main(void)
{
	//set data direction for output compare A and B, incl complements
	DDRB = (1<<PB4) | (1<<PB3) | (1<<PB1) | (1<<PB0);
 
	//setup timer1 with PWM. Will be using both A and B compare outputs.
	// both compares will be the same but only B will have dead time applied
	OCR1A = compare;
	OCR1B = compare;
	TCCR1 = (1<<PWM1A) | (1<<COM1A0); //Compare A PWM mode with complement outputs
	GTCCR = (1<<PWM1B) | (1<<COM1B0); //Compare B PWM mode with complement outputs
 
	//PLLCSR is not set so the PLL will not be used (are using system clock directly - "synchonous mode")
	//OCR1C determines the "top" counter value if CTC1 in TCCR1 is set. Otherwise "top" is normal: 0xFF
	OCR1C = top;
	TCCR1 |= (1<<CTC1);
	TCCR1 |= prescaleTimer; 
 
	//setup dead time for compare B. Note the prescaler is independent of timer1 prescaler (both receive the same clk feed)
	DTPS1 = prescaleDead;
	//DT1A is unset - output A has no dead time
	DT1B = (deadHigh<<4) | deadLow;
 
    while(1)
    {
        //do nothing
    }
}

40% duty cycle with no dead time on OC1A and different amounts of dead time added to OC1B and its complement.

Checking the dead time matches what is expected. The fractional differences can be ascribed to a combination of the on-chip clock oscilator not being exactly 8MHz with a minor addition effect from the logic analyser sampling rate (100Mhz)

End Stuff

Source code is also available from github.

All code is copyrighted and licenced as follows:

***Made available using the The MIT License (MIT)***
Copyright (c) 2012, Adam Cooper

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

AVR ADC #2 – Experiments in Operating the ADC at High/Low Sample Rates (ATtiny85)

This article builds on the previous one.

This experiment was stimulated by wanting to have a 128kHz system clock but still be able to use the ADC. Section 17.5 of the datasheet clearly says:

… requires an input clock frequency of between 50kHz and 200kHz to get maximum resolution. If a lower resolution than 10 bits is needed, the input clock frequency to the ADC can be higher than 200 kHz to get a higher sample rate. It is not recommended to use a higher input clock frequency than 1 MHz.

Section 17.8 goes on to say:

The ADC is optimized for analog signals with an output impedance of approximately 10 kΩ or less. If such a source is used, the sampling time will be negligible. If a source with higher impedance is used, the sampling time will depend on how long time the source needs to charge the S/H capacitor, with can vary widely. The user is recommended to only use low impedant sources with slowly varying signals, since this minimizes the required charge transfer to the S/H capacitor.

There is some information on the web, particularly about the limitations of ATtiny/mega for high symbol rate signal processing but I wanted to try for myself and gather some data. The questions are: how does precision vary as both frequency and impedance vary, especially outside the specified range. Given the information in the datasheet, both the comments above and the general description of the sample and hold circuitry, the worst performance should occur for high impedance and high frequency. It turns out this is observed but the story is a little more interesting.

The Circuit and the Code

This uses the same approach as the previous post.

The circuit is minimal and constructed on breadboard. ICSP from an AVR Dragon was fed into a header and left connected. A 100n cap was bridged from pins 4 to 8 over the IC. Three different potentiometers were used: 250k, 47k, 5k. 5V was supplied from the Dragon.

The same code is used with both a 8MHz and a 128kHz main clock – the fuses are set to use the internal oscillators – and the ADC clock frequency changed using the ADC prescaler; after taking an ADC reading, the prescaler is moved to the next higher division factor. See the code below.

Varying the ADC Clock
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Degrade_2(){
	unsigned char result[2];
	for(unsigned char prescale=1; prescale<=7; prescale++){
		//clear then re-assert the prescaler
		ADCSRA &= 0xF8;
		ADCSRA |= prescale;
		//start a conversion
		ADCSRA |= (1<<ADSC);
		//wait for end of conversion
		while (ADCSRA & (1<<ADSC));
		result[1] = ADCL;// datasheet says read low first
		result[0] = ADCH;
		sendBytes(result, 2);
	}
 
	//send a comma to separate readings
	unsigned char comma[]=",";
	sendBytes(comma,1);
}

Results 1: Low ADC Clock Frequency

This was a surprise. With a 128kHz main clock, the ADC gets clocked at from 64kHz down to 1kHz. No degradation of precision was observed, no matter which potentiometer was used and no matter what input voltage was selected.

Everything looked normal on the waveform, with the decreasing ADC clock rate clearly showing up as increasing conversion times.

Zoomed-out view of the ADC Clock Changing Experiment.

So it looks like I can just use a slow clock and not worry about the ADC.

Results 2: High ADC Clock Frequency

For a 8MHz system clock the ADC clock will be 4MHz, 2MHz, 1MHz, 500kHz, 250kHz, 125kHz, 62.5kHz according to the prescale value.

A representative range of input voltages were used for each potentiometer using a multimeter which was disconnected before taking ADC readings. The ADC error is calculated assuming the 62.5kHz reading is correct. Three or more readings were taken for each ADC clock rate to confirm stability; no more than 1LSB variation was observed and the median was used. In all cases 10 bit conversion results are considered.

(click for full size)

Although 4MHz looked OK sometimes, it is clearly very messed up! The effect of higher input impedance is clear but even so, we are getting 8 bit precision for most of the input voltage range at 500kHz. Remember the preferred input impedance is around 10k. Just outside the datasheet max freq, at 250kHz the error is down to the least significant bit. So it looks like this device at least is capable of adequate performance a little bit outside both impedance and frequency ranges.

(click for full size)
NB: the plot is slightly distorted since there is no 2V reading.

The second plot shows more clearly the change of error as the potentiometer is swept across its range. The 3V readings seem to be particulary badly affected by the very high frequencies.

(click for full size)

With a 47k potentiometer, which I consider to be compliant with the datasheet impedance requirement, the results look rather impressive. Even at 4MHz there is only a 3LSB error, i.e. we are getting 8 bits of precision (the top 8 bits of the 10 bit results) give or take some quantization error. Although… I am still rather suspicious of 4MHz and I did not explore a wider range of input voltages. At 2MHz, though, this device looks reliably better than 8 bits across most of the input voltage range.

(click for full size)

The final plot shows the case for 3V, which looks like the worst-case, and neatly summarises how far you can push this ATtiny85. The 5k potentiometer is not really significantly better than the 47k except for 4MHz.

End Stuff

Source code is also available from github. A spreadsheet of results and the plots is also there.

All code is copyrighted and licenced as follows:

***Made available using the The MIT License (MIT)***
Copyright (c) 2012, Adam Cooper

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

AVR ADC #1 – Basic Examples (ATtiny85)

As a precursor to investigating the precision of the AVR analogue to digital converter (on an ATtiny85 but assumed to be similar across many AVR devices) outside the recommended ranges of conversion frequency and input impedance, I set about to get to know the ADC better with a couple of “elementary” examples:

  • a simple read in a while(1) loop
  • a read triggered by Timer0

A Preliminary Diversion – observing the process with a logic analyser

There seemed like three main ways to see what the results were:

  1. transmit the results using the serial interface provided on the ATtiny and capture with a logic analyser
  2. as (1) but capturing the bytes on a PC (etc)
  3. direct display using an LCD or LEDs

#1 has the benefit of allowing inspection of the timing as well as capture of results. I have an “Open Logic Sniffer“, which is a great bit of kit for getting to know your MCU and it is a bargain (although has a few minor oddities), which I use with the Logic Sniffer Java Client. The OLS client has some nice analyser features. This was my choice, not least because I had zero experience with the ATtiny Universal Serial Interface (USI).

#2 sounds OK but the USI isn’t quite as universal as it might be – no USART – and I couldn’t be bothered to set up an arduino to relay data, although that is on my “to do” list. Also, I did want to watch the timing.

#3 looked like too much effort on an 8 pin device, given the objective.

Given my zero experience with the USI, I opted for a 3-wire setup that gives a signal that can be understood as SPI; the ATtiny plays the role of a master and just blasts out bytes assuming there is nothing to receive. The OLS client can decode the signals and serve up the transmitted bytes.

 
First we must setup the USI and data direction. Note that “DO” is the data out line but that Atmel have given this the synonym “MISO”, which makes sense if the ATtiny is a slave or is being programmed with an ICSP. PB0 is used as a “slave select” signal, which makes for easier interpretation of the signal traces in OLS, both by humans and the SPI analyser.

Code to setup ATtiny85 for 3-wire mode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//values to set USICR to strobe out
const unsigned char usi_low = (1<<USIWM0) | (1<<USITC);
const unsigned char usi_high = (1<<USIWM0) | (1<<USITC) | (1<<USICLK);
 
//setup pins for serial
//  PB2 (SCK/USCK/SCL/ADC1/T0/INT0/PCINT2)
// 	PB1 (MISO/DO/AIN1/OC0B/OC1A/PCINT1)
// 	PB0 (MOSI/DI/SDA/AIN0/OC0A/OC1A/AREF/PCINT0) - will not be used as DI
//	PB0 used as /CS
DDRB = (1<<DDB0) | (1<<DDB2) | (1<<DDB1);// /CS, USCK and DO as outputs
PORTB |= (1<<PB0);//slave not selected
 
//setup serial comms - 3 wire
USICR = (1<<USIWM0);

The following is called whenever one or more bytes should be sent out.

Code to send bytes in 3-wire mode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//sends the specified byte as serial (Three wire style).
void sendBytes(unsigned char bytes[], unsigned char size){
	//slave select
	PORTB &= ~(1<<PB0);
 
	//loop over bytes
	for(unsigned char b = 0; b<size;b++){
		//load the byte to the output register
		USIDR = bytes[b];
		//strobe USICLK 8 cycles (also toggles clock output thanks to USITC)
		//an unrolled loop gives 50% duty and saves 3 clock cycles per bit sent
		USICR = usi_low;
		USICR = usi_high;
		USICR = usi_low;
		USICR = usi_high;
		USICR = usi_low;
		USICR = usi_high;
		USICR = usi_low;
		USICR = usi_high;
		USICR = usi_low;
		USICR = usi_high;
		USICR = usi_low;
		USICR = usi_high;
		USICR = usi_low;
		USICR = usi_high;
		USICR = usi_low;
		USICR = usi_high;		
	}//bytes loop	
 
	//slave de-select
	PORTB |= (1<<PB0);		
}

Testing this with a “Hello World!” message

Say Hello World! in 3-wire mode
		unsigned char send[] = "Hello World!";
		sendBytes(send, sizeof(send)-1);	//-1 drops the null

and running through the OLS Client SPI Analyser tool gives:

(click for full size)

This is “mode 0” SPI style; see that the data is shifted out on a falling SCK and sampled on a rising edge. The alternating high and low assignments to USICR give a USI clock period 1/2 of the main clock since each assignment is a single cycle operation. Both can be seen in the following trace, which shows b01100100 being shifted out.

SCK (serial clock) vs Main Clock Timing and Illustration of Mode 0.

General Structure of the Code and Other Notes

Inside main() I first do some setup then I have a while(1) loop inside which is a function call. Each example/experiment exists as a separate function. Although this leads to the overhead of a few cycles to call the function, this is convenient in the present cases.

I also blew the fuse “CKOUT = [X]” so that the system clock is accessible to the logic analyser.

Simple Example

This assumes the fuse setting “SUT_CKSEL = INTRCOSC_8MHZ_6CK_14CK_0MS”, i.e. an 8MHz internal clock. Make sure that the logic analyser sampling rate is 20MHz or higher otherwise the clock signal will be mis-captured.

perform a single read and send H and L bytes to serial
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//assumes 8MHz clock, hence prescaling 
void SimpleRead(){
	//set prescaler to div64 --> 125kHz ADC clock
	ADCSRA |= (1<<ADPS2) | (1<<ADPS1);
	//start a conversion
	ADCSRA |= (1<<ADSC);
	//wait for end of conversion
	while (ADCSRA & (1<<ADSC))
	{
		//do nothing
	}
	unsigned char result[2];
	result[1] = ADCL;// datasheet says read low first
	result[0] = ADCH;
	sendBytes(result, 2);
}

SPI Analysis Results from OLS Client

Since this waits until the ADC conversion has finished, the interval between readings is effectively controlled by the time it takes for conversion. This is, in turn, determined by the ADC clock. The datasheet indicates the ADC clock should operate at between 50kHz and 200kHz. The datasheet also says a conversion takes 13 ADC clock cycles. Given the prescaling (code above), the conversion should take 13*64 = 832 cycles. The logic analyser (no screenshot shown) shows 901 cycles between /CS rising (happens at the end of sendBytes just before returning) and /CS falling again (happens just after entering sendBytes on the subsequent reading). Hence there are 69 main clock cyles spent doing other things: returning from sendBytes, returning from SimpleRead, process the while(1), call SimpleRead, setting ADCSRA, processing the while loop in SimpleRead, retrieving the low and high bytes, calling sendBytes. There is clearly some waste here that should be avoided in many real applications.

Timer-triggered Example

This is a rather more interesting example in which Timer0 is configured to trigger ADC conversion when the timer value = the “compare A” value. During both the timer delay and the ADC conversion, software is free to do other things since timer and ADC are hardware-controlled. At the end of the conversion an interrupt is triggered to do something with the result. In this case, just to send it out to serial.

Note that this example uses the 128kHz “watchdog” clock (fuse: “SUT_CKSEL = WDOSC_128KHZ_6CK_14CK_0MS”) to give a long enough timer 0 interval for me to detect the delay. I also used a 2s delay to change the ADC input during execution and make sure it is being properly captured. This means I had to reduce the ISP clock for in-system programming; I used 16kHz, which is reliable if slow.

The code is as follows, noting that Init_TimerTriggered() need only be called once. Note also the last line in the interrupt service routine… that one took me a while to work out!

Timer Triggered ADC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//setup for TimerTriggered
void Init_TimerTriggered(){
	//1. enable ADC completion interrupt
	sei();//global interrupts
	ADCSRA |= (1<<ADIE);//ADC interrupt
 
	//2. set the ADC to be timer triggered
	ADCSRB |= (1<<ADTS1) | (1<<ADTS0); //this defines the trigger source
	ADCSRA |= (1<<ADATE);//this is needed to enable auto-triggering
 
	//3. setup timer
	TCNT0 = 0x00;//counter to 0
	TCCR0A =  (1<<WGM01);//use "clear timer on compare match" mode
	OCR0A = 0x80;//output compare to 128 gives about 1s with 128kHz sys clock and prescaler (below)
	TCCR0B = (1<<CS02) | (1<<CS00);//prescaler to 1024, which enables the counter
}
 
//ADC completion interrupt service.
//Sends the data from the ADC register
ISR(ADC_vect)
{
	//read and send ADC
	unsigned char result[2];
	result[1] = ADCL;// datasheet says read low first
	result[0] = ADCH;
	sendBytes(result, 2);
 
	//clear timer compare flag otherwise the ADC will not be re-triggered!
	TIFR |= (1<<OCF0A);
}

Given the slower clock, the logic analyser sample rate and total capture package can be reduced too. I used a capture trigger (type=complex mode=serial in OLS… read the OLS tutorial!) to watch for /CS falling so 1kB of capture data at 500kHz sampling is ample to capture one timer-driven event.

The captured trace (following ADC completion), just for completeness is:

End Stuff

Source code is also available from github.

All code is copyrighted and licenced as follows:

***Made available using the The MIT License (MIT)***
Copyright (c) 2012, Adam Cooper

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.