Python driver for DFRobot DRI0050 PWM Motor & LED Controller (MODBUS RTU)
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dri0050-1.0.1.tar.gz.
File metadata
- Download URL: dri0050-1.0.1.tar.gz
- Upload date:
- Size: 26.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
86992dd62a30f575598a61e824d11397b35f013177529f7b14cc0529ff8d14af
|
|
| MD5 |
10d3de9ab2daf06e5eab71f73d76f6ed
|
|
| BLAKE2b-256 |
c0d89a34f992f2f1b05fcb6766d929c7e2858eed71f328c5e278852f8261682b
|
File details
Details for the file dri0050-1.0.1-py3-none-any.whl.
File metadata
- Download URL: dri0050-1.0.1-py3-none-any.whl
- Upload date:
- Size: 19.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7486f70f14af7e8f6b0b9ea3325a0ece6f8b0598733eb304ec525c3f77f48f52
|
|
| MD5 |
be217e9ccbd5ae9fc6986659784ac119
|
|
| BLAKE2b-256 |
fa5d4e7bfea5a4e2033c33617256b45fc44439e4d63925241748bc659de28dba
|