The Pico In MicroPython: ADC
Written by Mike James & Harry Fairhead   
Monday, 13 March 2023
Article Index
The Pico In MicroPython: ADC
The Protocol

Basic Configuration

Now we come to the configuration of the SPI bus. We have some rough figures for the SPI clock speed - around 10kHz to a little more than 1.35MHz. So a clock frequency of 500kHz seems a reasonable starting point.

From the datasheet, the chip select has to be active low and, by default, data is sent most significant bit first for both the master and the slave. The only puzzle is what mode to use? This is listed in the datasheet as mode 0 0 with clock active high or mode 1 1 with clock active low. For simplicity we will use mode 0 0.

We now have enough information to initialize the slave:

spi = SPI(0, sck=Pin(18), miso=Pin(16), mosi=Pin(19))
spi.init(baudrate=500_000, bits=8, polarity=0, 
phase=0, firstbit=SPI.MSB) CS = Pin(17, Pin.OUT) CS.high() sleep_ms(1)

The Protocol

Now we have the SPI initialized and ready to transfer data, but what data do we transfer? As already discussed in the previous chapter, the SPI bus doesn't have any standard commands or addressing structure. Each device responds to data sent in different ways and sends data back in different ways. You simply have to read the datasheet to find out what the commands and responses are.

Reading the datasheet might be initially confusing because it says that you have to send five bits to the slave - a start bit, a bit that selects its operating mode single or differential, and a 3-bit channel number. The operating mode is 1 for single-ended and 0 for differential.

So to read Channel 3, i.e. 011, in single-ended mode you would send the slave:


where an x can take either value. In response, the slave holds its output in a high impedance state until the sixth clock pulse, then sends a zero bit on the seventh, followed by bit 9 of the data on the eighth clock pulse.

That is, the slave sends back:


where x means indeterminate.

The remaining nine bits are sent back in response to the next nine clock pulses. This means you have to transfer three bytes to get all ten bits of data. This all makes reading the data in 8-bit chunks confusing.

The datasheet suggests a different way of doing the job that delivers the data more neatly packed into three bytes. What it suggests to send a single byte is:


At the same time, the slave transfers random data, which is ignored. The final 1 is treated as the start bit. If you now transfer a second byte with most significant bit indicating single or differential mode, then a 3-bit channel address and the remaining bits set to 0, the slave will respond with the null and the top two bits of the conversion. Now all you have to do to get the final eight bits of data is to read a third byte:

This way you get two neat bytes containing the data with all the low-order bits in their correct positions.

Using this information we can now write some instructions that read a given channel. For example, to read Channel 0 we first send a byte set to 0x01 as the start bit and ignore the byte the slave transfers. Next we send 0x80 to select single-ended and Channel 0 and keep the byte the slave sends back as the two high-order bits. Finally, we send a zero byte (0x00) so that we get the low-order bits from the slave:

write=bytearray([0x01, 0x80, 0x00])

Notice you cannot send the three bytes one at a time using transfer because that results in the CS line being deactivated between the transfer of each byte.

To get the data out of rBuff we need to do some bit manipulation:

data =  (read[1] & 0x03) << 8 |  read[2]    

The first part of the expression extracts the low three bits from the first byte the slave sent and, as these are the most significant bits, they are shifted up eight places. The rest of the bits are then ORed with them to give the full 10‑bit result. To convert to volts we use:

volts =  data * 3.3 / 1023.0

assuming that VREF is 3.3V.

In a real application you would also need to convert the voltage to some other quantity, like temperature or light level.

If you connect a logic analyzer to the SPI bus you will see something like:

You can see the commands and the response, in this case a reading of 0.693V.

The complete program is:

from utime import sleep_ms
from machine import Pin, SPI
from time import sleep
spi = SPI(0, sck=Pin(18), miso=Pin(16), mosi=Pin(19))
spi.init(baudrate=500_000, bits=8, polarity=0, 
phase=0, firstbit=SPI.MSB) CS = Pin(17, Pin.OUT) CS.high() sleep_ms(1) CS.low() write=bytearray([0x01, 0x80, 0x00]) read=bytearray(3) spi.write_readinto(write,read) CS.high() data = (read[1] & 0x03) << 8 | read[2] volts = data * 3.3 / 1023.0 print(volts) spi.deinit()

In chapter but not in this extract

  • SPI ADC Class
  • How Fast?


  • The Pico has a single ADC with four inputs. It is subject to a lot of noise from the power supply and circuits around it which reduces its accuracy.

  • You can read individual input lines using the ADC class.

  • The ADC class doesn’t support many of the more advanced features of the Pico’s ADC hardware.

  • Making SPI work with any particular device has four steps:

    1. Discover how to connect the device to the SPI pins. This is a matter of identifying pinouts and mostly what chip selects are supported.

    2. Find out how to configure the Pi's SPI bus to work with the device. This is mostly a matter of clock speed and mode.

    3. Identify the commands that you need to send to the device to get it to do something and what data it sends back as a response.

    4. Find, or work out, the relationship between the raw reading, the voltage and the quantity the voltage represents.

  • The MCP3000 range of A-to-D converters is very easy to use via SPI.

  • You can read data at rates as fast as 6kHz.


Programming the Raspberry Pi Pico/W In MicroPython Second Edition

By Harry Fairhead & Mike James


Buy from Amazon.


  • Preface
  • Chapter 1 The Raspberry Pi Pico – Before We Begin
  • Chapter 2 Getting Started
  • Chapter 3 Getting Started With The GPIO
  • Chapter 4 Simple Output
  • Chapter 5 Some Electronics
  • Chapter 6 Simple Input
             Extract: Simple Input 
  • Chapter 7 Advanced Input – Events and Interrupts
  • Chapter 8 Pulse Width Modulation
             Extract: PWM 
  • Chapter 9 Controlling Motors And Servos
             Extract: DC Motors
  • Chapter 10 Getting Started With The SPI Bus
  • Chapter 11 A-To-D and The SPI Bus ***NEW!
  • Chapter 12 Using The I2C Bus
  • Chapter 13 Using The PIO   
  • Chapter 14 The DHT22 Sensor Implementing A Custom Protocol
             Extract: A PIO Driver For The DHT22  
  • Chapter 15 The 1‑Wire Bus And The DS1820
  • Chapter 16 The Serial Port
  • Chapter 17 Using The Pico W - WiFi
             Extract: HTTP Client 
  • Chapter 18 Asyncio And Servers
  • Chapter 19 Direct To The Hardware
             Extract: Direct To The Hardware

Also of interest:

Raspberry Pico File System






To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.


JetBrains RustRover Now Commercially Available

JetBrains has announced the commercial release of RustRover, an IDE for Rust developers. The company describes RustRover  as combining advanced coding support with an integrated toolchain.

SQL Turns 50

The first release of SQL was in June 1974. Designed at IBM by Donald D. Chamberlin and Raymond F. Boyce, it was based on the relational model proposed by E.F. Codd. SQL became the most widely used dat [ ... ]

More News

C book



or email your comment to:

Last Updated ( Wednesday, 15 March 2023 )