CircuitPython Usage

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

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 in to 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 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!) inserted in the SD card holder.

Setup

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.  Remember both modules are for Adafruit CircuitPython firmware and not MicroPython.org firmware!

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.  For example the Circuit Playground Express guide has a great page on how to install the library bundle for both express and non-express boards.

Remember for non-express boards like the Trinket M0, Gemma M0, and Feather/Metro M0 basic you'll need to manually install the necessary libraries from the bundle:

  • adafruit_vc0706.mpy
  • adafruit_sd.mpy
  • adafruit_bus_device

Or download the file from the latest release on the Adafruit CircuitPython VC0706 releases page

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

Usage

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 main.py on your board, 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 board
import busio
import digitalio
import storage
import time

import adafruit_sdcard
import adafruit_vc0706


# Configuration:
SD_CS_PIN  = board.SD_CS  # CS for SD card (SD_CS is for Feather Adalogger)
RX_PIN     = board.RX     # RX pin of board, connected to VC0706 TX
TX_PIN     = board.TX     # TX pin of board, connected to VC0706 RX
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')

# Setup VC0706.
vc0706 = adafruit_vc0706.VC0706(RX_PIN, TX_PIN)

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

# Set the image size.
vc0706.image_size = adafruit_vc0706.VC0706_640x480 # Or set VC0706_320x240 or
                                                   # VC0706_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.VC0706_640x480:
    print('Using 640x480 size image.')
elif size == adafruit_vc0706.VC0706_320x240:
    print('Using 320x240 size image.')
elif size == adafruit_vc0706.VC0706_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!):

# Configuration:
SD_CS_PIN  = board.SD_CS  # CS for SD card (SD_CS is for Feather Adalogger)
RX_PIN     = board.RX     # RX pin of board, connected to VC0706 TX
TX_PIN     = board.TX     # TX pin of board, connected to VC0706 RX
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 this needs to be told the RX and TX pins of the camera for its serial connection:

# Setup VC0706.
vc0706 = adafruit_vc0706.VC0706(RX_PIN, TX_PIN)

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

# 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):

# Set the image size.
vc0706.image_size = adafruit_vc0706.VC0706_640x480 # Or set VC0706_320x240 or
                                                   # VC0706_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.VC0706_640x480:
    print('Using 640x480 size image.')
elif size == adafruit_vc0706.VC0706_320x240:
    print('Using 320x240 size image.')
elif size == adafruit_vc0706.VC0706_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:

# 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!

Saving Images to Internal Filesystem

Instead of using the SD card to store images it's also possible with CircuitPython 2.0+ 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

To get started first follow the steps in the CPU temperature logging guide to enable writing to internal storage.  In particular edit the boot.py on your board (creating it if it doesn't exist) and add these lines:

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 main.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!

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 internal storage demo.
# You must wire up the VC0706 to the board's serial port, and enable writes
# to the internal filesystem by following this page to edit boot.py:
#   https://learn.adafruit.com/cpu-temperature-logging-with-circuit-python/writing-to-the-filesystem
import board
import busio
import digitalio
import time

import adafruit_vc0706


# Configuration:
RX_PIN     = board.RX     # RX pin of board, connected to VC0706 TX
TX_PIN     = board.TX     # TX pin of board, connected to VC0706 RX
IMAGE_FILE = '/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 VC0706.
vc0706 = adafruit_vc0706.VC0706(RX_PIN, TX_PIN)

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

# Set the image size.
vc0706.image_size = adafruit_vc0706.VC0706_640x480 # Or set VC0706_320x240 or
                                                   # VC0706_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.VC0706_640x480:
    print('Using 640x480 size image.')
elif size == adafruit_vc0706.VC0706_320x240:
    print('Using 320x240 size image.')
elif size == adafruit_vc0706.VC0706_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!')
Last updated on 2017-11-09 at 05.27.24 PM Published on 2012-07-29 at 11.58.38 AM