Raspberry Pi Measurement Electronics Hardware and Software
By Yury Magda
Copyright © 2013-2014 by Yury Magda. All rights reserved. The programs, examples, and applications presented in this book and on the author’s Web site have been included for their instructional value. The author offer no warranty implied or express, including but not limited to implied warranties of fitness or merchantability for any particular purpose and do not accept any liability for any loss or damage arising from the use of any information in this book, or any error or omission in such information, or any incorrect use of these programs, procedures, and applications. No part of this publication may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior written permission of the author.
Contents
Introduction Disclaimer The brief introduction to GPIO Powering external circuits Simple control systems using a GPIO port Expanding GPIO using I2C interface The I/O expander using PCF8574 The I/O expander using MCP23008 The temperature measurement system using I2C Measuring analog signals using an analog-to-digital converter MCP3201 Using an analog-to-digital converter MAX187 Using low-pass filters in data acquisition systems The simple thermostat system Processing small analog signals using instrumentation amplifiers Using high-resolution Delta-Sigma analog-to-digital converters Using digital potentiometers: basic operations Using digital potentiometers: the PWM circuit with a 555 timer Using digital potentiometers: PWM circuits with analog comparators Using digital potentiometers: the resistor set wide-band oscillator LTC1799 Using digital potentiometers: the DC current source with an op-amp Using digital potentiometers: the DC current source with a difference amplifier Using dual-chain digital potentiometers Using 4-channel digital potentiometer AD5204: basic operations Using 4-channel digital potentiometers: the quadrature sinewave RC oscillator The digitally programmed wide-band square wave oscillator with LTC6903
The digitally controlled oscillator AD7741 Using digital-to-analog converters: the DC voltage source Using digital-to-analog converters: the PWM circuit with LTC6992 Generating analog waveforms using Direct Digital Synthesizer AD9833 Signal synthesis using AD9850 DDS Measuring a signal frequency using Frequency-To-Voltage converters Measuring frequencies of analog periodical signals
Introduction The popular Raspberry Pi miniature computer besides being used as a low-power generalpurpose desktop computer can form a basis for building measurement and control systems by both professionals and hobbyists. This is due to the general purpose input/output port (GPIO) available on the Raspberry Pi board; each pin of GPIO can be accessed through the P1 header. Every GPIO pin is bidirectional – it can provide output digital signals for handling external circuits or pick up signals coming from external circuitry. The direction of the signal flow – input or output – can be set by the software written in C or Python. This book describes projects which may be used as templates for building numerous measurement and control systems. Each project includes both hardware and software accompanied by the detail description of what is doing. Note that Raspbian OS is a true Linux operating system, so developers cannot develop applications for measuring and control system operating in ″ real-time ″ without writing or using specific kernel drivers. Nevertheless, it is possible to develop numerous applications that don’t require fast responses and short time intervals. Using an additional library developed for Python allows to measure relatively slow analog (continuous) and digital signals coming from various (temperature, humidity, pressure, light, etc.) sensors. The programs written in Python can also drive external loads (motors, relays) by bringing digital signals on GPIO pins. Although this book is thought as a highly practical guide for building measurement and control systems, some theory is given as well. The material of the book assumes that the readers are familiar, at least, with basics of designing and assembling electronic circuits. For most projects having some basic skills in electronics will serve the readers well and allow them to understand what is going on behind the scenes. Each project is accompanied by a brief description which helps to make things clear. All projects were designed using the Raspberry Pi Model B Rev.2 board running Raspbian OS. The source code was developed in Python. Most projects described in this guide can be easily improved or modified if necessary.
Disclaimer The design techniques described in this book have been tested on the Raspberry Pi Model B Rev.2 board without damage of the equipment. I will not accept any responsibility for damages of any kind due to actions taken by you after reading this book.
The brief introduction to GPIO Before starting off the projects we need to become familiar with the GPIO port of the Raspberry Pi. The production Raspberry Pi board has a 26-pin 2.54 mm expansion header, marked as P1, arranged in a 2x13 strip. They provide 8 GPIO pins plus access to I2C, SPI, UART), as well as +3.3 V, +5 V and GND supply lines. Each bit in GPIO port can individually be configured either as input or output.
Note that GPIO voltage levels are 3.3 V and are not 5 V tolerant. The Raspberry Pi board has no over-voltage protection – the intention is that people interested in serious interfacing will use an external board with buffers, level conversion and analog I/O rather than soldering directly onto the main board. Chances are that external circuitry could be poorly assembled and have short circuits, so I highly recommend you to apply a standalone DC 5V/3.3V power supply for powering your electronic circuits to prevent the damage of the Raspberry Pi. All the GPIO pins can be reconfigured to provide alternate functions, SPI, PWM, I2C and so. At reset only pins GPIO14 – 15 are assigned to the alternate function UART; these two can be switched back to GPIO to provide a total of 17 GPIO pins. Each of their functions and full details of how to access are detailed in the chipset datasheet. The maximum permitted current to be drawn from the 3.3 V pins is 50 mA, so be careful when attaching any external load to these pins. Maximum permitted current being taken from the 5 V pin is the USB input current (usually 1 A) minus any current drawn from the rest of the board. Note that Model A can provide max current draw 500 mA from 5V pin whereas Model B may draw 300 mA through this pin. I highly recommended you not to use on-board power supply of +3V and/or +5V when building you electronic circuits; it would be safer to take some stand-alone power supply for your design.
Be very careful with the 5V pins because if you short 5 V to any other P1 pin you may permanently damage your Raspberry Pi. Before probing P1, it’s a good idea to strip short pieces of insulation off a wire and push them over the 5V pins so you don’t accidentally short them with a probe.
When attaching your electronic circuits to the Raspberry Pi GPIO port use short wires (10-12cm long) so that to ensure minimum distortion of digital signals. Using long wires may cause your circuit to cease functioning properly. Let’s take a look at how a Python application can drive the GPIO port. First, we should install a little Python library called RPi.GPIO. That is easily in Raspbian OS and takes the following commands entered either in LXTerminal or a text console: $ wget http://pypi.python.org/packages/source/R/RPi.GPIO/RPi.GPIO-0.1.0.tar.gz $ tar zxf RPi.GPIO-0.1.0.tar.gz $ cd RPi.GPIO-0.1.0 $ sudo python setup.py install After that has been done, we can invoke the RPi.GPIO library functions to drive GPIO. Let’s enter the Python interpreter and execute the following command: $ sudo python To access the RPi.GPIO library functions, we should insert the import directive: >>> import RPi.GPIO as GPIO The next step to do is to identify the GPIO pins with an RPi board or BCM processor enumeration list. The RPi enumeration will be accessed after executing the following statement: >>> GPIO.setmode(GPIO.BOARD) To apply the BCM enumeration we need to execute the statement: GPIO.setmode(GPIO.BCM)
All projects in this book will use the BCM enumeration. The next step is to configure the direction of data flow (input or output) for each bit (pin) of the GPIO port. To set a pin as output we should enter: GPIO.setup(pin_number, GPIO.OUT) Here the pin_number parameter should be assigned the value that suits the selected enumeration. For example, to configure GPIO18 as output using the BCM enumeration the following command should be executed: GPIO.setup(18, GPIO.OUT) Then we can write either a False (logical level ″ 0 ″ ) or True (logical level ″ 1 ″ ) to this port. The next statement sets the GPIO18 output low (logical level ″ 0 ″ ): GPIO.output(18, False) To read digital data from some GPIO pin (for example, GPIO18), we have to configure the selected port as input by the statement: GPIO.setup(18, GPIO.IN) Then the data from GPIO18 can be read into some variable (inpVal, for instance): inpVal = GPIO.input(18) Note that a GPIO pin cannot draw heavy load since its output current is limited to several milliamperes, so some buffer circuit should be placed before the load. Such buffer will protect the Raspberry Pi from damaging by isolating high power / high voltage external circuitry from the board. Buffers themselves can draw relatively high current, so we need to apply a stand-alone DC power supply to feed them. Buffers also come in handy when we need to shift the 3.3V logic of the Raspberry Pi to TTL level (5V). The below circuit (Fig.1) illustrates using port GPIO18 and the buffer 74HC14.
Fig.1 In this circuit, the LED is driven by the GPIO18 pin via the buffer 74HC14. 74HC14 consists of Schmitt triggers, each of them inverts the input level, so the signal on the output (pin 2 of 74HC14) is inverted in relation to the GPIO18 output. When the GPIO18 output goes high (log. ″ 1 ″) , the pin 2 of 74HC14 is pulled low (log. ″ 0 ″ ) thereby driving the LED ON. Conversely, the low level at GPIO18 drives pin 2 to a high level thus turning the LED OFF. The 74HC14 buffer can be fed by the +3.3V power supply, while the LED network might be connected to an external +5V power supply. To drive external loads we can take some popular and low-cost optocoupler chip, say, either of 4N35 – 4N37 and connect it to the buffer output. The source code of the simplest Python application is shown in Listing 1. Listing 1. from time import sleep import RPi.GPIO as GPIO
GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) #using GPIO numbers rather than pin numbers GPIO.setup(18, GPIO.OUT) # set GPIO18 as output counter = 0 while (counter < 10): GPIO.output(18, False) sleep(1) GPIO.output(18, True) sleep(1) counter += 1 GPIO.output(18, False) Type the text from Listing 1 in some text editor (nano, for example) and save it in the file with the .py extension, say, gpio_test.py. The program written in this file can be executed by entering the following command line in the working directory: sudo python gpio_test.py The sudo prefix is required because the non-privileged users can’t execute the program code trying to access hardware in Linux operating systems. The general form of the command launching a Python application will be as follows: sudo python Here the filename.py refers to the name of a file containing the Python source code. GPIO inputs can also be used for capturing digital signals coming from external circuitry. The simple control system with the circuit shown in Fig.2 illustrates processing both input and output digital signals.
Fig.2 In this system, the program code reads the state of the switch SW1 on pin 3 of 74HC14 buffer every 2 seconds. After SW1 has been pressed, the LED wired to the pin 2 of 74HC14 is driven ON. Conversely, when SW1 is open, the LED will be OFF. Note that using a mechanical switch (SW1) or relay as input to the GPIO pin can cause the contact bounce. The reason for concern is that the time it takes for contacts to stop bouncing is measured in milliseconds, while digital circuits can respond in microseconds. There are several methods to solve the problem of contact bounce (that is, to ″ de-bounce ″ the input signals). A simple hardware debounce circuit for a momentary push-button switch may consist of the resistor R2 and a capacitor C1 (noted by the dashed line in Fig.2). Here the R2×C1 time constant is selected equal to 0.1 s (typical value) to swamp out the bounce. In general, a developer can pick R2 and C1 values to provide R2×C1 longer than the expected bounce time. The Schmitt trigger of 74HC14 connected to the switch network produces a sharp high-to-low transition. When using such debounce circuit, we should wait before pressing the switch again. If the switch has been pressed again too soon, it will not generate another signal. The Python source code driving this circuit is shown in Listing 2.
Listing 2. from time import sleep import RPi.GPIO as GPIO GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) #using GPIO numbers rather than pin numbers GPIO.setup(18, GPIO.OUT) # set GPIO18 as output GPIO.setup(23, GPIO.IN) # set GPIO23 as input while (True): bVal = GPIO.input(23) if (bVal != 0x0): print(‘SW1 is pressed. LED is turned ON’) GPIO.output(18, True) else: print(‘SW1 is released. LED is turned OFF’) GPIO.output(18, False) sleep(2) It is seen that the design of simple systems where the GPIO port processes only digital signals is relatively simple. GPIO can also be used when building control and measurement systems processing analog (continuous) signals usually taken from sensors – this will be discussed in the next sections. The following section is dedicated to powering our systems.
Powering external circuits The low-power external circuitry connected to the Raspberry Pi can be powered using power supply +3.3V located on the Raspberry Pi board. This, however, poses a risk of damaging the RPi board because there are chances of existing short circuits in external circuitry. It would be much better and safer to take a stand-alone DC power supply for powering your circuitry. When both +3.3V and +5V voltage are needed, we can take a DC power supply +5V for a mobile phone and connect its output to a low-dropout regulator (LDO) chip providing the steady +3.3V output. The following power circuit was used in all projects from this book (Fig.3).
Fig.3 Here the DC voltage +3.3V is obtained from +5V supply using the LDO chip LM11173.3. The current flowing through external circuitry shouldn’t exceed the maximum provided by LDO (800 mA for the LM1117). Obviously, any other LDO chip providing +3.3V and enough current in load can be applied as well.
Simple control systems using a GPIO port The following two projects illustrate using the GPIO port of the Raspberry Pi for processing analog (continuous) signals. Both systems allow to process analog signals from censors in the digital domain, without digitizing sensor outputs. The first project drives the LED ON /OFF depending on the visible light intensity. The analog signal is originated by a Light–Dependent Resistor (LDR) of 10k which changes its resistance as illumination varies. The schematic circuit of such a system is shown in Fig.4.
Fig.4 In this circuit, the LDR and resistor R3 form the voltage divider. The voltage drop across R3 fed to the inverting input of the comparator TLC3702 is compared with the voltage fed to the non-inverting input (pin 3) from the resistive divider R1–R2. The values of R1-R2 determine the threshold (1.65V, in our case). When light reaches the LDR the latter reduces its resistance, so the voltage level on the inverting input (pin 2) of the comparator TLC3702 rises above 1.65V thus pulling the comparator output (pin 1) low. Conversely, when LDR is shadowed, the voltage on pin 2 drops below 1.65V, so the comparator output goes high. The TLC3702 comparator provides the CMOS-compatible output at pin 1, so the pin is directly connected to GPIO23 pin configured as input. The program code periodically
checks the voltage level on GPIO23. The LED is driven OFF when the GPIO23 input is brought low; the LED is driven OFF when GPIO23 goes high. The Python source code handling this system is given in Listing 3. Listing 3. from time import sleep import RPi.GPIO as GPIO GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) #using GPIO numbers rather than pin numbers GPIO.setup(18, GPIO.OUT) # set GPIO18 as output GPIO.setup(23, GPIO.IN) # set GPIO23 as input while (True): bVal = GPIO.input(23) if (bVal == 0x0): print(‘Light intensity has arisen. LED is ON’) GPIO.output(18, False) else: print(‘Shadows fell.LED is OFF’) GPIO.output(18, True) sleep(2) Let’s consider the second project which describes a simple temperature control system. Compared to the previous project, the LDR – resistor R3 network is replaced with the temperature sensor LM35. The LM35 output is connected to the inverting input of the comparator, while the reference voltage (threshold) applied to the non-inverting input is taken from the voltage divider R1-R2. The source code may be almost the same as that in Listing 2, although slight modifications should be done. The schematic circuit of the temperature control system is shown in Fig.5.
Fig.5 The resistor divider R1–R2 provides the reference voltage 0.25 V on pin 3 of the comparator. This threshold corresponds to the temperature of about 25ºC measured by the LM35 sensor. When the temperature rises above 25ºC, the LM35 output exceeds 0.25V thus causing the comparator to go low. Conversely, when the temperature drops below 25ºC, the sensor output falls below 0.25V and the comparator output goes high. If some external load (DC motor, relay, etc) draws a large current, the GPIO output should be buffered, for example, by a power BJT transistor switch as is illustrated in Fig.6.
Fig.6 In this circuit, the power switch using the Darlington transistor Q1 (TIP122, in our case) is driven ON when the GPIO18 output is pulled high. This allows the power to be fed to the LOAD from the +VL power supply. When the GPIO18 output is pulled low (log. ″ 0 ″ ), Q1 is cut off, thereby breaking the power chain to the LOAD. The diode D1 (noted in the dashed line) is needed to protect the power transistor Q1 when driving the inductive load. This circuit works quite well, although some degree of noise may appear on the ″ ground ″ of the circuit, especially when toggling inductive loads. This can be ignored once there are no sensitive conditioning circuits such as analog-to-digital converters. The best way to prevent the high current flow through the common wire and lessen noise is to isolate low-power circuits on the Raspberry Pi side from high–power loads. That can be achieved by using some isolation circuits, for example, optocouplers (4N35-4N37).
Expanding GPIO using I2C interface The Raspberry Pi board has a relatively small number of GPIO pins; often a measurement system needs much more I/O pins to be involved than the Raspberry Pi can offer. In those cases some I/O expander chips may come in handy. One of the most suitable methods of expanding GPIO port is to apply an I2C interface. The main advantage of I2C is that it allows multiple devices to be attached to the same bus. This feature greatly expands the capabilities of measurement systems where many devices need to be controlled. The simplified diagram of an I2C bus with three devices attached is shown in Fig.7.
Fig.7 Communications over an I2C bus require two wires called SCL and SDA. The SCL signal line is used to synchronize all data transfers via the I2C bus, while SDA is the data line. All devices on the I2C bus are connected to the SCL and SDA lines. Additionally, all devices must be connected to the “ground” or “common wire” of a given circuitry. SCL and SDAoperate as ″ open collector ″ or ″ open drain ″ signal lines, so two pull-up resistors must be connected between a +5V power supply and signal lines. Note, that the I2C bus requires only two pull-up resistors, regardless of the number of
devices attached. The ″ open collector ″ configuration allows driving of the line only when the device output becomes low. The value of the resistors may vary from about 2k to 30–40k. Greater values tend to slow the performance of the bus. The common value for pull-up resistors is 4.7k. Let’s see how devices communicate on the I2C bus. Each device on the I2C bus can be either ″ master ″ or ″ slave ″ .The ″ master ″ controls the bus by driving the SCLclock line. Other devices that are ″ slaves ″ respond to the ″ master ″ .When working as the ″ master ″ , a device can initiate a data transfer over the I2C bus. Usually, there is only single ″ master ″ on the I2C bus and multiple slaves. In the Raspberry Pi systems with attached I2C devices, the Raspberry Pi will be the ″ master ″ , while all other devices function as ″ slaves ″ . Notethat both ″ master ″ and ″ slave ″ can transfer data over the I2C bus, though that operation is always controlled by the ″ master ″ . The simplified timing diagram of the I2C bus is shown in Fig.8.
Fig.8 When working with an I2C bus, we should follow two rules. First, data transfer may be initiated only when the bus is not busy. Second, during data transfer, the data line must be kept steady whenever the SCL clock line is high. Changes on the data line while the clock line is high will be interpreted as control signals. Thus, each data bit can be clocked only on a rising edge of the SCL pulse. The control signals are known as ″ start sequence ″ ( ″ start condition ″ )and ″ stop sequence ″ ( ″ stop condition ″ ).Each transaction with the ″ slave ″ begins with the ″ start sequence ″ and ends with the ″ stop sequence ″ .Start and stop sequences are initiated by the ″ master ″ . To issue the ″ start sequence ″ the master brings the SDA line low while keeping SCL high (see Fig.8). The ″ stop sequence ″ is generated when the master raises the SDA line from low to high while SCL is kept high. When data is being transferred, the data on the SDA line must be kept steady whilst SCL
is high. Different I2C devices may require different transactions, so the number of data bytes transferred between start and stop sequences is not limited and is determined by the ″ master ″ . Nevertheless, each transaction begins with the address byte which must contain the address of the ″ slave ″ device and the type of operation. Addresses of I2C devices are either 7 or 10 bits. Most devices use 7 bit addresses that allow attaching up to 128 devices to the I2C bus. Besides the 7 bits ofthe address, the ″ master ″ always sends an 8thbit that indicates if the ″ master ″ is writing to / reading from the ″ slave ″ ;if the bit is zero the ″ master ″ is writing to the ″ slave ″ ,a value ″ 1”in this bit indicates that the ″ master ″ is reading from the ″ slave ″ . The 7 bit address takes the higher 7 bits of the byte, while the Read/Write (R/W) bit is the LSB (Least Significant Bit) as shown in Fig.8. Depending upon the state of the R/W bit two types of data transfer may be performed. When data is transferred from the ″ master ″ to the ″ slave ″ , the address byte transmitted by the ″ master ″ containsthe ″ slave ″ address. Next follows a number of data bytes. The ″ slave ″ returns an acknowledge bit after each byte received. The second case is when data is transferred from the ″ slave ″ tothe ″ master ″ .The address byte, as usual, contains the ″ slave ″ address.The ″ slave ″ also returns an acknowledge bit. Next follows a number of data bytes transmitted by the ″ slave ″ to the ″ master ″ . The ″ master ″ returns an acknowledge bit after all bytes has been received. At the end of the last received byte, a ″ Not Acknowledge ″ bit is returned. Before using an I2C interface with the Raspberry Pi board we need to accomplish a few preparation steps. First, edit the /etc/modprobe.d/raspi-blacklist.conf file. To do that we can call the nano editor by the following statement: sudo nano /etc/modprobe.d/raspi-blacklist.conf
We will see a comment followed by two lines: # blacklist spi and i2c by default (many users don’t need them) blacklist spi-bcm2708 blacklist i2c-bcm2708
We need to comment out the line with I2C by adding a hash. The content of the file should look like the following: # blacklist spi and i2c by default (many users don’t need them) blacklist spi-bcm2708 #blacklist i2c-bcm2708 Second, we have to add the I2C module to the kernel. To do that we have to edit the file /etc/modules in the text editor (nano, in the given case): sudo nano /etc/modules We should see the following: # /etc/modules: kernel modules to load at boot time. # # This file contains the names of kernel modules that should be loaded # at the boot time, one per line. Lines beginning with “#” are ignored. # Parameters can be specified after the module name. snd-bcm2835 We have to add the i2c-dev to the end of the file. The content of the file will look like the following: # /etc/modules: kernel modules to load at boot time. # # This file contains the names of kernel modules that should be loaded # at boot time, one per line. Lines beginning with “#” are ignored. # Parameters can be specified after the module name.
snd-bcm2835 i2c-dev At the next step we should install a few packages required for handling the I2C interface. First of those packages, i2c-tools, may be installed by the command: sudo apt-get install i2c-tools Since we want to develop programs with the Python language, we have to install the python-smbus package by executing the following command: sudo apt-get install python-smbus To configure the software we must add the pi user to the I2C access group by running the command: sudo adduser pi i2c Once it has been done, we should reboot the system: sudo reboot After configuring the I2C interface we can apply it in our projects. The first of them discussed below illustrates using the PCF8574 I/O expander driven through I2C bus.
The I/O expander using PCF8574 Expanding GPIO port can be implemented using a low-cost I/O expander PCF8574 IC driven via I2C. PCF8574 has an 8-bit quasi–bidirectional I/O port (P0-P7), including latched outputs with high-current-drive capability for directly driving LEDs or small relays. Each quasi–bidirectional I/O port can be used as an input or output without the use of a data–direction control signal. The functional diagram of the PCF8574 chip is shown in Fig.9.
Fig.9 At power on, the I/O ports become inputs (high state) by default. In this mode, only a small current source to VCC is active. An additional strong pull–up to VCC during the last clock pulse allows fast rising edges into heavily loaded outputs. This device turns on when an output is written high, and is switched off by the negative edge of the SCL signal. The I/O ports should be high before being used as inputs. Note that you should be aware that if the quasi bi-directional input is written high, while it is externally being forced low, there will be a 1 clock long pulse of higher current ~1mA drawn from VCC to the output. I2C communication with this device is initiated by a ″ master ″ sending a start condition, a high-to-low transition on the SDA line while the SCL input is high. After the start condition, the device address byte is sent; most-significant bit (MSB) first, including the
data direction bit (R/W). This device does not respond to the general call address. After receiving the valid address byte, this device responds with an acknowledge by setting a low level on the SDA line during the high state of the acknowledge–related clock pulse. The address inputs (A0 – A2) of the slave device must not be changed between the start and the stop conditions. The data byte follows the address acknowledge. If the R/W bit is high, the data from this device are the values read from the P port. If the R/W bit is low, the data are from the ″ master ″ , to be output to the P port. The data byte is followed by the acknowledge sent from this device. If other data bytes are sent from the master, following the acknowledge, they are ignored by this device. Data are output only if complete bytes are received and acknowledged. The output data will be valid some time after the low–to–high transition of SCL and during the stop condition. The stop condition sent by the master is a low-to-high transition on the SDA line while the SCL input is high. The Fig.10 shows the interface definition.
Fig.10 The following demo project illustrates using input and output ports of PCF8574 for data transfer between external circuitry and the Raspberry Pi board. The schematic circuit of this project is shown in Fig.11.
Fig.11 In this circuit, the SCL (pin 14) and SDA (pin 15) signal lines of PCF8574A are connected to the SCL and SDA headers on the connector P1 of the Raspberry Pi board. The connections between the Raspberry Pi board and PCF8574A are detailed in the following table. Raspberry Pi P1 pin
PCF8574A pin
SCL
14 (SCL)
SDA
15 (SDA)
The common wire of the Raspberry Pi board (the ″ GND ″ pin) must be wired to the common wire of the PCF8574A circuit. The I/O expander circuit can be supplied either from an on-board voltage source ( ″ 3V ″ terminal) or a stand-alone DC 3.3V power supply. In this project, the LM1117-3.3 LDO regulator fed by the +5V gives +3.3V (Imax = 800 mA) for our circuit.
To program the I2C interface in this and other projects we will use the functions listed in the table below. SMBus functions Function
Description Parameters
Return value
SMBus Access write_quick(addr)
Quick int addr transaction.
long
read_byte(addr)
Read Byte int addr transaction.
long
write_byte(addr,val)
int Write Byte addr,char long transaction. val
read_byte_data(addr,cmd)
Read Byte int Data addr,char long transaction. cmd
write_byte_data(addr,cmd,val)
int Write Byte addr,char Data long cmd,char transaction. val
read_word_data(addr,cmd)
Read Word int Data addr,char long transaction. cmd
write_word_data(addr,cmd,val)
Write Word int Data addr,char long transaction. cmd,int val
process_call(addr,cmd,val)
Process int Call addr,char long transaction. cmd,int val Read Block int
read_block_data(addr,cmd)
Data addr,char long[] transaction. cmd
write_block_data(addr,cmd,vals)
Write Block int addr, Data char cmd, None transaction. long[]
block_process_call(addr,cmd,vals)
Block Process Call transaction.
int addr,char long[] cmd, long[]
I2C Access read_i2c_block_data(addr,cmd)
Block Read int addr, transaction. char cmd
long[]
int Block Write addr,char write_i2c_block_data(addr,cmd,vals) None transaction. cmd, long[] The Python source code handling PCF8574A is given in Listing 4. Listing 4. import smbus import time bus = smbus.SMBus(1) bWrite = 0xFF mask = 0xF7 # mask for bit P3 (0x8) bus.write_byte(0x38, 0xF7) # bringing outputs high # before reading (except P3) while (True): res = bus.read_byte(0x38)
res &= 0xC0 # reading bits P6-P7 state print(‘P6-P7 bit state: 0x%X’ %res) if ((res == 0x0) or (res == 0xC0)): # nothing happens if # the states of pins P6-P7 are the same time.sleep(2) continue if (res == 0x80): # if P6 is low bWrite = 0x8 | mask # the LOAD (pin 3) is ON bus.write_byte(0x38, bWrite) if (res == 0x40): # if P7 is low # the LOAD is driven OFF bWrite &= mask bus.write_byte(0x38, bWrite) time.sleep(2) The address of the PCF8574 device is 0x38 – that has been determined by the command: i2cdetect –y 1 The statement bus.write_byte(0x38, 0xF7) configures ports P0 – P2 and P4 – P7 as inputs. Port P3 is configured as output, so we can write any value ( ″ 0 ″ or ″ 1 ″ )to it. Remember that we can’t write ″ 0 ″ to a port if it is used as input. The program code periodically reads P6 – P7 inputs, and, depending on the binary code obtained, drives the P3 output either high or low. When both switches (SW1 – SW2) are simultaneously shorted to ground or opened, the pin outputs are left unchanged. That is checked by the following sequence: if ((res == 0x0) or (res == 0xC0)):
time.sleep(2) continue When the P6 port goes low while P7 stays high (the SW1 button is pressed), the P3 output is pulled high thus switching the transistor Q1 ON. This allows the LOAD to be fed by the +5V DC power supply. That is done by the following statements: if (res == 0x80): bWrite = 0x8 | mask bus.write_byte(0x38, bWrite) In the case when the P6 input stays high and P7 is brought low (SW2 is pressed), the transistor Q1 becomes cut off, so the power supply is disconnected from the LOAD. That is realized by the following fragment of code: if (res == 0x40): bWrite &= mask bus.write_byte(0x38, bWrite) Both inputs, P6 and P7, are checked every 2 seconds (the sleep(2) function).
The I/O expander using MCP23008 GPIO expanding can also be implemented using an MCP23008 I/O expander IC. The MCP23008 device provides 8-bit, general purpose, parallel I/O expansion for I2C bus. The MCP23008 consists of multiple 8-bit configuration registers for input, output and polarity selection. The system master can enable the I/O ports as either inputs or outputs by writing the I/O configuration bits. The data for each input or output is kept in the corresponding Input or Output register. The polarity of the Input Port register can be inverted with the Polarity Inversion register. All registers can be read by the system master. The MCP23008 device contains eleven registers that can be addressed through the I2C interface. All registers are listed in the table below. Register addresses Address
Access to:
00h
IODIR
01h
IPOL
02h
GPINTEN
03h
DEFVAL
04h
INTCON
05h
IOCON
06h
GPPU
07h
INTF
08h
INTCAP (Readonly)
09h
GPIO
0Ah
OLAT
In the simplest configuration, we need to configure only the IODIR register before accessing bits of the GP port of MCP23008. Each bit in IODIR controls the direction of
data I/O in the corresponding bit in GP. When the IODIR bit is set, the corresponding GP pin acts as input. Conversely, clearing the IODIR bit configures the corresponding GP pin as output. After writing the configuration word to the IODIR register an application can transfer data through the GP port. The next demo project illustrates basic operations over MCP23008 device; the schematic circuit is given in Fig.12.
Fig.12 Here the SCL (pin 1) and SDA (pin 2) signal lines of the MCP23008 I/O expander are connected to the SCL and SDA headers on the P1 connector of the Raspberry Pi board. The connections between the Raspberry Pi board and MCP23008 are detailed in the table below.
Raspberry Pi P1 pin
MCP23008 pin
SCL
1 (SCL)
SDA
2 (SDA)
The common wire of the Raspberry Pi board (the ″ GND ″ pin) must be wired to the common wire of the MCP23008 circuit. The I/O expander can be powered from either the on-board ( ″ 3V ″ terminal) or stand-alone DC power supply. In this project, the LDO device LM1117-3.3 provides a +3.3V for the I/O expander circuit. The system operates in the following way. The software periodically reads the state of the pin GP4 tied to the switch SW2. When SW2 has been pressed, the port GP2 reverses its output, thereby driving the LED ON/OFF. When SW2 has been released, GP2 keeps its last state until the GP4 input stays high. The address of MCP23008 on the I2C bus determined by the i2cdetect command is 0x20 (Fig.13).
Fig.13 The Python source code for driving the MCP23008 I/O Expander is given in Listing 5. Listing 5. import smbus import time bus = smbus.SMBus(1) bus.write_byte_data(0x20, 0x00, 0x10) # access IODIR to set all GPIO # bits as outputs except
# bit 4 (GP4, pin 14) while (True): bRead = bus.read_byte_data(0x20, 0x9) # check the state of bit 4 # (GP4, pin 14) bRead &= 0x10 if (bRead == 0x0): # if button SW2 has been # pressed ,the LED # (GP2, pin 12) flashes print(‘GP4 is low.’) bus.write_byte_data(0x20, 0x9, 4) time.sleep(1) bus.write_byte_data(0x20, 0x9, 0) time.sleep(1) First, we configure almost all GP pins as outputs, except GP4 that will operate as input. This is accomplished by the next statement which writes the appropriate binary code to the IODIR register (address 0x0): bus.write_byte_data(0x20, 0x00, 0x10) Then the program enters the endless while() loop periodically checking the pin GP4: bRead = bus.read_byte_data(0x20, 0x9) bRead &= 0x10 if (bRead == 0x0): … If GP4 is pressed (the bRead variable is assigned the value 0x0), the LED on pin GP2 starts blinking.
The temperature measurement system using I2C The following project allows to build the temperature measurement system using a temperature sensor DS1621. This sensor is configured and read through the I2C interface. The data produced by this sensor is in a digital format suitable for immediate processing. DS1621 is one of numerous sensors which provide digital (binary) output code of the physical quantity being measured. Those sensors are usually called ″ digital ″ sensors and include built–in analog sensors with conditioning circuits and Delta–Sigma analog–to– digital converters for digitizing analog signals. The basic parameters (resolution, accuracy, mode of measurements, etc.) of those sensors are configured by sending command words and accompanied byte blocks either through SPI or I2C interface. This demo project can be used as a template for developing measurement systems with other types of digital sensors. Let’s begin with the brief description of the DS1621 digital sensor taken from its datasheet. The DS1621 sensor serves as the Digital Thermometer and Thermostat providing 9-bit temperature readings, which indicate the temperature of the device. The thermal alarm output, Tout, is active when the temperature of the device exceeds a user-defined temperature TH. The output remains active until the temperature drops below user defined temperature TL, allowing for any hysteresis if necessary. User–defined temperature settings are stored in non–volatile memory, so parts may be programmed prior to insertion in a system. Temperature settings and temperature readings are all communicated to/from the DS1621 over a simple 2–wire serial I2C interface. In our project we will show how to implement a high accuracy temperature measurement with DS1621. The table below contains the detailed pin description of DS1621. Detailed pin description of DS1621 Pin
Symbol
Description
1
SDA
Data input/output pin for 2-wire serial
communication port 2
SCL
Clock input/output pin for 2-wire serial communication port
3
TOUT
Thermostat output. Active when temperature exceeds TH; will reset when temperature falls below TL
4
GND
Ground pin
5
A2
Address input pin
6
A1
Address input pin
7
A0
Address input pin
8
VDD
Supply voltage input power pin (2.7V to 5.5V)
The DS1621 sensor measures temperature using a bandgap – based temperature sensor. A Delta – Sigma analog – to – digital converter (ADC) converts the measured temperature to a digital value that is calibrated in °C; for °F applications, a lookup table or conversion routine must be used. The temperature reading is provided in a 9–bit, two’s complement reading by issuing the READ TEMPERATURE command. The table below describes the exact relationship of output data to measured temperature.
TEMPERATURE/DATA RELATIONSHIPS TEMPERATURE DIGITAL OUTPUT (Binary)
DIGITAL OUTPUT (Hex)
+125°C
01111101 00000000
7D00h
+25°C
00011001 00000000
1900h
+½°C
00000000 10000000
0080h
+0°C
00000000 00000000
0000h
-½°C
11111111 10000000
FF80h
-25°C
11100111 00000000
E700h
-55°C
11001001 00000000
C900h
Since the bit stream is transmitted over the 2–wire bus MSB first, the temperature data may be written to/read from the DS1621 as either a single byte (with temperature resolution of 1ºC) or as two bytes. The second byte would contain the value of the least significant (0.5ºC) bit of the temperature reading as it is seen from the table above. Note that the remaining 7 bits of this byte are set to all “0”s. Temperature is represented in the DS1621 in terms of a ½ºC LSB, yielding the following 9–bit format (Fig.14):
Fig.14 Higher resolutions may be obtained by reading the temperature and truncating the 0.5ºC bit (the LSB) from the read value. This value is called TEMP_READ. A Read Counter command should be issued to yield the COUNT_REMAIN value. The Read Slope command should then be issued to obtain the COUNT_PER_C value. The higher resolution temperature may then be calculated by the user using the formula shown below in Fig.15:
Fig.15 The DS1621 sensor always powers up in a low power idle state, and the Start Convert T command must be used to initiate conversions. DS1621 can be programmed to perform continuous consecutive conversions (continuous-conversion mode) or to perform single conversions on command (one-shot mode). The conversion mode is programmed through the 1SHOT bit in the configuration register as explained in the Operation and Control section of the datasheet. In continuous conversion mode, the DS1621 begins continuous conversions after a Start Convert T command is issued. Consecutive conversions continue to be performed until a Stop Convert T command is issued, at which time the device goes into a low-power idle state. Continuous conversions can be restarted at any
time using the Start Convert T command. In one-shot mode, the DS1621 performs a single temperature conversion when a Start Convert T command is issued. When the conversion is complete, the device enters a low-power idle state and remains in that state until a single temperature conversion is again initiated by a Start Convert T command. The DS1621 must have temperature settings resident in the TH and TL registers for thermostatic operation. A configuration/status register also determines the method of operation that the DS1621 sensor will use in a particular application, as well as indicating the status of the temperature conversion operation. The configuration register bits are defined as follows (Fig.16):
Fig.16 Below is a brief explanation of the bits. DONE = Conversion Donebit. ″ 1 ″ = Conversion complete, ″ 0 ″ = Conversion in progress. THF= Temperature High Flag. This bit will be set to ″ 1 ″ when the temperature is greater than or equal to the value of TH. It will remain ″ 1 ″ untilreset by writing ″ 0 ″ into this location or removing power from the device. This feature provides a method of determining if the DS1621 has ever been subjected to temperatures above TH while power has been applied. TLF = Temperature Low Flag. This bit will be set to ″ 1 ″ when the temperature is less than or equal to the value of TL. It will remain ″ 1 ″ untilreset by writing ″ 0 ″ into this location or removing power from the device. This feature provides a method of determining if the DS1621 has ever been subjected to temperatures below TL while power has been applied. NVB= Nonvolatile Memory Busy flag. ″ 1 ″ = Write to an E2 memory cell in progress, ″ 0 ″ = nonvolatile memory is not busy. A copy to E2 may take up to 10 ms. POL = Output Polarity Bit. ″ 1 ″ =active high, ″ 0 ″ = active low. This bit is nonvolatile. 1SHOT = One Shot Mode. If 1SHOTis ″ 1 ″ , the DS1621 will perform one temperature conversion upon receipt of the Start Convert T protocol. If 1SHOTis ″ 0 ″ , the DS1621 will continuously perform temperature conversions. This bit is nonvolatile.
X = Reserved. For typical thermostat operation, the DS1621 will operate in the continuous mode. However, for applications where only one reading is needed at certain times or to conserve power, the one-shot mode may be used. Note that the thermostat output (TOUT) will remain in the state it was in after the last valid temperature conversion cycle when operating in one-shot mode. A control byte is the first byte received following the START condition from the master device. The control byte consists of a 4-bit control code; for the DS1621, this is set to 1001 binary for read and write operations. The next 3 bits of the control byte are the device select bits (A2 – A0). They are used by the master device to select which of eight devices are to be accessed. These bits are in effect the 3 least significant bits of the slave address. The last bit of the control byte (R/W) defines the operation to be performed. When set to a ″ 1 ″ aread operation is selected, when set to a ″ 0 ″ a write operation is selected. Following the START condition the DS1621 monitors the SDA line checking the device type identifier being transmitted. Upon receiving the 1001 code and appropriate device select bits, the slave device outputs an acknowledge signal on the SDA line. The schematic circuit for our project is given in Fig.17.
Fig.17 Here the SCL (pin 2) and SDA (pin 1) signal lines of the DS1621 digital temperature sensor are connected to the SCL and SDA headers on the P1 connector of the Raspberry Pi board, respectively. The connections between the Raspberry Pi board and DS1621 are detailed in the following table. Raspberry Pi P1 pin
DS1621 pin
SCL
2 (SCL)
SDA
1 (SDA)
The Python source code for driving this system is given in Listing 6. Listing 6. import smbus
import time addrDS1621 = 0x90 >> 1 bus = smbus.SMBus(1) bus.write_byte_data(addrDS1621, 0xAC, 0x3) # Access Config command # which sets the One # Shot conversion mode while (True): bus.write_byte(addrDS1621, 0xEE) # start conversion time.sleep(3) tmp = bus.read_i2c_block_data(addrDS1621, 0xAA) # for the higher resolution we need to shed the LSB # and take the slope and the counter as well bus.write_byte(addrDS1621, 0xA8) # Read Counter command counter = float(bus.read_byte(addrDS1621)) # reading Counter bus.write_byte(addrDS1621, 0xA9) # Read Slope command slope = float(bus.read_byte(addrDS1621)) # reading Slope temp = float(tmp[0]) -0.25 + (slope -counter) / slope print(‘TEMPERATURE in the ROOM, deg.Celsius: %4.2f’%temp) First, we need is to configure the mode of operation. We will perform the single measurement (one shot mode) every 3 seconds, so the value 0x3 must be written in the IODIR register: bus.write_byte_data(addrDS1621, 0xAC, 0x3) Here the 0xAC code corresponds to the Access Config command.
Then program enters the endless while loop which begins with the Start Conversion (0xEE) command. The following statement starts the conversion: bus.write_byte(addrDS1621, 0xEE) Then the program waits 3 s (sleep(3) function) until the conversion is complete. Usually, the measurement cycle takes less than 1 s, so 3 s is more than enough to obtain the result. After 3 s interval is over, the program reads the high byte of the binary code representing the actual temperature: tmp = bus.read_i2c_block_data(addrDS1621, 0xAA) Here the code 0xAA indicates the Read Temperature command; the result is moved into the tmp variable. Since we want to implement measuring with the high resolution, we have to read the counter and slope as it is shown in formula from Fig.15. The following 4 statements sequentially read the counter and slope: bus.write_byte(addrDS1621, 0xA8) counter = float(bus.read_byte(addrDS1621)) bus.write_byte(addrDS1621, 0xA9) slope = float(bus.read_byte(addrDS1621)) Here the 0xA8 code corresponds to the Read Counter command and 0xA9 is the code of the Read Slope command. The counter and slope variables will hold the results. The actual temperature is then calculated according to the formula from Fig.15 by the next statement: temp = float(tmp[0]) -0.25 + (slope -counter) / slope The result goes to the console (the print function). The output window of the running application is given below (Fig.18).
Fig.18
Measuring analog signals using an analog-to-digital converter MCP3201 Measuring continuous (analog) signals requires some type of an analog-to-digital converter (ADC, A/D converter). The popular types of ADCs used in hobby projects are SAR or Delta-Sigma devices. This section is dedicated to using SAR (Successive Approximation Register) A/D converters. The term ″ SAR ″ refers to the device that uses the approximation technique to convert an analog input signal into a digital output code. SAR converters can typically operate in the 8- to 16-bit range and can have sample speeds up to several MSPS. One major benefit of a SAR converter is its ability to be connected to multiplexed inputs at a high data acquisition rate. The input is sampled and held on an internal capacitor, and this charge is converted to a digital output code using the successive approximation routine. Since this charge is held throughout the conversion time, only the initial sample and hold period or acquisition time is of concern to a fast-changing input. The conversion time is the same for all conversions. This makes the SAR converter ideal for many realtime applications, including motor control, touch-screen sensing, medical and other data acquisition systems. The next project describes programming a popular SAR analog-to-digital converter MCP3201 from Microchip Inc. This is a low-cost 12-bit single channel device driven through the SPI-compatible interface. The brief description of the device is given below (see more in the datasheet). The Microchip MCP3201 device is a successive approximation 12-bit analog-to-digital converter with on-board sample and hold circuitry. The device provides a single pseudodifferential input. Communication with the device can be established via a simple serial SPI-compatible interface. The device can operate with sampling rates up to 100 ksps at a clock rate of 1.6 MHz. The power supply to MCP3201 device may be of 2.7V through 5.5V. Low-current design permits operation with typical standby and active currents of only 500 nA and 300 µA, respectively. The schematic circuit of the project is shown below (Fig.19).
Fig.19 In this circuit, the analog input voltage to the MCP3201 (pin 2) is taken from the wiper of potentiometer R1. In real life, the analog signal may come from any sensor providing continuous output voltage. The connections between the Raspberry Pi and ADC MCP3201 are detailed in the following table. Raspberry Pi P1 pin
MCP3201 pin
GPIO24
7 (CLK)
GPIO23
6 (DOUT)
GPIO18
5 (CS)
The common wire of the Raspberry Pi board (the ″ GND ″ pin) must be wired to the common wire of the MCP3201 circuit. The MCP3201 device can be powered either from the board ( ″ 3V ″ terminal) or a stand-alone +3.3V DC power supply. To simplify the circuit the voltage reference to the VREF input (pin 1 of MCP3201) is taken from the power rail +3.3V, although high-precision measurements require the high stable reference voltage on the VREF input; this can be provided, for example, by a suitable stand-alone voltage reference IC. The binary data stream representing the input voltage is taken in succession through an SPI-compatible interface. The interface is driven by CS, DOUT and CLK lines according
to the timing diagram shown in Fig.20.
Fig.20 The single measurement cycle involves two phases. In the first phase the data sampling is executed during some time TSAMPLE. In the second phase the result of conversion (12-bit data stream) is transferred to a receiver (our Raspberry Pi board). The measurement cycle of the MCP3201 device begins with the CS signal going low. If the device was powered up with the CS pin low, it must be brought high and back low to initiate communication. The device will begin to sample an analog input on the first rising edge of CLK after CS is brought low. The sample period will end on the falling edge of the second CLK clock, at which time a device will output a low null bit. Next 12 clocks will output the result of a conversion with MSB going first. Data is always output from a device on the falling edge of the clock; a receiver should pick up the data bit on the rising edge of the CLK pulse. If all 12 data bits have been transmitted and the device continues to receive clocks while CS is held low, the device will output the conversion result with LSB going first. If more clocks are provided to the device while CS is still low (after the LSB first data has been transmitted), the device will indefinitely clock out zeros. The reference input VREF is determined by the analog input voltage range and the LSB size as: LSB size = VREF / 212 = VREF / 4096 As the reference input is reduced, the LSB size is reduced as well. The theoretical digital output code produced by the A/D Converter is a function of the analog input signal and the reference input. That fact is reflected by the formula:
Digital Output Code = (VIN * 4096) / VREF, where: VIN – the analog input voltage between pins IN+ and IN-, VREF – the reference voltage. The conversion process is driven by the Python application whose source code is shown in Listing 7. Listing 7. import RPi.GPIO as GPIO CS = 18 # GPIO18 as CS DOUT = 23 # GPIO23 as DOUT CLK = 24 # GPIO24 as CLK Vref = 3.27 # Vref = 3.27V GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) # using GPIO numbers rather than pin numbers GPIO.setup(CS, GPIO.OUT) # set GPIO18 as output GPIO.setup(DOUT, GPIO.IN) # set GPIO23 as input GPIO.setup(CLK, GPIO.OUT) # set GPIO24 as output binData = 0; GPIO.output(CS, True) # CS is brough high GPIO.output(CLK, True) # CLK goes high while CS is held high
GPIO.output(CS, False) # CS goes low thus starting conversion i1 = 14 while (i1 >= 0): GPIO.output(CLK, False) bitDOUT = GPIO.input(DOUT) GPIO.output(CLK, True) bitDOUT = bitDOUT << i1 binData |= bitDOUT i1 -= 1 GPIO.output(CS, True) binData &= 0xFFF res = Vref * binData/4096.0 print(res) Once the SPI-compatible interface uses pins GPIO18, GPIO23 and GPIO24, the program first initializes those for input/output operations using the sequence: GPIO.setmode(GPIO.BCM) GPIO.setup(CS, GPIO.OUT) GPIO.setup(DOUT, GPIO.IN) GPIO.setup(CLK, GPIO.OUT) The measurement cycle is performed by the while loop. As the timing diagram implies, the full measurement cycle requires 14 clock pulses to complete. Therefore, the loop variable i1 runs from 14 down to 0. Before the while loop is entered, the conversion is enabled by bringing the CS line up then down. Those operations are performed by the sequence: GPIO.output(CS, True) GPIO.output(CLK, True) GPIO.output(CS, False)
In each iteration, the bitDOUT variable is assigned the current data bit taken from the DOUT line upon the raising edge of the CLK pulse. That is done by the statements: GPIO.output(CLK, False) bitDOUT = GPIO.input(DOUT) GPIO.output(CLK, True) The current data bit then goes to the appropriate position in the binData variable after executing the following two statements: bitDOUT = bitDOUT << i1 binData |= bitDOUT After all bits have been transferred, the while loop exits and the conversion ends by bringing the CS line high: GPIO.output(CS, True) The binary code representing the analog input voltage is held in the binData variable. The only lower 12 bits should be taken to form the result, so the next statement is executed: binData &= 0xFFF; Then the binary code in binData is converted into the float representation and the result is sent to the console: res = Vref * binData/4096.0 print(res) Assuming that the source code is saved in the ADC_Test.py file, we can launch the Python application by typing:
sudo python ADC_Test.py To perform measurements periodically, say, every 5 second, we can use the following Python source code (Listing 8). Listing 8. from time import sleep import RPi.GPIO as GPIO CS = 18 # GPIO18 as CS DOUT = 23 # GPIO23 as DOUT CLK = 24 # GPIO24 as CLK Vref = 3.29 # Vref = 3.29V GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) # using GPIO numbers rather than pin numbers GPIO.setup(CS, GPIO.OUT) # set GPIO18 as output GPIO.setup(DOUT, GPIO.IN) # set GPIO23 as output GPIO.setup(CLK, GPIO.OUT) # set GPIO24 as output while (True): GPIO.output(CS, True) # CS is brough high GPIO.output(CLK, True) # CLK goes high while CS is held high GPIO.output(CS, False) # CS goes low thus starting conversion binData = 0 i1 = 14 while (i1 >= 0): GPIO.output(CLK, False) bitDOUT = GPIO.input(DOUT)
GPIO.output(CLK, True) bitDOUT = bitDOUT << i1 binData |= bitDOUT i1 -= 1 GPIO.output(CS, True) binData &= 0xFFF res = Vref * binData/4096.0 print(‘Input voltage = ‘ + str(res) + ‘V’) sleep(5) As compared to Listing 7, the above code contains an additional endless while loop with the sleep(5) function providing 5 s delay between the adjacent measurements.
Using an analog-to-digital converter MAX187 In this section we will discuss using another SAR A/D converter, a popular high-precision MAX187 IC. The MAX187/MAX189 serial 12-bit analog-to-digital converters operate from a single +5V supply and accept a 0 to 5V analog input. Both parts feature an 8.5Fs successive-approximation ADC, a fast track/hold (1.5Fs), an on-chip clock, and a highspeed 3-wire serial interface. The MAX187 digitizes signals at a 75ksps throughput rate. An external clock accesses data from the interface, which communicates without external hardware to most digital signal processors and microcontrollers. The interface is compatible with SPI, QSPIK, and MICROWIRE. The MAX187 has an on-chip buffered reference. Excellent AC characteristics and very low power consumption combined with ease of use and small package size make these converters ideal for remote DSP and sensor applications or for circuits where power consumption and space are crucial. The MAX187 convert input signals in the 0V to VREF range in 10µs, including track/hold (T/H) acquisition time. The MAX187’s internal reference is trimmed to 4.096V, while the device accepts external reference voltages from +2.5V to VDD. The serial interface requires only three digital lines: SCLK, CS and DOUT, and provides easy interface to microprocessors. The MAX187 can operate either with the internal or external reference. The internal reference operation is selected by forcing SHDN high; the external reference operation will be selected by floating SHDN. The following demo project illustrates use of MAX187 with the internal reference. The schematic circuit of the measurement system is shown in Fig.21.
Fig.21 In this circuit, the SCLK signal (pin 8 of MAX187) provides clocking while data transfer to the Raspberry Pi board. The SCLK line is tied to the GPIO18 pin. The CS signal (pin 7 of MAX187) wired to the GPIO23 enables the conversion and data transfer. The data stream being taken from the DOUT line (pin 6 of MAX187) is wired to the GPIO24 pin through the voltage divider using the resistors R1-R2. The R1-R2 network converts the 5V logical level on the DOUT output to the 3.3V level suitable for the Raspberry Pi digital inputs. Using the internal reference of MAX187 (4.096V) requires that the REF line (pin 4) be decoupled by the capacitor C3 of 4.7uF. Capacitors C1 and C2 should be tied as close as possible to the power input of the A/D converter (pin 1). To understand the theory of operation take a look at the timing diagram (Fig.22).
Fig.22
Conversion-start and data-read operations are controlled by the CS and SCLK digital inputs. A CS falling edge initiates a conversion sequence: the T/H stage holds input voltage, the ADC begins to convert, and DOUT changes from high impedance to logic low. SCLK must be kept inactive during the conversion. An internal register stores the data when the conversion is in progress. End of conversion (EOC) is signaled by DOUT going high. DOUT’s rising edge can be used as a framing signal. SCLK shifts the data out of this register any time after the conversion is complete. DOUT transitions on SCLK’s falling edge. The next falling clock edge produces the MSB of the conversion at DOUT, followed by the remaining bits. Since there are 12 data bits and one leading high bit, at least 13 falling clock edges are needed to shift out these bits. Extra clock pulses occurring after the conversion result has been clocked out, and prior to a rising edge of CS, produce trailing 0s at DOUT and have no effect on converter operation. For more detail you can consult the datasheet on the MAX187 device. The Python source code driving the A/D converter MAX187 is given in Listing 9. Listing 9. from time import sleep import RPi.GPIO as GPIO SCLK = 18 # GPIO18 as SCLK CS = 23 # GPIO23 as CS DOUT = 24 # GPIO24 as DOUT Vref = 4.096 # Internal reference Vref = 4.096V GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) # using GPIO numbers rather than pin numbers GPIO.setup(CS, GPIO.OUT) # set GPIO23 as output GPIO.setup(DOUT, GPIO.IN) # set GPIO24 as input GPIO.setup(SCLK, GPIO.OUT) # set GPIO18 as output
while (True): GPIO.output(SCLK, False) GPIO.output(CS, True) GPIO.output(CS, False) bitDOUT = 0x0 while (bitDOUT == 0x0): bitDOUT = GPIO.input(DOUT) binData = 0 i1 = 11 while (i1 >= 0): GPIO.output(SCLK, True) GPIO.output(SCLK, False) bitDOUT = GPIO.input(DOUT) bitDOUT = bitDOUT << i1 binData |= bitDOUT i1 -= 1 GPIO.output(CS, True) binData &= 0xFFF res = Vref * binData/4096.0 print(‘Input voltage on pin AIN of MAX187 = ‘ + str(res) + ‘V’) sleep(5) Here the code fragment bitDOUT = 0x0 while (bitDOUT == 0x0): bitDOUT = GPIO.input(DOUT)
checks when the EOC bit has been set. When EOC = 1, the conversion is complete, so the program can read the data. The binary data stream will be transferred to the Raspberry Pi using the following while loop: while (i1 >= 0): GPIO.output(SCLK, True) GPIO.output(SCLK, False) bitDOUT = GPIO.input(DOUT) bitDOUT = bitDOUT << i1 binData |= bitDOUT i1 -= 1 Each iteration begins with bringing the SCLK line high. When SCLK goes low, the program should read the DOUT line. The current bit is kept in the bitDOUT variable and further is put in the appropriate position in the binData variable. After all bits have been transferred, the CS line is pulled high, so the conversion cycle is complete. Off the binary code obtained the only lower 12 bits are data bits; those bits are separated by the following statement: binData &= 0xFFF Afterwards the binary code in the binData variable is converted into its floating-point equivalent and the result goes to the console.
Using low-pass filters in data acquisition systems Almost all signals regardless of their origin are contaminated with noise – that is true whether we acknowledge that fact or not. The best method to get rid of noise riding on a useful signal is to apply an analog low-pass filter (LPF). LPF should be in every circuit where there is an analog-to-digital conversion. This is true regardless of type of an analogto-digital converter (ADC) used for digitizing signals. An LPF stage must always be placed on the analog side before an analog signal reaches an analog input of analog-to-digital converter. LPF allows to reduce the high frequency noise that is outside half of a sampling frequency of analog-to-digital converter. After digitizing an input signal we can also apply a digital filter to reduce the lower in-band, frequency noise. Besides removing the superimposed higher frequency noise from an analog signal, an LPF also eliminates extraneous noise peaks. Note that digital filters cannot eliminate such peaks. With an LPF standing prior to ADC, the task of successfully achieving highresolution is placed squarely on the analog circuit design and converter. The simplest analog low-pass filter can be composed of a resistor and capacitor (Fig.23).
Fig.23 An input signal is passed through the R1C1 pair which attenuates frequencies higher than 1/(2×π×R1×C1). A frequency determined by this expression is called a ″ cut-off ″ frequencyof a low-pass filter. As you can see, the ″ cut-off ″ frequency may be set through selection of values of the resistor R1 and capacitor C1. For slow speed input signals there is a reason to set the ″ cut-off ″ frequency as low as possible, close to DC. Adding an operational amplifier (op-amp) to an LPF significantly improves its characteristics. Such LPF (also called ″ active LPF ″ ) will match the impedances between a signal source and an input stage of ADC. The LPF can be combined with a buffer or amplifier. The simplest active analog 1st order LPF is shown in Fig.24.
Fig.24 As it is seen, the R1C1 pair is wired to the non-inverting input of the op-amp A1 arranged as a voltage follower. Almost any single-supply op-amp may be used in such circuit (MCP601, MCP6001, TL061, etc). Remember that a full range of an op-amp output voltage is usually some tens or hundreds millivolts less than the supply voltage, so input signals close to the power rail will be clipped. The same is true for signals close to 0V – they will also be clipped. A developer should take care when dealing with input signals close to either power rail. When processing noisy signals, the circuit shown in Fig.19 can be rearranged by introducing the 1st order active LPF prior to the A/D converter. The modified circuit is shown in Fig.25:
Fig.25 Here the analog input signal is passed through the active low-pass filter using ½ of the opamp MCP6022 (A2). With the given valuesof R1 and C1 the ″ cut-off ″ frequency will be about 30Hz – that allows to effectively suppress high frequency noise components. Any general purpose single supply op-amp (MCP6021, OPA364, etc.) can be applied in this low–pass filter. To reach the high degree of noise suppressing we can employ high-order (2nd and higher) low-pass filters – you can easily find out a lot of information on that topic by surfing Internet.
The simple thermostat system With analog-to-digital converters it is easily to build control systems where analog signals from censors can be used for driving actuators (motors, relays, heaters, etc.). A following simple thermostat system can switch on/off power to the load (say, a cooler) depending on temperature in a room. Such system will comprise a temperature sensor connected to the analog input (pin 2 of MCP3201) and a power switch composed of a Darlington transistor and a power relay. The power switch will be driven through a GPIO pin. Our system will check the temperature of environment using the temperature sensor LM35 that gives the output voltage proportional to the absolute temperature. The relation between the temperature and the output voltage appears as follows: Vout = K × T where Vout – voltage level at the sensor output, T – absolute temperature, K – the temperature coefficient that equals 10 mV/ºC. According to this formula, it is easily to calculate the temperature by measuring the LM35 output voltage. For example, the output voltage of 0.27V corresponds to the temperature 0.27/0.01 = 27ºC. The output of LM35 can be wired to the analog input of the A/D converter digitizing the analog signal and passing the result to the Raspberry Pi. Suppose that our system will turn on the cooler when the temperature exceeds 30ºC. This means that the program code of our system will constantly check if the analog input becomes 0.3V or greater. Upon reaching this voltage level (threshold) the program code drives the power switch on, so the cooler gets powered. Again, when the temperature in a room drops below 30ºC the power switch is driven off and the cooler stops working. Our system will operate in the infinite loop providing temperature measurements every 5 seconds. The schematic circuit of our thermostat system is given in Fig.26.
Fig.26 It is seen that the power network (R1, Q1) is connected to the GPIO25 pin. Here the Darlington BJT transistor Q1 (TIP122) is opened by the high level (log. ″ 1 ″ ) from the GPIO25 output; in our system that level corresponds to voltage close to +3.3V. The LOAD then gets its power from +5V stand-alone power supply through the opened transistor Q1. Conversely, the low level on the GPIO25 pin (log. ″ 0 ″ ) will cut off Q1 thus breaking the power chain to the LOAD. The diode D1 (indicated by the dashed line) protects the power transistor Q1 when driving the inductive load. Some words about the LOAD. This can be either power relay with a 5V coil or a solid state relay (SSR) with the inner optocoupler circuit. The system was tested with power PCB relay OMIH-SH-105D and SSR Omron G3NA-210B. The connections between the Raspberry Pi board and the external circuitry are given in the following table. Raspberry Pi P1 pin
MCP3201 pin
GPIO24
7 (CLK)
GPIO23
6 (DOUT)
GPIO18
5 (CS)
GPIO25
Left end of the resistor R1
The common wire of the Raspberry Pi board (the ″ GND ″ pin) must be wired to the common wire of the MCP3201 circuit. The source code handling the hardware is shown in Listing 10. Listing 10. from time import sleep import RPi.GPIO as GPIO CS = 18 # GPIO18 as CS DOUT = 23 # GPIO23 as DOUT CLK = 24 # GPIO24 as CLK SW = 25 # GPIO25 drives the power switch Vref = 3.29 # Vref = 3.29V GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) # using GPIO numbers rather than pin numbers GPIO.setup(CS, GPIO.OUT) # set GPIO18 as output GPIO.setup(DOUT, GPIO.IN) # set GPIO23 as input GPIO.setup(CLK, GPIO.OUT) # set GPIO24 as output GPIO.setup(SW, GPIO.OUT) # set GPIO25 as output GPIO.output(SW, False) # power switch is initially off while (True): GPIO.output(CS, True) # CS is brough high
GPIO.output(CLK, True) # CLK goes high while CS is held high GPIO.output(CS, False) # CS goes low thus starting conversion binData = 0 i1 = 14 while (i1 >= 0): GPIO.output(CLK, False) bitDOUT = GPIO.input(DOUT) GPIO.output(CLK, True) bitDOUT = bitDOUT << i1 binData |= bitDOUT i1 -= 1 GPIO.output(CS, True) binData &= 0xFFF res = Vref * binData/4096.0 if (res >= 0.3): GPIO.output(SW, True) print(‘The cooler is ON!’) else: GPIO.output(SW, False) print(‘The cooler is OFF!’) sleep(5) The program code accomplishes its task in the endless while (True) loop. First, the output voltage from the temperature sensor is picked up by executing the inner while (i1 >= 0) loop. The calculated value of the temperature goes to the res variable. The if…else statement checks whether res has exceeded 0.3 (that corresponds to 30ºC). Once it happens, the GPIO25 pin is driven high thus turning the power switch ON. Conversely, when the res variable has gained value less than 0.3 the power switch is driven OFF.
Processing small analog signals using instrumentation amplifiers This section covers a common approach to processing small (low-level) analog signals in the Raspberry Pi measurement systems. Small signals are usually present in measurement systems for biology, medicine and physics – to name but a few. High precision measurements of small signals usually require applying special devices called ″ instrumentation amplifiers ″ or, in short, in-amps. Instrumentation amplifiers (in-amps) show up in a broad spectrum of applications: measuring heart signals, factory monitoring equipment, aircraft controls, and even animal tagging. Developers have found them to be a simple and effective way to amplify small signals and remove power-line noise. A typical in-amp usually has two adjustments: gain and reference voltage. Unlike op-amps, where poor feedback design means oscillation, inamps are quite stable. The instrumentation amplifier’s ease of use can lead to a sense of unconcern. While it is easy to get an in-amp up and running on the bench, poor attention to detail can lead to mediocre performance in the field. Since an in-amp is typically connected directly to a sensor, designers must think about the full range of signals this sensor could present. To illustrate the concept we will discuss using two popular in-amps: INA326 from TI and AD623 from Analog Devices. When you know how those devices operate, you will easily apply any other type of numerous in-amps available. Besides using in conditioning circuits, in-amps allow to build precision current/voltage sources, analog active filters, integrators and much more useful circuits. Let’s begin with the INA326 device. Fig.27 shows the basic connections required for operation of the INA326 in-amp. A bypass capacitor of 0.1µF placed close to and across the power-supply pins is strongly recommended for the highest accuracy.
Fig.27 Here Vin+ and Vin- are pins where the differential input signal goes to. For measuring positive signals we can connect the inverting input (pin 2) of INA326 to the ″ ground ″ . Note that the single-supply operation may require R2 > 100k for the full output swing. This may produce the higher input referred offset voltage. The output filter R0C0 serves as an anti-aliasing filter prior to an A/D converter. The voltage gain G of this circuit is determined as G = 2 × (R2/R1) The table below (Fig.28) lists the calculated values for the gain G at different R1, R2 and C2.
Fig.28 The schematic circuit of the measurement system processing low-level analog signals with the INA326 in-amp will look like that shown in Fig.29.
Fig.29 With the given R1 and R2, the gain G of the amplifier A2 will be equal to 10. This fact will be reflected in the Python program when calculating the value of the input voltage on pin 3 of A2. The Python source code for this system is shown in Listing 11. Listing 11. from time import sleep import RPi.GPIO as GPIO CS = 18 # GPIO18 as CS DOUT = 23 # GPIO23 as DOUT CLK = 24 # GPIO24 as CLK
Vref = 3.29 # Vref = 3.29V Gain = 2 * (98.7 / 20) # Vout = G * Vin = 2 * (R2 / R1) * Vin GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) # using GPIO numbers rather than pin numbers GPIO.setup(CS, GPIO.OUT) # set GPIO18 as output GPIO.setup(DOUT, GPIO.IN) # set GPIO23 as output GPIO.setup(CLK, GPIO.OUT) # set GPIO24 as output while (True): GPIO.output(CS, True) # CS is brough high GPIO.output(CLK, True) # CLK goes high while CS is held high GPIO.output(CS, False) # CS goes low thus starting conversion binData = 0 i1 = 14 while (i1 >= 0): GPIO.output(CLK, False) bitDOUT = GPIO.input(DOUT) GPIO.output(CLK, True) bitDOUT = bitDOUT << i1 binData |= bitDOUT i1 -= 1 GPIO.output(CS, True) binData &= 0xFFF res = Vref * binData/4096.0 res = res / Gain print(‘Analog voltage at in-amp input = ‘ + str(res) + ‘V’) sleep(5)
Since the input voltage at the ADC input (pin 2 of MCP3201) is Gain (approximately 10) times greater than the voltage level at pin 3 of INA326, the value stored in the res variable is divided by the Gain constant: res = res / Gain Note that we need to know the exact values of resistors R1 and R2 in order to obtain the exact value of Gain. Our program will simply output the data collected onto the console. The data will also be saved in the text file for further processing and analysis. The Python source code (Listing 12) driving the circuit (Fig.29) is a modified version of Listing 11. Listing 12. from time import asctime, sleep import RPi.GPIO as GPIO CS = 18 # GPIO18 as CS –> pin 5 of MCP3201 DOUT = 23 # GPIO23 as DOUT—> pin 6 of MCP3201 CLK = 24 # GPIO24 as CLK–> pin 9 of MCP3201 Vref = 3.29 # Vref = 3.29V Gain = 2 * (98.6 / 20) # Vout = G * Vin = 2 * (R2 / R1) * Vin GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) # using GPIO numbers rather than pin numbers GPIO.setup(CS, GPIO.OUT) # set GPIO18 as output GPIO.setup(DOUT, GPIO.IN) # set GPIO23 as output GPIO.setup(CLK, GPIO.OUT) # set GPIO24 as output while (True):
GPIO.output(CS, True) # CS is brough high GPIO.output(CLK, True) # CLK goes high while CS is held high GPIO.output(CS, False) # CS goes low thus starting conversion binData = 0 i1 = 14 while (i1 >= 0): GPIO.output(CLK, False) bitDOUT = GPIO.input(DOUT) GPIO.output(CLK, True) bitDOUT = bitDOUT << i1 binData |= bitDOUT i1 -= 1 GPIO.output(CS, True) binData &= 0xFFF res = Vref * binData/4096.0 res = res / Gain print(‘An analog voltage at the in-amp input = %5.3f V’ %res) fo = open(‘AnalogIO_Data’, ‘ab+’) fo.write(asctime() + ‘ : Input voltage = %5.3f V\n’ %res) fo.close sleep(5) The data stored in the res variable are added at the end of the file AnalogIO_Data. If the fie doesn’t exist, it is created by the open function and the descriptor fo will be associated with the opened file. The write function appends new data every 5 s; the close function closes the file descriptor fo after the write operation has been completed. The running program (MCP3201_ADC_InAmp_Test.py) yields the following output:
pi@raspberrypi ~/developer $ sudo python MCP3201_ADC_InAmp_Test.py An analog voltage at the in-amp input = 0.162 V An analog voltage at the in-amp input = 0.069 V An analog voltage at the in-amp input = 0.069 V An analog voltage at the in-amp input = 0.147 V An analog voltage at the in-amp input = 0.178 V An analog voltage at the in-amp input = 0.212 V An analog voltage at the in-amp input = 0.212 V We can easily check data written in the AnalogIO_Data text file by entering the cat Linux command: pi@raspberrypi ~/developer $ cat AnalogIO_Data Wed Jan 23 19:13:13 2013 : Input voltage = 0.162 V Wed Jan 23 19:14:55 2013 : Input voltage = 0.162 V Wed Jan 23 19:15:00 2013 : Input voltage = 0.069 V Wed Jan 23 19:15:06 2013 : Input voltage = 0.069 V Wed Jan 23 19:15:11 2013 : Input voltage = 0.147 V Wed Jan 23 19:15:16 2013 : Input voltage = 0.178 V Wed Jan 23 19:15:21 2013 : Input voltage = 0.212 V Wed Jan 23 19:15:26 2013 : Input voltage = 0.212 V Take a look at one more project where the instrumentation amplifier AD623 from Analog Devices is used. The brief description of this device taken from the datasheet is given below. The AD623 device is an integrated single-supply instrumentation amplifier that delivers rail-to-rail output swing on a 3 V to 12 V supply. AD623 offers superior user flexibility by allowing single gain set resistor programming and by conforming to the 8-lead industry standard pinout configuration. With no external resistor, the AD623 in-amp is configured for unity gain (G = 1), and with an external resistor, AD623 can be programmed for gains up to 1000. The AD623 device holds errors to a minimum by providing superior CMRR that increases with increasing gain. Line noise, as well as line harmonics, is rejected because the CMRR
remains constant up to 200 Hz. The AD623 in-amp has a wide input common mode range and can amplify signals that have a common-mode voltage 150 mV below ground. Although the design of AD623 was optimized to operate from a single supply, this chip still provides superior performance when powered from a dual voltage supply (±2.5 V to ±6.0 V). The schematic circuit of the conditioning circuit with the AD623 in-amp is shown in Fig.30.
Fig.30 In this circuit, the input voltage to be measured (VIN) is fed to the IN+ input while IN- is wired to ground. The amplified input voltage appears on pin 6 of A1 and drives the A/D converter input (pin 2 of MCP3201). The measurement circuit using AD623 will amplify the differential signal Vdif between +IN and –IN inputs according to the following formula: VOUT = (1 + 100K / RG) × Vdif + VREF,
where Vout is the output voltage on pin 6 of AD623, RG is the value of the resistor connected between pins 1 and 8, Vdif is the differential voltage applied between the +IN and –IN inputs and VREF is the reference voltage applied to the REF input (pin 5). As it is seen from the above formula, the gain G of the AD623 amplifier is determined by the single resistor RG: G = 1 + 100k / RG In our case, the gain G is equal to 3. Since the differential input voltage Vdif = VIN-0, the output voltage VOUT turns out to be VOUT = G ×VIN + VREF The above relationships should be taken into consideration when calculating the measured input voltage within the program code. The reference terminal REF (pin 5 of AD623) is assigned the potential which determines the output voltage at Vdif = 0; that is especially useful when the load does not share a precise ground with the rest of the system. It provides a direct means of injecting a precise offset to the output. The reference terminal is also useful when bipolar signals are being amplified because it can be used to provide a virtual ground voltage. The voltage on the reference terminal can be varied in the full range from the negative to positive power rail. Note that the REF pin should be tied to a low impedance point for optimal common-mode rejection (CMR), so don’t connect the resistive divider formed by R1 directly to the REF pin. In our circuit, the voltage from the divider R1 is fed to pin 5 of op-amp OPA364. The voltage follower formed by the op-amp OPA364 acts as a buffer providing very low output impedance suitable for REF. The potentiometer R1 should be of high precision and have the impedance from some tens Ohms to several KOhms. You can take any general purpose single supply op-amp (OPA348, OPA248, MCP601, MCP6021, etc.) to put in the voltage follower. But, remember, in order to process bipolar input signals you need to select the proper VREF voltage so that to avoid the distortion of input signals. Some words about powering the in-amp AD623. The power supply is connected to +VS and –VS terminals. The supply can be either bipolar (VS = ±2.5V to ±6V) or single supply
(-VS = 0V, +VS = 3.0V to 12V). Power supplies should be capacitively decoupled close to the power pins of the device. For the best results, use surface-mount 0.1 µF ceramic chip capacitors and 10 µF electrolytic tantalum capacitors. For better understanding a device, it would be worth learning the AD623 datasheet before the in-amp is put in the circuit.
Using high-resolution Delta-Sigma analog-to-digital converters To perform analog-to-digital conversions with very high precision we need to take some delta-sigma (also called ″ sigma-delta ″ ) analog-to-digital converter. The delta-sigma (ΔΣ) ADC is the converter of choice for modern voice band, audio and high precision industrial measurement applications. The delta-sigma ADC tackles the application demands of a slow analog signal that requires a high signal-to-noise-ratio (SNR) and wide dynamic range. Delta-sigma converters are ideal for converting signals over a wide range of frequencies from DC to several MHz with very high resolution. The signal chain for the delta-sigma converter application starts with the sensor (Fig.31). Unlike circuits with SAR analog-to-digital converters, Δ-Σ devices don’t require additional analog gain circuits such as amplifiers and instrumentation amplifiers, following the sensor block. Between the sensor and the delta-sigma A/D converter, there will only be an anti-aliasing, active or passive low-pass filter. As it is seen from Fig.31, the delta-sigma ADC generally requires only a 1st order passive filter.
Fig.31 Circuit designers may find the simplicity of this signal chain attractive – the required external elements are the passive, anti-aliasing filter and the voltage reference. Let’s discuss the following project which illustrates using a popular low-cost Δ-Σ analogto-digital converter MCP3551 from Microchip Corp. The MCP3551 device is 22-bit deltasigma ADC that include fully differential analog inputs, a third-order delta-sigma modulator, a fourth-order modified SINC decimation filter, an on-chip, low-noise internal oscillator, a power supply monitoring circuit and an SPI 3-wire digital interface. These
devices can be easily used to measure low-frequency, low-level signals such as those found in pressure transducers, temperature, strain gauge, industrial control or process control applications. The power supply range for this product family is 2.7V to 5.5V. The MCP3551/3 devices communicate with a measurement system through a simple 3wire SPI interface. The interface controls the conversion start event, with an added feature of an auto-conversion at system power-up by tying the CS signal line in to logic-low. The device can communicate with bus speeds of up to 5 MHz, with 50 pF capacitive loading. The interface offers two conversion modes: Single Conversion mode for multiplexed applications and a Continuous Conversion mode for multiple conversions in series. Every conversion is independent of each other. That is, all internal registers are flushed between conversions. When the device is not converting, it automatically goes into Shutdown mode and, while in this mode, consumes less than 1 µA. Before using this type of ADC (as well as other Δ-Σ device) it is worth learning appropriate datasheets, so that to implement the proper PCB layout and grounding. In this demo project, the Δ-Σ converter MCP3551 is put in the Single Conversion mode. The measurement system will read the positive analog input voltage from a wiper of a potentiometer and output the measured value in the terminal window. The schematic circuit of this project is given below (Fig.32).
Fig.32
In this circuit, the analog input voltage is applied to the input VIN+ (pin 2) while the VIN(pin 3) is wired to the common wire of the circuit. The connections between the Raspberry Pi board and MCP3551 Δ-Σ ADC are detailed in the table below. Raspberry Pi P1 pin
MCP3551 pin
GPIO24
5 (SCK)
GPIO23
6 (SDO/RDY)
GPIO18
7 (CS)
The reference voltage to the converter (pin 1 of MCP3551) is taken from the ″ 3V ″ pin of the Raspberry Pi, although for high-precision measurements you have to provide the highstable reference using some voltage reference IC. The common wire of the circuit should be tied to the ″ GND ″ pin of the Raspberry Pi. The bypass capacitor C1 should be tied as close as possible to pin 8 of the MCP3551 chip. The diagram below (Fig.33) illustrates running MCP3551 in the Single Conversion mode.
Fig.33 Serial communication between a control system and a MCP3551/3 device is achieved using CS, SCK and SDO/RDY signals. The CS signal controls the conversion start. There are 24 bits in the data word: 22 bits of conversion data and two overflow bits. The conversion process takes place via the internal oscillator and the status of this conversion must be detected by software. The status of the internal conversion is reflected by the SDO/RDY signal and is available with CS low.
A high state (log. ″ 1 ″ ) on SDO/RDY means the device is busy converting, while a low state (log. ″ 0 ″ ) means the conversion is finished. Line SDO/RDY remains in a highimpedance state when CS is held high. Data is ready for transfer when SCK goes low; each data bit is latched into a microcontroller when the SCK signal goes high. CS must be brought low before clocking out the data using SCK and SDO/RDY. The SPI-compatible interface can operate either in (0, 0) or (1, 1) mode. In the SPI mode (0, 0) data is read using 25 clocks or four byte transfers. Note that the data ready bit (SDO/RDY) is included in the transfer as the first bit in this mode. When the SPI mode (1, 1) is selected, data is read using only 24 clocks or three byte transfers. The data ready bit must be read by testing the SDO/RDY line prior to a falling edge of the clock. In our project we will put the SPI interface in mode (1, 1) whose timing diagram is shown in Fig.34.
Fig.34 Bit 22 is Overflow High (OH). When VIN >VREF - 1 LSB, OH toggles to logic ″ 1 ″ , detecting an overflow high in the analog input voltage. Bit 23 is Overflow Low (OL). When VIN < -VREF, OL toggles to logic ″ 1 ″ , detecting an overflow low in the analog input voltage. The state OH = OL = ″ 1 ″ is not defined and should be considered as an interrupt for the SPI interface meaning erroneous communication. Bit 21 to bit 0 represent the output code in 22-bit binary two’s complement. Bit 21 is the sign bit and is logic ″ 0 ″ when the differential analog input is positive and logic ″ 1 ″ when the differential analog input is negative. From Bit 20 to bit 0, the output code is given MSB first (MSB is bit 20 and LSB is Bit 0). When the analog input value is comprised between –V and V–1 LSB, the two overflow bits are set to logic ″ 0 ″ . Some words about checking RDY flag in the Single Conversion mode. At every falling edge of CS during the internal conversion, the state of the internal conversion is latched on the SDO/RDY pin to give ready or busy information. A high state means the device is
currently performing an internal conversion and data cannot be clocked out. A low state means the device has finished its conversion and the data is ready for retrieval on the falling edge of SCK. This operation is illustrated in Fig.35.
Fig.35 Note that the Ready state is latched on each falling edge of CS and will not dynamically update if CS is held low. CS must be toggled high then low to fix the Ready state. The Python source code for driving that ADC is given in Listing 13. Listing 13. import RPi.GPIO as GPIO CS = 18 # GPIO18 is CS SDO = 23 # GPIO23 is SDO SCK = 24 # GPIO24 is SCK Vref = 3.29 # Vref = 3.29V GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) # using GPIO numbers rather than pin numbers GPIO.setup(CS, GPIO.OUT) # set GPIO18 as output GPIO.setup(SDO, GPIO.IN) # set GPIO23 as output GPIO.setup(SCK, GPIO.OUT) # set GPIO24 as output binData = 0
GPIO.output(SCK, True) bitDOUT = 1 while (bitDOUT != 0x0): GPIO.output(CS, True) GPIO.output(CS, False) bitDOUT = GPIO.input(SDO) i1 = 23 while (i1 >= 0): GPIO.output(SCK, False) bitDOUT = GPIO.input(SDO) GPIO.output(SCK, True) bitDOUT = bitDOUT << i1 binData |= bitDOUT i1 -= 1 GPIO.output(CS, True) binData &= 0x3FFFFF res = Vref * binData / 2048 res = res / 1024 print(‘Input Voltage = ‘ + str(res) + ‘ V’) In this source code, the constants SCK, SDO and CS are associated with signals SCK, SDO/RDY and CS respectively. The Vref constant determines the reference voltage applied to pin 1 of the MCP3551 chip. In our experiment the reference was taken from pin ″ 3V ″ on the Raspberry Pi board; its measured value turned out to be 3.29V. To get the higher precision you should apply some stand-alone voltage reference chip. For example, you can take an AD680 chip providing 2.5V on its output; in this case pin 1 of MCP3551 (VREF) should be wired to the AD680 output and the Vref constant should be assigned the value of 2.5. You can also take a TLV431 precision voltage reference whose output voltage can be adjusted in a wide range using a couple of resistors. The analog-to-digital conversion consists of two phases: the internal conversion (data sampling) and data transfer. The internal conversion takes some time, so the program should check if sampling is complete before data reading. The following fragment of code
accomplishes this task: binData = 0 GPIO.output(SCK, True) bitDOUT = 1 while (bitDOUT != 0x0): GPIO.output(CS, True) GPIO.output(CS, False) bitDOUT = GPIO.input(SDO) The above statements initiate the internal A/D conversion and check when it has been completed by reading the SDO/RDY line in the while loop. When the bitDOUT variable is assigned 0x0 that means that the internal conversion has completed and the binary data can be transferred to the Raspberry Pi. The data transfer is performed within the while (i1 >= 0) loop. We need to read 24 bits, so the loop variable i1 runs from 23 back to 0. In the current iteration each data bit (saved in the bitDOUT variable) is brought to the SDO/RDY line on the falling edge of SCK and is latched into the Raspberry Pi board on the rising edge of SCK. Then the bit just taken is shifted to the proper position in the binData variable. The following sequence accomplishes those operations: GPIO.output(SCK, False) bitDOUT = GPIO.input(SDO) GPIO.output(SCK, True) bitDOUT = bitDOUT << i1 binData |= bitDOUT When the while loop exits, the binData variable will hold the 24-bit value where bits 2223 will be overflow bits and bit 21 will be a sign bit. In our experiment, we apply the positive analog voltage between 0 and 3.3V to pin 2 (VIN+), so bits 21-23 will be assigned 0 (in other cases they may take other values). To complete the single conversion the CS signal line is pulled high by the following statement:
GPIO.output(CS, True) The statement binData &= 0x3FFFFF leaves only 21 data bits (0 through 20) by clearing bits 21–23. The final result taken in the float format goes to the res variable: res = Vref * binData / 2048 res = res / 1024 Once we get 21–bit binary code, the LSB will be calculated as follows: LSB = Vref / 221 = Vref / (211 × 210) = Vref / (2048 × 1024)
Using digital potentiometers: basic operations Digital potentiometers may significantly expand capabilities of electronic circuits, as they can simplify building digitally controlled DC voltage sources and replace variable resistors. In this section we will discuss how to operate with the popular MCP41xx devices produced from Microchip Corp. Some excerpts from the relevant datasheets will clarify the matter. According to the datasheet, the MCP41XXX/42XXX devices are 256 position single and dual digital potentiometers that can be used in place of standard mechanical potentiometers. Digital potentiometers have resistance values of 10K, 50K and 100K. In the following projects we apply a MCP41010 device which has the resistance of 10K. Each potentiometer is made up of a variable resistor and an 8-bit (28 = 256 position) data register that determines the wiper position. The nominal wiper resistance equals 52 Ohm for the 10K version and 125 Ohm for the 50K and 100K versions. The resistance between the wiper and either of the resistor endpoints varies linearly according to the value stored in the data register. The MCP41XXX series provides 256 taps and accept a power supply from 2.7 through 5.5V. In our projects we feed the MCP41010 device with +5V. This also means that LSB will be equal to 5 / 28 = 0.0195 V. If, for example, application writes the binary code of 100 to the device, the digital potentiometer yields 0.0195 × 100 = 1.95 V on its output. Digital potentiometer applications usually fall into two categories: those that use a ″ rheostat ″ mode and applications where a ″ potentiometer ″ mode is employed. The ″ potentiometer ″ mode is frequently called a ″ voltage divider ″ mode. In the ″ rheostat ″ mode, the potentiometer acts as a two-terminal resistive element. The unused terminal should be tied to the wiper as shown in Fig.36. Note that reversing the polarity of the A and B terminals will not affect operation.
Fig.36 Using the device in this mode allows control of the total resistance between the two nodes. The total measured resistance would be the least at code 0x00, where the wiper is tied to the B terminal. The resistance at this code is equal to the wiper resistance, typically 52 Ohm for the 10k MCP4X010 devices, 125 Ohm for the 50k (MCP4X050), and 100k (MCP4X100) devices. For the 10k device, the LSB size would be 39.0625Ω (assuming 10k total resistance). The resistance would then increase with this LSB size until the total measured resistance at code 0xFFh would be 9985.94Ω. The wiper will never be directly connected to the A terminal of the resistor stack. In the 0x00 state, the total resistance is the wiper resistance. To avoid damage of the internal wiper circuitry in this configuration, care should be taken to ensure the current flow never exceeds 1 mA. In the ″ potentiometer ″ mode, all three terminals of the device are tied to different nodes in the circuit. This allows the potentiometer to output a voltage proportional to the input voltage (Fig.37). The potentiometer is used to provide a variable voltage V2 by adjusting the wiper position between two endpoints A and B. The A endpoint is wired to the voltage source V1 and the B point is tied to the common wire of a circuit ( ″ ground ″ ). Note that reversing the polarity of the A and B terminals will not affect operation.
Fig.37 For the MCP41XXX devices the output analog voltage V2 appears on the PW line. In the case of MCP41010 this will be pin 6 (“wiper”). Note that the digital potentiometers provide the relatively high output impedance, so an additional buffer should be inserted between the wiper and external circuitry. The buffer is also needed if an external load consumes the higher current. The simple buffer may be implemented as a voltage follower built around an op-amp.
As you can see, the voltage divider is composed of the RWA and RWB resistors. The total resistance between A and B endpoints is determined by the parameters of a particular device. MCP41010, for instance, has the full resistance equal to 10K. The resistance of each “resistor” of this voltage divider may be calculated as follows: RWA(Dn) = (RAB) × (256 – Dn) / 256 + RW RWB(Dn) = (RAB) × (Dn) / 256 +RW, where RWA is a resistance between the terminal PA and the wiper (see Fig.37), RWB is a resistance between the terminal PB and the wiper, RW is a wiper resistance, RAB is the overall resistance of the particular device and Dn is a 8–bit binary code that determines the output voltage of the potentiometer. In our projects we will ignore the wiper resistance because it is very low, though for very precision circuits this value should be considered. When the device is powered on, the data registers will be set to mid-scale (0x80). Take a look at how to program the MCP41XXX chips. In order to configure RWA and RWB resistors we should write the predetermined 16-bit value in the data register of the digital potentiometer. The following timing diagram illustrates the write operation (Fig.38).
Fig.38 Writing the bit stream into the digital potentiometer is accomplished via the SPIcompatible interface. As you can see, the 16-bit binary code consists of command and data bytes. We will assign the value 0x11 to the command byte – this value will be used in the following projects. The data byte (Dn) will determine of the output voltage. For more detail concerning bit assignments you can explore the datasheet on the device being used.
The write operation begins when the CS signal goes high then low. Each data bit of the bit stream must be brought onto the SI line while the clock signal SCK is kept low. The data bit is written to the data register on the rising edge of SCK. After all 16 bits have been passed into the device data register, the CS signal goes high thus completing the operation. The following project shows how to build the simple digitally programmed DC voltage source based upon the MCP41010 device. The schematic circuit of the project is shown in Fig.39.
Fig.39 Here the digital potentiometer MCP41010 operates in the ″ potentiometer ″ mode. The DC output voltage Vout can be taken from pin 6 of the device. MCP41010 is controlled via the SPI-compatible interface (SCK, SI and CS lines) from the GPIO pins of the Raspberry Pi board. The optional buffer A1 may be put between Vout and an external load to match impedances and provide the desired load capability. The supply voltage to the MCP41010 device may be taken from either Raspberry Pi ( ″ 3V ″ pin of P1) or from stand-alone +3.3V DC power supply. The ″ GND ″ pin of the Raspberry Pi should be wired to the common wire of the circuit. The pin connections of the above circuit are detailed in the following table.
Raspberry Pi GPIO pins of P1
MCP41010 pin
GPIO24
2 (SCK)
GPIO23
3 (SI)
GPIO18
1 (CS)
The Python source code for configuring the MCP41010 device is given in Listing 14. Listing 14. import RPi.GPIO as GPIO CS = 18 SI = 23 SCK = 24 cmd = 0x1100 binData = 64 # binData = 32, 1/8 of the full scale (FS) = 0.408 V (Vref = 3.27V) # bitData = 64, 2/8 of the FS = 0.817 V # binData = 96, 3/8 of the FS = 1.226 V # binData = 128, 4/8 of the FS = 1.635 V # binData = 160, 5/8 of the FS = 2.044 V # binData = 192, 6/8 of the FS = 2.453 V # binData = 224, 7/8 of the FS = 2.861 V
GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) #using GPIO numbers rather than pin numbers GPIO.setup(CS, GPIO.OUT) # set GPIO18 as output GPIO.setup(SI, GPIO.OUT) # set GPIO23 as output GPIO.setup(SCK, GPIO.OUT) # set GPIO24 as output binCode = cmd + binData GPIO.output(CS, True) #CS is brought high GPIO.output(CS, False) #CS goes low thus starting conversion i1 = 0 while (i1 < 16): GPIO.output(SCK, False) # SCK goes low tmp = binCode & 0x8000 GPIO.output(SI, False) if(tmp != 0x0): GPIO.output(SI, True) GPIO.output(SCK, True) binCode = binCode << 1 i1 += 1 GPIO.output(CS, True) Here the high byte of the binCode variable holds the command (0x11), while the low byte (0 through 255) is kept in the binData variable. binData is assigned 64 that corresponds to the output voltage of 0.817V at pin 6. The measured reference voltage is 3.27V and applied to pin 5 of MCP41010. The commented lines contain values of binData for different output voltages. The output voltage Vout appears on the wiper PW. Note that Vout depends on the voltage applied to the endpoints of the potentiometer (3.27V, in our case). Writing 16-bit value to the device is implemented within the while loop. The data transfer begins when the CS signal changes from high to low. After LSB has been written, the program code raises the CS line to complete the write operation.
We can replace the MCP41010 part by the similar device, for example, MCP41050 with the total resistance of 50k or MCP41100 with the resistance of 100k. This application can easily be improved when dual channel digital potentiometers MCP42XXX are required, though such evolution will require modifications in both hardware and software.
Using digital potentiometers: the PWM circuit with a 555 timer Digital potentiometers may come in handy when designing various RC-oscillators. The signal parameters (frequency, pulse width, amplitude) in those circuits are usually determined by RC networks. A discrete resistive element in an RC time-dependent chain of an oscillator can be replaced by a digital potentiometer, so we will be able to control a frequency/period of signals by software. The next project illustrates designing the pulse-width modulator (PWM) circuit where a duty cycle is adjusted programmatically through the digital potentiometer. The schematic circuit of the PWM circuit is based upon a CMOS timer TLC555 and the digital potentiometer MCP41010 (Fig.40).
Fig.40 The TLC555 chip may be replaced by any popular 555 timer (LMC555, 7555, etc.) operating at +3.3V. The capacitor C2, the digital potentiometer and the resistor R1 determine the frequency of the multivibrator. The diodes D1 and D2 provide separate paths for charging/discharging the capacitor C2. By altering the values of RWA and RWB resistors of the digital potentiometer we can change the duty cycle of the output signal on pin 3 of TLC555. The resistor R1 connected to the wiper PW0 restricts the current
through the terminals of MCP41010. The Python source code for this project is shown in Listing 15 (the binary codes for different duty cycles are listed in the commented lines). Listing 15. import RPi.GPIO as GPIO CS = 18 SI = 23 SCK = 24 cmd = 0x1100 # binData=224,Duty=79,8%,F=837Hz,Period=1.2mS,duration=0.95mS # binData=192,Duty=70,2%,F=835Hz,Period=1.2mS,duration=0.84mS # binData=128,Duty=50,8%,F=835Hz,Period=1.2mS,duration=0.61mS # binData=64,Duty=31,8%,F=839Hz,Period=1.2mS,duration=0.38mS binData = 32 #Duty=21,9%,F=839Hz,Period=1.2mS,duration=0.26mS GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) #using GPIO numbers rather than pin numbers GPIO.setup(CS, GPIO.OUT) # set GPIO18 as output GPIO.setup(SI, GPIO.OUT) # set GPIO23 as output GPIO.setup(SCK, GPIO.OUT) # set GPIO24 as output binCode = cmd + binData GPIO.output(CS, True) # CS is brought high GPIO.output(CS, False) # CS goes low thus starting conversion i1 = 0 while (i1 < 16):
GPIO.output(SCK, False) # SCK goes low tmp = binCode & 0x8000 GPIO.output(SI, False) if(tmp != 0x0): GPIO.output(SI, True) GPIO.output(SCK, True) binCode = binCode << 1 i1 += 1 GPIO.output(CS, True) This code allows to set the duty cycle to almost 22% at the binData value assigned 32.
Using digital potentiometers: PWM circuits with analog comparators Digitally controlled PWM circuits can be arranged using analog comparators. The schematic circuit of such PWM with the TLC3702 comparator IC is given below (Fig.41).
Fig.41 In this circuit, the comparator A2.1 (1/2 TLC3702) produces the triangle waveform of a frequency about 750 Hz. The capacitor C2 and the resistor R4 determine the frequency of a signal going to the non-inverting input (pin 5) of comparator A2.2. The bias applied to the inverting input (pin 6) of A2.2 from the wiper PW0 of the digital potentiometer determines the threshold. The duty cycle of the pulse train on the A2.2 output (pin 7) will be linearly proportional to the DC voltage at pin 6. This voltage will be set by software driving the digital potentiometer A1 (MCP41010). Any single-supply (+3.3V) high-speed comparator will fit this circuit. The TLC3702 device has push-pull outputs, so we don’t need to wire pull-up resistor at pins 1 and7. If a comparator has ″ open collector ″ (OC) output we should add a pull-up resistor to its outputs (the value of a resistor depends on the device parameters indicated in the
datasheet). The Python source code for driving this circuit will be the same as that shown in Listing 14. One more PWM circuit with the LMV393 comparators is given in Fig.42.
Fig.42 You can also take LMV339 or a similar general purpose comparator capable of operating at 3.3V. For a PWM circuit with a base frequency of several KHz and more a high-speed comparator IC should be taken.
Using digital potentiometers: the resistor set wide-band oscillator LTC1799 A very simple digitally controlled oscillator can be developed using a popular LTC1799 device from Linear Technology Corp. LTC1799 is easy to use precision oscillator designed for high accuracy operation (≤1.5% frequency error) without the need for external trim components. The LTC1799 device operates with a single 2.7V to 5.5V power supply and provides a rail-to-rail, 50% duty cycle square wave output. The CMOS output driver ensures fast rise/fall times and rail-to-rail switching. The oscillator circuit with LTC1799 is shown in Fig.43.
Fig.43 The oscillator frequency is programmed by a single external resistor (RSET) using the formula shown in Fig.44.
Fig.44
The frequency-setting resistor can vary from 3K to 1M to select a master oscillator frequency between 1KHz and 33MHz. In this circuit, the RSET consists of the R1 resistor (200k) and the digital potentiometer PA0-PW0-PB0 (10k) connected in series. When the resistance of the PA0-PW0-PB0 reaches 0k, the output frequency will achieve 5000 Hz; at resistance of 10k, the output frequency decreases to 4750 Hz. Thus we can change the output frequency in the range from 4750 to 5000 Hz by adjusting the value of the digital potentiometer. The three-state DIV input determines whether the master clock is divided by 1, 10 or 100 before appearing at the LTC1799 output. Therefore, DIV determines three frequency ranges spanning 1KHz to 33MHz . In our circuit, the output frequency is divided by 100, since the DIV pin is connected to +3.3V. LTC1799 may be replaced by the LTC6900 oscillator that is a lower power version of the LTC1799. Note that the pin configuration of the LTC1799 chip in Fig.43 relates to the 5-Lead Plastic TSOT-23 package.
Using digital potentiometers: the DC current source with an op-amp Current sources are used in plenty applications including bias networks, surge protection, low power references, ramp generation, LED and laser diode drivers, temperature sensing, etc. Combining the Raspberry Pi with digital potentiometers allows to build current source circuits being controlled by software. Take a look at how to control voltage sources by software. The first project uses the circuit formed by the digital potentiometer MCP41010 operating in ″potentiometer″ mode , opamp OPA364 and bipolar transistor 2N3904 (Fig.45).
Fig.45 In this circuit, the emitter current IE of the transistor Q1 is determined by both the Vref voltage and resistor R5 as: IE = Vref / R5 The output current IC flowing through the Q1 collector will be equal to IE – IB; at a very small IB we can consider IC ≈ IE. To achieve the higher precision we should minimize IB; this can be done by taking the transistor Q1 with a large DC current gain (denoted as hfe or β). The Vref voltage is taken from the voltage divider formed by the digital
potentiometer PA0-PW0-PB0 put in “potentiometer” mode. Using negative feedback, the op-amp OPA364 tries to maintain the voltage drop across the resistor R5. Even if the resistance RL of the load changes, the drop across R5 remains the same. Remember, though, this current control has operational limits; it can only swing the output voltage so far to compensate for load variance. Once these limits are reached, the current regulation can no longer exist.
Using digital potentiometers: the DC current source with a difference amplifier A more precision digitally controlled current source can be built using a ″ difference amplifier ″ circuit (Fig.46).
Fig.46 In this circuit, the current source is formed by the op-amp A3 configured as a difference amplifier with a unit gain (R4=R5=R6=R7). The output current IL is determined by the formula IL = Vref / R8 where Vref is the reference voltage applied to the non-inverting input of the op-amp A3 through the resistor R4. The reference voltage Vref is provided by the combination of the voltage divider PA0-PW0-PB0 and the voltage follower (buffer) A2. Since the voltage reference Vref is being controlled by software, the current IL through the load RL varies
according to the above formula. When designing this circuit, a developer should provide the operation of the difference amplifier in the linear region.
Using dual-chain digital potentiometers Many ″ classical ″ electronic circuits use two variable mechanical resistors for configuring and adjusting their parameters. These include adjustable signal filters, oscillator circuits, signal attenuators, to name but a few. Digital potentiometers containing two or more channels allow to build complicated conditioning circuits controlled programmatically. As in the case of single-channel counterparts, they can effectively replace mechanical potentiometers and resistors in various circuits. Replacing mechanical resistors by their electronic counterparts – digital potentiometers – allows to control those circuits digitally. The next project shows basic operations of a popular and low-cost dual-channel digital potentiometer AD5242 driven through the I2C interface. Take a look at how AD5242 operates. The AD5242 provides a dual-channel, 256-position, digitally controlled variable resistor (VR) device. These devices perform the same electronic adjustment function as a potentiometer, trimmer, or variable resistor. Each VR offers a completely programmable value of resistance between the A terminal and the wiper, or the B terminal and the wiper. For the AD5242, the fixed A-to-B terminal resistance of 10k, 100k, or 1M has a 1% channel-to-channel matching tolerance. The nominal temperature coefficient of both parts is 30 ppm/°C. Wiper position programming defaults to midscale at system power on. When powered, the VR wiper position is programmed by an I2C-compatible, 2-wire serial data interface. Both parts have two extra programmable logic outputs available that enable users to drive digital loads, logic gates, LED drivers, and analog switches in their system. The schematic circuit for the demo project is shown in Fig.47.
Fig.47 In this circuit, both potentiometers (A1-W1-B1 and A2-W2-B2) of AD5242 are driven by I2C interface. The signal lines SDA (pin 8) and SCL (pin 7) of the digital potentiometer are wired to pins SDA and SCL of the P1 header of the Raspberry Pi respectively. The address lines AD0 and AD1of the device are connected to ″ ground ″ . Both channels of the digital potentiometer are set to ″ potentiometer ″ mode, so the terminals A1 – A2 are connected to the DC +3.3V power supply, whereas B1 – B2 are tied to ″ ground ″ . The DC output voltages Vout1 and Vout2 are taken from the wipers W1 and W2, respectively. The I2C interface to the AD5242 digital potentiometer operates according to the timing diagram shown below (Fig.48).
Fig.48 This diagram illustrates the write operation to the inner RDAC1 and/or RDAC2 registers while configuring the device. The first byte of the sequence determines the address of the AD5242 device (we can easily determine it by invoking the i2cdetect utility). The second byte is the instruction itself and the third byte contains 8-bit data to be written. Below is the brief description of the separate bits of this sequence: Bits
Description
AD1, AD0
Package pin programmable address bits. Must be matched with the logic states at Pins AD1 and AD0.
R/W
Read enable at high and output to SDA. Write enable at low.
A/B
RDAC subaddress select; 0 for RDAC1 and 1 for RDAC2.
RS
Midscale reset, active high
SD
Shutdown in active high. Same as SHDN except inverse logic.
O1, O2
Output logic pin latched values
D7, D6, D5, D4, D3, D2, D1, D0
data bits
X
don’t care
The Python source code that drives both channels of the AD5242 device is given in Listing 16. Listing 16. import smbus addr = 0x2c # may be detected by the i2cdetect utility
cmd1 = 0x0 # configure dig.pot 1 cmd2 = 0x80 #configure dig.pot 2 Vref = 3.245 # a reference voltage to the potentiometer FS = 256 bus = smbus.SMBus(1) Vout1 = 0.17 Dn = int((Vout1 * FS)/Vref) bus.write_byte_data(addr, cmd1, Dn) Vout2 = 2.96 Dn = int((Vout2 * FS)/Vref) bus.write_byte_data(addr, cmd2, Dn) Here the addr variable keeps the address of the AD5242 device. We should assign this variable the value either calculated manually or obtained by the i2cdetect utility. The cmd1 variable determines the settings for the channel 1 (A1-W1-B1) of the digital potentiometer. In our case, this variable is assigned 0x0. The cmd2 variable allows to configure the channel 2 (A2-W2-B2). The only difference between cmd1 and cmd2 is the value of MSB (bit A/B). In the case of cmd2, the A/B bit is assigned 1. The Vref variable is assigned the value of the reference voltage applied to the digital potentiometers. In our case, this equals 3.245 V (the measured value). Note that the voltage applied to the potentiometers has to be within the range determined in the datasheet on the device. The FS variable determines the full scale of digital codes (28 = 256) for the device. The program code adjusts the DC output voltage (variable Vout1) on pin W1 of the channel 1 to be 0.17V. The wiper W2 will produce the 2.96V (variable Vout2). As you can see, we need two write operations to configure both channels of the digital potentiometer. We can also check the state of RDAC1 or RDAC2 register by executing the read operation on the I2C bus; more details are given in the datasheet on the device. A couple of simple demo circuits where dual-channel digital potentiometers can be applied are represented below.
The circuit in Fig.49 illustrates using a dual-channel digital potentiometer for adjusting the frequency and amplitude of the signal produced by the oscillator using the OPA364 opamp.
Fig.49 This demo circuit comprises the square wave oscillator built around the op-amp OPA364 (A1), the attenuator which uses the digital potentiometer (Dig.Pot.2, A2-W2-B2) and the amplifier stage with op-amp OPA364 (A2). The frequency of the oscillator can be adjusted programmatically by moving the wiper of the digital potentiometer 1 (Dig.Pot.1, A1-W1B1). The voltage divider formed by the Dig.Pot.2 allows to adjust the magnitude of the pulses yielded by the oscillator. The magnitude of the pulse train taken from the voltage divider will be developed about two times by the inverting amplifier A2. The AC gain of the amplifier stage is approximately equal to -R6/R5, because a developer should also take into consideration the additional resistance introduced by the Dig.Pot.2 which is AC coupled to the feedback chain R5-R6 via the capacitor C3. In real life, the gain of the A2 stage will be a bit less than the -R6/R5. Using the A2 amplifier stage allows to isolate the low-power oscillator circuit from load. The AD5242 or similar dual-channel device (MCP42010, for example) will fit the above circuit. We can also use a dual-chain digitalpotentiometer when amplifying a difference of two signals using the circuit called a ″ differenceamplifier ″ . The difference amplifier can be either a stand-alone circuit or a stage of an instrumentation amplifier (in-amp).
The following circuit illustrates using a dual-chain digital potentiometer for adjusting the gain of the difference amplifier (Fig.50).
Fig.50 Assuming R1= R2 and R3 = R4, we obtain the following formula for the given circuit: Vout = (Vin2 – Vin1)(R3/R1) If the values of R1 (Dig.Pot.1) and R2 (Dig.Pot.2) remains equal (R1= R2), the gain of this circuit can be adjusted in a wide range.
Using 4-channel digital potentiometer AD5204: basic operations This section is dedicated to using 4-channel digital potentiometers. Such devices have numerous applications due to availability of four independent digitally controlled resistors. The following projects will illustrate using a popular 4-channel digital potentiometer AD5204. The AD5204 provides 4-channel, 256-position digitally controlled variable resistor (VR) devices. These devices perform the same electronic adjustment function as potentiometers or variable resistors. Each channel of the AD5204 contains a fixed resistor with a wiper contact that taps the fixed resistor value at a point determined by a digital code loaded into the SPI-compatible serial-input register. The resistance between the wiper and either end-point of the fixed resistor varies linearly with respect to the digital code transferred into the VR latch. The variable resistor offers a completely programmable value of resistance between the A terminal and the wiper or the B terminal and the wiper. The fixed A-to-B terminal resistance of 10k, 50k, or 100k has a nominal temperature coefficient of 700 ppm/°C. The simple project illustrates the basic programming techniques applied to AD5204. The schematic circuit of the project is shown in Fig.51.
Fig.51 In this circuit, the AD5204 digital potentiometer of 10k is driven by the Raspberry Pi board through the SPI-compatible interface. The framing signal line CS is wired to pin GPIO18, the clock source line CLK is driven by pin GPIO23 and the SDI data line is connected to pin GPIO24. We will not deal with shutdown and reset modes in this project, so SHDN and PR lines are connected to the +3.3V power rail. The bypass capacitor C1 should be tied as close as possible to the power pins of AD5204 device. Here all channels (RDAC1-RDAC4) of AD5204 are configured as the variable resistors ( ″ rheostat ″ mode). The nominal resistance of the RDACx between Terminal A and Terminal B is available with values of 10k, 50k, and 100k. The last digits of the part number determine the nominal resistance value; for example, 10k = 10 and 100k = 100.The nominal resistance (RAB) has 256 contact points accessed by the wiper terminal, plus Terminal B contact. The 8-bit data-word in the RDACx latch is decoded to select one of the 256 possible settings. The first connection of the wiper starts at Terminal B for the 0x00 data. The Terminal B connection has a wiper contact resistance of 45 Ohm. The second connection (for a 10k part) is the first tap point, located at 84 Ohm. The resistance for the 0x01 data will be equal to
RAB (nominal resistance) / 256 + RW = 84 Ohm + 45 Ohm The third connection is the next tap point, representing 78 + 45 = 123 Ohm for the 0x02 data. Each LSB data value increase moves the wiper up the resistor ladder until the last tap point is reached at 10.006k Note that the wiper is not directly connected to Terminal A. The general transfer equation determining the digitally programmed output resistance between the WX and BX terminals is given by the following formula: RWB(DX) = (DX) / 256 x RAB + RW where DX is the data contained in the 8-bit RDACx latch, and RAB is the nominal end-toend resistance. For example, when VB = 0 V and Terminal A is open, the output resistance values are set as outlined in the table below for the RDACx latch codes (applies to the 10k potentiometer). The table below shows relationships between the RDACx resistances and binary codes when terminal A is open. Output Resistance Values for the RDACx Latch Codes D(Decimal)
RWB, Ohm
Output State
255
10006
Full scale
128
5045
Midscale
1
84
1 LSB
0
45
Zero scale (wiper contact resistance)
In the zero-scale condition, a finite total wiper resistance of 45 Ohm is present. Regardless of which setting the part is operating in, care should be taken to limit the current between Terminal A to Terminal B, Wiper W to Terminal A, and Wiper W to Terminal B, to the maximum continuous current of ±5.65 mA (10 K) or ±1.35 mA (50 K and 100 K) or pulse current of ±20 mA. Otherwise, degradation or possible destruction of the internal switch contact can occur.
Like the mechanical potentiometer that the RDACx replaces, the RDACx is completely symmetrical. The resistance between Wiper W and Terminal A produces a digitally controlled resistance RWA. When these terminals are used, Terminal B should be tied to the wiper. Setting the resistance value for RWA starts at a maximum value of resistance and decreases as the data loaded to the latch is increased in value. The general transfer equation for this operation is given by: RWA(DX) = (256 – DX) / 256 x RAB + RW where DX is the data contained in the 8-bit RDACx latch, and RAB is the nominal end-toend resistance. For example, when VA = 0 V and Terminal B is tied to wiper W, the output resistance values outlined in the below table are set for the RDAC latch codes. D (Decimal) RWA (Ohm)
Output State
255
84
Full scale
128
5045
Midscale
1
10006
1 LSB
0
10045
Zero scale
The timing diagram of operation of the AD5204 device is shown in Fig.52.
Fig.52
Here the CS signal frames (enables) the transfer of 11-bit data stream to the device. When CS is pulled low, each data bit on the SDI line is shifted into the serial input register on the rising edge of CLK. After CS has been pulled high, the data bits are latched into the corresponding RDACx register. The address of RDACx is determined by the A2 – A0 bits of the data stream. The source code for this project is given in Listing 17. Listing 17. import RPi.GPIO as GPIO CS = 18 # GPIO18 is assigned CS SDI = 24 # GPIO24 is assigned SDI CLK = 23 # GPIO23 is assigned CLK addr = (0x0, 0x1, 0x2, 0x3) # RDAC1 through RDAC4 binCode = (125, 239, 119, 194) # binCodes for RDACx #binCode = 39, Vout = 0.499V at Vcc = 3.27V #binCode = 71, Vout = 0.907V #binCode = 119, Vout = 1.52V #binCode = 194, Vout = 2.478V #binCode = 125, Vout = 1.597V #binCode = 173, Vout = 2.21V #binCode = 200, Vout = 2.554V #binCode = 217, Vout = 2.772V #binCode = 239, Vout = 3.053V def WriteDP(addr, code): GPIO.output(CS, True)
GPIO.output(CLK, True) GPIO.output(CLK, False) GPIO.output(CS, False) i1 = 0 while (i1 < 3): GPIO.output(CLK, False) tmp = addr & 0x4 GPIO.output(SDI, False) if (tmp != 0x0): GPIO.output(SDI, True) GPIO.output(CLK, True) addr = addr << 1 i1 += 1 i1 = 0 while (i1 < 8): GPIO.output(CLK, False) tmp = code & 0x80 GPIO.output(SDI, False) if (tmp != 0x0): GPIO.output(SDI, True) GPIO.output(CLK, True) code = code << 1 i1 += 1 GPIO.output(CS, True) GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) #using GPIO numbers rather than pin ones GPIO.setup(CS, GPIO.OUT) # set GPIO18 as output GPIO.setup(CLK, GPIO.OUT) # set GPIO23 as output
GPIO.setup(SDI, GPIO.OUT) # set GPIO24 as output WriteDP(addr[0], binCode[0]) WriteDP(addr[1], binCode[1]) WriteDP(addr[2], binCode[2]) WriteDP(addr[3], binCode[3]) As the timing diagram in Fig.52 implies, the data stream to AD5204 first starts out with 3 address bits A2 – A0 followed by the data byte (bits D7 – D0) that determine the RDACx value. The address of RDACx can vary from 0x0 (RDAC1) to 0x3 (RDAC4). Configuring AD5204 is accomplished by the WriteDP() function which takes two parameters, the address of the channel and the data byte. The while (i1 < 3) loop uses to pass the address bits A2 – A0 to the device, while the while (i1 < 8) loop transfers the data bits. In our project, the channels RDAC1-RDAC4 (address 0x0 to 0x3)) are configured using the values in the addr array. The configuration word for the RDACx channels is taken from the binCode array. That is done by the following sequence: WriteDP(addr[0], binCode[0]) WriteDP(addr[1], binCode[1]) WriteDP(addr[2], binCode[2]) WriteDP(addr[3], binCode[3]) The next section describes one possible use of the four-channel digital potentiometers.
Using 4-channel digital potentiometers: the quadrature sinewave RC oscillator One application of the use of four channel digital potentiometers is digitally controlled RC sinewave oscillators. Such oscillators comprise a few identical timing RC networks. Replacing mechanical resistors in those networks by digital potentiometers allows to control RC oscillators by software. The following project will illustrate using the four-channel digital potentiometer AD5204 for driving the quadrature sine/cosine waveform RC oscillator. In this circuit, all channels of AD5204 are put into ″ rheostat ″ mode as is shown in Fig.51. The basic circuit of the quadrature sine/cosine wave RC oscillator is shown in Fig.53.
Fig.53 This quadrature oscillator circuit is based upon the phase shift that is determined by three RC sections (R1C1, R2C2 and R3C3). Each section contributes 90° of phase shift, so the oscillator provides both sine and cosine waveform outputs (the outputs are quadrature, or 90° apart), which is a distinct advantage over other phase shift oscillators. The idea of the quadrature oscillator is to use the fact that the double integral of a sine wave is a negative sine wave of the same frequency and phase. The phase of the second integrator is then inverted and applied as positive feedback to induce oscillation. The above circuit oscillates when R1C1 = R2C2 = R3C3. When R1C1 = R2C3 = R3C3 = RC, then the output frequency f is determined as:
f = 1/2πRC Note that the real frequency is dependent on discrepancies between components. Both outputs have relatively high distortion that can be reduced with a gain stabilizing circuit. Adjusting the gain can increase the amplitudes, although this causes narrowing the bandwidth. To convert the RC quadrature oscillator into the digitally controlled device we need to replace resistors R1-R3 by the digital potentiometers. The following circuit (Fig.54) shows how to apply the AD5204 digital potentiometer channels (terminals Wx-Bx) instead of the resistors R1-R3.
Fig.54 In this circuit, each of R1-R3 resistors is replaced by RDACx resistors (each 100k) and the 500 Ohm resistor Rx’. The latter is needed to restrict the current flowing through the wiper Wx when RDACx is close to zero Ohms. While experimenting with this circuit, the value of Rx’ was taken from 100 to 680 Ohms. You should take the value of Rx’ into consideration when calculating the frequency of the oscillator. To configure the oscillator the three virtual resistors (channels RDAC1, RDAC2 and RDAC4, in our case) should be set to the same value. A few possible values for those virtual resistors are listed below: # binCode = 25, Rwb = 10k
# binCode = 17, Rwb = 7k # binCode = 37, Rwb = 15k # binCode = 50, Rwb = 20k # binCode = 57, Rwb = 23k # binCode = 7, Rwb = 3k # binCode = 42, Rwb = 17k The source code for driving the RC quadrature oscillator can be taken from the previous section (Listing 17). The output signal of the quadrature sine/cosine wave RC oscillator is illustrated by the LabVIEW virtual oscilloscope (Fig.55).
Fig.55
The digitally programmed wide-band square wave oscillator with LTC6903 When operating, most electronic systems use periodical digital signals( ″ pulse trains ″ ). With the Raspberry Pi, it is problematic to generate pulse trains with predetermined parameters in a wide band using only GPIO port. We can develop, however, high quality oscillator circuits by applying versatile single-chip oscillators driven by either the SPI or I2C interface. This project illustrates the design of a digitally programmed wide–band oscillator using a popular chip LTC6903 from Linear Technology. This chip provides a TTL-compatible high-stable and high-accuracy signal in the range from 1 KHz through 68 MHz. LTC6903 is driven through the SPI-compatible interface, so we can connect the device to the GPIO port of the Raspberry Pi board. Let’s begin with the brief description of LTC6903 excerpted from the datasheet on the device. The LTC6903 device is a low-power, self contained digital frequency source providing a precision frequency from 1 KHz to 68 MHz, setable through a serial port. LTC6903 requires no external components other than a power supply bypass capacitor, and it operates over a single wide supply range of 2.7V to 5.5V. In our project the supply voltage will be 3.3V. As the datasheet says, the LTC6903 oscillator chip features a proprietary feedback loop that linearizes the relationship between the digital control setting and the output frequency, resulting in a very simple frequency setting equation: f = 2OCT × 2078(Hz) / (2 - DAC/1024) (1), where 1 KHz < f < 68 MHz, OCT is a 4-bit integer value (0-15) represented by the serial port register bits OCT [3:0] and DAC is a 10-bit integer value (from 0 through 1023) represented by the serial port register bits DAC[9:0]. The output frequency of LTC6903 can be set programmatically via a 3-wire SPIcompatible interface controlled by the Raspberry Pi. The schematic circuit of the project is shown in Fig.56.
Fig.56 Here the port GPIO18 of the Raspberry Pi is wired to the signal line SEN of the LTC6903 chip, GPIO23 goes to the SCK line and GPIO24 is connected to the data line SDI. The ″ GND ″ pin of the Raspberry Pi board must be wired to the common wire of the circuit. The pin connections of the above circuit are detailed in the following table. Raspberry Pi GPIO pins of LTC6903 pin P1 GPIO24
2 (SDI)
GPIO23
3 (SCK)
GPIO18
4 (SEN)
The output frequency appears on the CLK output (pin 6 of LTC6903). The inverse output signal can be taken from pin 5; we will not use this output, so pin 5 is left unconnected. The OE signal line wired to the source voltage +5V allows both CLK outputs. In order to reduce the noise and increase the accuracy of the pulse train the bypass capacitor C1 of about 0.01uF is tied close to the power pins of the LTC6903 chip. It is worth cutting off any excess of the capacitor leads as much as possible to minimize their series inductance.
Take a look at how to configure the LTC6903’s output frequency. The desired output frequency of LTC6903 can be set by applying formula (1). To configure the output frequency the following steps are required: 1. Selection of the appropriate value of OCT from the table shown below: Output Frequency Range vs. OCT Setting (Frequency Resolution 0.001 * f)
f ≥
f <
OCT
34.05 MHz
68.03 MHz
15
17.02 MHz
34.01 MHz
14
8.511 MHz
17.01 MHz
13
4.256 MHz
8.503 MHz
12
2.128 MHz
4.252 MHz
11
1.064 MHz
2.126 MHz
10
532 KHz
1063 KHz
9
266 KHz
531.4 KHz
8
133 KHz
265.7 KHz
7
66.5 KHz
132.9 KHz
6
33.25 KHz
66.43 KHz
5
16.62 KHz
33.22 KHz
4
8.312 KHz
16.61 KHz
3
4.156 KHz
8.304 KHz
2
2.078 KHz
4.152 KHz
1
1.039 KHz
z
0
2. Choosing the variable DAC through the following formula, and rounding DAC to nearest integer: DAC = 2048 – 2078(Hz) × 2(10+OCT) / f (2)
3. After OCT and DAC have been determined, we should make up the 16-bit binary code to be written to the serial register of the LTC6903 chip (Fig.57):
Fig.57 In this particular example, the D1 – D0 fields are assigned 0. Once the 16-bit code has been determined, we can write it into the LTC6903 device using the timing diagram shown in Fig.58.
Fig.58 As it is seen, the SEN signal frames the write operation which transfers the 16-bit data stream into the serial register of LTC6903. When SEN goes low, the data transfer is enabled. The program code puts each bit on the SDI line (MSB goes first) and latches this bit into the device on the rising edge of the SCK pulse. After the last bit (D0) has been written, SEN goes high. The Python source code driving the LTC6903-based oscillator is shown in Listing 18. Listing 18. import RPi.GPIO as GPIO
SEN = 18 # GPIO18 as SEN SDI = 24 # GPIO24 as SDI SCK = 23 # GPIO23 as SCK GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) #using GPIO numbers rather than pin ones GPIO.setup(SEN, GPIO.OUT) # set GPIO18 as output GPIO.setup(SCK, GPIO.OUT) # set GPIO23 as output GPIO.setup(SDI, GPIO.OUT) # set GPIO24 as output freq = 0x844 # F = 1400 Hz #freq = 0x1774 F = 2710 Hz #freq = 0x1C54 F = 3380 Hz #freq = 0x2274 F = 4500 Hz #freq = 0x262C F = 5150 Hz GPIO.output(SEN, True) GPIO.output(SEN, False) i1 = 0 while (i1 < 16): GPIO.output(SCK, False) tmp = freq & 0x8000 GPIO.output(SDI, False) if (tmp != 0x0): GPIO.output(SDI, True) GPIO.output(SCK, True) freq = freq << 1 i1 += 1 GPIO.output(SEN, True)
The program controls the data transfer through three GPIO pins (GPIO18, GPIO23 and GPIO24) configured as outputs by the following sequence: GPIO.setup(SEN, GPIO.OUT) # set GPIO18 as output GPIO.setup(SCK, GPIO.OUT) # set GPIO23 as output GPIO.setup(SDI, GPIO.OUT) # set GPIO24 as output The SCK pin produces the clock signal SCKl, SEN provides the framing signal on the SEN line and SDI keeps the bit shifted onto the SDI line. The freq variable keeps the value of the desired output frequency. In our case, the output frequency is set to 1400 Hz, so freq is assigned the value 0x844. The tmp variable holds the current MSB being taken out of the freq variable in each iteration. Before writing the data word into the LTC6903 registers, the SEN signal is brought high, then low. The low level of SEN enables the data transfer. The following two statements accomplish that: GPIO.output(SEN, True) GPIO.output(SEN, False) Since the data word contains 16 bits (D15 through D0), the while (i1 < 16) loop is entered. The loop will run until 16 iterations have passed. In each iteration, the current MSB is driven on the SDI line while the SCK signal is kept low. This data bit is then written to the LTC6903 device by bringing the SCK signal high. The single iteration is executed by the following sequence: GPIO.output(SCK, False) tmp = freq & 0x8000 GPIO.output(SDI, False) if (tmp != 0x0):
GPIO.output(SDI, True) GPIO.output(SCK, True) freq = freq << 1 i1 += 1 The freq = freq << 1 statement in this sequence shifts all bits in freq to the left by 1 in preparation for the next iteration. After all 16 data bits have been processed, the loop exits. The framing signal SEN that follows goes high thus disabling the write operation: GPIO.output(SEN, True) To set the output frequency other than that in this project, you should recalculate the value of the freq variable.
The digitally controlled oscillator AD7741 A simple but effective oscillator can be developed using a special device known as a ″ Voltage-To-Frequency ″ converter (V-to-F, VFC). VFCs are handy when we need to measure or generate signals. A VFC device converts a voltage level at its input to a pulse train whose frequency is linearly proportional to the input voltage. High precision V-to-F converters are basically used in voltage controlled oscillators (VCO) and distributed measurement systems processing signals from remote sensors. Usually, analog outputs produced by sensors can be converted into relevant frequencies and transferred to remote receivers. Using frequencies instead of voltage significantly increases the reliability of such measurement systems. V-to-F converters allow to pass signals through isolated circuits (optocouplers or transformers) thus separating high-power parts of systems from low-power precision measurement circuits – this essentially reduces an unwanted noise component in useful signals. The following project illustrates using a popular V-to-F chip AD7741 from Analog Devices in the Raspberry Pi control and measurement systems. AD7741 can generate digital pulse trains in a wide range of frequencies (from a few tens of Hz up to 3 MHz) with high linearity. Take a look at the schematic circuit of the project (Fig.59).
Fig.59 In this circuit, the MCP41010 digital potentiometer provides the control voltage Vout to the AD7741 oscillator. Here the pin GPIO18 of the Raspberry Pi is wired to the framing signal line (CS) of the MCP41010, GPIO23 is connected to the SI data line and GPIO24 is connected to the clock source input (SCK). The ″ GND ″ pin of the Raspberry Pi board must be wired to the common wire of the circuit. The pin connections of the above circuit are detailed in the following table. Raspberry Pi GPIO pins of MCP41010 pin P1 GPIO24
2 (SCK)
GPIO23
3 (SI)
GPIO18
1 (CS)
The AD7741 chip is driven through the voltage divider formed by the resistor PA0-PW0PB0 of the digital potentiometer. The control voltage taken from the wiper (PW0, pin 6 of
MCP41010) is directly applied to the Vin input of AD7741 (pin 6). To calculate the parameters of the AD7741-based signal generator we need to refer to the transfer characteristic of the device available in the datasheet (Fig.60).
Fig.60 Here the fCLKIN is the clock frequency fed to the AD7741 chip. In the given project, fCLKIN will be equal to 2.4576MHz – that is the resonant frequency of the crystal connected to pins 3-4. The REFIN pin of AD7741 has left loose thus allowing the inner reference voltage of 2.5V to be used; thereby the REFIN point from Fig.60 corresponds to 2.5V. With these data it is easily to calculate the output frequency of VFC. From Fig.60 we obtain the following formula: fOUT = fOUTMIN + K × Vin (1), where fOUT – the output frequency on pin 8 of AD7741, fOUTMIN – the output frequency at the input voltage equal to 0 (Vin = 0), K – some coefficient which will be calculated a bit later, Vin – the input voltage applied to pin 6 of AD7741. The coefficient K may be calculated through the transfer characteristic (Fig.60). K = (fOUTMAX – fOUTMIN) / REFIN (2),
where REFIN = 2.5. In our case, fOUTMIN = 2457600 × 0.05 = 122880 (Hz). This frequency will appear on the output of the converter when Vin = 0 V. The fOUTMAX will be evaluated to: fOUTMAX = 2457600 × 0.45 = 1105920 (Hz). That will be the output frequency at maximum Vin = 2.5V. Finally, we can calculate K from formula (2): K = (1105920 – 122880)/2.5 = 393216 (Hz/V) Now we can rewrite formula (1): fOUT = 122880 + 393216 × Vin (3) Applying formula (3) we can calculate the output frequency fOUT at any given input voltage Vin. Conversely, knowing fOUT we can calculate the voltage Vin applied to the control input of the V-to-F converter (pin 6 of AD7741). As it is seen from formulas (1-2), the frequency range of AD7741 depends upon two values, fOUTMIN and fOUTMAX. Both values, in turn, are determined by the clock frequency of the inner crystal oscillator. Since the frequency of the inner oscillator is dependent upon the crystal attached to pins 3-4, we can adjust the output frequency in a wide range by choosing a crystal with a desired frequency. Note that it is also possible to feed an external clock frequency (up to 6 MHz) directly to the CLKIN pin leaving CLKOUT unconnected. AD7741 can be applied when measuring signals from remote sensors. Once an output frequency of AD7741 is taken, it is easily to calculate the voltage applied to the input (pin 6) using the above formulas 1-3.
Using digital-to-analog converters: the DC voltage source Digitally controlled high-stable analog signals may be produced by stand-alone digital-toanalog converters (D/A converter, DAC). A common 12-bit DAC powered from +5V voltage source can provide the resolution of 5/212 = 5/4096 = 0.00122 V that is acceptable for many applications where high precision is needed. The following project illustrates interfacing and programming the popular low-cost 12-bit DAC MCP4921 from Microchip Corp. The schematic circuit of the project is shown in Fig.61.
. Fig.61 The connections between the Raspberry Pi and the MCP4921 DAC are detailed in the table below: Raspberry Pi P1 pin
MCP4921 pin
GPIO18
3 (SCK)
GPIO23
2 (CS)
GPIO24
4 (SDI)
GPIO25
5 (LDAC)
The ″ GND ″ pin of the Raspberry Pi board must be tied to the common wire of the circuit. A timing diagram of the MCP4921 device shown in Fig.62 determines the relationships between interface signals while running the D/A conversion. Note that most DACs use similar timing diagrams, so understanding MCP4921 can help when applying other DAC chips.
Fig.62 The MCP4921 DAC is designed to operate through a serial SPI-compatible interface, so it is easily to drive this device from the Raspberry Pi board. Commands and data are sent to DAC via the SDI pin, with data being clocked in on the rising edge of SCK. The communications are unidirectional, so data cannot be read out of the MCP492X devices. The CS pin must be held low for the duration of a write command. The write command is composed of 16 bits and is used to configure the DAC’s control and data latches. Writing is initiated by pulling the CS pin low, followed by clocking four configuration bits and 12 data bits into the SDI pin on the rising edge of SCK. After processing all bits the CS signal must be brought high, thus causing the data to be latched into the selected DAC’s input registers. The MCP4921 chip includes a double-buffered latch structure to allow DAC output to be synchronized with the LDAC pin, if necessary.
When the LDAC signal goes low, the value held in the DAC input register is transferred into the DAC output register. Since all write operations to the MCP492X are 16-bit wide, any clocks past 16 will be ignored. The most significant four bits (15 – 12) are configuration bits, while the remaining 12 bits are data bits. When CS goes high, no data can be transferred into the device. Note that if the CS signal is driven high before all 16 bits have been transferred, shifting the data into the input registers will be aborted. Before running a write operation we need to make up a 16-bit word whose bits are described in the table below. Bit
Name
Description
15
A/B
″ 1 ″ means the data will be written to DACB, ″ 0 ″ allows writing to DACA
14
BUF
″ 1 ″ enables the buffered mode of the input, while ″ 0 ″ puts the input to the unbuffered mode
13
GA
″ 1 ″ sets the gain of the output signal equal to 1, ″ 0 ″ allows to double the magnitude of the output signal
12
SHDN
″ 1 ″ allows all operations on DAC, ″ 0 ″ disables the output buffer although write operations are still allowed
11–0
D11–D0
Data bits which represent the 12-bit number between 0 and 4095
In the given case, we will set the configuration bits 15 – 12 to 0111 (0x7 in the hexadecimal notation). The source code for driving the MCP4921 DAC is shown below (Listing 19). Listing 19. import RPi.GPIO as GPIO
SCK = 18 # GPIO18 ––-> SCK CS = 23 # GPIO23 ––-> CS SDI = 24 # GPIO24 ––-> SDI LDAC = 25 # GPIO25 ––-> LDAC Vref = 3.285 # voltage reference in Volts at pin 6 of DAC GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) # using GPIO numbers rather than pin ones GPIO.setup(CS, GPIO.OUT) # set GPIO23 as output GPIO.setup(SDI, GPIO.OUT) # set GPIO24 as output GPIO.setup(SCK, GPIO.OUT) # set GPIO18 as output GPIO.setup(LDAC, GPIO.OUT) # set GPIO4 as output Vout = input(‘Enter DAC output voltage, V:’) fDAC = float(Vout) binCode = int((fDAC / Vref) * 4096) cmd = 0x7000 # the highest 4 bits compose the command tmp = 0x0 fword = cmd | binCode # making up word to write GPIO.output(CS, True) # CS is brought high GPIO.output(CS, False) # CS goes low thus enabling the write #operation GPIO.output(LDAC, True) # LDAC goes high i1 = 0 while (i1 < 16): GPIO.output(SCK, False) # SCK goes low
tmp = fword & 0x8000 GPIO.output(SDI, False) if(tmp != 0x0): GPIO.output(SDI, True) GPIO.output(SCK, True) fword = fword << 1 i1 += 1 GPIO.output(CS, True) GPIO.output(LDAC, False) GPIO.output(LDAC, True) Here the cmd variable is assigned the command with a code 0x7. The binCode variable represents the binary code for the DAC’s output. The program waits until a user has entered the value of the desired output voltage (the input function), then the data of a float type will be assigned to the fDAC variable. The binary code representing the value stored in fDAC is calculated by the following statement: binCode = int((fDAC / Vref) * 4096) The full 16-bit word to be written to the DAC (the fword variable) is the combination of the value stored in the cmd variable and binCode: fword = cmd | binCode The SCK, SDI, CS and LDAC constants determine the pins where the DAC signal lines should be wired to. Since we are allowed only to transfer the data to MCP4921, all pins are configured as outputs. The data transfer requires 16 iterations running within the while loop with the loop variable i1 being incremented from 0 to 15. Before the loop is entered, the conversion is enabled by pulling the CS line high, then low. The LDAC line must be high. The following statements execute that: GPIO.output(CS, True)
GPIO.output(CS, False) GPIO.output(LDAC, True) In each iteration, the current MSB of fword is taken and stored in the tmp variable: tmp = fword & 0x8000; Depending on the value held in the tmp variable, the SDI line is set either high or low. Shortly afterwards the bit on SDI is clocked out by the SCK signal. The following statements perform those operations: GPIO.output(SDI, False) if(tmp != 0x0): GPIO.output(SDI, True) GPIO.output(SCK, True) The value in the fword variable is then shifted to the left by 1 in preparation for the next iteration: fword = fword << 1; When the loop exits, the CS signal is pulled high thus disabling the data transfer. The data obtained will be written to the output buffer of DAC by bringing the LDAC line low. The following sequence performs those steps: GPIO.output(CS, True) GPIO.output(LDAC, False) GPIO.output(LDAC, True) To test the application we first should save the source code in the MCP4921_DAC_Test.py file and launch this program. While running, the program prompts a user to enter the desired DAC output voltage:
pi@raspberrypi ~/developer $ sudo python MCP4921_DAC_Test.py Enter DAC output voltage, V:0.55 pi@raspberrypi ~/developer $ sudo python MCP4921_DAC_Test.py Enter DAC output voltage, V:2.39 pi@raspberrypi ~/developer $ sudo python MCP4921_DAC_Test.py Enter DAC output voltage, V:0.16 The voltage level at the DAC’s output (pin 8 of MCP4921) can be checked using a multimeter or voltmeter.
Using digital-to-analog converters: the PWM circuit with LTC6992 This section describes the use of a versatile chip LTC6992 from Linear Technology which can be applied as a ″ Voltage-to-Duty Cycle ″ converter. The brief description of LTC6992 taken from its datasheet is given below. The LTC6992 is a silicon oscillator with an easy-to-use analog voltage-controlled pulse width modulation (PWM) capability. The LTC6992 is part of the TimerBlox family of versatile silicon timing devices. The LTC6992 operates with a single 2.7V to 5.5V power supply and provides the frequency range from 3.81 Hz to 1 MHz. A single resistor, RSET, programs the LTC6992’s internal master oscillator frequency. The output frequency is determined by this master oscillator and an internal frequency divider, NDIV, programmable to eight settings from 1 to 16384 (Fig.63).
Fig.63 Applying a voltage between 0V and 1V on the MOD pin sets the duty cycle. The four versions differ in their minimum/maximum duty cycle (see the table below). DEVICE NAME
PWM DUTY CYCLE RANGE
LTC6992-1
0% to 100%
LTC6992-2
5% to 95%
LTC6992-3
0% to 95%
LTC6992-4
5% to 100%
Note that a minimum duty cycle limit of 0% or maximum duty cycle limit of 100% allows oscillations to stop at the extreme duty cycle settings.
The circuit shown in Fig.64 allows to generate the PWM signal with a variable duty cycle which can be adjusted by the voltage on the DAC MCP4921 output. The DAC, in turn, is driven by the Raspberry Pi software, so we can set the PWM duty cycle programmatically. Note that the pin configuration of the LTC6992 chip corresponds to the 6-Lead Plastic TSOT-23package.
Fig.64 In this circuit, the LTC6992-1 chip provides the output pulse train on pin 6 whose duty cycle will be linearly proportional to the modulated voltage VMOD provided by the D/A converter. The signal VMOD arrives at the Pulse Width Modulation Input (MOD, pin 1 of LTC6992-1). The linear control of the duty cycle should range between approximately 100mV to 900mV on the MOD pin. Beyond those limits, the output will either clamp at 5% or 95%, or stop oscillating (0% or 100% duty cycle), depending on the version. The resistor RSET which determines the oscillation frequency is connected to the SET input (pin 3). In our case, the RSET is taken equal to 100K. The DIV input (pin 4) is a Programmable Divider and Polarity Input. The DIV pin connects to an internal, V+ referenced 4-bit A/D converter that determines the DIVCODE value. DIVCODE programs two settings on the LTC6992. First, it determines the output frequency divider setting, NDIV. Second, DIVCODE determines the output polarity. The input voltage to DIV is generated by the resistor divider R1-R2 connected between V+ and GND. To ensure an accurate result a developer must use at least 1% resistors. The DIV pin and resistors should be shielded from the OUT pin or any other traces that have
fast edges. In this circuit, the NDIV is set to 1024, thus determining the output frequency (OUT, (pin 6 of LTC6992-1) to be 488 Hz (see formula in Fig.63). Other possible values of NDIV can be calculated using the approach described in the device datasheet. The relationship between the analog voltage VMOD applied to the MOD input (pin 1) and the duty cycle of the output frequency is given by the following formula (Fig.65).
Fig.65 Knowing the value of the duty cycle D, it is easily to calculate the value of the input voltage VMOD applied to the MOD input of LTC6992-1. The basic source code for driving LTC6992-1 PWM circuit can be the same as that shown in Listing 18, although a few modifications should be done.
Generating analog waveforms using Direct Digital Synthesizer AD9833 Direct Digital Synthesis (DDS) is a method of producing an analog waveform (usually a sine wave) by generating a time-varying signal in digital form and then performing a digital-to-analog conversion. Because operations within a DDS device are primarily digital, this gives fast switching between output frequencies, fine frequency resolution, and operation over a broad spectrum of frequencies. The ability to accurately produce and control waveforms of various frequencies and profiles has become a key requirement common to a number of industries. Many possibilities for frequency generation are open to a designer, but the DDS technique is rapidly gaining acceptance for solving frequency (or waveform) generation requirements in both communications and industrial applications because single-chip IC devices can generate programmable analog output waveforms simply and with high resolution and accuracy. By using modern DDS chips, we can obtain sine wave, triangular and rectangle signals with low-level total harmonic distortion (THD). This section describes a simple but effective function generator built around a popular chip AD9833 from Analog Devices. The DDS chip is driven by the Raspberry Pi board through an SPI-compatible interface. The schematic circuit of the function generator is shown in Fig.66.
Fig.66 The AD9833 chip is connected to the Raspberry Pi via 3 signal lines: FSYNC, SCLK and SDATA. All three lines are inputs to AD9833 and are driven by the Raspberry Pi software. The FSYNC (pin 8 of AD9833) is the frame synchronization signal for the input data. When FSYNC is driven low, the internal logic of the DDS chip is informed that a new word is being loaded into the device. The FSYNC line is wired to pin GPIO18 of the Raspberry Pi. The configuration data-word is brought to the SDATA line. Each data-word consists of the 16 bits that are sequentially passed to the SDATA input of the device; SDATA (pin 6 of AD9833) is wired to pin GPIO24 of the Raspberry Pi. The SCLK signal line (pin 7 of AD9833) serves as a serial clock input. Data on the SDATA line are clocked into the AD9833 on each falling edge of SCLK. The SCLK line is wired to pin GPIO23 of the Raspberry Pi. The output signal is taken from the VOUT pin. Both analog and digital outputs from the AD9833 are available at the VOUT pin. An external load resistor to this pin is not required because the device has a 200 Ohm built-in resistor. In our project the output signal is taken from the inner 10-bit digital-to-analog converter (DAC). Our waveform generator will be configured to output either a sine wave or a triangle signal. The AD9833 DDS device is fed by an external digital clock through the MCLK input.
DDS output frequencies are expressed as a binary fraction of the frequency of MCLK. The output frequency accuracy and phase noise are determined by this clock as well. In this project the single-chip crystal oscillator with a frequency of 25 MHz delivers clock pulses to the MCLK input. You can take any crystal oscillator giving 25 MHz TTLcompatible signal and connect its output to the MCLK input. The MCLK input can also be driven by other frequency (say, 10 MHz) – in that case you should recalculate the configuration parameters to AD9833. Some words about programming AD9833. The theory of operation is complicated enough, so we will not discuss this in detail. I encourage you to read more on the subject in AD9833 datasheet and look up the relevant information in application notes concerning the DDS technique provided by Analog Devices. From the programming point of view, we need to configure at least three registers of AD9833: the control register, one of two frequency registers and one of two phase registers. The 16-bit control register of AD9833 allows a developer to configure the operation mode of a DDS chip. All control bits other than the mode bit are sampled on the internal falling edge of MCLK. The frequency register (0 or 1) defines the output frequency as a fraction of the MCLK frequency. The register contains 32 bits where the lower 28 bits are data bits used for configuring the output frequency. The phase register (0 or 1) has the contents being added to the output of the phase accumulator. The lower 12 bits of each phase register are data bits. In order to change the entire contents of a frequency register, two consecutive writes to the same address must be performed. Each write operation takes 16 bit. The phase register requires one writing operation. For complete configuring, we need at least 5 write operations, each 16 bit long. The sequence must begin with writing the control register (16 bit), then two 16-bit write cycles to the frequency register should follow. Afterwards the 16-bit data word should be written to the phase register and finally the 16-bit data goes to the control register thus terminating the write cycle. The timing diagram for the write operation is given below (Fig.67).
Fig.67 It is seen that data is loaded into the AD9833 device as a 16-bit word under the control of a serial clock input, SCLK. The FSYNC input is a level-triggered input that acts as a frame synchronization and chip enable. Data can be transferred into the device only when FSYNC is low. After bringing FSYNC low the serial data stream is shifted into the input shift register of the device on the falling edges of SCLK for 16 clock pulses. FSYNC may be taken high after the 16th falling edge of SCLK. Alternatively, the FSYNC line can be kept low for a multiple of 16 SCLK pulses and then brought high at the end of the data transfer. In this way, a continuous stream of 16-bit words can be loaded while FSYNC is held low; FSYNC goes high only after the 16th SCLK falling edge of the last word loaded. The SCLK can be continuous, or it can idle high or low between write operations. In either case, it must be high when FSYNC goes low. The following Python source code (Listing 20) allows to configure the AD9833 DDS chip to produce a sine wave of 10.37 KHz at the clock frequency MCLK = 25 MHz. Listing 20. import RPi.GPIO as GPIO FSYNC = 18 # AD9833 pin 8 is tied to GPIO18 SCLK = 23 # AD9833 pin 7 is tied to GPIO23 SDATA = 24 # AD9833 pin 6 is tied to GPIO24 DATA = (0x2100, 0x72F3, 0x4006, 0xC000, 0x2000) #F = 10,37KHz # DATA = (0x2100, 0x6D2D, 0x4009, 0xC000, 0x2000) #F = 14.81KHz
# DATA = (0x2100, 0x7AFE, 0x4004, 0xC000, 0x2000) #F = 7.51KHz # DATA = (0x2100, 0x6C70, 0x400C, 0xC000, 0x2000) #F = 19.37 KHz # DATA = (0x2100, 0x7538, 0x4007, 0xC000, 0x2000) #F = 11.95 KHz # DATA = (0x2100, 0x58AC, 0x4002, 0xC000, 0x2000) #F = 3.64 KHz # DATA = (0x2100, 0x593e, 0x400F, 0xC000, 0x2000) #F = 23.49 KHz # DATA = (0x2100, 0x4245, 0x4001, 0xC000, 0x2000) #F = 1.58 KHz # DATA = (0x2100, 0x5E79, 0x4004, 0xC000, 0x2000) #F = 6.83 KHz # DATA = (0x2100, 0x5ADE, 0x4005, 0xC000, 0x2000) #F = 8.27 KHz def WriteDDS(code): GPIO.output(SCLK, True) GPIO.output(FSYNC, True) GPIO.output(FSYNC, False) i1 = 0 while (i1 < 16): GPIO.output(SCLK, True) tmp = code & 0x8000 GPIO.output(SDATA, False) if(tmp != 0x0): GPIO.output(SDATA, True) GPIO.output(SCLK, False) code = code << 1 i1 += 1 GPIO.output(FSYNC, True) GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) #using GPIO numbers GPIO.setup(FSYNC, GPIO.OUT) # set GPIO18 as output GPIO.setup(SCLK, GPIO.OUT) # set GPIO23 as output GPIO.setup(SDATA, GPIO.OUT) # set GPIO24 as output
i1 = 0 while(i1 < 5): WriteDDS(DATA[i1]) i1 += 1 Here the initialization sequence to AD9833 is held in the DATA array containing 5 elements. The values of elements of DATA correspond to the sine wave output signal with a frequency of 10.37 KHz at MCLK = 25 MHz. The first element, DATA[0], holds a value to be written in the control register. DATA[0] determines the operating mode and allows to reset the chip before configuring the frequency register 0 and phase register 0. Elements DATA[1] and DATA[2] contain 32-bit data to be written in the frequency register 0. The 16-bit value of DATA[3] will be written to the phase register 0. The last element, DATA[4], keeps a value for the control register. After all bits of DATA[4] have been transferred, the reset mode exits and the DDS chip goes to its operating mode. We can calculate values of elements of the DATA array manually using formulas given in the AD9833 datasheet. It is much easier, however, to launch a special programming tool from Analog Devices called DDS Configuration Assistant that can be found in http://designtools.analog.com/dt/ad98334/ad9833.html. Below you can see the example of the DDS Configuration Assistant window with parameters set for the sine wave signal of 10.37 KHz (Fig.68).
Fig.68 Let’s back to our source code. In order to generate a triangle signal of the same frequency (10.37 KHz) we can modify the first and last elements of the DATA array. The DATA array will then look like the following: DATA = (0x2102, 0x72F3, 0x4006, 0xC000, 0x2002) The WriteDDS function accomplishes the write operation for a single 16-bit value on the SPI interface. Pins GPIO18, GPIO23 and GPIO24 of the Raspberry Pi board must be put in the OUTPUT mode prior to writing. The operability of this function generator was tested by using LabVIEW virtual oscilloscope built upon the multifunction DAQ NI PCI-6221. The output window of the LabVIEW oscilloscope (Fig.69) shows the sine wave unfiltered signal of 11.95 KHz taken from the AD9833 output.
Fig.69 Note that the amplitude of the analog signal at the DAC output of AD9833 is about 0.6V, so some applications may require to amplify the signal on VOUT. Additionally, filtering the output DDS signal using low-pass or band-pass filter can reduce the noise thereby lowering THD. Both functions (amplifying and filtering) may be combined in a single stage formed by an op-amp. Almost any general purpose op-amp (OPA348, OPA364, MCP6021, TL071, etc.) would suite such conditioning circuit.
Signal synthesis using AD9850 DDS This section describes one more project with DDS. The project is based upon using the popular DDS chip AD9850 which can produce the sine wave and square wave signals in a wide range of frequencies. The device output frequency is controlled through the SPIcompatible interface by the Raspberry Pi board. Before we start describing the hardware/software let’s look at how AD9850 works. The brief description that follows is taken from the datasheet on the device. The AD9850 is a highly integrated device that uses advanced DDS technology coupled with an internal high speed, high performance D/A converter and comparator to form a complete, digitally programmable frequency synthesizer and clock generator function. When referenced to an accurate clock source, the AD9850 generates a spectrally pure, frequency/phase programmable, analog output sine wave. This sine wave can be used directly as a frequency source, or it can be converted to a square wave for agile-clock generator applications. The AD9850’s innovative high speed DDS core provides a 32-bit frequency tuning word, which results in an output tuning resolution of 0.0291 Hz for a 125 MHz reference clock input. The AD9850’s circuit architecture allows the generation of output frequencies of up to one-half the reference clock frequency (or 62.5 MHz), and the output frequency can be digitally changed (asynchronously) at a rate of up to 23 million new frequencies per second. The device also provides five bits of digitally controlled phase modulation, which enables phase shifting of its output in increments of 180°, 90°, 45°, 22.5°, 11.25°, and any combination thereof. The AD9850 also contains a high speed comparator that can be configured to accept the (externally) filtered output of the DAC to generate a low jitter square wave output. This facilitates the device’s use as an agile clock generator function. The frequency tuning, control, and phase modulation words are loaded into the AD9850 via a parallel byte or serial loading format. The parallel load format consists of five iterative loads of an 8-bit control word (byte). The first byte controls phase modulation, power-down enable, and loading format; Bytes 2 to 5 comprise the 32-bit frequency tuning word. Serial loading is accomplished via a 40-bit serial data stream on a single pin. The AD9850 Complete DDS uses advanced CMOS technology to provide this breakthrough level of functionality and performance on just 155 mW of power dissipation (3.3 V supply). The following common scheme (Fig.70) can be used for connecting AD9850 to external circuitry.
Fig.70 In this circuit, the framing FQ_UD signal (pin 8 of AD9850) can be controlled by the GPIO18 pin of the Raspberry Pi. The data line DATA (D7, pin 25 of AD9850) receives the data bit stream from pin GPIO24; the data are clocked by the signal arriving at the W_CLK line (pin 7 of AD9850) from the GPIO23 pin. Assembling such a circuit can be a hard work, so it would be much better to apply one of numerous ready-to-use modules with AD9850. One of such modules taken to this project is shown in Fig.71.
Fig.71 It is seen that the reference clock fed to the AD9850 chip is 125 MHz, therefore the output frequency can reach 62.5 MHz. As to this module, it is stated that the real output frequency can reach 40 MHz because of the low-pass filter placed at the DAC output of
AD9850. However, the developers experimenting with this module will be capable to pull up the output to its maximal frequency. The project described here uses the above module. Fig.72 shows connections between the AD9850 board and the Raspberry Pi.
Fig.72 In this circuit, the DATA line (pin D7 of AD9850) is connected to pin GPIO24 of the Raspberry Pi board. The framing signal FQ_UD comes from pin GPIO18 and the clock input W_CLK of AD9850 is wired to pin GPIO24. The RESET line of the AD9850 chip is wired to ″ ground ″ . The output signal can be taken either from the SINA or SINB pin. Note that the connections in circuit shown in Fig.72 are specific for my own AD9850 board purchased at www.dx.com; the pin configuration on other AD9850 boards may be different, so you must carefully examine your own AD9850 board before connecting it to the Raspberry Pi! All wires to AD9850 must be as short as possible, usually 5-8 cm long. It is also important to provide the power to AD9850 from a stand-alone steady DC +5V voltage source. Although it is possible to power AD9850 DDS from the Raspberry Pi onboard +5V source, I don’t recommend to do this because AD9850 draws relatively large current. This can cause undesired behavior of the Raspberry Pi system! Our application takes the data for configuring the AD9850 chip from the special application DDS Configuration Assistant that can be found at http://designtools.analog.com/dt/dds/ad9850.html. In this project, the output frequency is set to 7 KHz. The source code for driving AD9850
is shown in Listing 21. Listing 21. import RPi.GPIO as GPIO FQ_UD = 18 # AD9850 FQ_UD pin is tied to GPIO18 W_CLK = 23 # AD9850 W_CLK pin is tied to GPIO23 DAT = 24 # AD9850 DATA pin is tied to GPIO24 SDATA = (0x61, 0xD5, 0xC0, 0x00, 0x00) # F = 7.0KHz #SDATA = (0x49, 0xB2, 0xC0, 0x00, 0x00) # F = 6.3KHz #SDATA = (0xB5, 0xFE, 0xA0, 0x00, 0x00) # F = 10.488KHz #SDATA = (0xCA, 0xD2, 0x10, 0x00, 0x00) # F = 15.820KHz #SDATA = (0xC3, 0xEA, 0x90, 0x00, 0x00) # F = 17.82KHz #SDATA = (0xD5, 0xD7, 0x90, 0x00, 0x00) # F = 18.922KHz #SDATA = (0x17, 0x8D, 0x50, 0x00, 0x00) # F = 20.399KHz #SDATA = (0xDB, 0xC5, 0x30, 0x00, 0x00) # F = 24.109KHz #SDATA = (0x96, 0x28, 0x88, 0x00, 0x00) # F = 32.577KHz def WriteDDS(code): i1 = 0 while (i1 < 8): GPIO.output(W_CLK, False) tmp = code & 0x80 tmp = tmp >> 7 GPIO.output(DAT, tmp) GPIO.output(W_CLK, True) code = code << 1 i1 += 1
GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) # using GPIO numbers GPIO.setup(FQ_UD, GPIO.OUT) # set GPIO18 as output GPIO.setup(W_CLK, GPIO.OUT) # set GPIO23 as output GPIO.setup(DAT, GPIO.OUT) # set GPIO24 as output GPIO.output(W_CLK, True) GPIO.output(W_CLK, False) GPIO.output(FQ_UD, True) GPIO.output(FQ_UD, False) i1 = 0 while(i1 < 5): WriteDDS(SDATA[i1]) i1 += 1 GPIO.output(FQ_UD, True) As it is seen, the SDATA elements were calculated for various frequencies. In our case, the program uses the SDATA array corresponding to the frequency of 7.0 KHz; those data were obtained using the DDS Configuration Assistant as is shown in Fig.73.
Fig.73
Measuring a signal frequency using Frequency-To-Voltage converters Frequency-To-Voltage (F-To-V, FVC) converters are used for direct frequency measurements in systems where sensors yield signals whose frequency changes proportionally to physical quantity (temperature, humidity, pressure, light, etc.) being measured. FVC picks up an input frequency and produces the output voltage proportional to this frequency. An output signal of FVC may be fed to some conditioning circuit with an analog-to-digital converter, so a value of input frequency may be calculated. F-To-V converters may also be regarded as some kind of a digital-to-analog converter which converts an input pulse train into an output DC voltage. This section is dedicated to the LM231/LM331 family of Voltage-To-Frequency/Frequency-To-Voltage converters; those devices are ideally suited for use in simple low-cost circuits for analog-to-digital conversion, precision Frequency-To-Voltage (FVC) conversion, long-term integration, linear frequency modulation or demodulation, and many other functions. The following project uses a popular low-cost device LM231 configured as FVC for measuring a frequency of digital pulse trains. The schematic circuit of the project is built around the LM231 chip, although LM131/331 can be used as well. In this circuit LM231 operates as a Frequency-To-Voltage converter providing a reliable high-precision F-to-V conversion. The output signal of LM231 goes to the input of the analog-to-digital converter MCP3201. The A/D converter, in turn, produces the binary representation of the input signal, so the Python program can calculate the frequency at the LM231 input. Take a look at the schematic circuit (Fig.74).
Fig.74 In this circuit, the input signal with the frequency fIN comes to the THR input (pin 6) of FVC through the coupling capacitor C1. The output voltage Vout will be extremely linear to the input frequency fIN, resistor RL and the RtCt pair (see formula in Fig.74). The F-To-V conversion begins with differentiating the input frequency by the capacitor C1 and resistor R1. The pulse train then goes to pin 6 (threshold) of the LM231 chip. The negative edge of the pulse on pin 6 affects the built-in comparator circuit which, in turn, triggers the timer circuit. The current flowing through pin 6 will be proportional to the input frequency and the timing constant determined by the RtCt pair. LM231 provides an output current proportional to the input frequency on terminal IOUT (pin 1). Since we need the output voltage Vout rather than output current, the RLC2
network is wired to pin 1. The pair RLC2 also serves as the simplest low-pass filter and provides the load for the output current. The Vout signal then goes to the analog input IN+ of the MCP3201 A/D converter. The ″ ground ″ wire of the circuit should be connected to the ″ GND ″ terminal on the Raspberry Pi board. The LM231 chip should be powered using the steady DC +5V voltage source. The value of R3 depends on the supply voltage and should be calculated using the formula: R3 = (Vs – 2V) / (0.2mA) You may equally take LM331 instead of LM231 without any modifications of the circuit. The Python source code for this project is given in Listing 22. Listing 22. from time import sleep import RPi.GPIO as GPIO CS = 18 # GPIO18 as CS DOUT = 23 # GPIO23 as DOUT CLK = 24 # GPIO24 as CLK Vref = 3.29 # Vref = 3.29V GPIO.setwarnings(False); GPIO.setmode(GPIO.BCM) # using GPIO numbers rather than pin numbers GPIO.setup(CS, GPIO.OUT) # set GPIO18 as output GPIO.setup(DOUT, GPIO.IN) # set GPIO23 as output GPIO.setup(CLK, GPIO.OUT) # set GPIO24 as output while (True): GPIO.output(CS, True) # CS is brough high GPIO.output(CLK, True) # CLK goes high while CS is held high
GPIO.output(CS, False) # CS goes low thus starting conversion binData = 0 i1 = 14 while (i1 >= 0): GPIO.output(CLK, False) bitDOUT = GPIO.input(DOUT) GPIO.output(CLK, True) bitDOUT = bitDOUT << i1 binData |= bitDOUT i1 -= 1 GPIO.output(CS, True) binData &= 0xFFF res = Vref * binData/4096.0 print(‘Input voltage, V = %5.2f’ % res) K = 925.2 Freq = int(K * res) print(‘Input Frequency, Hz = ‘ + str(Freq)) print sleep(5) The binData holds the binary code produced by the analog-to-digital converter after processing the signal on input IN+ of MCP3201. The res variable is assigned the measured voltage level on IN+. The value of the input frequency is stored in the Freq variable. The Kf variable is calculated using the formula below: Vout = fIN x 2.09 x (RL/RS) x (Rt x Ct) Then Kf will be equal to:
Kf = 1 / (2.09 x (RL/RS) x (Rt x Ct)) The F-To-V converter can also be used for generating DC voltage controlled by an input frequency. In that case the signal fIN may come from some digital pin of the Raspberry Pi board. Once fIN varies, the corresponding DC output voltage varies as well; this way it is possible to build a digitally controlled DC voltage source driven by a pulse train from the Raspberry Pi.
Measuring frequencies of analog periodical signals Often a developer needs to measure a frequency (period) of periodical analog (continuous) signals coming from analog sensors and/or external circuits. Those signals usually appear to be non-rectangular with variable amplitude. Well-known non-rectangular waveforms are sine wave, triangular and sawtooth. It is impossible for the Raspberry Pi board running Linux to directly measure the frequency (period) of such signals without involving a special programming technique and hardware. However, we can measure the frequency of periodical signals using additional electronic circuits. The common block diagram of a system measuring a frequency of periodical signals may look like that shown in Fig.75.
Fig.75 Let’s analyze this diagram. Amplitudes of analog input signals may vary within a wide range, from several tens millivolts through several volts, so it may turn out that we need either amplify or attenuate a signal of interest in preparation for further processing. Usually we need an amplifier to increase an amplitude of a small signal (about several tens millivolts or smaller). The good solution would be to apply some amplifier circuit built around an operational amplifier (op-amp) or take an instrumentation amplifier (in-amp) circuit. If the amplitude of an incoming signal turns out to be too large, this can cause non-linear effects in circuits that follow. To prevent that, we need to attenuate signal amplitude to a reasonable level. As the simplest attenuator circuit we can use a voltage divider consisting
of two resistors connected in series or take a potentiometer. Both amplifier and attenuator stages are optional, so we can omit either of them if the input signal is suitable for the following stage (usually a low-pass filter). Experimental measurements are never perfect, even with sophisticated modern instruments, so almost every signal contains unwanted component called ″ noise ″ . Noise is a random fluctuation in an electrical signal when unpredictable variations in the measured signal from moment to moment or from measurement to measurement occur. Remember that every analog signal has high and low frequency noise. We can easily shed high noise by applying a simple active low-pass or band-pass filter with appropriate parameters. The filter should be followed by a zero-crossing detector which creates a rectangular waveform from a random analog signal. The zero-crossing detector produces a pulse train compatible with TTL logic, suitable for processing by the Frequency-ToVoltage converter (LM231/LM331) stage. The DC voltage appearing on the F-To-V output can easily be digitized by an analog-todigital converter (MCP3201) that follows. Using the binary code obtained from the A/D converter, the Raspberry Pi software can further calculate the frequency of the original signal at the input of the system. The practical measurement circuit (Fig.76) based upon approaches described above may come in handy when measuring periodical non-rectangular signals. The input signals to this circuit were taken from the LabVIEW virtual signal generator based upon NI PCI6221 DAQ module. The signals were of various waveforms (sine wave, sawtooth, triangular and rectangular); their frequencies ranged from about 100 Hz through 5000 Hz and amplitudes varying from about 0.15V to 2V. The useful signals were deliberately combined with low-level noise of white and 1/F types.
Fig.76 In this circuit, the analog input signal goes first to the low-pass active filter formed by the R1C2 network and op-amp OPA364 through the coupling capacitor C1. The ″ cut-off ″ frequency of this filter is determined as 1/2×π×R1×C2 that is close to 6 KHz. By altering the values of R1 and/or C2,we can extend or narrow ″ cut-off ″ frequency of the filter. The filtered signal is then fed to the next stage that is a zero-crossing detector built around the comparator TLC3702 (A2). The zero-crossing detector converts the analog input signal into the TTL-compatible pulse train suitable for the Frequency-To-Voltage Converter. FVC is based upon an LM231/LM331 device and provides the output voltage driving the analog-to-digital converter (MCP3201 or other A/D chip). Finally, the Raspberry Pi Python application calculates the value of a frequency present at the input of our circuit using the binary code obtained from the A/D converter. The above circuit allows to reliably measure the frequency of analog signals (sine wave, triangle and sawtooth) whose amplitude changes from about 0.15 V through 1.9V. The frequency of signals may range from several tens Hz to 5 KHz. Note that when the noise level increases the reliable measurements would be possible at the amplitudes greater than 0.4-0.5V.