CircuitPython & Python Usage

In addition to taking pictures with the camera in Arduino, you can also use Python and CircuitPython to snap photos and save them to a SD card, computer or Raspberry Pi!  The Adafruit CircuitPython VC0706 library is your key to accessing the TTL camera and grabbing images over a serial connection.

You can use this camera with any CircuitPython microcontroller board or with a computer that has GPIO and Python thanks to Adafruit_Blinka, our CircuitPython-for-Python compatibility library.

CircuitPython Microcontroller Wiring

First you'll need to connect the TTL camera and a micro SD card holder to your CircuitPython board.  The easiest and recommended option is to use a Feather M0 Adalogger board loaded with CircuitPython. This gives you a micro SD card holder that's pre-wired and ready to go, just connect the camera to the board.  Here's an example of connecting the camera to a Feather M0 Adalogger:

Just like connecting the camera to an Arduino you need to connect these wires:

  • Camera 5V to board USB or 5V power (note this means you must have the board plugged into a USB / 5V power supply to properly power the camera).
  • Camera GND to board GND.
  • Camera RX to board TX.
  • Camera TX to board RX.

In addition, please make sure a micro SD card formatted with the FAT32 filesystem (highly recommended to use the official SD card formatter here and not your operating system's formatter!) is inserted in the SD card holder.

Python Computer Wiring

Since there's dozens of Linux computers/boards you can use, we will show wiring for Raspberry Pi. For other platforms, please visit the guide for CircuitPython on Linux to see whether your platform is supported.

Here you have two options: An external USB-to-serial converter, or the built-in UART on the Pi's TX/RX pins. Here's an example of wiring up the USB-to-serial converter:

  • Camera Vin  to USB 5V or 3V (red wire on USB console cable)
  • Camera Ground to USB Ground (black wire)
  • Camera RX (white wire) to USB TX (green wire)
  • Camera TX (green wire) to USB RX (white wire)

Here's an example using the Pi's built-in UART:

  • Camera 5V (black wire)  to PI 3V or 5V
  • Camera GND (black wire) to Pi Ground 
  • Camera RX (white wire) to Pi TX
  • Camera TX (green wire) to Pi RX

If you want to use the built-in UART, you'll need to disable the serial console and enable the serial port hardware in raspi-config. See the UART/Serial section of the CircuitPython on Raspberry Pi guide for detailed instructions on how to do this.

All single board computers are a bit different. Some expose the serial port/UART, others have it soft connected to the console, while others do not allow UART use by the user. Please see your board documentation to see what using your board UART may entail.

CircuitPython Installation of VC0706

As mentioned, you'll also need to install the Adafruit CircuitPython VC0706 library on your CircuitPython board. In addition, the Adafruit CircuitPython SD library is used to read and write data to the SD card.  

First make sure you are running the latest version of Adafruit CircuitPython for your board.

Next you'll need to install the necessary libraries to use the hardware--carefully follow the steps to find and install these libraries from Adafruit's CircuitPython library bundle.  The Welcome to CircuitPython guide has a great page on how to install the library bundle.

After downloading the bundle, copy the necessary libraries from the bundle:

  • adafruit_vc0706.mpy
  • adafruit_sd.mpy
  • adafruit_bus_device

Before continuing, make sure your board's lib folder has the adafruit_vc0706.mpy, adafruit_sd.mpy, and adafruit_bus_device files and folders copied over.

Next connect to the board's serial REPL so you are at the CircuitPython >>> prompt.

Python Installation of VC0706 Library

You'll need to install the Adafruit_Blinka library that provides the CircuitPython support in Python. This may also require enabling UART on your platform and verifying you are running Python 3. Since each platform is a little different, and Linux changes often, please visit the CircuitPython on Linux guide to get your computer ready!

Once that's done, from your command line run the following command:

  • sudo pip3 install adafruit-circuitpython-vc0706

If your default Python is version 3 you may need to run 'pip' instead. Just make sure you aren't trying to use CircuitPython on Python 2.x, it isn't supported!

Microcontroller CircuitPython Usage (not for Linux/SBC)

To demonstrate the usage of the camera, let's look at an example that will capture an image and save it to the micro SD card as a jpeg file.  Load up the example below and save it as code.py on your CIRCUITPY drive, then open the serial REPL to see the output:

"""VC0706 image capture to SD card demo.
You must wire up the VC0706 to the board's serial port, and a SD card holder
to the board's SPI bus.  Use the Feather M0 Adalogger as it includes a SD
card holder pre-wired to the board--this sketch is setup to use the Adalogger!
In addition you MUST also install the following dependent SD card library:
https://github.com/adafruit/Adafruit_CircuitPython_SD
See the guide here for more details on using SD cards with CircuitPython:
https://learn.adafruit.com/micropython-hardware-sd-cards"""

import time

import board
import busio
import digitalio
import storage

import adafruit_sdcard
import adafruit_vc0706


# Configuration:
SD_CS_PIN = board.D10  # CS for SD card (SD_CS is for Feather Adalogger)
IMAGE_FILE = '/sd/image.jpg'  # Full path to file name to save captured image.
                              # Will overwrite!

# Setup SPI bus (hardware SPI).
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Setup SD card and mount it in the filesystem.
sd_cs = digitalio.DigitalInOut(SD_CS_PIN)
sdcard = adafruit_sdcard.SDCard(spi, sd_cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, '/sd')

# Create a serial connection for the VC0706 connection, speed is auto-detected.
uart = busio.UART(board.TX, board.RX, timeout=250)
# Setup VC0706 camera
vc0706 = adafruit_vc0706.VC0706(uart)

# Print the version string from the camera.
print('VC0706 version:')
print(vc0706.version)

# Set the baud rate to 115200 for fastest transfer (its the max speed)
vc0706.baudrate = 115200

# Set the image size.
vc0706.image_size = adafruit_vc0706.IMAGE_SIZE_640x480 # Or set IMAGE_SIZE_320x240 or
                                                       # IMAGE_SIZE_160x120
# Note you can also read the property and compare against those values to
# see the current size:
size = vc0706.image_size
if size == adafruit_vc0706.IMAGE_SIZE_640x480:
    print('Using 640x480 size image.')
elif size == adafruit_vc0706.IMAGE_SIZE_320x240:
    print('Using 320x240 size image.')
elif size == adafruit_vc0706.IMAGE_SIZE_160x120:
    print('Using 160x120 size image.')

# Take a picture.
print('Taking a picture in 3 seconds...')
time.sleep(3)
print('SNAP!')
if not vc0706.take_picture():
    raise RuntimeError('Failed to take picture!')

# Print size of picture in bytes.
frame_length = vc0706.frame_length
print('Picture size (bytes): {}'.format(frame_length))

# Open a file for writing (overwriting it if necessary).
# This will write 50 bytes at a time using a small buffer.
# You MUST keep the buffer size under 100!
print('Writing image: {}'.format(IMAGE_FILE), end='')
with open(IMAGE_FILE, 'wb') as outfile:
    wcount = 0
    while frame_length > 0:
        # Compute how much data is left to read as the lesser of remaining bytes
        # or the copy buffer size (32 bytes at a time).  Buffer size MUST be
        # a multiple of 4 and under 100.  Stick with 32!
        to_read = min(frame_length, 32)
        copy_buffer = bytearray(to_read)
        # Read picture data into the copy buffer.
        if vc0706.read_picture_into(copy_buffer) == 0:
            raise RuntimeError('Failed to read picture frame data!')
        # Write the data to SD card file and decrement remaining bytes.
        outfile.write(copy_buffer)
        frame_length -= 32
        # Print a dot every 2k bytes to show progress.
        wcount += 1
        if wcount >= 64:
            print('.', end='')
            wcount = 0
print()
print('Finished!')

You should see output like the following as the program prints information about the camera and saves an image to the micro SD card:

Be aware saving the image to the card takes some time, as the data is transferred over both a serial connection from the camera and the SPI connection to the micro SD card.  A full image capture at 640x480 pixels takes about 30 seconds, but might take longer depending on your board and micro SD card speed.

Once the image capture finishes, you'll see a message printed:

Exit the REPL and power down the board, then remove the SD card and connect it to your computer.  You should see an image.jpg file saved on it, and inside will be a picture captured from the camera:

Woo hoo, that's all there is to the basics of capturing an image with the serial TTL camera and CircuitPython!  Let's look at the code in a tiny bit more detail to understand the usage.

First the example needs to setup the SD card and mount it on the filesystem.  This is all boilerplate code from the CircuitPython SD card guide (highly recommended to read it too!):

Download: file
# Configuration:
SD_CS_PIN = board.D10  # CS for SD card (SD_CS is for Feather Adalogger)
IMAGE_FILE = '/sd/image.jpg'  # Full path to file name to save captured image.
                              # Will overwrite!

# Setup SPI bus (hardware SPI).
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Setup SD card and mount it in the filesystem.
sd_cs = digitalio.DigitalInOut(SD_CS_PIN)
sdcard = adafruit_sdcard.SDCard(spi, sd_cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, '/sd')

Now the VC0706 module is setup and an instance of the VC0706 class is created.  Notice we need to create a UART device on whatever pins have hardware support and then this is passed to the camera creator.

Download: file
# Create a serial connection for the VC0706 connection, speed is auto-detected.
uart = busio.UART(board.TX, board.RX, timeout=250)
# Setup VC0706 camera
vc0706 = adafruit_vc0706.VC0706(uart)

Once the VC0706 instance is created you can read some interesting properties, like the version string:

Download: file
# Print the version string from the camera.
print('VC0706 version:')
print(vc0706.version)

Or even set and get the size of the image (640x480, 320x240, 160x120):

Download: file
# Set the image size.
vc0706.image_size = adafruit_vc0706.IMAGE_SIZE_640x480 # Or set IMAGE_SIZE_320x240 or
                                                       # IMAGE_SIZE_160x120
# Note you can also read the property and compare against those values to
# see the current size:
size = vc0706.image_size
if size == adafruit_vc0706.IMAGE_SIZE_640x480:
    print('Using 640x480 size image.')
elif size == adafruit_vc0706.IMAGE_SIZE_320x240:
    print('Using 320x240 size image.')
elif size == adafruit_vc0706.IMAGE_SIZE_160x120:
print('Using 160x120 size image.')

Now the real fun, you can capture an image!  This works by first telling the camera to 'freeze' the current image frame in memory with the take_picture function.  Then you need to make a loop that calls the read_picture_into function repeatedly to grab buffers of image data from the camera.  Once you have image data it's up to you to do something with it, like write it to a SD card file (although you don't have to do that, you could send it to a web service or do other fun thing!).

The code in this example will capture an image and then save it to a file on the SD card:

Download: file
# Take a picture.
print('Taking a picture in 3 seconds...')
time.sleep(3)
print('SNAP!')
if not vc0706.take_picture():
    raise RuntimeError('Failed to take picture!')

# Print size of picture in bytes.
frame_length = vc0706.frame_length
print('Picture size (bytes): {}'.format(frame_length))

# Open a file for writing (overwriting it if necessary).
# This will write 50 bytes at a time using a small buffer.
# You MUST keep the buffer size under 100!
print('Writing image: {}'.format(IMAGE_FILE), end='')
with open(IMAGE_FILE, 'wb') as outfile:
    wcount = 0
    while frame_length > 0:
        # Compute how much data is left to read as the lesser of remaining bytes
        # or the copy buffer size (32 bytes at a time).  Buffer size MUST be
        # a multiple of 4 and under 100.  Stick with 32!
        to_read = min(frame_length, 32)
        copy_buffer = bytearray(to_read)
        # Read picture data into the copy buffer.
        if vc0706.read_picture_into(copy_buffer) == 0:
            raise RuntimeError('Failed to read picture frame data!')
        # Write the data to SD card file and decrement remaining bytes.
        outfile.write(copy_buffer)
        frame_length -= 32
        # Print a dot every 2k bytes to show progress.
        wcount += 1
        if wcount >= 64:
            print('.', end='')
            wcount = 0

One thing to be aware of is that the size of the buffer passed to read_picture_into must be a multiple of 4. This is an requirement of the camera hardware itself.  In addition, it must be below 100 to fit within an internal buffer.  Stick with using a value of 32 like the example here shows!

That's all there is to capturing and saving an image to an SD card using CircuitPython!

Saving Images to CircuitPython Internal Filesystem

Instead of using the SD card to store images it's also possible with CircuitPython or Python to save images to the internal filesystem where your code and other data files live.  This is possible with a few caveats, in particular once you enable writing to the internal storage you can't set or change your code over the USB drive connection to your computer.  This means you probably want to get your program working first on SD storage or ignoring the file save, and then switch to using internal storage when you know your code is working and ready to write files.

Also be aware internal storage is quite limited on some boards.  The non-express boards only have ~64kb or space and a single 640x480 JPEG image from the camera can occupy 50 kilobytes of more of space alone!  You likely only want to save images to the internal storage for Express boards that have 2 megabytes of space, however even on those boards take care to not store too many images as they will quickly add up

Activate Internal storage on Microcontrollers

This step is not used on Linux / Single Board Computers

To get started first follow the steps on the CircuitPython Storage page of the CircuitPython Essentials guide to enable writing to internal storage.  In particular edit the boot.py on your CIRCUITPY drive (creating it if it doesn't exist) and add these lines:

Download: file
import digitalio
import board
import storage
 
switch = digitalio.DigitalInOut(board.D5)
switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.UP
 
# If the D5 is connected to ground with a wire
# you can edit files over the USB drive again.
storage.remount("/", not switch.value)

Remember once you remount("/") you cannot edit code over the USB drive anymore!  That means you can't edit boot.py which is a bit of a conundrum. So we configure the boot.py to selectively mount the internal filesystem as writable based on a switch or even just alligator clip connected to ground.  Like the CPU temperature guide shows. In this example we're using D5 but select any available pin.

This code will look at the D5 digital input when the board starts up and if it's connected to ground (use an alligator clip or wire, for example, to connect from D5 to board ground) it will disable internal filesystem writes and allow you to edit code over the USB drive as normal.  Remove the alligator clip, reset the board, and the boot.py will switch to mounting the internal filesystem as writable so you can log images to it again (but not write any code!). 

Remember when you enable USB drive writes (by connecting D5 to ground at startup) you cannot write files to the internal filesystem and any code in your code.py that attempts to do so (like the example below) will fail.  Keep this in mind as you edit code, once you modify code you need to remove the alligator clip, reset the board to re-enable internal filesystem writes, and then watch the output of your program.

If you ever get stuck, you can follow the steps mentioned in https://learn.adafruit.com/cpu-temperature-logging-with-circuit-python/writing-to-the-filesystem to remove boot.py from the REPL if you need to go back and edit code!

Example Code for saving to internal file system (CircuitPython or Linux / SBC)

Now we can use a slightly modified version of the example that will save to the internal filesystem instead of a SD card. The code is exactly the same as for SD cards except instead of mounting the SD card and opening a file there, we open a file on the internal storage.  The exact same VC0706 functions and control loop are used because Python's read and write functions don't care if they're writing to a SD card or internal storage--it's all the same to Python!

"""VC0706 image capture to local storage.
You must wire up the VC0706 to a USB or hardware serial port.
Primarily for use with Linux/Raspberry Pi but also can work with Mac/Windows"""

import time
import busio
import board
import adafruit_vc0706

# Set this to the full path to the file name to save the captured image. WILL OVERWRITE!
# CircuitPython internal filesystem configuration:
IMAGE_FILE = '/image.jpg'
# USB to serial adapter configuration:
# IMAGE_FILE = 'image.jpg'  # Full path to file name to save captured image. Will overwrite!
# Raspberry Pi configuration:
# IMAGE_FILE = '/home/pi/image.jpg'  # Full path to file name to save image. Will overwrite!


# Create a serial connection for the VC0706 connection.
uart = busio.UART(board.TX, board.RX, baudrate=115200, timeout=0.25)
# Update the serial port name to match the serial connection for the camera!
# For use with USB to serial adapter:
# import serial
# uart = serial.Serial("/dev/ttyUSB0", baudrate=115200, timeout=0.25)
# For use with Raspberry Pi:
# import serial
# uart = serial.Serial("/dev/ttyS0", baudrate=115200, timeout=0.25)

# Setup VC0706 camera
vc0706 = adafruit_vc0706.VC0706(uart)

# Print the version string from the camera.
print('VC0706 version:')
print(vc0706.version)

# Set the image size.
vc0706.image_size = adafruit_vc0706.IMAGE_SIZE_640x480
# Or set IMAGE_SIZE_320x240 or IMAGE_SIZE_160x120

# Note you can also read the property and compare against those values to
# see the current size:
size = vc0706.image_size
if size == adafruit_vc0706.IMAGE_SIZE_640x480:
    print('Using 640x480 size image.')
elif size == adafruit_vc0706.IMAGE_SIZE_320x240:
    print('Using 320x240 size image.')
elif size == adafruit_vc0706.IMAGE_SIZE_160x120:
    print('Using 160x120 size image.')

# Take a picture.
print('Taking a picture in 3 seconds...')
time.sleep(3)
print('SNAP!')
if not vc0706.take_picture():
    raise RuntimeError('Failed to take picture!')

# Print size of picture in bytes.
frame_length = vc0706.frame_length
print('Picture size (bytes): {}'.format(frame_length))

# Open a file for writing (overwriting it if necessary).
# This will write 50 bytes at a time using a small buffer.
# You MUST keep the buffer size under 100!
print('Writing image: {}'.format(IMAGE_FILE), end='', flush=True)
stamp = time.monotonic()
with open(IMAGE_FILE, 'wb') as outfile:
    wcount = 0
    while frame_length > 0:
        t = time.monotonic()
        # Compute how much data is left to read as the lesser of remaining bytes
        # or the copy buffer size (32 bytes at a time).  Buffer size MUST be
        # a multiple of 4 and under 100.  Stick with 32!
        to_read = min(frame_length, 32)
        copy_buffer = bytearray(to_read)
        # Read picture data into the copy buffer.
        if vc0706.read_picture_into(copy_buffer) == 0:
            raise RuntimeError('Failed to read picture frame data!')
        # Write the data to SD card file and decrement remaining bytes.
        outfile.write(copy_buffer)
        frame_length -= 32
        # Print a dot every 2k bytes to show progress.
        wcount += 1
        if wcount >= 64:
            print('.', end='', flush=True)
            wcount = 0
print()
print('Finished in %0.1f seconds!' % (time.monotonic() - stamp))

Saving Images to Computer or Raspberry Pi / Linux

Saving images to a Raspberry Pi or other Linux computer is very similar to the CircuitPython internal filesystem. You simply need to comment out a line and uncomment two more depending on what set up you're using.

To comment out a line of code, put a '#' before the line of code. To uncomment a line of code, delete the '# ' (including the space!) before the line of code.

Regardless of which set up you're using, you'll need to comment out the following line:

uart = busio.UART(board.TX, board.RX, baudrate=115200, timeout=0.25)

USB to Serial Converter

If using a USB to serial converter, uncomment the following lines:

# import serial

# uart = serial.Serial("/dev/ttyUSB0", baudrate=115200, timeout=0.25)

Raspberry Pi / Linux

If using a Raspberry Pi, uncomment the following lines (if you're using a different single board computer, you may need to update the serial port!):

# import serial

# uart = serial.Serial("/dev/ttyS0", baudrate=115200, timeout=0.25)

The rest of the code works the same way. Smile!

This guide was first published on Jul 29, 2012. It was last updated on Jul 29, 2012. This page (CircuitPython & Python Usage) was last updated on Nov 14, 2019.