Saturday, February 28, 2015

DACs, part 2

Since the previous post, I've made a slight change to the circuit I use, and written code for getting the DAC to generate various waveforms. The circuit change is a simple one: connecting the -LDAC pin of the DAC to output 8 of the Arduino. The function of this pin is that it causes changes in the input to only be reflected in the output when it is pulled low.

After experimenting with various options, I decided to write the code in an interrupt-based version. It uses an internal timer of the Arduino to call an interrupt routine every N microseconds. The interrupt routine just increments a variable for how many time it has been called, and in addition if a new value is ready for output, it pulses -LDAC to make it take effect. This means you can do the calculations for the next value to output without needing to worry about timing, then send the new value to the DAC ready for the next timing interrupt.

Lots of code follows. It will generate square, sawtooth, triangle and sine wave output. It's pretty limited: square waves only go to 10kHz reliably, and the other waveforms to much lower frequencies, the exact value depending on the number of samples you ask for. Too many samples and some will get skipped, or you get timing jitter. The sine wave is the worst, though it could be considerably improved by getting the values from a lookup table rather than the sin() function. If you want to see how to do this much better than I did, take a look at http://www.instructables.com/id/Arduino-Waveform-Generator/?ALLSTEPS, a really nice piece of work. I also used this code as an excuse for coding closer to the hardware, for example setting output pins by writing to the ports rather than using the digitalWrite() function. I also made some use of types such as unsigned long to get better precision in the calculations without resorting to floating point calculation, and unsigned int for many of the other variables. You have to be quite careful when coding this way, and use casts to avoid truncating values early.

(Code formatted using http://hilite.me/. It does a nice job. I'm not sure I like including code as opposed to giving a download link - opinions welcome, should anyone actually be reading this :-))

  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
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// Interrupt-based version of the DAC driver.

#include "SPI.h"

// Waveform: define only one of the following.
#undef SQUARE
#undef SAWTOOTH
#undef TRIANGLE
#define SINE

// The MCP4921 has a 12 bit range. We do all calculations of the output value
// using values which are 65536 times the value we want. This gets us more precision
// without needing to use floating point arithmetic, provided we calculate with longs.
#define VALUESHIFT 16

// SS pin for the DAC.
#define DAC1SS 10

// Mask to select SS PIN on port b.
#define DAC1SSHI B100
#define DAC1SSLO B11111011

// Pin and mask for LDAC pin.
#define LDAC 9
#define LDACHI B10
#define LDACLO B11111101

// Range of the DAC.
#define MIN_DAC 0
#define MAX_DAC 0xfff
#define MIN_VALUE ((unsigned long)MIN_DAC << VALUESHIFT)
#define MAX_VALUE ((unsigned long)MAX_DAC << VALUESHIFT)

// Clock frequency in Hz.
const unsigned long clock_freq = 16000000UL;

// Set this to the desired frequency in Hz.
const unsigned long freq = 30UL;

// Set this to the number of samples per cycle (including one at the
// end of a cycle).
#ifdef SQUARE
const unsigned int samples = 2;
#else
const unsigned int samples = 128;
#endif
const float pi = 3.1415926;

// Interrupt frequency in Hz.
const unsigned long interrupt_freq = freq * samples;

// Using timer 1 (a 16 bit timer), the interrupt frequency is
// 16000000 / (prescaler * (compare match register + 1))
// where the compare match register must be 0 to 65535,
// and the prescaler can be 1, 8, 64, 256 or 1024.
// Rearranging, CMR = (16000000/(prescaler * int.freq))-1
// Due to rounding, we might not get an exact value.
unsigned int CMR(unsigned int prescaler, unsigned long freq) {
  return ((clock_freq / prescaler) / freq)-1;
}
unsigned long ActualFreq(unsigned int prescaler, unsigned int cmr) {
  return (clock_freq / prescaler) / (cmr+1);
}
unsigned int CMRAndError(
  unsigned int prescaler, unsigned long freq, unsigned long* error) {
  unsigned int cmr = CMR(prescaler, freq);
  unsigned long actual_freq = ActualFreq(prescaler, cmr);
  *error = (freq > actual_freq) ? freq - actual_freq : actual_freq - freq;
  return cmr;
}

// Given a prescaler value, and its cmr and error, and a new one prescaler value,
// if the new one is better, update the prescaler, cmr and error.
void ImproveParameters(
  unsigned int* prescaler, unsigned int* cmr, unsigned long* error,
  unsigned long freq, unsigned int new_prescaler) {
  if (*error == 0) {
    return;
  }
  unsigned long new_error;
  unsigned long new_cmr = CMRAndError(new_prescaler, freq, &new_error);
  if (new_error < *error) {
    *prescaler = new_prescaler;
    *cmr = new_cmr;
    *error = new_error;
  }
}

void SetValue(unsigned long value) {
  PORTB &= DAC1SSLO;
  unsigned int v = value >> VALUESHIFT;
  if (v > MAX_DAC) {
    v = MAX_DAC;
  }
  byte data = (highByte(v) & 0x0f) | 0x30;
  SPI.transfer(data);
  data = lowByte(v);
  SPI.transfer(data);
  PORTB |= DAC1SSHI;
}

unsigned long last_value = MIN_VALUE;
unsigned long next_value = MIN_VALUE;
unsigned int updates = 0;
unsigned long sample_step = 0;
unsigned int sample_number = 0;
const unsigned int samples2 = samples / 2;
float angle_step;
const unsigned long range_multiplier = (MAX_VALUE - MIN_VALUE) / 2;
bool have_new_value = false;

void setup() {  
#ifdef SAWTOOTH
  sample_step = (((unsigned long)MAX_DAC+1 - MIN_DAC) << VALUESHIFT) / (samples-1);
#endif
#ifdef TRIANGLE
  sample_step = 2 * (((unsigned long)MAX_DAC+1 - MIN_DAC) << VALUESHIFT) / samples;
#endif
#ifdef SINE
  angle_step = 2 * pi / samples;
#endif

  // Try each prescaler to find the best.
  unsigned long error;
  unsigned int prescaler = 1;
  unsigned int cmr = CMRAndError(prescaler, interrupt_freq, &error);
  ImproveParameters(&prescaler, &cmr, &error, interrupt_freq, 8);
  ImproveParameters(&prescaler, &cmr, &error, interrupt_freq, 64);
  ImproveParameters(&prescaler, &cmr, &error, interrupt_freq, 256);
  ImproveParameters(&prescaler, &cmr, &error, interrupt_freq, 1024);

  pinMode(DAC1SS, OUTPUT);
  pinMode(LDAC, OUTPUT);
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);

  SetValue(next_value);

  cli(); 
  // Set interrupt timer.
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0; // Counter value
  OCR1A = cmr;
  TCCR1B |= (1 << WGM12);
  if (prescaler == 1) {
    TCCR1B |= (1 << CS10);
  } else if (prescaler == 8) {
    TCCR1B |= (1 << CS11);
  } else if (prescaler == 64) {
    TCCR1B |= (1 << CS11) | (1 << CS10);
  } else if (prescaler == 256) {
    TCCR1B |= (1 << CS12);  
  } else {
    TCCR1B |= (1 << CS12) | (1 << CS10);
  }
  // Enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  sei();
}

ISR(TIMER1_COMPA_vect){
  if (have_new_value) {
    PORTB &= LDACLO;
    have_new_value = false;
    PORTB |= LDACHI;
  }
  updates += 1;
}

void loop() {
  if (updates > 0) {
    unsigned int u = updates;
    updates = 0;
    sample_number = (sample_number + u) % samples;
#ifdef SQUARE
    next_value = (sample_number >= samples2) ? MAX_VALUE : MIN_VALUE;
#endif
#ifdef SAWTOOTH
    next_value = MIN_VALUE + sample_number * sample_step;
#endif
#ifdef TRIANGLE
    if (sample_number < samples2) {
      next_value = MIN_VALUE + sample_step * sample_number;
    } else {
      next_value = MIN_VALUE + sample_step * (samples - sample_number);
    }
#endif
#ifdef SINE
    next_value = (unsigned long)(range_multiplier * (sin(sample_number * angle_step)+1));
#endif
    if (next_value != last_value) {
      SetValue(next_value);
      last_value = next_value;
      have_new_value = true;
    }
  }
}

Sunday, February 22, 2015

DACs, part 1

I've been thinking of building a signal generator based on an Arduino and a DAC. As an experiment, I got hold of a MCP4921 12-bit SPI interface DAC. Connecting this up to an Arduino is really simple:

  • pins 1 and 6 to 5V.
  • pin 2 to Arduino pin 10 (or any other - this is the select line).
  • pin 3 to Arduino pin 13.
  • pin 4 to Arduino pin 11.
  • pins 5 and 7 to ground.
  • and the output is on pin 8.
My first question is how fast you can drive this. It's limited by the capabilities of the DAC and how quickly the Arduino can send data to it. The datasheet says that the MCP4921 takes about 4.5uS to settle, and to see how fast I can make the code go, I wrote a minimal sketch that toggles between a high and a low value. All values in the code are constants and there are no function calls other than setup() and loop(). So the code looks like this:

#include "SPI.h"

#define DAC1SS 10

void setup() {
  pinMode(DAC1SS, OUTPUT);
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
}

void loop() {
  digitalWrite(DAC1SS, LOW);
  SPI.transfer(0x30);
  SPI.transfer(0);
  digitalWrite(DAC1SS, HIGH);
  digitalWrite(DAC1SS, LOW);
  SPI.transfer(0x3f);
  SPI.transfer(0xff);
  digitalWrite(DAC1SS, HIGH);
}

Looking at the output on a scope, the first thing to notice is that it isn't square:


The timebase here is 5us per division, so the rise time is worse than the settling time of the DAC would suggest. It shows that the minimum time for a cycle is about 31us, so limiting the frequency to around 32kHz. For anything other that square waves, you would need multiple steps, further reducing the effective frequency. The frequency seems consistent with this project, which is working with 22kHz samples. You might be able to squeeze a little but more by running the SPI interface faster, using SPI.setClockDivider(SPI_CLOCK_DIV2) in setup(). This does make a difference, but a tiny one.

Tuesday, February 17, 2015

Very, very small

Many of the interesting ICs you can get only come in surface mount packages like SOIC or TSSOP. For example, I was looking at this interesting device only to find there is no through hole version. These packages are physically much smaller than through hole ones, and as it's already hard to see what I'm soldering with my aged eyes, I had more or less given up on them. But all is not lost. I bought a simple headset magnifier from Digikey for $10, and it is good enough for me to work with these packages and even with the teeny tiny capacitors and resistors that go with them. For learning surface mount soldering, Jameco have a kit and a video (by someone who is really irritating, but useful all the same). Here's a small 555 circuit from the kit:

Not pretty, but it works. Here what the headset magnifier looks like:
This is not to the same scale as the picture above, just in case you thought my head was 4cm in diameter.

Tuesday, February 10, 2015

Beyond the noodlescope

A few months back, when I was starting to get interested in building electronic circuits again, I thought it would be useful to have an oscilloscope. I came across several web pages describing how to use the headphone/microphone socket on an Android phone with some open source software to make a really cheap scope that would work at audio bandwidths. Based on one of the pages (which I can't find now), I knocked up a 10:1 probe consisting of a FET, a capacitors for uncoupling the DC and some resistors. I tried this with a couple of programs: OsciPrime and XYZ-Apps Oscilloscope, running on a very old Nexus 1.

I connected it up to a small, fixed frequency oscillator with sine, square and triangle wave output, this Velleman kit. Here's how the sine wave looks on OsciPrime:

If you can tell from the out of focus photograph, it looks OK.

Here's the triangle wave:
As you can see, it's different from the sine wave, but far from triangular.

Here is the alleged square wave:
This is surely the most horrible square wave in the world. And it's strange as well. If it was just losing the higher harmonics, you would expect each pulse to be symmetrical in time.

Many months later, a kind friend made me guardian of his old Tektronics 465 so now I can see what the Velleman signal generator is really producing. Here's the square and triangle waves:
You can see the square wave is pretty clean (as you'd expect - the circuit is a 555 and a filter network to remove the harmonics). The triangle wave is a bit soft edges, but is also noticeably more symmetric.

So what was going on with the square wave and to a lesser degree with the triangle wave? Based on some simulation, I think it that as well as losing the higher harmonics, there is a phase shift on the remaining ones, and the shift is different at each frequency. 45 degrees on each of the odd harmonics results in something close to the observed square wave. I assume the Androids audio circuitry or the software behind it does this. I wonder why.


Saturday, January 17, 2015

Seven segment displays on an Arduino

Adafruit sells a four digit, seven segment LED display. Unlike many of their products, they don't give a lot of help in hooking it up. They suggest that you should connect it with the transistors or a shift register on the common cathodes to sink enough current. I think this is probably unnecessary. If you connect the anodes through 1k resistors you get pretty good brightness. The current per segment should be something like 5V minus 0.6V for the diode (it may be more than this - 0.6V is for silicon and they are probably GaAsP), so this gives a current of 4.4mA per segment, or 35mA if you have all the segments on, provided you only drive one digit at a time. The Arduino should be able to handle this OK. At least mine didn't blow up when I set it up like this.

There's a couple of difficulties in getting it connected, and so I'll go through what I did in case it is helpful to anyone else. I don't think this kind of display is very well suited to using on an Arduino, at least if it is connected directly. You need to use 12 or 13 I/O lines for it, and the code has to continually refresh the display so it can't do much else. You can reduce the number of lines by using a shift register or and I/O extender, though the refresh problem remains. The description that follows shows how direct interfacing can be done if you really want to try it.

The datasheet that Adafruit links to is a bit misleading. It makes it look as if the display has twelve pins, 1-6 on the lower edge and 12-7 on the top one. In fact it has 14 pins, 1-7 on the lower edge and 14-8 on the upper one, reading from left to right if you are looking down on the display. The model I got had part number KW4-56NCLB-P, so you have to use the second of the two diagrams on the datasheet for the pins. It's a common cathode version, meaning the pointy end of the LEDs which the current comes out of are joined together. There cathode for each digit are connected together, as are the ones for the colon separator in the middle of the display. Typically, you would use it by setting one of the common cathodes to low (0V) to select that digit, with all the others high. Then you would set the pins for the segments you want to turn on to high while keeping the others low. For example, to display a 1 on the leftmost digit, pin 14 would be low and pins 11, 7, 10 and 6 high for the digit, and pins 9 and 4 would be high, with 13, 2, 1, 12, 3, 5, and 8 high for the segments.

This example may make the second difficulty apparent: the ordering of the pins is not at all logical: the segments don't come out in a nice order such as pins 1-8, they are in the order I just mentioned 13,2, 1, 12 etc. I assume this makes manufacturing easier by avoiding crossing tracks within the package. When you are using a breadboard, a trick that can help with this is to take jumper wires from the pins to another area of the breadboard, where you can put them into a more logical order. I've done this with ICs that have a weird pin ordering as well. There are eight LEDs per digit (seven segments plus a decimal point), it makes sense to bring them out with four in each block.

The breadboard that I have is a fairly common one in which there are two block of contacts, with 5 in each column.  The columns are labelled 1 through 64. I placed the display so that pins 1 and 14 were on column 50. I'll call the blocks of the breadboard A and B, with the top of the display (pins 8-14) going into A. The table for the wiring them goes like this:
Pin on the display Function of the pin Column on the breadboard Connect a jumper to...
1 Segment E 50B 41B
2 Segment D 51B 38A
3 Decimal point 52B 38B
4 Segment C 53B 39A
5 Segment G 54B 39B
6 Digit 4 55B 23B
7


8


9 Segment B 55A 40A
10 Digit 3 54A 23A
11 Digit 2 53A 24A
12 Segment F 52A 40B
13 Segment A 51A 41A
14 Digit 1 50A 25A

I didn't connect pins 7 and 8, which are for the colon. Now we have the four digit cathodes on 25A, 24A, 23A and 22A, segments A to D on 41A, 40A, 39A and 38A, and segments E to G and the decimal point on 41B, 40B, 39B and 38B. This is a much more logical ordering and it helps to stop your brain from hurting.

As I said before, each anode (segment) will have a 1k resistor on it. We need this to limit the current through the segment. Some people suggest putting the resistors on the digit cathodes, as then you only need four of them, or five if you use the colon. However, if you do it this way, the brightness will vary according to what the display is showing, as with more segments on there will be more current through the resistor and hence a larger voltage drop across it. So now you can connect up 8 1k resistors, keeping the nice ordering of the segments we achieved earlier. The resistors can span across some of the jumper wires to keep things tidy. Here's the table for the resistors:
Segment 10k resistor from... to...
Segment A 41A 37A
Segment B 40A 36A
Segment C 39A 35A
Segment D 38A 34A
Segment E 41B 37B
Segment F 40B 36B
Segment G 39B 35B
Decimal point 38B 34B

Now all that remains is to connect it up to the Arduino. I connected digits 1-4 (25A-22A on the breadboard) to I/O pins 2-5, and segments A-G plus the decimal point (37A-34A and 37B-34B) to pins 6-12. It looks like this:

The connections to the Arduino are very tidy with all the mess in the jumpers on the breadboard.

There is a code snippet here. It picks a random number from 0 to 9999 and displays it. To do the display, there is a function called displayNumber, which selects each digit in turn by setting the right one of pins 2-5 to LOW, sets up the segments that are on by setting some of pins 6-12 to HIGH and delays briefly to reduce flickering. To keep the display refreshed, this all has to be done in a loop.

On my display, there was one dead segment (the D segment of the first digit), but otherwise this works quite nicely.


Saturday, January 10, 2015

Persistence

The Arduino Sensorium that I described in previous posts is limited by the amount of RAM on the Arduino. Recall that it stores readings from its sensors periodically in a circular buffer, discarding old entries when it runs out of space. The example version I described earlier used a DHT11 sensor, and the reading was stored as temperature, humidity and team, each as a 16 bit integers. This only gives you around 170 entries before you get close to the RAM limits of the Arduino Uno.

A simple way of adding memory is to use a FRAM chip. Adafruit has a 32k byte one accessed through the I2C interface, and an 8k byte SPI version as well. The 32k byte chip would allow around 5,000 sensor readings of three integers. In addition, it is non-volatile memory, meaning you can turn the power off and still retain the data.

The main issue with using this chip is that you can't address it in a simple way, that is through pointers. Instead, you have to read or write it byte by byte using functions in the library provided by Adafruit. I wrote a class which makes this a little easier. The interface is meant to give roughly the same functionality as a native C fixed size array. The specific methods are different, and in particular, I decided not to overload the [] operator. Instead, there are Get and Set methods which take pointers to bytes to specify the location of the objects to be read or written, with templated GetItem and SetItem methods to allow writing of other data types. The code is here. It's written as a base class (StoredArray), and two subclasses. One of them (FRAMArray) stores the data in FRAM. The other (MemoryArray) stores the data in ordinary RAM, allowing you to easily switch code between the two kinds of storage.

I have a new version of the Sensorium code which illustrates this here. You select between FRAM or normal RAM with a #define. When you select the FRAM version it will use the existing data in FRAM, so that you can power down the Arduino, then restart it and add more sensor reading later (though the timestamps will not make sense if you do so). To clear the data, press down the button on input 3 within 3 seconds of resetting the Arduino. This is the same button used in the earlier version for initiating upload.

A quick note on the hardware configuration for this and for the original Sensorium.
  • Arduino pin 3 connects to a push button switch. This side of the switch has a 10k resistor to ground, the other side goes to 5V.
  • Arduino pin 4 goes to pin 2 of a DHT11 temperature/humidity sensor. This pin has a 10k resistor to 5V. Pin 1 (leftmost with the "grid" side facing you) goes to 5V, pin 4 to ground.
  • The new hardware for this version of a FRAM breakout board described above. It has 5V and ground connections as shown on the board (pins 1 and 2). SCL (pin 4 of the board) goes to the Arduino's A5 pin, and SDA (pin 5 of the board) goes to Arduino A4.
Next, to try some different sensors.

Sunday, January 04, 2015

All ears and thumbs

The circuit design for the Arduino sensorium from a couple of days ago is trivial, so that just leaves the physical construction. There aren't many external parts - a couple of resistors, a button, a battery clip and the sensor itself - but I wanted something physically quite stable. I didn't have a convenient plastic box for putting it in, so in the short term I decided to improvise. My first thought for the components was to put everything except the sensor on a tiny piece of stripboard (or Veroboard, as I can't help calling it). The sensor could then be connected on a longer cable to make it more convenient to place.

I did initially put it together this way, and then decided on something better. When I placed my initial order with Adafruit, they sent me a free "perma-proto" board (https://www.adafruit.com/products/1609). This is nice in a couple of ways. First, it's about the same size as the Arduino Uno board, something I'll come back to in a moment. Secondly, it has very fine tracks and plated through holes, which makes soldering to it easier than stripboard. If there is a piece of advice that I would offer my teenage self just getting started in electronics, it is this: relish your youth. When you get to the your 50s, it's going to be very hard to focus on small things, and soldering will involve much guesswork. Thank you, my teenage self will say, now can you tell me how to get more sex?

Because the perma-proto board is about the same size as the Arduino Uno board, you can easily strap them together. I did this with a piece of cardboard between them for insulation, holding the whole thing together with a large rubber band I find in my desk drawer. I had a couple of spare headers which I soldered to the perma-proto board and then connected the 5V, Ground and input pins from the Uno board with ordinary jumper cables. The battery clip goes to the Vin termainal on the board, again connected through a spare bit of header. The result, while not pretty, is just about robust enough to be carried round in a pocket. It'll do until I've ordered a more rigid box instead. Here's a couple of pictures.