Skip to main content

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

AI Cost AI Model

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) and modbus_tk (official DFRobot)
  • Context-manager support (with statement) 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

Board top view

Board pinout

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 ch341 kernel 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

  1. USB Connection: Your PC/Raspberry Pi connects to the DRI0050 via USB-C
  2. USB-Serial Bridge: The CH340/CH341 chip converts USB to serial (RS-232-like)
  3. Serial Port: Appears as /dev/ttyUSB0 (Linux) or COM3 (Windows)
  4. MODBUS RTU Protocol: Python sends MODBUS RTU commands over the serial link
  5. DRI0050 MCU: Receives commands, updates PWM generator registers
  6. 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 minimalmodbus library
  • 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_tk library (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() returns bool, DRI0050ModbusTK.get_enable() returns int (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() accepts bool, DRI0050ModbusTK.set_enable() accepts int (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 with raspi-config.

Raspberry Pi Deployment

The install.sh script handles:

  1. System dependency installation
  2. CH340/CH341 kernel module verification (USB-serial chip)
  3. Python virtual environment setup + package installation
  4. Serial port permissions (dialout group)
  5. Udev rules for CH340 USB-serial auto-symlink (/dev/dri0050)
  6. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dri0050-1.0.1.tar.gz (26.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dri0050-1.0.1-py3-none-any.whl (19.2 kB view details)

Uploaded Python 3

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

Hashes for dri0050-1.0.1.tar.gz
Algorithm Hash digest
SHA256 86992dd62a30f575598a61e824d11397b35f013177529f7b14cc0529ff8d14af
MD5 10d3de9ab2daf06e5eab71f73d76f6ed
BLAKE2b-256 c0d89a34f992f2f1b05fcb6766d929c7e2858eed71f328c5e278852f8261682b

See more details on using hashes here.

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

Hashes for dri0050-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7486f70f14af7e8f6b0b9ea3325a0ece6f8b0598733eb304ec525c3f77f48f52
MD5 be217e9ccbd5ae9fc6986659784ac119
BLAKE2b-256 fa5d4e7bfea5a4e2033c33617256b45fc44439e4d63925241748bc659de28dba

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page