技术标签: linux学习 学习 stm32 笔记 linux 驱动开发
PWM是很常用到功能,可以通过PWM来控制电机速度,也可以使用PWM来控制LCD的背光亮度。本章就来学习一下如何在Linux下进行PWM驱动开发。
不在介绍PWM是什么了,直接进入使用。
给LCD的背光引脚输入一个PWM信号,这样就可以通过调整占空比的方式来调整LCD背光亮度了。提高占空比就会提高背光亮度,降低占空比就会降低背光亮度,重点就在于PWM信号的产生和占空比的控制。
STM32MP157有很多路PWM,这些PWM都是由定时器产生的:
可以看出,STM32MP157的PWM通道非常多,不同的PWM通道功能也不同,可以
根据实际情况选择合适的PWM通道。本节使用PA10这个引脚来实现PWM功能,注意!PA10这个引脚被用作USB的ID引脚,如果所使用的开发板使用了PA10作为USB OTG的ID引脚,那么在做本实验的时候开发板的USB OTG接口不能连接到电脑上!正点原子STM32MP157开发板的USB接口采用TypeC接口,因此没有用到PA10作为ID引脚。
打开STM32MP157的数据手册,可以看到PA10可以作为TIM1的通道3。
这里其实可以去看裸机开发的笔记,对TIM1这个高级定时器的介绍,我这边只关注Linux驱动部分。
接下来看一下TIM1的设备树,STM32定时器设备树绑定信息文档为:Documentation/devicetree/bindings/mfd/stm32-timers.txt,简单总结一下定时器节点信息。
1、必须的参数
2、可选的参数
3、可选的子节点
STM32定时器有多种功能,比如计时、PWM、计数器等,不同的功能需要用不同的子节点来表示,可选子节点有三种,分别对应不同的功能:
了解完定时器的绑定文档以后,来看一下STM32MP157实际的定时器节点,打开stm32mp151.dtsi,找到名为“timers1”的设备节点,这个就是TIM1定时器节点,内容如下:
第19-23行,TIM1的pwm功能子节点,这个是本小节重点关注的。
通过上面对定时器绑定文档的讲解,知道PWM作为定时器的子节点,这里就来看一下PWM子节点绑定文档:Documentation/devicetree/bindings/pwm/pwm-stm32.txt,简单总结一下PWM子节点属性信息:
STM32MP157的PWM节点的compatible属性为“st,stm32-pwm”,可以在Linux内核源码中搜索这个字符串找到PWM驱动文件,这个文件为:drivers/pwm/pwm-stm32.c。
Linux内核提供了PWM子系统框架,编写PWM驱动的时候一定要符合这个框架。PWM子系统的核心是pwm_chip结构体,定义在文件include/linux/pwm.h中,定义如下:
第292行,pwm_ops结构体就是PWM外设的各种操作函数集合,编写PWM外设驱动的时候需要开发人员实现。pwm_ops结构体也定义在pwm.h头文件中,定义如下:
pwm_ops中的这些函数不一定全部实现,但是配置PWM的函数必须实现,比如apply或者config。第264行的apply函数是最新的PWM配置函数,通过此函数来配置PWM的周期以及占空比,老的内核里面会使用第271行的config函数来配置PWM。其中第271-276行的config、set_polarity、enable和disable都是老版本内核所使用的函数。
PWM子系统驱动的核心初始化pwm_chip结构体,然后向内核注册初始化完成以后的pwm_chip。这里就要用到pwmchip_add函数,此函数定义在drivers/pwm/core.c文件中,函数原型如下:
int pwmchip_add(struct pwm_chip *chip)
函数参数和返回值含义如下:
卸载PWM驱动的时候需要将前面注册的pwm_chip从内核移除掉,这里要用到
pwmchip_remove函数,函数原型如下:
int pwmchip_remove(struct pwm_chip *chip)
函数参数和返回值含义如下:
简单分析一下Linux内核自带的STM32MP157 PWM驱动,驱动文件是pwm-stm32.c这个文件。打开这个文件,可以看到,这是一个标准的平台设备驱动文件,有如下所示:
第2行,当设备树PWM节点的compatible属性值为“st,stm32-pwm”的话就会匹配此驱动。
第14行,当设备树节点和驱动匹配以后stm32_pwm_probe函数就会执行。
在看stm32_pwm_probe函数之前先来看下stm32_pwm结构体,这个结构体是ST官方创建的STM32 PWM结构体,这个结构体会贯穿整个PWM驱动。stm32_pwm结构体定义在pwm-stm32.c文件中,结构体内容如下:
重点看一下第2行,这是一个pwm_chip结构体成员变量chip,前面说了PWM子系统的核心就是pwm_chip。
stm32_pwm_probe函数如下(有缩减):
示例代码39.1.3.3 stm32_pwm_probe函数
608 static int stm32_pwm_probe(struct platform_device *pdev)
609 {
610 struct device *dev = &pdev->dev;
611 struct device_node *np = dev->of_node;
612 struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent);
613 struct stm32_pwm *priv;
614 int ret;
615
616 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
617 if (!priv)
618 return -ENOMEM;
619
620 mutex_init(&priv->lock);
621 priv->regmap = ddata->regmap;
622 priv->clk = ddata->clk;
623 priv->max_arr = ddata->max_arr;
624 priv->chip.of_xlate = of_pwm_xlate_with_flags;
625 priv->chip.of_pwm_n_cells = 3;
626
627 if (!priv->regmap || !priv->clk)
628 return -EINVAL;
629
630 ret = stm32_pwm_probe_breakinputs(priv, np);
631 if (ret)
632 return ret;
633
634 stm32_pwm_detect_complementary(priv);
635
636 priv->chip.base = -1;
637 priv->chip.dev = dev;
638 priv->chip.ops = &stm32pwm_ops;
639 priv->chip.npwm = stm32_pwm_detect_channels(priv);
640
641 ret = pwmchip_add(&priv->chip);
642 if (ret < 0)
643 return ret;
644
645 platform_set_drvdata(pdev, priv);
646
647 return 0;
648
第616行,priv是一个stm32_pwm类型的结构体指针变量,这里为其申请内存。stm32_pwm结构体有个重要的成员变量chip,chip是pwm_chip类型的。所以这一行就引出了PWM子系统核心部件pwm_chip,稍后的重点就是初始化chip。
第621-625行,初始化priv的各个成员变量,第624和625还初始化了pwm_chip的of_xlate和of_pwm_n_cells这两个成员变量。
第630行,调用stm32_pwm_probe_breakinputs函数来读取“st,breakinput”属性,设置break输入,本章例程用不到。
第634行,调用stm32_pwm_detect_complementary函数来检测是否使能TIM1的互补输出功能。
第636-639行,重点,初始化pwm_chip的各个成员变量,第638行设置pwm_chip的ops函数为stm32pwm_ops,stm32pwm_ops里面包含了PWM的具体操作,稍后重点分析。第639行设置pwm_chip的npwm,也就是设置当前打开多少路PWM。这里直接使用stm32_pwm_detect_channels函数来读取TIM1的CCER寄存器,CCER寄存器的CC1E(bit0)、CC2E(bit4)、CC3E(bit8)和CC4E(bit12)这4个位用于开启TIM1的4通道PWM,如果为1就表示对应的PWM通道打开。所以stm32_pwm_detect_channels函数就会直接读取这4个位来判断对应的PWM通道是否打开。
重点来看一下stm32pwm_ops,定义如下:
第487行stm32_pwm_apply_locked就是最终的PWM设置函数,在应用中设置的PWM频率和占空比最终就是由stm32_pwm_apply_locked函数来完成的,此函数会最终操作STM32相关的寄存器。
stm32_pwm_apply_locked函数源码如下:
第478行,加互斥锁,防止竞争的产生。一次只有一个应用可以设置PWM。
第479行,调用stm32_pwm_apply函数来设置 PWM。
stm32_pwm_apply函数内容如下:
第453行,在设置PWM之前,先调用stm32_pwm_disable函数关闭PWM。
第458行,调用stm32_pwm_set_polarity函数设置指定PWM通道的极性。
第460行,调用stm32_pwm_config来设置PWM的频率以及占空比。
第465行,PWM设置完成以后调用stm32_pwm_enable函数使能PWM。
stm32_pwm_config函数内容如下:
示例代码39.1.3.7 stm32_pwm_config函数
322 static int stm32_pwm_config(struct stm32_pwm *priv, int ch,
323 int duty_ns, int period_ns)
324 {
325 unsigned long long prd, div, dty;
326 unsigned int prescaler = 0;
327 u32 ccmr, mask, shift;
328
329 /* Period and prescaler values depends on clock rate */
330 div = (unsigned long long)clk_get_rate(priv->clk) * period_ns;
331
332 do_div(div, NSEC_PER_SEC);
333 prd = div;
334
335 while (div > priv->max_arr) {
336 prescaler++;
337 div = prd;
338 do_div(div, prescaler + 1);
339 }
340
341 prd = div;
342
343 if (prescaler > MAX_TIM_PSC)
344 return -EINVAL;
345
346 /*
347 * All channels share the same prescaler and counter so when two
348 * channels are active at the same time we can't change them
349 */
350 if (active_channels(priv) & ~(1 << ch * 4)) {
351 u32 psc, arr;
352
353 regmap_read(priv->regmap, TIM_PSC, &psc);
354 regmap_read(priv->regmap, TIM_ARR, &arr);
355
356 if ((psc != prescaler) || (arr != prd - 1))
357 return -EBUSY;
358 }
359
360 regmap_write(priv->regmap, TIM_PSC, prescaler);
361 regmap_write(priv->regmap, TIM_ARR, prd - 1);
362 regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE, TIM_CR1_ARPE);
363
364 /* Calculate the duty cycles */
365 dty = prd * duty_ns;
366 do_div(dty, period_ns);
367
368 write_ccrx(priv, ch, dty);
369
370 /* Configure output mode */
371 shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT;
372 ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift;
373 mask = CCMR_CHANNEL_MASK << shift;
374
375 if (ch < 2)
376 regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr);
377 else
378 regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr);
379
380 regmap_update_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE, TIM_BDTR_MOE);
381
382 return 0;
383 }
PWM的设置主要就是两方面:频率和占空比。第330~362行都是设置PWM频率的,函数参数period_ns为周期值,也就是PWM的频率。TIM的PSC寄存器用来设置定时器分频值,当TIM时钟源确定以后,设置PSC分频值即可得到TIM最终的时钟频率。TIM的ARR寄存器是自动加载寄存器,将TIM设置为向下计数器,定时器开启以后每个时钟周期,计数器减一,直到计数器减为0。这个时候再将ARR里面的值加载到计数器里面,计数器就会重新开始倒计时,如此一直重复。因此PSC和ARR这两个寄存器就决定了PWM的周期值。 要注意!由于一个定时器有4通道的 PWM,而这4路PWM只能设置成同一个周期,如果想要多路周期不同的PWM信号,那就要使用多个不同的TIM!
第365-380行,设置PWM的占空比,参数duty_ns表示占空比。一个定时器下的4路PWM可以设置不同的占空比,相当于一个定时器下的4路PWM信号,周期是一样的,但是占空比可以不同。占空比的设计原理比较简单,前面我们已经知道当定时器时钟频率确定以后(PSC分频值不变),ARR寄存器里面的值就决定了PWM周期,这个数值就叫比较值,改变比较值就可以改变PWM的占空比。STM32MP157一个定时器有4路PWM通道,每个通道都有个用来存放比较值的寄存器,因此一共有4个寄存器CCR1-CCR4,这4个寄存器就叫做比较寄存器。所以第365、366行就是根据参数duty_ns算出对应的CCRx(x=1~4)寄存器对应的值,然后在368行通过write_ccrx函数将相应的值写入到对应的
CCRx寄存器里面。
第371-378行是设置PWM输出模式,通道1和通道2使用CCMR1寄存器,通道3和通道4使用CCMR2寄存器。最后的380行设置BDTR寄存器,这个寄存器是break和死区控制相关的,本章用不到。
至此,STM32MP157的PWM驱动就分析完了。
PWM驱动就不需要再编写了,ST已经写好了,前面也已经详细的分析过这个驱动源码了。在实际使用的时候只需要修改设备树即可(这个可以类比裸机开发用HAL库),STM32MP157开发板上的JP1排针引出了 PA10这个引脚,如下图所示:
PA10可以作为TIM1的通道3的PWM输出引脚,所以需要在设备树里面添加PA10的引脚信息以及TIM1通道3的PWM信息。
打开stm32mp15-pinctrl.dtsi文件,在iomuxc节点下添加GPIO1_IO04的引脚信息,如下所示:
示例代码39.2.1.1 TIM1 PWM引脚信息
1 pwm1_pins_a: pwm1-0 {
2 pins {
3 pinmux = <STM32_PINMUX('E', 9, AF1)>, /* TIM1_CH1 */
4 <STM32_PINMUX('E', 11, AF1)>, /* TIM1_CH2 */
5 <STM32_PINMUX('E', 14, AF1)>; /* TIM1_CH4 */
6 bias-pull-down;
7 drive-push-pull;
8 slew-rate = <0>;
9 };
10 };
11
12 pwm1_sleep_pins_a: pwm1-sleep-0 {
13 pins {
14 pinmux = <STM32_PINMUX('E', 9, ANALOG)>, /* TIM1_CH1 */
15 <STM32_PINMUX('E', 11, ANALOG)>, /* TIM1_CH2 */
16 <STM32_PINMUX('E', 14, ANALOG)>; /* TIM1_CH4 */
17 };
18 };
可以看出ST官方已经设置好了TIM1的CH1、CH2和CH4这三个通道的引脚配置,但是这里只需要CH3,因此将示例代码29.2.1.1改成如下所示:
示例代码39.2.1.2 PA10引脚配置
1 pwm1_pins_a: pwm1-0 {
2 pins {
3 pinmux = <STM32_PINMUX('A', 10, AF1)>; /* TIM1_CH3 */
4 bias-pull-down;
5 drive-push-pull;
6 slew-rate = <0>;
7 };
8 };
9
10 pwm1_sleep_pins_a: pwm1-sleep-0 {
11 pins {
12 pinmux = <STM32_PINMUX('A', 10, ANALOG)>; /* TIM1_CH3 */
13 };
14 };
示例代码39.2.1.2中仅仅将PA10复用为TIM1的CH3,一定要根据自己所使用的板子硬件来配置引脚。
stm32mp151.dtsi文件中已经有了“timers1”节点,但是这个节点默认是disable的,而且还不能直接使用。需要在stm32mp157d-atk.dts文件中向timers1节点追加一些内容,在stm32mp157d-atk.dts文件中加入如下所示内容:
示例代码39.2.1.3 向timers1添加的内容
1 &timers1 {
2 status = "okay";
3 /* spare all DMA channels since they are not needed for PWM output */
4 /delete-property/dmas;
5 /delete-property/dma-names;
6 pwm1: pwm {
7 pinctrl-0 = <&pwm1_pins_a>;
8 pinctrl-1 = <&pwm1_sleep_pins_a>;
9 pinctrl-names = "default", "sleep";
10 #pwm-cells = <2>;
11 status = "okay";
12 };
13 };
第 4、5行,关闭DMA功能,因为PWM输出不需要DMA。
第7行,pinctrl-0属性指定TIM1的CH3所使用的输出引脚对应的pinctrl节点,这里设置
为示例代码39.2.1.2中的pwm1_pins_a。
检查一下设备树中有没有其他外设用到PA10,如果有的话需要屏蔽掉!注意,不能只屏蔽掉PA10的 pinctrl配置信息,也要搜索一下“gpioa 10”,看看有没有哪里用到,用到的话也要屏蔽掉。
设备树修改完成后重新编译设备树,然后使用新的设备树启动系统。
ST官方的Linux内核已经默认使能了PWM驱动,所以不需要修改,但是为了学习,还是需要知道怎么使能。打开Linux内核配置界面,按照如下路径找到配置项:
-> Device Drivers -> Pulse-Width Modulation (PWM) Support -> <*> STMicroelectronics STM32 PWM //选中 |
配置如下图所示:
使用新的设备树启动系统,然后将开发板上的PA10引脚连接到示波器上,通过示波器来查看PWM波形图。可以直接在用户层来配置PWM,进入目录/sys/class/pwm中,如下图所示:
注意!上图中有个pwmchip0,但是并不知道这个pwmchip0是否为TIM1对应的文件。可以通过查看pwmchip0对应的地址是否和TIM1定时器寄存器起始地址是否一致来确定其是否属于TIM1。进入到pwmchip0目录下,会打印出其路径:
从上图可以看出pwmchip0对应的定时器寄存器起始地址为0X44000000,根据示例代码39.1.1.1中的timers1节点,可以知道TIM1这个定时器的寄存器起始地址就是0X44000000。因此,pwmchip0就是TIM1对应的文件。
为什么要用这么复杂的方式来确定定时器对应的pwmchip文件呢?因为当STM32MP157开启多个定时器的PWM功能以后,其pwmchip文件就会变!
pwmchip0是整个TIM1的总目录,而TIM1有4路PWM,每路都可以独立打开或关闭。CH1-CH4对应的编号为0~3,因此打开TIM1的CH3输入如下命令:
echo 2 > /sys/class/pwm/pwmchip0/export |
上述命令中2就是TIM1_CH3,如果要打开TIM1的CH1,那就是0。执行完成会在pwmchip0目录下生成一个名为“pwm2”的子目录,如下图所示:
注意,这里设置的是周期值,单位为ns,比如20KHz频率的周期就是50000ns,输入如下命令:
echo 50000 > /sys/class/pwm/pwmchip0/pwm2/period |
这里不能直接设置占空比,而是设置的一个周期的ON时间,也就是高电平时间,比如20KHz频率下20%占空比的ON时间就是10000,输入如下命令:
echo 10000 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle |
一定要先设置频率和波特率,最后在开启定时器,否则会提示参数错误!输入如下命令使能TIM1的通道3这路PWM:
echo 1 > /sys/class/pwm/pwmchip0/pwm2/enable |
设置完成使用示波器查看波形是否正确,正确的话如下图所示:
从上图可以看出,此时PWM频率为20KHz,占空比为20%,与设置的一致。如果要修改频率或者占空比的话一定要注意这两者时间值,比如20KHz频率的周期值为50000ns,那么在调整占空比的时候ON时间就不能设置大于50000,否则就会提示参数无效。
前面也可以修改PWM的极性,上面设置的PWM占空比为20%,只需要修改极性就可以将占空比变为80%。向/pwmchip0/pwm2/polarity文件写入“inversed”即可反转极性,命令如下:
echo "inversed" > /sys/class/pwm/pwmchip0/pwm2/polarity |
极性反转以后占空比就变为了80%,如果要恢复回原来的极性,向/pwmchip0/pwm2/polarity文件写入“normal”即可,命令如下:
echo "normal" > /sys/class/pwm/pwmchip0/pwm2/polarity |
Linux内核直接修改PWM,只需要在pinctrl和设备树中添加相应的内容即可。
在stm32mp15-pinctrl.dtsi中,在iomuxc节点下,找到本次实验中使用的TIM1的复用,就是pwm1_pins_a和pwm1_sleep_pins_a节点,添加电气属性内容。
在stm32mp157d-atk.dts中,向&timer1追加内容,把status置为“okay”使能,然后把pinctrl加入即可。
最后就可以通过在/sys/class/pwm里面对应的pwmchip文件中,在./export通过“echo”命令打开对应的PWM输出,在./period设置频率和在./duty_cycle设置占空比;最后在./enable中“echo 1”打开PWM输出。
文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态
文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境
文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn
文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker
文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机
文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk
文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入
文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。 Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。
文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动
文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计
文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;gt;Jni-&amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图
文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法