DRI0050 — PWM DC Motor & LED Strip Driver for Python
AI Cost Tracking
This project uses AI-generated code. Total cost: $0.1500 with 1 AI commits.
Generated on 2026-04-13 using openrouter/qwen/qwen3-coder-next
Python driver for the DFRobot DRI0050 PWM Motor Speed & LED Strip Lights Controller.
Communicates via MODBUS RTU over serial (USB Type-C / UART). No pinpong dependency required.
Features
- Two MODBUS RTU driver backends:
minimalmodbus(lightweight) andmodbus_tk(official DFRobot) - Context-manager support (
withstatement) for safe serial cleanup - CLI tools for quick testing (
dri0050-info,dri0050-pwm) - Full register access: PID, VID, version, frequency, duty, enable
- Input validation with clear error messages
- Frequency prescaler helpers and high-frequency lookup table
Hardware Overview
| Parameter | Value |
|---|---|
| SKU | DRI0050 |
| Input Voltage Range | 5V – 24V |
| Max Control Current | 10A |
| PWM Duty Ratio Range | 0 – 255 |
| PWM Frequency Range | 183 Hz – 46875 Hz |
| Number of PWM Channels | 1 |
| Start/Stop Button | ×1 |
| Control Modes | Potentiometer / UART / USB (PC) / Python |
| USB Interface | Type-C |
| UART Interface | PH2.0-4P |
| Potentiometer Interface | 2.54 pin header + binding post |
| Mounting Hole Size | 30mm × 50mm, Ø 3.1mm |
| PCB Size | 37mm × 57mm (1.46" × 2.24") |
| USB-Serial Chip | CH340/CH341 |
| MODBUS Slave Address | 0x32 (default) |
| Serial Baud Rate | 9600 (default) |
Board Overview
Pinout
┌──────────────────────────────┐
Potentiometer ←─┤ [Pot 2.54] [USB-C] ├─→ USB to PC/RPi
│ │
│ [Start/Stop] │
│ │
UART (PH2.0)←─┤ [UART 4P] [Motor+] ├─→ Load (+)
│ [Motor-] ├─→ Load (-)
│ │
Power Supply ──┤ [VIN+] [GND] │
└──────────────────────────────┘
| Connector | Pin | Function |
|---|---|---|
| USB-C | — | Data + power to PC/RPi (CH340 USB-serial) |
| UART (PH2.0-4P) | 1 | GND |
| 2 | VCC | |
| 3 | TX (from board) | |
| 4 | RX (to board) | |
| Potentiometer | 2.54 header | External pot wiper signal |
| Motor+/Motor- | Binding post | PWM output to load |
| Power Input | VIN+ / GND | 5–24V supply for load |
USB-Serial Driver (CH340)
The DRI0050 uses a CH340/CH341 USB-to-serial converter. Most Linux kernels include built-in CH340 support. On Windows and macOS you may need to install a driver:
- Windows: CH340/CH341 driver — supports Win 7/8/8.1/10/11 (32/64-bit)
- macOS: CH340/CH341 driver — supports 32/64-bit macOS
- Linux: Built-in
ch341kernel module (usually pre-installed) - Android: Available from the same DFRobot driver page
After installing the driver and connecting via USB, the board appears as a serial port:
- Windows:
COM3,COM4, etc. (check Device Manager) - Linux/macOS:
/dev/ttyUSB0,/dev/ttyUSB1, etc. - Raspberry Pi UART:
/dev/ttyS0(Pi 3/4/5) or/dev/ttyAMA0(older models)
How It Works
Architecture Overview
┌──────────────┐ USB-C ┌──────────────┐ MODBUS RTU ┌──────────────┐
│ PC / RPi │ ─────────────► │ CH340/CH341 │ ───────────────► │ DRI0050 MCU │
│ (Python) │ (USB-Serial) │ USB-Serial │ (Serial 9600) │ (PWM Gen) │
└──────────────┘ └──────────────┘ └──────────────┘
│
▼
┌──────────────┐
│ DC Motor / │
│ LED Strip │
└──────────────┘
Communication Flow
- USB Connection: Your PC/Raspberry Pi connects to the DRI0050 via USB-C
- USB-Serial Bridge: The CH340/CH341 chip converts USB to serial (RS-232-like)
- Serial Port: Appears as
/dev/ttyUSB0(Linux) orCOM3(Windows) - MODBUS RTU Protocol: Python sends MODBUS RTU commands over the serial link
- DRI0050 MCU: Receives commands, updates PWM generator registers
- PWM Output: Motor+/Motor- outputs PWM signal to your load
MODBUS RTU Protocol
The DRI0050 speaks MODBUS RTU (Remote Terminal Unit) — a standard industrial protocol:
| Function Code | Operation | Registers |
|---|---|---|
| 0x03 (03) | READ_HOLDING_REGISTERS | Read PID, VID, version, freq, duty, enable |
| 0x06 (06) | WRITE_SINGLE_REGISTER | Write single value (duty, freq, or enable) |
| 0x10 (16) | WRITE_MULTIPLE_REGISTERS | Write multiple values (duty + freq atomically) |
Example: Setting frequency to 1000 Hz
1. Python: drv.set_freq(1000)
2. Driver calculates prescaler: int(12MHz / 256 / 1000) - 1 = 45
3. MODBUS frame: [Slave=0x32][FC=06][Reg=0x07][Value=45][CRC]
4. Serial transmit: 8 bytes at 9600 baud
5. DRI0050 MCU: Receives, sets prescaler register to 45
6. PWM generator: Outputs 1019 Hz (prescaler quantization)
Driver Backends
This package provides two MODBUS RTU implementations with identical APIs:
DRI0050 (minimalmodbus backend)
from dri0050 import DRI0050
with DRI0050(port="/dev/ttyUSB0") as drv:
drv.set_freq(1000) # → FC 06 (write single)
drv.set_duty(0.5) # → FC 06 (write single)
drv.set_enable(True) # → FC 06 (write single)
- Uses
minimalmodbuslibrary - Lightweight, well-documented
- Each
set_*call = one MODBUS transaction - Good default choice for most applications
DRI0050ModbusTK (modbus_tk backend)
from dri0050 import DRI0050ModbusTK
with DRI0050ModbusTK(port="/dev/ttyUSB0") as drv:
drv.pwm(freq=1000, duty=0.5) # → FC 16 (write multiple, atomic)
drv.set_enable(1)
- Uses
modbus_tklibrary (official DFRobot recommendation) pwm()uses FC 16 to write duty+freq in one transaction (atomic)- Matches official DFRobot wiki examples exactly
- Use when you need atomic freq+duty updates
CLI Tools Workflow
The CLI tools are simple Python scripts that use the driver:
# dri0050-info
$ dri0050-info /dev/ttyUSB0
→ Opens serial port
→ Reads registers 0, 1, 2, 5, 6, 7, 8 (FC 03)
→ Prints formatted info
# dri0050-pwm
$ dri0050-pwm /dev/ttyUSB0 --freq 1000 --duty 0.5 --enable 1
→ Opens serial port
→ Writes freq (FC 06)
→ Writes duty (FC 06)
→ Writes enable (FC 06)
→ Reads back and prints
Example Scripts Workflow
All examples follow this pattern:
from dri0050 import DRI0050
PORT = "/dev/ttyUSB0" # or "COM3" on Windows
with DRI0050(port=PORT) as drv: # Opens serial, sets up MODBUS
# Read current state
info = drv.info()
print(f"Current: {info}")
# Change settings
drv.set_freq(1000) # Send MODBUS command
drv.set_duty(0.75) # Send MODBUS command
drv.set_enable(True) # Send MODBUS command
# Verify
print(f"New: freq={drv.get_freq()}Hz duty={drv.get_duty():.0%}")
# Serial automatically closed here
Frequency Prescaler Details
The DRI0050 doesn't store frequency directly — it stores a prescaler value:
prescaler = (12,000,000 / 256 / target_freq) - 1
actual_freq = 12,000,000 / 256 / (prescaler + 1)
Why this matters: Due to integer prescaler values, you can't set every exact frequency. For example:
- Target 1000 Hz → prescaler 45 → actual 1019 Hz
- Target 860 Hz → prescaler 53 → actual 868 Hz
- Target 366 Hz → prescaler 126 → actual 366 Hz (exact match)
The driver handles this conversion automatically.
High-Frequency Limitation
For frequencies above 2 kHz, only discrete values are achievable due to prescaler resolution:
| Target Hz | Actual Hz |
|---|---|
| 3000 | 2929 (closest) |
| 5000 | 4687 (closest) |
| 10000 | 9375 (exact) |
| 20000 | 19531 (closest) |
| 46875 | 46875 (max, exact) |
Use nearest_valid_freq() to find the closest achievable frequency.
Installation
From source (recommended)
git clone https://github.com/maskservice/rpi-motor-DRI0050.git
cd rpi-motor-DRI0050
pip install .
Dependencies only
pip install -r requirements.txt
Raspberry Pi — quick deploy
chmod +x install.sh
./install.sh
Getting Started — Step by Step
1. Connect the Hardware
Power Supply (5-24V) ──► DRI0050 (VIN+ / GND)
PC / RPi ──────────────► DRI0050 (USB-C)
Motor / LED Strip ──────► DRI0050 (Motor+ / Motor-)
2. Find the Serial Port
Linux / macOS:
ls /dev/ttyUSB* # USB-serial devices
# Output: /dev/ttyUSB0
Windows:
- Open Device Manager
- Expand "Ports (COM & LPT)"
- Note the COM number (e.g., "USB Serial Port (COM3)")
3. Quick Test with CLI
# Check device info
dri0050-info /dev/ttyUSB0
# Set PWM: 1kHz, 50% duty, enabled
dri0050-pwm /dev/ttyUSB0 --freq 1000 --duty 0.5 --enable 1
# Disable output
dri0050-pwm /dev/ttyUSB0 --enable 0
4. Write Your First Script
Create motor_control.py:
from dri0050 import DRI0050
PORT = "/dev/ttyUSB0" # or "COM3" on Windows
with DRI0050(port=PORT) as drv:
print("Device:", drv.info())
# Ramp up motor
for duty in [0.2, 0.4, 0.6, 0.8, 1.0]:
drv.set_duty(duty)
drv.set_enable(True)
print(f"Duty: {duty:.0%}")
input("Press Enter to continue...")
# Stop motor
drv.set_enable(False)
print("Stopped")
Run it:
python3 motor_control.py
5. Common Patterns
Read current state:
info = drv.info()
print(f"Freq: {info['freq_hz']}Hz, Duty: {info['duty']:.0%}")
Set frequency and duty together:
drv.set_freq(1000)
drv.set_duty(0.75)
drv.set_enable(True)
Atomic update (modbus_tk only):
from dri0050 import DRI0050ModbusTK
with DRI0050ModbusTK(port=PORT) as drv:
drv.pwm(freq=1000, duty=0.5) # Single MODBUS transaction
Factory reset:
drv.factory_reset() # 366Hz, 50%, disabled
Choosing a Driver Backend
This package provides two MODBUS RTU driver classes with the same API:
| Class | Library | When to use |
|---|---|---|
DRI0050 |
minimalmodbus |
Lightweight, well-documented, good default choice |
DRI0050ModbusTK |
modbus_tk |
Official DFRobot approach, matches wiki examples, supports WRITE_MULTIPLE_REGISTERS for atomic freq+duty updates |
Both classes share the same method names and register map. The pwm() method in DRI0050ModbusTK uses WRITE_MULTIPLE_REGISTERS (function code 16) to set duty and frequency atomically in a single MODBUS transaction, while DRI0050.pwm() sends two separate WRITE_SINGLE_REGISTER commands.
Quick Start
Using DRI0050 (minimalmodbus backend)
from dri0050 import DRI0050
# Linux / Raspberry Pi
with DRI0050(port="/dev/ttyUSB0") as drv:
# Read device info
print(drv.info())
# Set PWM: 1kHz, 75% duty, enable output
drv.pwm(freq=1000, duty=0.75)
# Read back
print(f"freq={drv.get_freq()}Hz duty={drv.get_duty():.0%}")
# Disable output
drv.set_enable(False)
Using DRI0050ModbusTK (modbus_tk backend — official DFRobot)
from dri0050 import DRI0050ModbusTK
# Windows
with DRI0050ModbusTK(port="COM3") as drv:
drv.set_freq(860)
drv.set_duty(0.82)
drv.set_enable(1)
print(f"freq={drv.get_freq()}Hz duty={drv.get_duty():.2f}")
API Reference
Both DRI0050 and DRI0050ModbusTK share the same API:
Constructor
# minimalmodbus backend
DRI0050(port, slave=0x32, baudrate=9600, timeout=1.0)
# modbus_tk backend
DRI0050ModbusTK(port, slave=0x32, baudrate=9600, timeout=5.0)
| Parameter | Type | Default | Description |
|---|---|---|---|
port |
str |
required | Serial port path (/dev/ttyUSB0, COM3, etc.) |
slave |
int |
0x32 |
MODBUS slave address |
baudrate |
int |
9600 |
Serial baud rate |
timeout |
float |
1.0 / 5.0 |
MODBUS response timeout (seconds) |
Read Methods
| Method | Returns | Register | Description |
|---|---|---|---|
get_pid() |
int |
0 | Product ID |
get_vid() |
int |
1 | Vendor ID |
get_addr() |
int |
2 | MODBUS slave address |
get_version() |
int |
5 | Firmware version |
get_duty() |
float |
6 | Duty cycle 0.0–1.0 |
get_duty_raw() |
int |
6 | Duty raw value 0–255 |
get_freq() |
int |
7 | Frequency in Hz |
get_freq_raw() |
int |
7 | Prescaler register value |
get_enable() |
bool/int |
8 | Output enabled state |
Note:
DRI0050.get_enable()returnsbool,DRI0050ModbusTK.get_enable()returnsint(0 or 1).
Write Methods
| Method | Args | Register | Description |
|---|---|---|---|
set_duty(duty) |
float 0.0–1.0 |
6 | Set duty cycle |
set_duty_raw(raw) |
int 0–255 |
6 | Set raw duty value |
set_freq(freq) |
int 183–46875 Hz |
7 | Set frequency |
set_freq_raw(prescaler) |
int |
7 | Set raw prescaler |
set_enable(enable) |
bool/int |
8 | Enable/disable output |
Note:
DRI0050.set_enable()acceptsbool,DRI0050ModbusTK.set_enable()acceptsint(0 or 1).
Convenience Methods
| Method | Description |
|---|---|
pwm(freq, duty, ...) |
Set freq + duty. DRI0050ModbusTK does this atomically via WRITE_MULTIPLE_REGISTERS |
factory_reset() |
Restore defaults (366Hz, 50%, disabled) |
info() |
Read all device info as dict |
close() |
Close serial connection |
MODBUS Register Map
The DRI0050 uses MODBUS RTU with function codes 03 (read holding registers) and 06/16 (write single/multiple registers).
| Register | Name | Access | Description |
|---|---|---|---|
| 0 | PID | R | Product ID |
| 1 | VID | R | Vendor ID |
| 2 | Address | R | MODBUS slave address |
| 5 | Version | R | Firmware version |
| 6 | Duty | R/W | PWM duty 0–255 |
| 7 | Frequency | R/W | Prescaler: freq = 12MHz / 256 / (val+1) |
| 8 | Enable | R/W | Output enable: 0=off, 1=on |
Frequency Prescaler
The frequency register stores a prescaler value, not Hz directly:
prescaler = (12_000_000 / 256 / freq) - 1
freq = 12_000_000 / 256 / (prescaler + 1)
For frequencies above 2 kHz, only discrete values are achievable:
| Freq (Hz) | Freq (Hz) | Freq (Hz) | Freq (Hz) |
|---|---|---|---|
| 46875 | 23437 | 15625 | 11718 |
| 9375 | 7812 | 6696 | 5859 |
| 5208 | 4687 | 4261 | 3906 |
| 3605 | 3348 | 3125 | 2929 |
| 2757 | 2604 | 2467 | 2343 |
| 2232 | 2130 | 2038 |
Use nearest_valid_freq() to find the closest achievable frequency.
CLI Tools
After pip install ., two CLI commands are available:
dri0050-info — read device information
dri0050-info /dev/ttyUSB0
dri0050-info COM3 --slave 0x32 --baudrate 9600
dri0050-pwm — set PWM parameters
dri0050-pwm /dev/ttyUSB0 --freq 1000 --duty 0.75 --enable 1
dri0050-pwm COM3 --duty 0.5
dri0050-pwm /dev/ttyUSB0 --enable 0
Examples
| File | Description |
|---|---|
examples/official_direct_control.py |
Official DFRobot wiki example (modbus_tk backend) |
examples/basic_control.py |
Set frequency, duty, enable; read back; factory reset |
examples/motor_ramp.py |
Smooth ramp-up / ramp-down for a DC motor |
examples/led_breathing.py |
Sine-wave breathing effect on an LED strip |
examples/device_info.py |
Read and print all device registers |
Wiring Guide
USB Connection (Python / PC control)
PC ──[USB-C cable]──> DRI0050 ──[Motor+/Motor-]──> DC Motor / LED Strip
│
[5-24V Power Supply]
UART Connection (embedded control)
Raspberry Pi / MCU DRI0050
┌──────────────┐ ┌────────────────┐
│ GPIO 14 (TX)├────►│ UART RX (PH2.0)│
│ GPIO 15 (RX)│◄────│ UART TX (PH2.0)│
│ GND ├────►│ GND (PH2.0)│
└──────────────┘ └────────────────┘
Note: When using UART on Raspberry Pi, the port is typically
/dev/ttyS0(Pi 3/4/5) or/dev/ttyAMA0(older models). Disable the serial console first withraspi-config.
Raspberry Pi Deployment
The install.sh script handles:
- System dependency installation
- CH340/CH341 kernel module verification (USB-serial chip)
- Python virtual environment setup + package installation
- Serial port permissions (
dialoutgroup) - Udev rules for CH340 USB-serial auto-symlink (
/dev/dri0050) - Serial port detection check
chmod +x install.sh
sudo ./install.sh
Serial Port Permissions
If you get a permission error accessing /dev/ttyUSB0:
sudo usermod -aG dialout $USER
# Log out and back in for the change to take effect
Troubleshooting
| Problem | Solution |
|---|---|
Permission denied on serial port |
Add user to dialout group: sudo usermod -aG dialout $USER |
No such file or directory for port |
Check port exists: ls /dev/ttyUSB* or ls /dev/ttyACM* |
| USB device not recognized (Windows) | Install CH340 driver, reconnect USB |
| CH341 module not loaded (Linux) | Run sudo modprobe ch341, or reboot after connecting USB |
| Device not responding | Verify USB cable, check power supply (5-24V), confirm baudrate |
| Wrong frequency above 2kHz | Use nearest_valid_freq() — only discrete steps are possible |
modbus.NoResponseError |
Check wiring, slave address (default 0x32), and that device is powered |
Project Structure
rpi-motor-DRI0050/
├── dri0050/
│ ├── __init__.py # Package init, exports DRI0050 & DRI0050ModbusTK
│ ├── driver.py # Core driver (minimalmodbus backend)
│ ├── driver_modbus_tk.py # Official driver (modbus_tk backend)
│ └── cli.py # CLI entry points
├── examples/
│ ├── official_direct_control.py # Official DFRobot wiki example
│ ├── basic_control.py # Basic set/read example
│ ├── motor_ramp.py # Motor ramp up/down
│ ├── led_breathing.py # LED breathing effect
│ └── device_info.py # Read device info
├── tests/
│ └── test_driver.py # Unit tests (13 passing)
├── install.sh # Raspberry Pi deployment script
├── requirements.txt # Python dependencies
├── pyproject.toml # Build configuration
├── LICENSE # MIT License
└── README.md # This file
License
Licensed under Apache-2.0.
References
Metadata
Release files for dri0050 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dri0050-1.0.1.tar.gz | 26.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dri0050-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 45.2 kB
Release files / dri0050-1.0.1.tar.gz
| Download URL | dri0050-1.0.1.tar.gz |
|---|---|
| Size | 26.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
86992dd62a30f575598a61e824d11397b35f013177529f7b14cc0529ff8d14af
|
|
BLAKE2b-256 checksum How to use checksums |
c0d89a34f992f2f1b05fcb6766d929c7e2858eed71f328c5e278852f8261682b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Release files / dri0050-1.0.1-py3-none-any.whl
| Download URL | dri0050-1.0.1-py3-none-any.whl |
|---|---|
| Size | 19.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7486f70f14af7e8f6b0b9ea3325a0ece6f8b0598733eb304ec525c3f77f48f52
|
|
BLAKE2b-256 checksum How to use checksums |
fa5d4e7bfea5a4e2033c33617256b45fc44439e4d63925241748bc659de28dba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|