# PWM 使用指南

## 概述

K230 平台包含两个 PWM 控制器，每个控制器提供 3 个通道。

| 控制器 | 基地址     | 通道范围  | 通道数 | sysfs 设备 |
| ------ | ---------- | --------- | ------ | ---------- |
| pwm0_2 | 0x9140a000 | PWM0-PWM2 | 3      | pwmchip0   |
| pwm3_5 | 0x9140a040 | PWM3-PWM5 | 3      | pwmchip3   |

> **说明**：同一控制器下的 3 个通道具有相同的 PWM 周期，仅占空比可独立配置。

每个控制器的寄存器映射如下：

| 寄存器    | 偏移 | 说明                 |
| --------- | ---- | -------------------- |
| PWMCFG    | 0x00 | 配置寄存器           |
| PWMCOUNT  | 0x08 | 计数器寄存器         |
| PWMS      | 0x10 | 状态寄存器           |
| PWMCMP(0) | 0x20 | 周期比较值 (Period)  |
| PWMCMP(1) | 0x24 | 通道 0 占空比 (Duty) |
| PWMCMP(2) | 0x28 | 通道 1 占空比 (Duty) |
| PWMCMP(3) | 0x2C | 通道 2 占空比 (Duty) |

---

## 设备树配置

K230 PWM 控制器需要在设备树中进行配置，包括控制器本身和 IOMUX 引脚复用配置。

```dts
// arch/riscv/boot/dts/canaan/k230.dtsi
&pwm0_2: pwm0_2@9140a000 {
    compatible = "canaan,k230-pwm";
    reg = <0x0 0x9140a000 0x0 0x40>;
    clocks = <&pwm_pclk_gate>;
};

&pwm3_5: pwm3_5@9140a040 {
    compatible = "canaan,k230-pwm";
    reg = <0x0 0x9140a040 0x0 0x40>;
    clocks = <&pwm_pclk_gate>;
};
```

```dts
// arch/riscv/boot/dts/canaan/k230-canmv-01studio-lcd.dts
&iomux {
    pwm2_pins: pwm2_pins {
        pins = K230_IO46;
        function = K230_IO46_PWM2;
    };
    pwm3_pins: pwm3_pins {
        pins = K230_IO47;
        function = K230_IO47_PWM3;
    };
};

&pwm0_2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&pwm2_pins>;
};

&pwm3_5 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&pwm3_pins>;
};
```

> **注意**：
>
> - 上述示例仅配置了 PWM2 和 PWM3 引脚，如需使用其他 PWM 通道，请参考此配置修改对应的 IOMUX 设置。
> - PWM 驱动代码位于：`drivers/pwm/pwm-k230.c`

---

## 用户态操作 PWM

### 使用 pwm-test 工具

pwm-test 工具使用示例：

```bash
# 1kHz PWM, 45.5% 占空比, pwmchip0 channel 2 (PWM2)
pwm-test.sh 0 2 1000 45.5

# 10kHz PWM, 25.5% 占空比, pwmchip3 channel 0 (PWM3)
pwm-test.sh 3 0 10000 25.5

# 查看帮助
pwm-test.sh --help
```

### 通过 sysfs 配置

使用 sysfs 接口配置 PWM 的示例如下：

```bash
# 导出 PWM chip0 的 channel 2 (PWM2)
echo 2 > /sys/class/pwm/pwmchip0/export

# 启用 PWM
echo 1 > /sys/class/pwm/pwmchip0/pwm2/enable

# 设置周期 (单位: 纳秒) - 1MHz (1us)
echo 1000 > /sys/class/pwm/pwmchip0/pwm2/period

# 设置占空比 (单位: 纳秒) - 50% 占空比
echo 500 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle

# --- 常用配置示例 ---
# 1kHz, 50% 占空比 (周期 1ms)
echo 1000000 > /sys/class/pwm/pwmchip0/pwm2/period
echo 500000 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle

# 100Hz, 50% 占空比 (周期 10ms)
echo 10000000 > /sys/class/pwm/pwmchip0/pwm2/period
echo 5000000 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle

# 10kHz, 25% 占空比 (周期 100us)
echo 100000 > /sys/class/pwm/pwmchip0/pwm2/period
echo 25000 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle

# 取消导出 PWM 通道
echo 2 > /sys/class/pwm/pwmchip0/unexport
```

PWM 设备结构

sysfs 中的 PWM 设备结构如下：

```bash
/root # ls -la /sys/class/pwm/
total 0
drwxr-xr-x    2 root     root             0 May 20 06:29 .
drwxr-xr-x   56 root     root             0 Jan  1  1970 ..
lrwxrwxrwx    1 root     root             0 May 20 06:29 pwmchip0 -> ../../devices/platform/soc/9140a000.pwm0_2/pwm/pwmchip0
lrwxrwxrwx    1 root     root             0 May 20 06:29 pwmchip3 -> ../../devices/platform/soc/9140a040.pwm3_5/pwm/pwmchip3

# 导出 channel 2 后，会生成对应的 pwm2 目录
/root # ls -la /sys/class/pwm/pwmchip0/
total 0
drwxr-xr-x    2 root     root             0 May 20 06:29 .
drwxr-xr-x    56 root     root             0 Jan  1  1970 ..
-rwxrwxrwx    1 root     root          4096 May 20 06:29 alias
-rwxrwxrwx    1 root     root          4096 May 20 06:29 device
-rwxrwxrwx    1 root     root          4096 May 20 06:29 export
lrwxrwxrwx    1 root     root             0 May 20 06:29 pwm2 -> ./pwm2
-rwxrwxrwx    1 root     root          4096 May 20 06:29 uevent
-rwxrwxrwx    1 root     root          4096 May 20 06:29 unexport
```

查看当前配置

```bash
# 查看周期
cat /sys/class/pwm/pwmchip0/pwm2/period

# 查看占空比
cat /sys/class/pwm/pwmchip0/pwm2/duty_cycle

# 查看极性
cat /sys/class/pwm/pwmchip0/pwm2/polarity

# 查看启用状态
cat /sys/class/pwm/pwmchip0/pwm2/enable
```

> **注意：检查 IOMUX 配置**
>
>使用前请确认 IOMUX 引脚复用配置正确：
>
>```bash
># 检查 IO46 (PWM2) 的功能配置
>k230_iomux.py --pin io46 --funcs
>
># 检查 IO47 (PWM3) 的功能配置
>k230_iomux.py --pin io47 --funcs
>```

---

## C 语言示例

该示例是一个命令行 PWM 测试工具，使用示例如下：

```bash
# 默认配置：pwmchip0, channel 2, 1kHz, 50% 占空比
./pwm_test

# 自定义配置
./pwm_test 0 2 1000 50      # pwmchip0, channel 2, 1kHz, 50%
./pwm_test 3 0 10000 25     # pwmchip3, channel 0, 10kHz, 25%

# 查看帮助
./pwm_test --help
```

详细代码如下：

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

#define SYSFS_PWM_DIR "/sys/class/pwm"
#define MAX_LEN 256

static int current_chip = 0;
static int current_channel = 0;

// Function declarations
int write_sysfs_attr(const char *path, const char *value);
int pwm_export(int chip, int channel);
int pwm_unexport(int chip, int channel);
int pwm_set_polarity(int chip, int channel, const char *polarity);
int pwm_set_period(int chip, int channel, unsigned long period_ns);
int pwm_set_duty_cycle(int chip, int channel, unsigned long duty_ns);
int pwm_enable(int chip, int channel, int enable);
unsigned long pwm_read_value(int chip, int channel, const char *attr);

// Signal handler to cleanup PWM on Ctrl+C
void cleanup_handler(int sig)
{
    (void)sig;
    printf("\nStopping PWM... clean \n");
    exit(0);
}

// 写入 sysfs 属性
int write_sysfs_attr(const char *path, const char *value)
{
    int fd = open(path, O_WRONLY);
    if (fd < 0) {
        printf("Failed to open sysfs attribute path=%s \n",path);
        return -1;
    }

    ssize_t len = strlen(value);
    if (write(fd, value, len) != len) {
        printf("Failed to write sysfs attribute p=%s v=%s\n", path,value);
        close(fd);
        return -1;
    }

    close(fd);
    return 0;
}

// 导出 PWM 通道
int pwm_export(int chip, int channel)
{
    char path[MAX_LEN];
    snprintf(path, sizeof(path), "%s/pwmchip%d/export", SYSFS_PWM_DIR, chip);
    char value[16];
    snprintf(value, sizeof(value), "%d", channel);
    return write_sysfs_attr(path, value);
}

// 取消导出 PWM 通道
int pwm_unexport(int chip, int channel)
{
    char path[MAX_LEN];
    char check_path[MAX_LEN];

    snprintf(check_path, sizeof(check_path), "%s/pwmchip%d/pwm%d", SYSFS_PWM_DIR, chip, channel);
    if (access(check_path, F_OK) != 0) {
        return 0;
    }

    snprintf(path, sizeof(path), "%s/pwmchip%d/unexport", SYSFS_PWM_DIR, chip);
    char value[16];
    snprintf(value, sizeof(value), "%d", channel);
    return write_sysfs_attr(path, value);
}

// 设置 PWM 极性
int pwm_set_polarity(int chip, int channel, const char *polarity)
{
    char path[MAX_LEN];
    snprintf(path, sizeof(path), "%s/pwmchip%d/pwm%d/polarity",
             SYSFS_PWM_DIR, chip, channel);
    return write_sysfs_attr(path, polarity);
}

// 设置 PWM 周期
int pwm_set_period(int chip, int channel, unsigned long period_ns)
{
    char path[MAX_LEN];
    snprintf(path, sizeof(path), "%s/pwmchip%d/pwm%d/period",
             SYSFS_PWM_DIR, chip, channel);
    char value[32];
    snprintf(value, sizeof(value), "%lu", period_ns);
    return write_sysfs_attr(path, value);
}

// 设置 PWM 占空比
int pwm_set_duty_cycle(int chip, int channel, unsigned long duty_ns)
{
    char path[MAX_LEN];
    snprintf(path, sizeof(path), "%s/pwmchip%d/pwm%d/duty_cycle",
             SYSFS_PWM_DIR, chip, channel);
    char value[32];
    snprintf(value, sizeof(value), "%lu", duty_ns);
    return write_sysfs_attr(path, value);
}

// 启用/禁用 PWM
int pwm_enable(int chip, int channel, int enable)
{
    char path[MAX_LEN];
    snprintf(path, sizeof(path), "%s/pwmchip%d/pwm%d/enable",
             SYSFS_PWM_DIR, chip, channel);
    return write_sysfs_attr(path, enable ? "1" : "0");
}

// 读取 PWM 值
unsigned long pwm_read_value(int chip, int channel, const char *attr)
{
    char path[MAX_LEN];
    snprintf(path, sizeof(path), "%s/pwmchip%d/pwm%d/%s",
             SYSFS_PWM_DIR, chip, channel, attr);

    int fd = open(path, O_RDONLY);
    if (fd < 0) {
        perror("Failed to open sysfs attribute");
        return 0;
    }

    char buffer[32];
    ssize_t len = read(fd, buffer, sizeof(buffer) - 1);
    close(fd);

    if (len > 0) {
        buffer[len] = '\0';
        return strtoul(buffer, NULL, 10);
    }

    return 0;
}

void print_usage(const char *program)
{
    printf("K230 PWM Basic Test\n");
    printf("====================\n\n");
    printf("Usage: %s [chip] [channel] [frequency] [duty]\n\n", program);
    printf("Arguments:\n");
    printf("  chip     - PWM chip number (0 or 3), default: 0\n");
    printf("  channel  - PWM channel number (0, 1, 2), default: 0\n");
    printf("  frequency- Frequency in Hz, default: 1000\n");
    printf("  duty     - Duty cycle (0-100), default: 50\n\n");
    printf("Examples:\n");
    printf("  %s           # Default: pwmchip0 channel 0, 1kHz, 50%%\n", program);
    printf("  %s 0 2 1000 50   # pwmchip0 channel 2, 1kHz, 50%%\n", program);
    printf("  %s 3 0 10000 25  # pwmchip3 channel 0, 10kHz, 25%%\n", program);
}

int main(int argc, char *argv[])
{
    int chip = 0;
    int channel = 2;
    int frequency = 1000;
    int duty = 50;

    if (argc > 1) {
        if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
            print_usage(argv[0]);
            return 0;
        }
        chip = atoi(argv[1]);
    }
    if (argc > 2) {
        channel = atoi(argv[2]);
    }
    if (argc > 3) {
        frequency = atoi(argv[3]);
    }
    if (argc > 4) {
        duty = atoi(argv[4]);
    }

    current_chip = chip;
    current_channel = channel;

    unsigned long period_ns = 1000000000UL / frequency;
    unsigned long duty_ns = period_ns * duty / 100;

    printf("Configuring PWM %d:%d\n", chip, channel);
    printf("  Frequency: %d Hz\n", frequency);
    printf("  Period: %lu ns\n", period_ns);
    printf("  Duty: %d%% (%lu ns)\n\n", duty, duty_ns);

    pwm_unexport(chip, channel);
    usleep(10);

    printf("Exporting PWM...\n");
    if (pwm_export(chip, channel) < 0) {
        printf("PWM already exported or error\n");
    }
    pwm_enable(chip, channel, 1);

    pwm_set_polarity(chip, channel, "inversed");
    pwm_set_period(chip, channel, period_ns);
    pwm_set_duty_cycle(chip, channel, duty_ns);

    printf("\nCurrent Configuration:\n");
    printf("  Period: %lu ns\n", pwm_read_value(chip, channel, "period"));
    printf("  Duty: %lu ns\n", pwm_read_value(chip, channel, "duty_cycle"));
    printf("  Enable: %lu\n", pwm_read_value(chip, channel, "enable"));

    printf("\nPWM running... Press Ctrl+C to stop.\n");

    signal(SIGINT, cleanup_handler);

    while(1) sleep(30);
    return 0;
}
```

编译和运行：

```bash
# 编译
riscv64-unknown-linux-gnu-gcc -o pwm_test pwm_test.c

# 运行
./pwm_test 0 2 1000 50    # pwmchip0, channel 2, 1kHz, 50%
./pwm_test 3 0 10000 25   # pwmchip3, channel 0, 10kHz, 25%
```

---

## Python 示例

### 使用 pwmio 库（CircuitPython）

```python
import pwmio
import board
import time

# 创建 PWM 输出，频率 50Hz
pwm = pwmio.PWMOut(board.pin.PWM2, frequency=50)
pwm.duty_cycle = 2 ** 15  # 50% 占空比 (2**16 的一半)

while True:
    time.sleep(1)
```

### 纯 Python 示例 - 基本控制

```python
#!/usr/bin/env python3
"""
K230 PWM Python Example - Basic Control

Usage:
    python pwm_basic.py              # Default: chip=0, channel=2, 1kHz, 50%
    python pwm_basic.py -c 0 -C 2    # chip=0, channel=2
    python pwm_basic.py -f 10000     # 10kHz
    python pwm_basic.py -d 25        # 25% duty cycle
    python pwm_basic.py --help       # Show help
"""

import os
import sys
import time
import argparse

SYSFS_PWM_DIR = "/sys/class/pwm"

class K230PWM:
    def __init__(self, chip=0, channel=0):
        """
        Initialize PWM controller

        Args:
            chip: PWM chip number (0 or 3)
            channel: PWM channel number (0, 1, 2)
        """
        self.chip = chip
        self.channel = channel
        self.pwm_path = f"{SYSFS_PWM_DIR}/pwmchip{chip}/pwm{channel}"
        self.exported = False

    def export(self):
        """Export PWM channel"""
        export_path = f"{SYSFS_PWM_DIR}/pwmchip{self.chip}/export"
        if not os.path.exists(self.pwm_path):
            with open(export_path, 'w') as f:
                f.write(str(self.channel))
            self.exported = True
            time.sleep(0.1)

    def unexport(self):
        """Unexport PWM channel"""
        if self.exported:
            unexport_path = f"{SYSFS_PWM_DIR}/pwmchip{self.chip}/unexport"
            with open(unexport_path, 'w') as f:
                f.write(str(self.channel))
            self.exported = False

    def set_polarity(self, polarity="inversed"):
        """
        Set PWM polarity

        Args:
            polarity: "normal" or "inversed"
        """
        with open(f"{self.pwm_path}/polarity", 'w') as f:
            f.write(polarity)

    def set_period(self, period_ns):
        """
        Set PWM period (nanoseconds)

        Args:
            period_ns: Period length in nanoseconds
        """
        with open(f"{self.pwm_path}/period", 'w') as f:
            f.write(str(period_ns))

    def set_duty_cycle(self, duty_ns):
        """
        Set PWM duty cycle (nanoseconds)

        Args:
            duty_ns: Duty cycle time in nanoseconds
        """
        with open(f"{self.pwm_path}/duty_cycle", 'w') as f:
            f.write(str(duty_ns))

    def set_frequency(self, frequency_hz):
        """
        Set PWM frequency (Hz)

        Args:
            frequency_hz: Frequency in Hz
        """
        period_ns = int(1000000000 / frequency_hz)
        self.set_period(period_ns)

    def set_duty_percent(self, duty_percent):
        """
        Set PWM duty cycle percentage

        Args:
            duty_percent: Duty cycle percentage (0-100)
        """
        with open(f"{self.pwm_path}/period", 'r') as f:
            period_ns = int(f.read().strip())

        duty_ns = int(period_ns * duty_percent / 100)
        self.set_duty_cycle(duty_ns)

    def enable(self):
        """Enable PWM"""
        with open(f"{self.pwm_path}/enable", 'w') as f:
            f.write("1")

    def disable(self):
        """Disable PWM"""
        with open(f"{self.pwm_path}/enable", 'w') as f:
            f.write("0")

    def get_state(self):
        """Get current state"""
        state = {}
        try:
            with open(f"{self.pwm_path}/period", 'r') as f:
                state['period'] = int(f.read().strip())
            with open(f"{self.pwm_path}/duty_cycle", 'r') as f:
                state['duty_cycle'] = int(f.read().strip())
            with open(f"{self.pwm_path}/polarity", 'r') as f:
                state['polarity'] = f.read().strip()
            with open(f"{self.pwm_path}/enable", 'r') as f:
                state['enabled'] = bool(int(f.read().strip()))
        except Exception as e:
            state['error'] = str(e)
        return state

    def __enter__(self):
        self.export()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.disable()
        self.unexport()


def parse_args():
    """Parse command line arguments"""
    parser = argparse.ArgumentParser(
        description="K230 PWM Python Test Tool",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python pwm_basic.py              # Default: chip=0, channel=0, 1kHz, 50%
  python pwm_basic.py -c 0 -C 2  -f 10000  -d 25  # chip=0, channel=2 10kHz 25% duty
  python pwm_basic.py -f 10000     # 10kHz
  python pwm_basic.py -d 25        # 25% duty cycle
        """
    )
    parser.add_argument('-c', '--chip', type=int, default=0,
                        help='PWM chip number (0 or 3), default: 0')
    parser.add_argument('-C', '--channel', type=int, default=2,
                        help='PWM channel number (0, 1, 2), default: 2')
    parser.add_argument('-f', '--frequency', type=int, default=1000,
                        help='Frequency in Hz, default: 1000')
    parser.add_argument('-d', '--duty', type=float, default=50,
                        help='Duty cycle percentage (0-100), default: 50')
    parser.add_argument('-p', '--polarity', type=str, default='inversed',
                        choices=['normal', 'inversed'],
                        help='Polarity (normal/inversed), default: inversed')
    return parser.parse_args()


def main():
    args = parse_args()

    print("K230 PWM Python Test")
    print("====================\n")
    print(f"Configuration:")
    print(f"  Chip: {args.chip}")
    print(f"  Channel: {args.channel}")
    print(f"  Frequency: {args.frequency} Hz")
    print(f"  Duty: {args.duty}%")
    print(f"  Polarity: {args.polarity}")
    print()

    with K230PWM(chip=args.chip, channel=args.channel) as pwm:
        pwm.enable()
        pwm.set_frequency(args.frequency)
        pwm.set_duty_percent(args.duty)
        print(f"State: {pwm.get_state()}")
        print("Running... Press Ctrl+C to stop")
        while True:
            time.sleep(1)


if __name__ == "__main__":
    main()
```

### 纯 Python 示例 - 电机控制

```python
#!/usr/bin/env python3
"""
K230 PWM Motor Speed Control Example - Console Version
"""

import argparse
import os
import sys
import time


class MotorController:
    def __init__(self, chip=0, channel=0, frequency=2000, min_duty=10, max_duty=100):
        self.chip = chip
        self.channel = channel
        self.frequency = frequency
        self.min_duty = min_duty
        self.max_duty = max_duty
        self.running = False
        self.duty = 0
        self.period_ns = int(1e9 / frequency)

        self._export()

    def _unexport(self):
        """Unexport PWM channel"""
        unexport_path = f"/sys/class/pwm/pwmchip{self.chip}/unexport"
        if os.path.exists(f"/sys/class/pwm/pwmchip{self.chip}/pwm{self.channel}"):
            with open(unexport_path, 'w') as f:
                f.write(str(self.channel))

    def _export(self):
        self._unexport()
        time.sleep(0.1)

        export_path = f"/sys/class/pwm/pwmchip{self.chip}/export"
        with open(export_path, 'w') as f:
            f.write(str(self.channel))

        with open(f"/sys/class/pwm/pwmchip{self.chip}/pwm{self.channel}/period", 'w') as f:
            f.write(str(self.period_ns))
        with open(f"/sys/class/pwm/pwmchip{self.chip}/pwm{self.channel}/duty_cycle", 'w') as f:
            f.write(str(int(self.period_ns * self.min_duty / 100)))
        with open(f"/sys/class/pwm/pwmchip{self.chip}/pwm{self.channel}/polarity", 'w') as f:
            f.write("inversed")

    def set_speed(self, percent):
        """Set motor speed (0-100%)"""
        if percent < 0:
            percent = 0
        if percent > 100:
            percent = 100

        duty = self.min_duty + (self.max_duty - self.min_duty) * percent / 100
        duty_ns = int(self.period_ns * duty / 100)

        with open(f"/sys/class/pwm/pwmchip{self.chip}/pwm{self.channel}/duty_cycle", 'w') as f:
            f.write(str(duty_ns))

        self.duty = duty
        return duty

    def start(self):
        """Start motor"""
        with open(f"/sys/class/pwm/pwmchip{self.chip}/pwm{self.channel}/enable", 'w') as f:
            f.write("1")

    def stop(self):
        """Stop motor"""
        with open(f"/sys/class/pwm/pwmchip{self.chip}/pwm{self.channel}/enable", 'w') as f:
            f.write("0")

    def ramp_up(self, target_percent, duration=2):
        """Ramp up speed"""
        steps = 20
        interval = duration / steps
        current = 0

        for _ in range(steps):
            if current >= target_percent:
                break
            current = min(current + (target_percent - current) / 2, target_percent)
            self.set_speed(current)
            time.sleep(interval)

        self.set_speed(target_percent)

    def ramp_down(self, target_percent, duration=2):
        """Ramp down speed"""
        steps = 20
        interval = duration / steps
        current = self.duty if self.duty > 0 else 100

        for _ in range(steps):
            if current <= target_percent:
                break
            current = max(current - (current - target_percent) / 2, target_percent)
            self.set_speed(current)
            time.sleep(interval)

        self.set_speed(target_percent)

    def cleanup(self):
        """Cleanup"""
        self.stop()
        unexport_path = f"/sys/class/pwm/pwmchip{self.chip}/unexport"
        with open(unexport_path, 'w') as f:
            f.write(str(self.channel))


def main():
    parser = argparse.ArgumentParser(
        description="K230 Motor Speed Control Example",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python pwm_motor.py              # Default: chip=0, channel=2, freq=2000Hz
  python pwm_motor.py -c 0 -C 2    # Use chip0 channel2
  python pwm_motor.py --min-duty 5 --max-duty 100  # Wider speed range
  python pwm_motor.py --help       # Show help

Configuration:
  Frequency: 2000 Hz (2kHz) default, max ~2200Hz
  Duty range: 10-100% (adjustable via --min-duty and --max-duty)
        """
    )
    parser.add_argument('-c', '--chip', type=int, default=0,
                        help='PWM chip number (0 or 3), default: 0')
    parser.add_argument('-C', '--channel', type=int, default=2,
                        help='PWM channel number (0, 1, 2), default: 2')
    parser.add_argument('-f', '--frequency', type=int, default=2000,
                        help='PWM frequency in Hz, default: 2000')
    parser.add_argument('--min-duty', type=float, default=10,
                        help='Minimum duty cycle %% (start speed), default: 10')
    parser.add_argument('--max-duty', type=float, default=100,
                        help='Maximum duty cycle %% (max speed), default: 100')
    args = parser.parse_args()

    print("K230 Motor Control Example")
    print("==========================\n")
    print(f"Configuration: chip={args.chip}, channel={args.channel}, frequency={args.frequency}Hz\n")
    print(f"Duty range: {args.min_duty}% - {args.max_duty}%\n")

    motor = MotorController(chip=args.chip, channel=args.channel, frequency=args.frequency,
                           min_duty=args.min_duty, max_duty=args.max_duty)

    try:
        print("Starting motor...")
        motor.start()
        time.sleep(1)

        print("Accelerating to 100%...")
        motor.ramp_up(100, duration=2)
        time.sleep(1)

        print("Decelerating to 0%...")
        motor.ramp_down(0, duration=2)
        time.sleep(1)

        print("Stopping...")
        motor.stop()

    finally:
        motor.cleanup()
        print("Cleanup done.")


if __name__ == "__main__":
    main()
```
