一、原理
SG90舵机是一种常见的旋转电机模块。它能转动的角度主要有三种类型:0到90度的小范围转动,0到180度的半圆转动,以及可以完整旋转360度的全周模式。这种电机常用于需要精准控制转动的场景,例如:
- 智能垃圾桶的自动开盖
- 让智能小车实现灵活转向
- 摄像头支架的多角度调整
- 机械臂的关节运动控制
它的设计简单实用,特别适合需要精确控制角度的自动化项目。

SG90舵机通常通过PWM信号控制角度。它的工作原理很简单:
- 信号周期:每20毫秒(0.02秒)发送一次控制信号。
- 高电平时间:在这20毫秒里,给舵机发送高电平的时长决定了转动角度。
- 占空比公式:占空比 = 高电平时间 ÷ 20毫秒
比如:
- 当高电平持续1毫秒时,舵机转到0度;
- 高电平1.5毫秒时转到中间位置90度;
- 2毫秒时则转到180度。
简单来说,就是通过调整每次信号中高电平的持续时间(1-2毫秒之间),就能精准控制舵机转动的角度。

SG90舵机角度控制说明(信号周期固定为20ms):
脉冲宽度对应角度关系:
- 0.5ms → 0°
- 1.0ms → 45°
- 1.5ms → 90°
- 2.0ms → 135°
- 2.5ms → 180°
::: (通过调整高电平信号的持续时间,可在20ms周期内控制舵机转向不同角度) :::
二、硬件连接
SG90舵机的三根线功能如下:
- 棕线:连接电源地(GND);
- 红线:连接电源(需4.8V到7.2V的电压);
- 黄线:接收PWM控制信号(用于调整舵机角度)。
泰山派40Pin连接建议:
本实验使用泰山派 40Pin排针的 Pin 33(PWM8_M0 / GPIO3_B1) 连接SG90黄线(PWM信号),棕线接Pin 39(GND),红线接Pin 2(5V)。
| SG90舵机 | 泰山派40Pin | 说明 |
|---|---|---|
| 棕线 (GND) | 39 | 地线 |
| 红线 (VCC) | 2 | 5V电源 |
| 黄线 (PWM) | 33 | PWM8_M0 信号 |
三、实验代码
3.1 检查GPIO占用
目的:确保你要用的GPIO引脚没有被其他功能占用 操作:
- 查看RK3566芯片手册,确认目标GPIO的复用功能是否已关闭
- 检查设备树配置,确保该GPIO未被设置为其他设备的信号引脚
3.2 配置内核驱动
步骤1:修改内核 Makefile
文件路径: /home/book/rk/tspi/kernel/drivers/pwm/Makefile
添加一行:
obj-$(CONFIG_PWM_SG90) += pwm-sg90.o(这行代码的作用是告诉内核需要编译这个驱动模块)
步骤2:添加配置选项
文件路径: /home/book/rk/tspi/kernel/drivers/pwm/Kconfig
添加:
config PWM_SG90
tristate "SG90舵机驱动"
help
启用对SG90伺服电机PWM驱动的支持2
3
4
步骤3:启用驱动支持
文件路径: 泰山派SDK内核配置文件(通常为 kernel/arch/arm64/configs/rockchip_defconfig,具体文件名根据实际SDK选择)
添加:
CONFIG_PWM_SG90=y:::
总结操作流程
- 检查并确认GPIO可用性
- 在驱动目录修改两个配置文件(Makefile 和 Kconfig)
- 在内核配置文件中启用该驱动选项 :::
这样修改后,内核编译时就会包含对SG90伺服电机的PWM控制支持。

3.3、修改设备树
文件路径: /home/book/rk/tspi/kernel/arch/arm64/boot/dts/rockchip/rk3566-tspi.dts
打开设备树文件,在根节点 / {} 内追加 hc_sg90 节点,并启用 pwm8 控制器:
/ {
// 注意:保留根节点内原有所有内容,仅在末尾追加以下节点
hc_sg90 {
compatible = "hc-sg90";
pwms = <&pwm8 0 20000000 0>;
status = "okay";
};
};
&pwm8 {
pinctrl-names = "active";
pinctrl-0 = <&pwm8m0_pins>;
status = "okay";
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
注:本示例使用泰山派40Pin的 Pin 33 (PWM8_M0 / GPIO3_B1) 连接SG90信号线(黄线)。如使用其他PWM引脚(如Pin 35的PWM9),请将
&pwm8和pwm8m0_pins对应修改为&pwm9和pwm9m0_pins,并确认设备树中该pinctrl节点存在。
3.4、驱动代码(pwm-sg90.c)
文件路径: /home/book/rk/tspi/kernel/drivers/pwm/pwm-sg90.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/timekeeping.h>
#include <linux/wait.h>
#include <linux/irqflags.h>
#include <linux/pwm.h>
static int major;
static struct class *class;
static struct pwm_device *pwm_test;
static int sg90_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
printk("sg90 match success \n");
if (node) {
/* 从子节点中获取PWM设备 */
pwm_test = devm_of_pwm_get(&pdev->dev, node, NULL);
if (IS_ERR(pwm_test)) {
printk(KERN_ERR" pwm_test,get pwm error!!\n");
return -1;
}
} else {
printk(KERN_ERR" pwm_test of_get_next_child error!!\n");
return -1;
}
pwm_config(pwm_test, 1500000, 20000000); /* 配置PWM:1.5ms,90度,周期:20000000ns = 20ms */
pwm_set_polarity(pwm_test, PWM_POLARITY_NORMAL); /* 设置输出极性:占空比为高电平 */
pwm_enable(pwm_test); /* 使能PWM输出 */
return 0;
}
static int sg90_remove(struct platform_device *dev)
{
pwm_config(pwm_test, 500000, 20000000); /* 配置PWM:0.5ms,0度 */
pwm_free(pwm_test);
return 0;
}
static const struct of_device_id sg90_of_match[] = {
{ .compatible = "hc-sg90" },
{ }
};
static struct platform_driver sg90_platform_driver = {
.driver = {
.name = "my_sg90",
.of_match_table = sg90_of_match,
},
.probe = sg90_probe,
.remove = sg90_remove,
};
static int sg90_open(struct inode *node, struct file *filp)
{
return 0;
}
// 0.5ms ———— 0度
// 1.0ms ———— 45度
// 1.5ms ———— 90度
// 2.0ms ———— 135度
// 2.5ms ———— 180度
// data[0] ---> 500000
static ssize_t sg90_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int res;
unsigned char data[1];
if (size != 1)
return 1;
res = copy_from_user(data, buf, size);
/* 配置PWM:旋转任意角度(单位1度) */
pwm_config(pwm_test, data[0], 20000000);
return 1;
}
static int sg90_release(struct inode *node, struct file *filp)
{
return 0;
}
static struct file_operations sg90_ops = {
.owner = THIS_MODULE,
.open = sg90_open,
.write = sg90_write,
.release = sg90_release,
};
static int sg90_init(void)
{
major = register_chrdev(0, "sg90", &sg90_ops);
class = class_create(THIS_MODULE, "sg90_class");
device_create(class, NULL, MKDEV(major, 0), NULL, "sg90");
platform_driver_register(&sg90_platform_driver);
return 0;
}
static void sg90_exit(void)
{
platform_driver_unregister(&sg90_platform_driver);
device_destroy(class, MKDEV(major, 0));
class_destroy(class);
unregister_chrdev(major, "sg90");
}
module_init(sg90_init);
module_exit(sg90_exit);
MODULE_LICENSE("GPL");2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
3.5、编译与烧录
编译内核与设备树
cd /home/book/rk/tspi/kernel
make ARCH=arm64 CROSS_COMPILE=/home/book/rk/tspi/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- -j$(nproc)
make ARCH=arm64 CROSS_COMPILE=/home/book/rk/tspi/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- dtbs2
3
烧录镜像
将编译生成的内核镜像和设备树(arch/arm64/boot/dts/rockchip/rk3566-tspi.dtb)烧录到开发板。
3.6、应用测试
SG90驱动注册后会创建 /dev/sg90 设备节点。
## 查看设备节点是否存在
ls -l /dev/sg902
注意:本驱动示例中
sg90_write()通过write()接收1字节数据直接作为PWM高电平时间(单位:纳秒)。由于1字节范围仅为0-255,无法覆盖舵机所需的500000-2500000纳秒范围,此版本为简化教学演示。如需精确控制角度,建议修改驱动接收字符串形式的纳秒值(如echo "1500000" > /dev/sg90),或在内核中将传入值做映射转换。