Metadata-Version: 2.4
Name: dri0050
Version: 1.0.1
Summary: Python driver for DFRobot DRI0050 PWM Motor & LED Controller (MODBUS RTU)
Author: MaskService
Author-email: Tom Sapletta <tom@sapletta.com>
License-Expression: Apache-2.0
Keywords: dri0050,dfrobot,pwm,motor,led,modbus,driver
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: minimalmodbus>=2.0.0
Requires-Dist: modbus_tk>=1.1.0
Requires-Dist: pyserial>=3.5
Requires-Dist: goal>=2.1.0
Requires-Dist: costs>=0.1.20
Requires-Dist: pfix>=0.1.60
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Requires-Dist: goal>=2.1.0; extra == "dev"
Requires-Dist: costs>=0.1.20; extra == "dev"
Requires-Dist: pfix>=0.1.60; extra == "dev"
Provides-Extra: minimalmodbus
Requires-Dist: minimalmodbus>=2.0.0; extra == "minimalmodbus"
Provides-Extra: modbus-tk
Requires-Dist: modbus_tk>=1.1.0; extra == "modbus-tk"
Dynamic: license-file

# DRI0050 — PWM DC Motor & LED Strip Driver for Python

## AI Cost Tracking

![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.15-brightgreen) ![AI Model](https://img.shields.io/badge/AI%20Model-openrouter%2Fqwen%2Fqwen3-coder-next-lightgrey)

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](https://openrouter.ai/models/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](img.png)

![Board pinout](img_1.png)

### 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](https://github.com/DFRobot/CH_Driver) — supports Win 7/8/8.1/10/11 (32/64-bit)
- **macOS**: [CH340/CH341 driver](https://github.com/DFRobot/CH_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)

```python
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)

```python
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:

```bash
# 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:

```python
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)

```bash
git clone https://github.com/maskservice/rpi-motor-DRI0050.git
cd rpi-motor-DRI0050
pip install .
```

### Dependencies only

```bash
pip install -r requirements.txt
```

### Raspberry Pi — quick deploy

```bash
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:**
```bash
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

```bash
# 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`:
```python
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:
```bash
python3 motor_control.py
```

### 5. Common Patterns

**Read current state:**
```python
info = drv.info()
print(f"Freq: {info['freq_hz']}Hz, Duty: {info['duty']:.0%}")
```

**Set frequency and duty together:**
```python
drv.set_freq(1000)
drv.set_duty(0.75)
drv.set_enable(True)
```

**Atomic update (modbus_tk only):**
```python
from dri0050 import DRI0050ModbusTK
with DRI0050ModbusTK(port=PORT) as drv:
    drv.pwm(freq=1000, duty=0.5)  # Single MODBUS transaction
```

**Factory reset:**
```python
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)

```python
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)

```python
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

```python
# 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

```bash
dri0050-info /dev/ttyUSB0
dri0050-info COM3 --slave 0x32 --baudrate 9600
```

### `dri0050-pwm` — set PWM parameters

```bash
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

```bash
chmod +x install.sh
sudo ./install.sh
```

### Serial Port Permissions

If you get a permission error accessing `/dev/ttyUSB0`:

```bash
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](https://github.com/DFRobot/CH_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

- [DFRobot DRI0050 Product Wiki](https://wiki.dfrobot.com/Light_and_Motor_Driver_for_Python_SKU_DRI0050)
- [DFRobot DRI0050 Software (GitHub)](https://github.com/DFRobot/DRI0050_soft_V1.0)
- [minimalmodbus documentation](https://minimalmodbus.readthedocs.io/)
- [modbus_tk on GitHub](https://github.com/ljean/modbus-tk)
- [MODBUS RTU specification](https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf)
