Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save stonehippo/06f3b4e423d341279df40c8f154c1fdd to your computer and use it in GitHub Desktop.

Select an option

Save stonehippo/06f3b4e423d341279df40c8f154c1fdd to your computer and use it in GitHub Desktop.
A note to self about using the CircuitPython native USB for serial data with usb_cdc

Using the CircuitPython native USB for serial data

I was trying to get some CP code to talk to Processing over a serial port interface. Of course, the CP REPL is normally available on whatever serial port the board mounts by default. This is a little complicated, because although you can use something like print from the REPL (or code.py) to print a value to the console, sys.stdin and sys.stdout are text-based streams, which may not be what you want. And you'll get REPL noise to boot. Not ideal.

This is where usb_cdc comes in. It's a handy library for managing the USB CDC (serial) on most CircuitPython boards. First, usb_cdc can be used to control how many serial ports CP will provide at startup. You can modify boot.py to make this work:

import usb_cdc

usb_cdc.enable(console=True, data=False)

This is equivalent to the standard setup. When you plug in your CP device, you'll get one serial port, which will have the REPL on it, and is mapped to sys.stdin and sys.stdout, all of which goes through the console object, which is a binary stream. This means you can do stuff like this in your code.py:

import usb_cdc

console = usb_cdc.console
console.write(bytes([1]))

This is great, since it means you can send binary data, which is useful if you don't want to muck with the REPL's textstream. However, you're still using the same serial port. We can do better. If you put this in boot.py, you'll get a second serial port when you plug in your board:

import usb_cdc

usb_cdc.enable(console=True, data=True)

Now you can connect to the second serial port and avoid the REPL noise. You can do this with the usb_cdc.data binary stream, in either the REPL or from code.py:

import usb_cdc

console = usb_cdc.data
console.write(bytes([1]))

For reference, take a look at the documentation for the usb_cdc library and this handy guide to configuring the USB ports in CircuitPython.

Checking on CircuitPython serial ports

There is a handy utility library, Adafruit_Board_Toolkit, that provides some utilities for inspecting the serial ports on a system to see if they're avaliable from CP and if so, if they console or data reports.

Once you've installed this library in Python, it's pretty easy to use. For example, to list all of the likely CP serial ports:

from adafruit_board_toolkit import circuitpython_serial as cps

print([port.device for port in cps.comports()])
@bernard01
Copy link

import usb_cdc

usb_cdc.enable(console=True, data=True)

Thank you for confirming that it works on these devices.

When I do that on a Pico, then I lock myself out completely and I have to nuke it. I do not even see safe mode. I have never seen it even once.

When I try to use my switching approach, then I run into fundamental errors because:

If the USB data is not used because it is in a switched-off code branch, then the compiler still sees the code and it can't compile it because the data device is not there. I have seen something like that but I might try again. It's a painful process.

I have also tried logging. But logging is useless because it cannot log syntax errors.

From my perspective, this sucks because it is so basic. I discovered the hard way that one cannot develop CircuitPython without the console and REPL. That is the only supported work flow. So one would perhaps have to figure out how to get the needed endpoint pairs that are discussed in

Customizing USB Devices in CircuitPython

But anyway, once again I thank you very much for getting back to me with your device info.

@stonehippo
Copy link
Author

stonehippo commented May 9, 2025

@bernard01 strictly speaking, you don't need the REPL for CP development, as long as you can mount the CIRCUITPY volume and edit files. But having access to it does make things easier! (And it's one of the selling points versus something like Arduino, where you'll likely have to resort to a debugger for any serious runtime troubleshooting)

I am a bit mystified by why you're not getting both ports on a Pico, TBH. The endpoints are defined in hardware, so CP should be able to use them if they're there. And in that chip, they are.

If I was trying to figure out what was happening, I'd put vanilla CP on your pico, nothing in code.py or main.py, and just try the boot.py dual port setup. And to check if was working, I'd look at the OS and available serial ports before doing anything else with CP. If they don't show up, that might not be CP on the Pico, it could be an issue on the PC side.

If you are exceeding the number of endpoint pairs, that shouldn't kill the console. On the contrary, it should put the board in safe mode and the console/REPL will still be available (see the note at the end of the Customizing USB Devices page).

Anyhow, good luck and let me know if I can try anything out for you.

@bernard01
Copy link

All solved. Was an operating system problem. It appears that most devices support both port so no issue. Thanks.

@b-blake
Copy link

b-blake commented Sep 9, 2025

Hello,

I am trying to read data from my GPS/GLONASS U-blox7. It is a USB device. I believe its data rate is 9600 bps.
I am using an AdaFruit Fruit Jam with an RP2350 MCU chip.

My boot py is:

import usb_cdc
usb_cdc.enable(console=True, data=True)
print("boot.py")

booy.py is printed into boot_out.txt

my code.py so far is:

import time
import usb_cdc
console = usb_cdc.data
print(usb_cdc.console.connected)
while True:
    line = usb_cdc.console.readline(1)
    print(line)
    time.sleep(0.001)

It does not crash, which I count as a plus. However it just sits on usb_cdc.console.readline(1)
Is reading data from the GPS receiver possible? In all my reading I do not see an example of how to do it.
print(usb_cdc.console.connected) is True

Thanks in advance
Bruce

@stonehippo
Copy link
Author

@b-blake if I understand your question correctly, you're trying to have read serial data coming from the U-blox device from the FruitJam. If that's the case, I'm not sure that usb-cdc is going to do what you want. It's really meant to enable a CircuitPython device to send data to a host system via a second serial port while still allowing for access to the REPL on the device.

However, there is a way to use a UART on your board to talk to a serial device like GPS, the serialio module. Take a look at the documentation here:

https://learn.adafruit.com/circuitpython-essentials/circuitpython-uart-serial.

I'll not sure of your exist connections between devices, since you said the U-blox has a USB interface. I know that the FruitJam has USB Host support, but I haven't gotten my hands on one yet. If you have the GPS connected to one of the FruitJam USB ports (rather than a plain UART) you will probably want to look at the usb library. Specifically, you would want usb.core. That will let you read and write data from the USB device, though you may have to mess with descriptors. Take a look here for the usb.core api:

https://docs.circuitpython.org/en/latest/shared-bindings/usb/core/index.html

And here for some examples of using devices with the FruitJam USB Host:

https://learn.adafruit.com/adafruit-fruit-jam/usb-host

I hope this helps!

@b-blake
Copy link

b-blake commented Sep 10, 2025

@stonehippo,

Thank you for your reply.

The GPS receiver I am trying to use only has a USB connector.
In looking at the available functions in the CircuitPython versions of usb_cdc for the RP2350 it shows the following:
`>>> import usb_cdc

dir(usb_cdc)
['class', 'name', 'Serial', 'dict', 'console', 'data', 'disable', 'enable']

dir(usb_cdc.console)
['class', 'next', 'read', 'readinto', 'readline', 'write', 'connected', 'flush', 'in_waiting', 'out_waiting', 'readlines', 'reset_input_buffer', 'reset_output_buffer', 'timeout', 'write_timeout']

dir(usb_cdc.data)
['class']
`
With read, readinto, readline, and readlines I assumed what I want to do is supported. Oh, well.

Bruce

@stonehippo
Copy link
Author

stonehippo commented Sep 10, 2025

@b-blake yes, usb_cdc has the sort of methods you need, but it's intended for presenting a serial port to a USB host device, e.g., a laptop or desktop PC. So it's not quite what you need.

What you need is usb.core.Device. Specifically usb.core.Device.read(), which is the right way to get data from a USB device when the CircuitPython device is the USB Host.

@b-blake
Copy link

b-blake commented Sep 10, 2025

Thank you very much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment