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 引脚复用配置。
// 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>;
};
// 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 工具使用示例:
# 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 的示例如下:
# 导出 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 设备结构如下:
/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
查看当前配置
# 查看周期
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 引脚复用配置正确:
# 检查 IO46 (PWM2) 的功能配置 k230_iomux.py --pin io46 --funcs # 检查 IO47 (PWM3) 的功能配置 k230_iomux.py --pin io47 --funcs
C 语言示例#
该示例是一个命令行 PWM 测试工具,使用示例如下:
# 默认配置: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
详细代码如下:
#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;
}
编译和运行:
# 编译
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)#
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 示例 - 基本控制#
#!/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 示例 - 电机控制#
#!/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()
