STM32(六):定时器PWM呼吸灯 (标准库函数)

前言

上一篇文章已经介绍了如何用STM32单片机中的TIMER定时器来控制LED灯的交替闪烁,实现了点灯的第五种方式。这篇文章我们来介绍一下如何用STM32单片机中的定时器的PWM波来实现LED的“呼吸”。

一、实验原理

关于定时器这边就不多加赘述,详细请看上一篇文章,链接放在下面了

STM32(五):TIMER定时器 (标准库函数)

这边我们介绍一下PWM

1.PWM的介绍

PWM的全称是脉冲宽度调制(Pulse-width modulation),是指在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效的地获得所需要的模拟参量,常应用于电机控速等领域。在STM32微控制器的应用中,PWM技术也非常重要,因为它能够提供精确的信号控制,从而达到对设备行为的精确管理。

  • 频率:一秒钟内从高电平时间在到低电平时间,再从低电平跳到高电平的瞬间次数,也就是一秒钟内有多少个PWM的周期,f = 1/Ts。
  • 周期:一秒钟内从高电平时间在到低电平时间,Ts= 1/f。
  • PWM信号产生:用于控制电机速度、灯光亮度等。
  • 占空比:一个周期内高电平时间和总时间的比值,Ton/Ts。
  • 分辨率:占空比变化步距。

2.PWM参数计算公式

PWM主要参数计算公式如下所示:

  • PWM频率 Freq=CK_PSC/[(PSC+1) (ARR+1)], CK_PSC=72M
  • PWM占空比=CCR/(ARR+1)
  • PWM分辨率=1/(ARR+1)

3.PWM工作原理

以下是PWM输入模式时序工作原理图,以下对其符号进行简单的解释说明

  1. T1:PWM信号的波形,显示了信号的高电平和低电平状态。
  2. TIMx_CNT :定时器的计数器(Counter),它的值会在定时器时钟的脉冲下从0开始增加,直到达到自动重载寄存器(ARR)的值后归零,循环开始新的周期。
  3. TIMx_CCR1TIMx_CCR2 :捕捉/比较寄存器(Capture/Compare Register),用于设置PWM的高电平宽度。它们的值决定了在计数器达到这些值时输出比较匹配事件,影响PWM输出信号的占空比。
  • TIMx_CCR1 设置为 0004,代表在计数器计数到4时,第一个通道的PWM输出从高电平跳变到低电平。
  • TIMx_CCR2 设置为 0002,代表在计数器计数到2时,第二个通道的PWM输出从高电平跳变到低电平。

 接下来简单介绍一下PWM信号的一个典型的边缘对齐模式,

  • ARR = 8:设置了定时器的计数上限,决定了PWM周期的长度,计数器会从0计数到8,然后重置为0,开始新的周期。
  • CCRx = 4:决定了PWM信号从高电平变为低电平的切换点,当计数器计数到4时,输出信号从高电平切换到低电平。
  • 红线(有效电平):PWM输出从高电平切换到低电平的时刻,即在计数器值等于CCR值时,输出发生变化。
  • 蓝线(无效电平):PWM输出在一个完整的周期结束时仍然是低电平。
  • CCxIF:捕捉/比较中断标志,当定时器的计数器值与CCR寄存器的预设值相匹配时,该标志被设置。

  • OCxREF:输出比较引用信号,通常与PWM的实际输出相对应,反映了PWM信号的状态更改。

二、实验步骤

1.引脚设置

这边在h文件的宏定义中定义即可

static void GENERAL_TIMx_GPIO_Config(void) 
{GPIO_InitTypeDef GPIO_InitStructure;/* 使能定时器始终:设置TIM3CLK 为 72MHZ */GENERAL_TIM_APBxClock_FUN (GENERAL_TIM_CLK, ENABLE);/* 使能定时器通道引脚GPIO时钟 */GENERAL_TIM_GPIO_APBxClock_FUN(GENERAL_TIM_GPIO_CLK1|GENERAL_TIM_GPIO_CLK1, ENABLE); /* 配置定时器通道3输出引脚模式:复用推挽输出模式 */GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_PIN1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GENERAL_TIM_PORT1, &GPIO_InitStructure);
}

2.时钟中断

时钟中断函数void NVIC_Config_PWM(void),这边是对中断来源以及优先级的配置,前面在Systick中有所介绍,可以看一下之前的连接:STM32(四):Systick (标准库函数)-CSDN博客

static void NVIC_Config_PWM(void)
{NVIC_InitTypeDef NVIC_InitStructure;/* 选择中断优先级配置组为2个抢占式优先级和2个子优先级,可以参考misc.h文件了解相关设置 */NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);/* 配置TIM3_IRQ中断为中断源 */NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIMx_IRQn;/* 设置抢占式优先级为0 */NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;/* 设置子优先级为3 */NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;/* 使能外部中断通道 */NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}

但到这里我们的中断配置还没结束!!重点!!易踩坑!!

我们还需要在stm32f10x_it.c的文件里面加上新的中断服务函数 GENERAL_TIMx_IRQHandler (),记得加上头文件,例如:

#include "bsp/GeneralTIM/bsp_GeneralTIM.h"
extern uint8_t indexWave[];void GENERAL_TIMx_IRQHandler(void)
{	static uint8_t pwm_index = 0;			/* 用于PWM查表 */static uint8_t period_cnt = 0;		/* 用于计算周期数 *//* 定时器更新中断 */if (TIM_GetITStatus(GENERAL_TIMx, TIM_IT_Update) != RESET)	{			period_cnt++;/* 若输出的周期数大于20,输出下一种脉冲宽的PWM波 */if(period_cnt >= 20)										{/* 根据PWM表修改定时器的比较寄存器值 */GENERAL_TIMx->GENERAL_TIM_CCRx = indexWave[pwm_index];	/* 标志PWM表的下一个元素 */pwm_index++;												/* 若PWM脉冲表已经输出完成一遍,重置PWM查表标志 */if( pwm_index >=  40)								{pwm_index=0;								}/* 重置周期计数标志 */period_cnt=0;												}/* 必须要清除中断标志位 */TIM_ClearITPendingBit (GENERAL_TIMx, TIM_IT_Update);	}
}

3.定时器配置

定时周期和预分频需要根据实际需要进行调整,而这边TIM_Pulse=0是因为我们给了一个脉冲配置的数组indexWave[],若不用,调整这个大小会改变“呼吸”的速率。

static void GENERAL_TIMx_Configuration(void)
{TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;TIM_OCInitTypeDef  TIM_OCInitStructure;/* 当定时器从0计数到255,即为256次,为一个定时周期 */TIM_TimeBaseStructure.TIM_Period = 255; /* 设置预分频:不预分频,即为72MHz,输出脉冲频率:72MHz/(1999+1)/(255+1) */TIM_TimeBaseStructure.TIM_Prescaler = 1999;/* 设置时钟分频系数:不分频(这里用不到) */TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;/* 向上计数模式 */TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(GENERAL_TIMx, &TIM_TimeBaseStructure);/* 模式配置:PWM模式1 */TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;/* 输出状态设置:使能输出 */TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	/* 设置跳变值,当计数器计数到这个值时,电平发生跳变 */TIM_OCInitStructure.TIM_Pulse = 0;/* 当定时器计数值小于CCR1_Val时为低电平 */TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;/* 初始化定时器通道1输出PWM */GENERAL_TIM_OCxInit(GENERAL_TIMx, &TIM_OCInitStructure);/* 定时器比较输出通道1预装载配置:使能预装载 */GENERAL_TIM_OCxPreloadConfig(GENERAL_TIMx, TIM_OCPreload_Enable);  /* 使能定时器重载寄存器ARR */TIM_ARRPreloadConfig(GENERAL_TIMx, ENABLE);/* 使能定时器 */TIM_Cmd(GENERAL_TIMx, ENABLE);  /* 配置NVIC */NVIC_Config_PWM();/* 定时器更新中断 */TIM_ITConfig(GENERAL_TIMx, TIM_IT_Update, ENABLE);}

4.PWM信号初始化


void GENERAL_TIMx_PWM_Init(void)
{GENERAL_TIMx_GPIO_Config();GENERAL_TIMx_Configuration();	
}

三、实操代码

程序分为3个文件:bsp_GeneralTIM.c、bsp_GeneralTIM.h、main.c

1.bsp_GeneralTIM.c

#include "bsp/GeneralTIM/bsp_GeneralTIM.h" 
uint8_t indexWave[] = {1,1,2,2,3,4,6,8,10,14,19,25,33,44,59,80,107,143,191,255,255,191,143,107,80,59,44,33,25,19,14,10,8,6,4,3,2,2,1,1};/*** 函数功能: 配置TIMx复用输出PWM时用到的I/O*/
static void GENERAL_TIMx_GPIO_Config(void) 
{GPIO_InitTypeDef GPIO_InitStructure;/* 使能定时器始终:设置TIM3CLK 为 72MHZ */GENERAL_TIM_APBxClock_FUN (GENERAL_TIM_CLK, ENABLE);/* 使能定时器通道引脚GPIO时钟 */GENERAL_TIM_GPIO_APBxClock_FUN(GENERAL_TIM_GPIO_CLK1|GENERAL_TIM_GPIO_CLK1, ENABLE); /* 配置定时器通道3输出引脚模式:复用推挽输出模式 */GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_PIN1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GENERAL_TIM_PORT1, &GPIO_InitStructure);}/*** 函数功能: 配置嵌套向量中断控制器NVIC*/
static void NVIC_Config_PWM(void)
{NVIC_InitTypeDef NVIC_InitStructure;/* 选择中断优先级配置组为2个抢占式优先级和2个子优先级,可以参考misc.h文件了解相关设置 */NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);/* 配置TIM3_IRQ中断为中断源 */NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIMx_IRQn;/* 设置抢占式优先级为0 */NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;/* 设置子优先级为3 */NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;/* 使能外部中断通道 */NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}/*** 函数功能: 配置TIMx输出的PWM信号的模式,如周期、极性、占空比*/static void GENERAL_TIMx_Configuration(void)
{TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;TIM_OCInitTypeDef  TIM_OCInitStructure;/* 定时器基本参数始终 */		 /* 当定时器从0计数到255,即为256次,为一个定时周期 */TIM_TimeBaseStructure.TIM_Period = 255; /* 设置预分频:不预分频,即为72MHz,输出脉冲频率:72MHz/(1999+1)/(255+1) */TIM_TimeBaseStructure.TIM_Prescaler = 1999;/* 设置时钟分频系数:不分频(这里用不到) */TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;/* 向上计数模式 */TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(GENERAL_TIMx, &TIM_TimeBaseStructure);/* 定时器输出通道1模式配置 *//* 模式配置:PWM模式1 */TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;/* 输出状态设置:使能输出 */TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	/* 设置跳变值,当计数器计数到这个值时,电平发生跳变 */TIM_OCInitStructure.TIM_Pulse = 0;/* 当定时器计数值小于CCR1_Val时为低电平 */TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;/* 初始化定时器通道1输出PWM */GENERAL_TIM_OCxInit(GENERAL_TIMx, &TIM_OCInitStructure);/* 定时器比较输出通道1预装载配置:使能预装载 */GENERAL_TIM_OCxPreloadConfig(GENERAL_TIMx, TIM_OCPreload_Enable);  /* 使能定时器重载寄存器ARR */TIM_ARRPreloadConfig(GENERAL_TIMx, ENABLE);/* 使能定时器 */TIM_Cmd(GENERAL_TIMx, ENABLE);  /* 配置NVIC */NVIC_Config_PWM();/* 定时器更新中断 */TIM_ITConfig(GENERAL_TIMx, TIM_IT_Update, ENABLE);}/*** 函数功能: TIMx输出PWM信号初始化* 说    明:只要调用这个函数TIMx的通道就会有PWM信号输出*/
void GENERAL_TIMx_PWM_Init(void)
{GENERAL_TIMx_GPIO_Config();GENERAL_TIMx_Configuration();	
}

2.bsp_GeneralTIM.h

#ifndef __GENERAL_TIM_H__
#define __GENERAL_TIM_H__/* 包含头文件 ----------------------------------------------------------------*/
#include <stm32f10x.h>/* 类型定义 ------------------------------------------------------------------*/
/* 宏定义 --------------------------------------------------------------------*/#define GENERAL_TIMx                        TIM3
#define GENERAL_TIM_APBxClock_FUN           RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK                     RCC_APB1Periph_TIM3
#define GENERAL_TIM_GPIO_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define GENERAL_TIM_GPIO_CLK1               RCC_APB2Periph_GPIOA
#define GENERAL_TIM_PORT1                   GPIOA
#define GENERAL_TIM_PIN1                    GPIO_Pin_11
#define GENERAL_TIM_OCxInit                 TIM_OC3Init
#define GENERAL_TIM_OCxPreloadConfig        TIM_OC3PreloadConfig
#define GENERAL_TIM_CCRx                    CCR3
#define GENERAL_TIMx_IRQn                   TIM3_IRQn              //中断
#define GENERAL_TIMx_IRQHandler             TIM3_IRQHandler/* 扩展变量 ------------------------------------------------------------------*/
/* 函数声明 ------------------------------------------------------------------*/void GENERAL_TIMx_PWM_Init(void);#endif	/* __GENERAL_TIM_H__ */

3.main.c


/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f10x.h"
#include "bsp/led/bsp_led.h"
#include "bsp/key/bsp_key.h"
#include "bsp/delay/delay.h"
#include "bsp/systick/bsp_SysTick.h"
#include "bsp/GeneralTIM/bsp_GeneralTIM.h" 
/* 函数体 --------------------------------------------------------------------*//*** 函数功能: 主函数.*/
int main(void)
{/* 初始化定时器PWM输出 */GENERAL_TIMx_PWM_Init();  while (1){//空}
}

四、实验效果

呼吸灯

结束语

本文以STM32VET6为例讲解了用STM32单片机中用定时器的PWM波来实现LED的“呼吸”,并指出其中的易坑点。希望对大家有所帮助!如果还有什么问题,欢迎评论区留言,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3032115.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

selenium进行xhs图片爬虫:03获取一篇图文的图片

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️感谢大家点赞&#x1f44d;&…

如何选择合适加密软件来保护信息资产|精选加密软件分析

五款加密软件对比分析&#xff0c;是一项复杂而必要的任务&#xff0c;旨在帮助用户选择最适合其需求的加密工具。在数字化时代&#xff0c;信息安全显得尤为重要&#xff0c;因此&#xff0c;对加密软件的评估与比较显得尤为关键。 首先&#xff0c;我们要考虑的是这些加密软件…

小程序分包

上传时主包不能过大&#xff0c;采用分包的方式&#xff0c;这里是taro框架 要访问的话

飞跨电容型的三电平(FC-NPC)逆变器simulink仿真模型

本人搭建了飞跨电容型的三电平逆变器simulink仿真模型&#xff0c;相较于二极管钳位型三电平逆变器而言&#xff0c;钳位二极管变为飞跨的电容。采用SPWM调制和均流均压控制&#xff0c;通过搭建仿真模型得到三电平波形。 三电平拓扑中的飞跨电容是指在电路的输出端使用电容来实…

代码随想录第五十天|最佳买卖股票时机含冷冻期、买卖股票的最佳时机含手续费

题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 最佳买卖股票时机含冷冻期与打家劫舍的题目有异曲同工之妙&#xff0c;主要是出现了天数的间隔&#xff0c;一次需要在买卖股票的最佳时机II 题目上做一点调整&#xff0c;代码如下&#xff1a; 如代码所示&…

逻辑卷管理-LVM

目录 1. LVM的基本概念 2. Linux下创建和管理LVM 3. 环境准备 4. 物理卷管理 4.1. 创建物理卷 4.2. 显示物理卷 4.3. 删除物理卷 4. 卷组管理 4.1. 创建卷组 4.2. 显示卷组 4.3. 扩展卷组 4.4. 缩减卷组 4.5. 删除卷组 4.6. 分割卷组 4.7 组合卷组 5. 逻辑卷管…

OpenGL导入的纹理图片错位

在OpenGL中导入图片的纹理照片的函数为 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, p_w, p_h, 0, GL_BGR, GL_UNSIGNED_BYTE, pic_data);其中p_w, p_h为图片的宽和高&#xff0c;pic_data为指向图片存储空间的的地址(unsigned char *类型) 在OpenGL中图片默认是4字节对齐的&…

数据结构与算法===递归

文章目录 定义适用场景爬楼梯代码实现 小结 定义 递归(Recursion)是指函数的自身调用。 这个算法演变为了程序员之间的梗&#xff0c;所表达的意思近似于“套娃”&#xff0c;表示不断重复引用别人的话从而产生循环。 适用场景 这个应该很多的&#xff0c;像一些树的遍历&am…

【基于 PyTorch 的 Python 深度学习】5 机器学习基础(1)

前言 文章性质&#xff1a;学习笔记 &#x1f4d6; 学习资料&#xff1a;吴茂贵《 Python 深度学习基于 PyTorch ( 第 2 版 ) 》【ISBN】978-7-111-71880-2 主要内容&#xff1a;根据学习资料撰写的学习笔记&#xff0c;该篇主要介绍了机器学习的基本任务、机器学习的一般流程&…

Leetcode—295. 数据流的中位数【困难】

2024每日刷题&#xff08;132&#xff09; Leetcode—295. 数据流的中位数 实现代码 class MedianFinder { public:MedianFinder() {}void addNum(int num) {if(maxHeap.empty() || num < maxHeap.top()) {maxHeap.push(num);} else {minHeap.push(num);}if(maxHeap.size(…

Verilog_学习路线(小白)

#前言&#xff1a; 自从专心学习专业课后&#xff0c;发现知识点得用&#xff0c;越用越熟练&#xff0c;工具也一样&#xff0c;高级工具的学习可帮助我们在工作中极大地提高效率&#xff0c;但这里要记住一点&#xff0c;任何工具都是为解决实际问题出现的&#xff0c;即落脚…

武汉星起航助力新手卖家掌握亚马逊政策,开启跨境电商新征程

在数字化浪潮席卷全球的今天&#xff0c;亚马逊平台以其强大的影响力和广阔的市场前景&#xff0c;吸引了越来越多的卖家涌入其中。然而&#xff0c;对于初涉亚马逊市场的新手卖家而言&#xff0c;如何在激烈的市场竞争中立足&#xff0c;并成功开展跨境电商业务&#xff0c;却…

用python进行接口测试(详细教程)

前言 其实我觉得接口测试很简单&#xff0c;比一般的功能测试还简单&#xff0c;现在找工作好多公司都要求有接口测试经验&#xff0c;也有好多人问我什么是接口测试&#xff0c;本着不懂也要装懂的态度&#xff0c;我会说&#xff1a;所谓接口测试就是通过测试不同情况下的入…

微火全域运营是什么?为什么一上线就火了?

近日&#xff0c;以共享WiFi贴和智慧数字经营等项目闻名业内的品牌微火又提出了新概念——微火全域运营&#xff0c;并同步上线了微火全域运营平台。这一举动无疑是给全域运营赛道和有意向做微火全域运营服务商的创业者群体中投下了一枚重磅炸弹&#xff0c;有创业者透露&#…

ICode国际青少年编程竞赛- Python-4级训练场-绿色能量1

ICode国际青少年编程竞赛- Python-4级训练场-绿色能量1 1、 Dev.step(3) Dev.turnLeft() Dev.step(3) Spaceship.step(4) Spaceship.turnRight() Spaceship.step(4) Dev.step(3) while Item[1].y ! Dev.y:wait()2、 Dev.step(4) while Item[0].x ! Dev.x:wait() Dev.turnLe…

Unity射击游戏开发教程:(13)如何在Unity中播放音效

在本文中,我将向大家展示一些为游戏添加声音的不同方法。 我们为游戏添加声音的第一种方法是播放背景音乐。在此,我们将创建游戏对象(“音频管理器”)并创建一个子游戏对象(“背景音乐”)。该子游戏对象将是播放音乐的对象,因此需要向其添加音频源组件。如果没有音频源组…

如何获得临时谷歌邮箱?

什么是临时谷歌邮箱&#xff1f; 临时谷歌邮箱&#xff0c;也称为一次性谷歌邮箱或匿名谷歌邮箱&#xff0c;可以用来作为你的个人临时谷歌邮箱账户&#xff0c;而不需要亲自注册谷歌账户就可以使用。这些邮箱在一定时间后自动销毁&#xff0c;期间无需用户进行任何操作。它们…

你不知道的ConstraintLayout高级用法

文章目录 1. ConstraintLayout介绍2. 高级用法2.1 Gone Margin2.2 偏移2.3 居中2.4 尺寸约束2.5 链2.6 角度定位&#xff08;圆形定位&#xff09; 3. 工具类3.1 Guideline&#xff08;参考线&#xff09;3.2 Barrier&#xff08;栅栏&#xff09;3.3 Group&#xff08;组&…

OpenCV-android-sdk配置及使用(NDK)

opencv官网下载Android版Releases - OpenCV 下载好OpenCV-android-sdk并解压好,然后新建一个jni文件夹测试,测试项目目录结构如下: ├── jni │ ├── Android.mk │ ├── Application.mk │ └── test.cpp Application.mk: APP_STL := c++_static APP_CPP…

JAVA毕业设计138—基于Java+Springboot+Vue的医院预约挂号小程序(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootVue的医院预约挂号小程序(源代码数据库)138 一、系统介绍 本系统前后端分离带小程序和后台 小程序&#xff08;用户端&#xff09;&#xff0c;后台管理系统&a…