当前位置: 首页 > news >正文

Linux下的PWM驱动

PWM


PWM简介⭕

**PWM(Pulse Width Modulation,脉冲宽度调制)**是一种利用微处理器的数字输出对模拟电路进行控制的技术。通过改变脉冲的占空比,可以控制模拟电路的输出电压或电流。PWM技术广泛应用于电机控制、灯光调节、音频信号生成等领域。

PWM频率和占空比⭕

周期:PWM信号的重复周期,即一个PWM信号从高电平到低电平再到高电平的时间间隔。

频率:PWM信号的重复频率,即每秒钟PWM信号的重复次数。频率与周期成反比,频率 = 1 / 周期。

占空比:PWM信号的脉冲宽度与周期的比值,即高电平时间占整个周期的比例。占空比可以取0到1之间的任意值。

PWM的应用⭕

面积等效原理:
冲量相等而形状不同的窄宽脉冲加在具有惯性的环节上,其效果基本相同

  1. 冲量相等而形状不同是指面积相等
  2. 惯性环节在电路和系统分析中,当输入信号发生变化时,其输出不会立即跟随变化,而是需要经过一段时间后才能逐渐达到新的稳态值。

通俗的说:电压不同,时间不同的俩个信号,当他们的电压和时间的乘积相等的时候,输出的波形信号是相同的

alt text

pwm的子系统框架图

1723971810701

两种驱动方法:

  1. 直接在应用层操作sys/class/pwm
  2. 编写驱动程序后在应用层调用

PWM驱动编写:⭕

Linux内描述一个PWM控制器的结构体:

struct pwm_chip {struct device *dev;const struct pwm_ops *ops;int base;unsigned int npwm;struct pwm_device * (*of_xlate)(struct pwm_chip *pc,const struct of_phandle_args *args);unsigned int of_pwm_n_cells;/* only used internally by the PWM framework */struct list_head list;struct pwm_device *pwms;ANDROID_KABI_RESERVE(1);
};

PWM常有API:

  1. pwm_config函数
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) //改变pwm配置
参数作用
*pwmpwm_device
duty_ns占空比
period_ns周期

成功返回0,失败返回负数

  1. pwm_set_polarity函数
int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) //设置pwm极性
参数作用
*pwmpwm_device
polaritypwm极性

成功返回0,失败返回负数

  1. pwm_enable函数
int pwm_enable(struct pwm_device *pwm) //使能pwm
参数作用
*pwmpwm_device

成功返回0,失败返回负数

  1. pwm_disable函数
int pwm_disable(struct pwm_device *pwm) //禁止pwm
参数作用
*pwmpwm_device

成功返回0,失败返回负数

  1. pwm_request函数
struct pwm_device *pwm_request(int pwm, const char *label) //申请pwm
参数作用
pwmpwm号
labelpwm标签

成功返回pwm_device,失败返回NULL

  1. pwm_free函数
void pwm_free(struct pwm_device *pwm) //释放pwm
参数作用
*pwmpwm_device

无返回

  1. devm_pwm_get函数
int devm_pwm_get(struct device *dev, const char *con_id)//获取PWM设备句柄
参数作用
*dev设备
con_idpwm标签

成功返回pwm_device句柄,失败返回负数

driver层的使用:⭕

使用platform_driver的实现驱动注册,匹配设备树节点即可。

#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/pwm.h>struct pwm_device *pwm_dev;dev_t dev_num; // 设备号static int major = 0; /* 主设备号, 0 表示由系统分配 */struct class *class; // 类和对象static int pwm_driver_open(struct inode *, struct file *)
{pwm_config(pwm_dev, 500000, 2000000);           // 周期2000000ns,占空比500000nspwm_set_polarity(pwm_dev, PWM_POLARITY_NORMAL); // 设置极性pwm_enable(pwm_dev);                            // 启动PWMreturn 0;
}
static int pwm_driver_release(struct inode *, struct file *)
{pwm_free(pwm_dev);return 0;
}static struct file_operations pwm_fops = {.owner = THIS_MODULE,.open = pwm_driver_open,.release = pwm_driver_release};static int pwm_driver_probe(struct platform_device *pdev)
{int ret = 0;pwm_dev = devm_of_pwm_get(&pdev->dev, dev->dev.of_node, NULL);if (IS_ERR(pwm_dev)){printk("get pwm device failed\n");return -1;}// 添加字符设备节点int err;major = register_chrdev(0, "hello", &pwm_fops);class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(class);if (IS_ERR(class)){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "hello");return -1;}device_create(class, NULL, MKDEV(major, 0), NULL, "hello"); /* 设备节点/dev/hello创建 */return 0;
}static int pwm_remove(struct platform_device *pdev)
{unregister_chrdev(major, "hello");class_destroy(class);if (!pwm_dev){pwm_free(pwm_dev);}return 0;
}static const struct of_device_id pwm_of_match[] = {{.compatible = "pwm_test"},{},
};
MODULE_DEVICE_TABLE(of, pwm_of_match);static struct platform_driver pwm_driver = {.driver = {.name = "pwm_test",.of_match_table = pwm_of_match,},.probe = pwm_driver_probe,.remove = pwm_remove,
};static int __init pwm_init(void)
{return platform_driver_register(&pwm_driver);
}static void __exit pwm_exit(void)
{platform_driver_unregister(&pwm_driver);
}
module_init(pwm_init);
module_exit(pwm_exit);
MODULE_LICENSE("GPL");

模拟PWM

使用模拟PWM,即使用定时器来模拟PWM信号。给GPIO配置为输出模式,然后通过定时器来控制GPIO的电平变化,从而实现PWM信号的产生。
1725775680191
配置设备树,指定一个LED的GPIO引脚

led {compatible = "gpio-led";gpios = <&gpio0 9 GPIO_ACTIVE_LOW>;
};

在驱动中,通过与设备树probe获取GPIO引脚,然后配置为输出模式,并使用定时器来控制GPIO的电平变化,从而实现PWM信号的产生。

高精度定时器

普通定时器的时钟频率可以设置在 100Hz 到 1000Hz 之间,所以精度只能限制在毫秒级别。所以无法满足精度较高的场景当中,为此 Linux 提供了高精度定时器,可以提供纳秒级别的精度。

struct hrtimer结构体

// include/linux/hrtimer.h高精度定时器
struct hrtimer {struct timerqueue_node		node;ktime_t				_softexpires;//定时时间enum hrtimer_restart		(*function)(struct hrtimer *);//超时服务函数struct hrtimer_clock_base	*base;u8				state;u8				is_rel;u8				is_soft;u8				is_hard;ANDROID_KABI_RESERVE(1);
};
// include/linux/timer.h普通定时器
struct timer_list {struct list_head entry;unsigned long expires; //定时时间void (*function)(unsigned long);//超时服务函数unsigned long data;unsigned int flags;int slack;
};

hrtimer_init函数

//初始化一个定时器
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,enum hrtimer_mode mode);
参数作用
timer要初始化的定时器
which_clock定时器所使用的时钟类型,比如 CLOCK_REALTIME、CLOCK_MONOTONIC 等
hrtimer_mode定时器模式,比如 HRTIMER_MODE_REL、HRTIMER_MODE_ABS 等

ktime_set函数

//设置定时时间
ktime_t ktime_set(const <error-type> secs, const unsigned long nsecs);
参数作用
secs
nsecs纳秒

hrtimer_start函数

//启动定时器
int hrtimer_start(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode);
参数作用
timer要启动的定时器
time定时时间
mode定时器模式

hrtimer_forward函数

//定时器延时
void hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);
参数作用
timer要延时的定时器
now当前时间
interval延时时间

hrtimer_cancel函数

//取消定时器
int hrtimer_cancel(struct hrtimer *timer);
参数作用
timer要取消的定时器

Source_code

#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/pwm.h>struct pwm_data{int sum_conut; // 总计数int high_count; // 高电平计数struct gpio_desc *gpio; // GPIOstruct hrtimer pwm_timer; // 定时器int time; // 定时时间
};
struct pwm_data *data;
struct pwm_device *pwm_dev;dev_t dev_num; // 设备号static int major = 0; /* 主设备号, 0 表示由系统分配 */struct class *class; // 类和对象
enum hrtimer_restart pwm_timer_func(struct hrtimer *timer){//container_of 宏来从一个结构体成员的指针中获取包含它的结构体指针/*timer 是指向 pwm_timer 成员的指针。struct pwm_data 是包含 pwm_timer 成员的结构体类型。pwm_timer 是 struct pwm_data 结构体中的一个成员。*/static int timer_count = 0;struct pwm_data *mydata = container_of(timer, struct pwm_data, pwm_timer); if(timer_count == mydata->sum_conut){gpiod_set_value(mydata->gpio, 1);timer_count = 0;}if(timer_count == mydata->high_count){gpiod_set_value(mydata->gpio, 0);}timer_count++if(mydata->high_count == 0){timer_count = 0;}hrtimer_forward(timer, timer->_softexpires, mydata->time); // 定时器重新启动, 调整定时器的到期时间为当前时间加上mydata->timereturn HRTIMER_RESTART; // 重启定时器
}
static int pwm_driver_open(struct inode *, struct file *)
{return 0;
}
static int pwm_driver_release(struct inode *, struct file *)
{return 0;
}
static long pwm_driver_ioctl(struct file *file, unsigned int cmd, unsigned long arg){}
static ssize_t pwm_driver_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){int ret = 0;int kbuf[2];ret = copy_from_user(kbuf, buf, count);data->sum_conut = kbuf[0]; //总计数,周期data->high_count = kbuf[1];//高电平计数,占空比return ret;
}
static struct file_operations pwm_fops = {.owner = THIS_MODULE,.open = pwm_driver_open,.release = pwm_driver_release,.unlocked_ioctl = pwm_driver_ioctl,.write = pwm_driver_write,
};static int pwm_driver_probe(struct platform_device *pdev)
{data = kmalloc(sizeof(struct pwm_data), GFP_KERNEL);data->sum_conut = 20;data->high_count = 10;// 添加字符设备节点int err;major = register_chrdev(0, "hello", &pwm_fops);class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(class);if (IS_ERR(class)){printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "hello");return -1;}device_create(class, NULL, MKDEV(major, 0), NULL, "hello"); /* 设备节点/dev/hello创建 */data->gpio = gpiod_get(&pdev->dev,"gpio-led",GPIOF_OUT_INIT_HIGH) //获取GPIOgpiod_set_value(data->gpio, 1);//设置GPIO高电平data->time = ktime_set(0,1000000); //定时器时间1ms,返回总时间hrtimer_init(&data->pwm_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);//初始化定时器,CLOCK_MONOTONIC表示定时器从系统启动开始计时,HRTIMER_MODE_REL表示定时器从当前时间开始计时data->pwm_timer.function = pwm_timer_func;//定时器回调函数hrtimer_start(&data->pwm_timer, data->time, HRTIMER_MODE_REL);//启动定时器return 0;
}static int pwm_remove(struct platform_device *pdev)
{return 0;
}static const struct of_device_id pwm_of_match[] = {{.compatible = "pwm_test"},{},
};
MODULE_DEVICE_TABLE(of, pwm_of_match);static struct platform_driver pwm_driver = {.driver = {.name = "pwm_test",.of_match_table = pwm_of_match,},.probe = pwm_driver_probe,.remove = pwm_remove,
};static int __init pwm_init(void)
{return platform_driver_register(&pwm_driver);
}static void __exit pwm_exit(void)
{hrtimer_cancel(&data->pwm_timer);kfree(data);platform_driver_unregister(&pwm_driver);device_destroy(class, MKDEV(major, 0));class_destroy(class);unregister_chrdev(major, "hello");}
module_init(pwm_init);
module_exit(pwm_exit);
MODULE_LICENSE("GPL");

相关文章:

Linux下的PWM驱动

PWM PWM简介⭕ **PWM&#xff08;Pulse Width Modulation&#xff0c;脉冲宽度调制&#xff09;**是一种利用微处理器的数字输出对模拟电路进行控制的技术。通过改变脉冲的占空比&#xff0c;可以控制模拟电路的输出电压或电流。PWM技术广泛应用于电机控制、灯光调节、音频信号…...

日语输入法平假名和片假名切换

在学日语输入法的时候&#xff0c;我们在使用罗马音输入的时候&#xff0c;在进行平假名和片假名切换&#xff1a; 1、使用电脑在打字&#xff0c;日语输入法切换的时候使用 Shift Alt 如果日语输入法显示为 A 需要切换为 あ的话可以按Caps Lock键 。&#xff08;相当于中文…...

Oracle向量搜索及其应用场景

Oracle 向量搜索&#xff08;AI Vector Search&#xff09;是一个集成到 Oracle 数据库中的功能&#xff0c;旨在优化人工智能&#xff08;AI&#xff09;工作负载。它允许用户存储和查询非结构化数据的语义内容&#xff0c;如文档、图像等&#xff0c;形式为向量。 向量数据类…...

【排序算法】六、快速排序补充:三指针+随机数法

「前言」文章内容是对快速排序算法的补充&#xff0c;之前的算法流程细节多难处理&#xff0c;这里补充三指针随机数法&#xff08;递归&#xff09;&#xff0c;这个容易理解&#xff0c;在时间复杂度上也更优秀。 快排&#xff1a;三指针随机数法 原理跟之前的一致&#xff…...

PyTorch torch.cdist函数介绍及示例代码

1. torch.cdist 函数介绍 torch.cdist 是 PyTorch 中用于计算两组向量之间成对距离的函数。它可以计算两个张量(矩阵)中的每对向量之间的距离,支持多种距离度量方式,如欧氏距离(默认)或 p 范数距离。 函数原型 torch.cdist(x1, x2, p=2.0, compute_mode=use_mm_for_eu…...

CTK框架(四): 插件编写

目录 1.生成插件 1.1.环境说明 1.2.服务类&#xff0c;纯虚类&#xff0c;提供接口 1.3.实现插件类&#xff0c;实现纯虚函数 1.4.激活插件&#xff0c;加入ctk框架的生命周期中 1.5.添加资源文件 1.6..pro文件 2.使用此插件 3.总结 1.生成插件 1.1.环境说明 编译ct…...

深入理解C代码中的条件编译

引言 条件编译是 C 编程中的一个重要特性&#xff0c;它允许开发人员根据不同的条件选择性地编译源代码的不同部分。这一特性对于编写跨平台的程序、优化代码性能或控制编译时资源消耗等方面非常重要。本文将深入探讨条件编译的工作原理、使用场景、高级应用以及注意事项&…...

Ubuntu16.04操作系统-内核优化

1. 概述 本文所用优化是生产环境中经过长期验证的内核优化策略&#xff0c;针对的服务器与POD主要用于高CPU、高内存、高IO的业务场景。 备注: OS: ubuntu16.04, 内核&#xff1a; 4.15.0-147-generic 主要涵盖以下内容优化&#xff1a; ulimit优化加强tcp参数其他内存参数 …...

Qt/C++编写的Onvif调试助手调试神器工具/支持云台控制/预置位设置等/有手机版本

一、功能特点 广播搜索设备&#xff0c;支持IPC和NVR&#xff0c;依次返回。可选择不同的网卡IP进行对应网段设备的搜索。依次获取Onvif地址、Media地址、Profile文件、Rtsp地址。可对指定的Profile获取视频流Rtsp地址&#xff0c;比如主码流地址、子码流地址。可对每个设备设…...

【原创】java+swing+mysql密码管理器系统设计与实现

个人主页&#xff1a;程序员杨工 个人简介&#xff1a;从事软件开发多年&#xff0c;前后端均有涉猎&#xff0c;具有丰富的开发经验 博客内容&#xff1a;全栈开发&#xff0c;分享Java、Python、Php、小程序、前后端、数据库经验和实战 文末有本人名片&#xff0c;希望和大家…...

JavaEE-HTTPHTTPS

目录 HTTP协议 一、概念 二、http协议格式 http请求报文 http响应报文 URL格式 三、认识方法 四、认识报头 HTTP响应中的信息 HTTPS协议 对称加密 非对称加密 中间人攻击 解决中间人攻击 HTTP协议 一、概念 HTTP (全称为 "超⽂本传输协议") 是⼀种应⽤…...

iLogtail 开源两周年:社区使用调查报告

作者&#xff1a;玄飏 iLogtail 作为阿里云开源的可观测数据采集器&#xff0c;以其高效、灵活和可扩展的特性&#xff0c;在可观测采集、处理与分析领域受到了广泛的关注与应用。在 iLogtail 两周年之际&#xff0c;我们对 iLogtail 开源社区进行了一次使用调研&#xff0c;旨…...

Ubuntu 比较两个文件夹

比较两个文件夹下的大量文件是否一致&#xff0c;可以通过以下几种方式完成&#xff1a; 1. 使用 diff 命令 diff 命令不仅可以比较文件&#xff0c;还能递归比较文件夹。可以使用 -r 选项来递归比较两个目录下的文件&#xff1a; diff -r /path/to/dir1 /path/to/dir2 如…...

两数之和--力扣1

两数之和 题目思路C代码 题目 思路 根据题目要求&#xff0c;元素不能重复且不需要排序&#xff0c;我们这里使用哈希表unordered_map。注意题目说了只对应一种答案。 所以我们在循环中&#xff0c;使用目标值减去当前循环的nums[i]&#xff0c;得到差值&#xff0c;如果我们…...

vue原理分析(三)new()创建Vue实例

今天我们来分析Vue实例的创建 代码如下&#xff1a; Vue.config.productionTip falsenew Vue({render: h > h(App),}).$mount(#app) Vue.config.productionTip false 这个配置成false&#xff0c;是阻止启动生产消息 new Vue({render: h > h(App),}).$mount(#app)…...

Spring MVC: 构建Web应用的强大框架

Spring MVC: 构建现代Web应用的强大框架 1. MVC设计模式简介 MVC (Model-View-Controller) 是一种广泛使用的软件设计模式,它将应用程序的逻辑分为三个相互关联的组件: Model (模型): 负责管理数据、业务逻辑和规则。View (视图): 负责用户界面的展示,将数据呈现给用户。Con…...

网络学习-eNSP配置NAT

NAT实现内网和外网互通 #给路由器接口设置IP地址模拟实验环境 <Huawei>system-view Enter system view, return user view with CtrlZ. [Huawei]undo info-center enable Info: Information center is disabled. [Huawei]interface gigabitethernet 0/0/0 [Huawei-Gigabi…...

动态规划-最长回文子串

题目描述 给你一个字符串 s&#xff0c;找到 s 中最长的 回文子串。 对于该题使用中心扩展法在某些情况下可以比动态规划方法更优&#xff0c;尤其是在处理较长字符串时。这是因为中心扩展法具有更好的空间复杂度&#xff0c;并且在实际应用中可能具有更快的运行速度&#xf…...

海康威视 嵌入式 面经 海康威视嵌入式软件 嵌入式硬件总结面试经验 面试题目汇总

标题海康威视 嵌入式 面经 海康威视嵌入式软件 嵌入式硬件总结面试经验 面试题目汇总 整理总结了海康威视嵌入式的面试题目&#xff01;&#xff0c;可以供大家面试参考 标题海康威视 嵌入式 面经 五月底投递&#xff0c;六月初面试&#xff0c;一场技术面&#xff0c;一场H…...

使用图论技巧——有遍数限制的最短路

给定一个 n个点 m 条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c; 边权可能为负数。 请你求出从 11 号点到 n 号点的最多经过 k 条边的最短距离&#xff0c;如果无法从 1 号点走到 n 号点&#xff0c;输出 impossible。 注意&#xff1a;图中可能 存在负权回路…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

服务器硬防的应用场景都有哪些?

服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式&#xff0c;避免服务器受到各种恶意攻击和网络威胁&#xff0c;那么&#xff0c;服务器硬防通常都会应用在哪些场景当中呢&#xff1f; 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

QT开发技术【ffmpeg + QAudioOutput】音乐播放器

一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下&#xff0c;音视频内容犹如璀璨繁星&#xff0c;点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频&#xff0c;到在线课堂中知识渊博的专家授课&#xff0c;再到影视平台上扣人心弦的高清大片&#xff0c;音…...