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

基于STM32CubeMX和keil采用通用定时器中断实现固定PWM可调PWM波输出分别实现LED闪烁与呼吸灯

文章目录

  • 前言
  • 1. PWM波阐述
  • 2. 通用定时器
    • 2.1 为什么用TIM14
    • 2.2 TIM14功能介绍
    • 2.3 一些配置参数解释
    • 2.4 PWM实现流程&中断
      • 2.4.1 非中断PWM输出(LED闪烁)
      • 2.4.2 中断PWM输出(LED呼吸灯)
  • 3. STM32CubeMX配置
    • 3.1 GPIO配置
    • 3.2 时钟配置
    • 3.3 定时器相关参数配置
    • 3.4 Debug配置
    • 3.5 中断配置
    • 3.6 代码生成
  • 4.代码编写
    • 4.1 LED闪烁代码编写
    • 4.2 LED呼吸灯代码编写
  • 总结

前言

上一篇博客里面写了基本定时器实现LED闪烁,主要就是一个预分频以及计数的使用,再加上一个电平翻转,原理以及具体操作比较简单。
这篇博客选择通用定时器进行稍微难一点的效果演示,用到输出PWM波的功能。
这篇博客写输出固定PWM以及可变PWM,进一步实现LED0闪烁以及LED0呼吸灯效果。
功能实现效果:
PF9输出PWM波控制LED0闪烁,1s周期,0.5s改变一次状态。
PF9输出PWM波控制LED0实现呼吸灯,一个时钟周期20ms。


1. PWM波阐述

PWM波,即脉宽调制波(Pulse Width Modulation),是一种在电信号中调整脉冲宽度的技术。它是通过控制信号的高电平时间和低电平时间的占空比来传输信息或实现对输出的精确控制。
在PWM波中,周期是固定的,周期是指一个完整高低电平的时间长度。而脉冲宽度则是高电平或低电平的持续时间,可以根据需要进行调整。脉冲宽度与信号的幅度呈正比,即脉冲宽度越宽,信号幅度越大,脉冲宽度越窄,信号幅度越小。

PWM波的优点包括:
1.简单可靠:PWM调制电路相对简单,成本低廉,可靠性高。
2.高效节能:由于PWM波的输出平均值可以通过调节占空比来控制,因此可以实现高效能的能量转换,减少功率损耗。
3.精确控制:通过调整脉冲宽度,可以精确控制输出的电压、电流或功率。
4.适应性强:PWM技术适用于不同类型的负载,可以调整频率和占空比来满足各种需求。

简单点概括一下就是,输出高低电平的一个方波,这个占空比可调。


2. 通用定时器

2.1 为什么用TIM14

首先是功能需求,这篇博客因为是LED0的闪烁以及呼吸灯效果,所以其实PWM波的输出都要给到PF9引脚上面去。
我们通过下面的原理图可以看到,PF9不仅接着LED0,同时也可以作为TIM14_CH1的输出。
在这里插入图片描述
在这里插入图片描述
如果直接在STM32CubeMX里面启用TIM14,可能并不是PF9,因为TIM14在STM32F407里面有两个引脚可以配置。如果配置了PA7,那么输出的PWM波其实并不能给到LED0上。这里为了方便起见,我们使用LED0的PF9的TIM14_CH1进行配置。
在这里插入图片描述

2.2 TIM14功能介绍

TIM14属于通用定时器的一种,它具有如下功能,其中这篇博客我们用到的是PWM生成功能。
1.计时功能:TIM14可以用作一个简单的计时器,用于测量时间间隔或持续时间。通过递增计数器的值来记录经过的时间,并根据预分频系数和时钟源来确定计时的精度。
2.定时中断:TIM14可以配置为在达到特定计数值时产生中断请求。通过设置计数器的自动重载值和使能中断,可以触发中断事件来执行特定的任务或操作。
3.PWM生成:TIM14可以用于生成简单的脉冲宽度调制(PWM)信号。通过设置计数器的自动重载值和比较寄存器的值,可以实现对脉冲的频率和占空比的控制。
4.单脉冲模式:TIM14还支持单脉冲模式,在特定的触发条件下,生成一个单脉冲信号。可以配置触发源、触发极性和脉冲宽度,用于各种应用场景。
5.捕获/比较模式:TIM14可以在输入捕获模式下,检测外部事件或信号的边沿,并记录计数器的当前值。同时,也可以在比较模式下,与比较寄存器中的值进行比较,触发相应的事件或输出行为。

2.3 一些配置参数解释

关于预分频系数等这些东西,详情参考基于STM32CubeMX和keil采用STM32F407的基本定时器中断实现LED闪烁,这里就不再赘述。
这里仅阐述相比于上一篇博客,PWM新增的部分功能阐述,即PWM Generation Channel 1配置

在这里插入图片描述

Mode:这里可以选择PWM mode 1和PWM mode 2模式,两者的区别在于:
前者在递增计数下,只要CNT<CCR,即计数器的值小于设定的比较寄存器的值,通道就是有效。反之有效。
前者在递减计数下,只要CNT<CCR,即计数器的值小于设定的比较寄存器的值,通道就是无效。反之有效。
后者与前者正好相反,例如递增计数情况下,CNT<CCR,通道无效。反之有效。

Pulse:PWM脉冲宽度,即设置CCR的值。这里的值应该小于整个计数周期。
Output compare preload:输出比较预装载。和auto-reload preload类似。这个值为enable时,我们修改CCR的值(这里也就是pluse的值),修改的值需要等到下一个UEV事件时才生效,否则立刻生效。
Fast Mode:是否使用输出比较快速模式,就是设置寄存器TIMx_CCMR1中的OC1FE位,用于加快触发输入事件对CC输出的影响,一般设置为Disable即可。
CH Polarity:通道极性。就是有效的状态是什么,如果设置为High,则通道有效的时候,输出为高电平。


2.4 PWM实现流程&中断

PWM实现的整体过程大致如下:
1.初始化定时器:首先,需要选择一个合适的定时器,并初始化它的基本配置。这包括选择定时器的模式、设置时钟源和预分频系数,以及配置计数器的位数。
2.设置PWM周期:根据需要设置PWM波的周期。这可以通过设置定时器的自动重载值(自动装载寄存器)来实现。自动重载值决定了计数器计数到多少后重新加载,并重新开始计数,从而确定PWM波的周期。
3.设置PWM占空比:根据期望的PWM波形的占空比,设置定时器的比较值(比较寄存器)。比较寄存器的值决定了计数器计数到多少时,PWM波的状态(高电平或低电平)发生变化。通过适当调整比较值,可以实现所需的占空比。
4.使能定时器和PWM输出:启用定时器并使能PWM输出。这通常包括使能定时器的计数器、开启PWM输出通道,以及配置相应的引脚和输出端口。
5.等待定时器中断或轮询:根据具体的实现方式,可以选择等待定时器中断请求,或者通过轮询定时器的状态来判断是否达到比较值,从而进行适当的处理或更新PWM波形的状态。

这个实验在做的时候我用STM32CubeMX配置了一堆,烧录了之后发现没作用,仔细查看代码,发现没给它使能。
下面写一下配置完成后如何使能启用PWM。

2.4.1 非中断PWM输出(LED闪烁)

PWM的输出是需要使能的,需要使用到函数HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel),这个函数的第一个参数是TIM的一个事件处理类的对象,这里也就是我们的TIM14的一个对象,后面的是通道。因为TIM14里面只有一个通道,所以这里我们应该为CH1。

在使用HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)这个函数之前,还需要使用HAL_TIM_Base_Start(TIM_HandleTypeDef *htim),这个函数是前者的前置条件,也就是先有Base_Start,再有PWM_Start。这也比较好理解,就是先有基本一些功能启用,再开启高级一点的功能。
LED闪烁其实就是固定占空比的情况,所以只需要输出固定占空比的PWM波,不需要中断。

2.4.2 中断PWM输出(LED呼吸灯)

中断PWM输出其实就是每一个PWM的周期结束,都会触发一次中断。那么和上面一样,也需要先开启基本的功能,再开启高级的功能。因为是中断方式,所以除了初始化中断以及配置中断向量优先级之类的,只需要调用对应的中断相关的启动函数。中断相关的函数后面一般都会有个IT(Interrupt)

HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
HAL_TIM_PWM_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);

使用中断的好处就是我可以每一次中断触发的时候在回调函数里修改CCR的值,从而改变占空比,从而实现呼吸灯效果。


3. STM32CubeMX配置

3.1 GPIO配置

在这里插入图片描述

3.2 时钟配置

开启外部晶振(其实也可以不开启,看你时钟的最终频率设置多少,一般开启外部晶振是为了设置时钟的时候能适配更多的时钟频率)
在这里插入图片描述下图的时钟树中我的时钟来自于内部高速晶振,其实和外部晶振开启与否并没有关系,因为我最终得到100MHz的频率使用内部高速晶振经过分频器和锁相环就可以得到。
下面时钟的HSI RC提供16MHz,经过分配器,变为2MHz,再倍频100,再除以2,片选PLLCLK,一倍分频,就得到了我们最终所设置的100MHz。
由于TIM14是在APB1总线上,所以我们还要关注一下APB1总线的时钟频率,这里自动设置为了50MHz。
也就是说TIM14获得的最初始的时钟是50MHz,后面的定时器的分频是在50MHz基础上进行的。
在这里插入图片描述

3.3 定时器相关参数配置

如果实现0.5s闪烁则配置如下
这里分频系数的设定是为了获得单次时钟周期为1ms时钟,ARR设置为999是为了获得周期为1s的PWM波周期。
Pulse为500是为了获得占空比为 500 ∗ 1 m s 1000 m s = 500 m s 1000 m s = 0.5 \frac{500*1ms}{1000ms}=\frac{500ms}{1000ms}=0.5 1000ms5001ms=1000ms500ms=0.5的PWM波。

在这里插入图片描述
如果实现呼吸灯效果则配置如下
这里分频系数的设定是为了获得单次时钟周期为0.1ms时钟,ARR设置为199是为了获得周期为20ms的PWM波周期。
Pulse为50是为了获得占空比为 50 ∗ 0.1 m s 20 = 5 m s 20 m s = 0.25 \frac{50*0.1ms}{20}=\frac{5ms}{20ms}=0.25 20500.1ms=20ms5ms=0.25的PWM波。
在这里插入图片描述

为什么要分开设置,是因为我自己这样设置效果比较明显,你也可以根据自己的需要进行修改。


3.4 Debug配置

在这里插入图片描述

3.5 中断配置

在这里插入图片描述

3.6 代码生成

在这里插入图片描述
在这里插入图片描述


4.代码编写

4.1 LED闪烁代码编写

如果不使用中断,代码比较简单,其实只需要在main函数里面使能即可。
在这里插入图片描述

	//不使用中断即输出固定占空比的PWMHAL_TIM_Base_Start(&htim14);HAL_TIM_PWM_Start(&htim14,TIM_CHANNEL_1);

LED闪烁的初始化里面参数:
在这里插入图片描述

4.2 LED呼吸灯代码编写

呼吸灯要用到中断,因为要调整PWM的占空比即CCR变量的值。
首先是中断使能

	//使用中断,在回调函数里改变占空比//HAL_TIM_Base_Start_IT(&htim14);//HAL_TIM_PWM_Start_IT(&htim14,TIM_CHANNEL_1);

回调函数是HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim),这个函数在计数器到达脉冲设置的值是触发。
为了调整脉宽等,我们还需要设定参数。

uint16_t plusWidth=500;//脉宽
uint16_t dirInc=1;//脉宽变化方向,1为递增,0递减

回调函数代码:

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{//只针对TIM14进行中断处理if(htim->Instance!=TIM14){return;}//如果是脉宽递增则增加脉宽if(dirInc==1){plusWidth++;if(plusWidth>=195){plusWidth=195;dirInc=0;}}//如果是脉宽递减则减少脉宽if(dirInc==0){plusWidth--;if(plusWidth<=5){plusWidth=5;dirInc=1;}}//__HAL_TIM_SetCompare(定时器对象指针,定时器通道,需要设置的CCR值);__HAL_TIM_SetCompare(&htim14,TIM_CHANNEL_1,plusWidth);
}	

呼吸灯的初始化参数配置
在这里插入图片描述


总结

这篇博客对定时器的PWM功能进行了归纳总结以及应用实现,写的非常详细,对应的MX工程以及Keil代码我放在了开头,需要自取。

相关文章:

基于STM32CubeMX和keil采用通用定时器中断实现固定PWM可调PWM波输出分别实现LED闪烁与呼吸灯

文章目录 前言1. PWM波阐述2. 通用定时器2.1 为什么用TIM142.2 TIM14功能介绍2.3 一些配置参数解释2.4 PWM实现流程&中断2.4.1 非中断PWM输出(LED闪烁)2.4.2 中断PWM输出(LED呼吸灯) 3. STM32CubeMX配置3.1 GPIO配置3.2 时钟配置3.3 定时器相关参数配置3.4 Debug配置3.5 中…...

mysql大表的深度分页慢sql案例(跳页分页)

1 背景 有一张表&#xff0c;内容是 redis缓存中的key信息&#xff0c;数据量约1000万级&#xff0c; expiry列上有一个普通B树索引。 -- test.top definitionCREATE TABLE top (database int(11) DEFAULT NULL,type varchar(50) DEFAULT NULL,key varchar(500) DEFAULT NUL…...

集中/本地转发、AC、AP

1.ADSL ADSL MODEM&#xff08;ADSL 强制解调器&#xff09;俗称ADSL猫 ADSL是一种异步传输模式&#xff08;ATM)。ADSL是指使用电话线上网&#xff0c;需要专用的猫&#xff08;Modem)&#xff0c;在上网的时候高频和低频分离&#xff0c;所以上网电话两不耽误&#xff0c;速…...

Spring集成Seata

Seata的集成方式有&#xff1a; 1. Seata-All 2. Seata-Spring-Boot-Starter 3. Spring-Cloud-Starter-Seata 本案例使用Seata-All演示&#xff1a; 第一步&#xff1a;下载Seata 第二步&#xff1a;为了更好看到效果&#xff0c;我们将Seata的数据存储改为db 将seata\sc…...

三种方式创建对象的几种方式及new实例化时做了什么?

创建对象的几种方式 利用对象字面量创建对象 const obj {}2.利用 new Object创建对象 const obj new Object()3.使用 构造函数实例化对象 function Fn(name) {this.name name} const obj new Fn(张三) console.log(obj.name); //张三为什么要用构造函数的形式&#xff1…...

vue2-vue实例挂载的过程

1、思考 new Vue()这个过程中究竟做了什么&#xff1f;过程中是如何完成数据的绑定&#xff0c;又是如何将数据渲染到视图的等等。 2、分析 首先找到vue的构造函数。 源码位置&#xff1a;/src/core/instance/index.js options是用户传递过来的配置项&#xff0c;如data、meth…...

C++ 右值引用案例

C 右值引用案例 右值引用&#xff08;Rvalue reference&#xff09;是 C11 引入的新特性&#xff0c;它的主要意义是实现移动语义&#xff08;Move semantics&#xff09;和完美转发&#xff08;Perfect forwarding&#xff09;。这两者都可以提高代码的性能和灵活性。 一、移…...

2.文件的逻辑结构

第四章 文件管理 2.文件的逻辑结构 顺序文件采用顺序存储则意味着各个逻辑上相邻的记录在物理上也是相邻的存储的。所以如果第0号记录的逻辑地址为0的话&#xff0c;则i号记录的逻辑为i *L。 特别的如果这个定长记录的顺序文件采用串结构&#xff0c;也就是这些记录的顺序和他…...

20天学rust(一)和rust say hi

关注我&#xff0c;学习Rust不迷路 工欲善其事&#xff0c;必先利其器。第一节我们先来配置rust需要的环境和安装趁手的工具&#xff0c;然后写一个简单的小程序。 安装 Rust环境 Rust 官方有提供一个叫做 rustup 的工具&#xff0c;专门用于 rust 版本的管理&#xff0c;网…...

牢记这16个SpringBoot 扩展接口,写出更加漂亮的代码

1、背景 Spring的核心思想就是容器&#xff0c;当容器refresh的时候&#xff0c;外部看上去风平浪静&#xff0c;其实内部则是一片惊涛骇浪&#xff0c;汪洋一片。Springboot更是封装了Spring&#xff0c;遵循约定大于配置&#xff0c;加上自动装配的机制。很多时候我们只要引…...

c++两种设计模式 单例和工厂模式

c两种设计模式 单例和工厂模式 一.单例 1.单例的概念 1.当前的类最多只能创建一个实例 2.当前这个唯一的实例&#xff0c;必须由当前类创建&#xff08;自主创建&#xff09;&#xff0c;而不是调用者创建 3.必须向整个系统提供全局的访问点&#xff0c;来获取唯一的实例 …...

2023-08-05——JVM 栈

栈 stack 栈&#xff1a;数据结构 程序数据结构算法 栈&#xff1a;先进后出&#xff0c;后进先出 好比一个&#xff1a;桶 队列&#xff1a;先进先出&#xff08;FIFO &#xff1a;First Input First Out&#xff09; 好比一个&#xff1a;管道 栈&#xff1a;喝多了吐。队列…...

Camera之PhysicalCameraSettingsList/SurfaceMap/CameraMetadata/RequestList的关系(三十二)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…...

【ONE·Linux || 基础IO(二)】

总言 文件系统与动静态库相关介绍。 文章目录 总言2、文件系统2.1、背景知识2.2、磁盘管理2.2.1、磁盘文件系统图2.2.2、inode与文件名 2.3、软硬链接 3、动静态库3.1、站在编写库的人的角度&#xff1a;如何写一个库&#xff1f;3.1.1、静态库制作3.1.3、动态库制作 3.2、站在…...

【LeetCode 算法】Power of Heroes 英雄的力量

文章目录 Power of Heroes 英雄的力量问题描述&#xff1a;分析代码Math Tag Power of Heroes 英雄的力量 问题描述&#xff1a; 给你一个下标从 0 开始的整数数组 nums &#xff0c;它表示英雄的能力值。如果我们选出一部分英雄&#xff0c;这组英雄的 力量 定义为&#xff…...

合宙Air724UG LuatOS-Air script lib API--ntp

ntp Table of Contents ntp ntp.timeSync(period, fnc, fun) ntp 模块功能&#xff1a;网络授时. 重要提醒&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 本功能模块采用多个免费公共的NTP服务器来同步时间 并不能保证任何时间任何地点都能百分…...

LangChain+ChatGLM大模型应用落地实践(一)

LLMs的落地框架&#xff08;LangChain&#xff09;&#xff0c;给LLMs套上一层盔甲&#xff0c;快速构建自己的新一代人工智能产品。 一、简介二、LangChain源码三、租用云服务器实例四、部署实例 一、简介 LangChain是一个近期非常活跃的开源代码库&#xff0c;目前也还在快速…...

PSO粒子群优化算法

PSO粒子群优化算法 算法思想matlab代码python代码 算法思想 粒子群算法&#xff08;Particle Swarm Optimization&#xff09; 优点: 1&#xff09;原理比较简单&#xff0c;实现容易&#xff0c;参数少。 缺点: 1&#xff09;易早熟收敛至局部最优、迭代后期收敛速度慢的…...

记一次 .NET某医疗器械清洗系统 卡死分析

一&#xff1a;背景 1. 讲故事 前段时间协助训练营里的一位朋友分析了一个程序卡死的问题&#xff0c;回过头来看这个案例比较经典&#xff0c;这篇稍微整理一下供后来者少踩坑吧。 二&#xff1a;WinDbg 分析 1. 为什么会卡死 因为是窗体程序&#xff0c;理所当然就是看主…...

C# 基于Rijndael对文件进行加解密

介绍&#xff1a; Rijndael 是一种对称加密算法&#xff0c;也是 AES&#xff08;Advanced Encryption Standard&#xff09;的前身。它用于数据的加密和解密&#xff0c;并提供了安全且高效的加密功能。 在.NET Framework 中&#xff0c;Rijndael 类是一个实现了 Rijndael 算法…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践

6月5日&#xff0c;2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席&#xff0c;并作《智能体在安全领域的应用实践》主题演讲&#xff0c;分享了在智能体在安全领域的突破性实践。他指出&#xff0c;百度通过将安全能力…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...

6.9-QT模拟计算器

源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...

针对药品仓库的效期管理问题,如何利用WMS系统“破局”

案例&#xff1a; 某医药分销企业&#xff0c;主要经营各类药品的批发与零售。由于药品的特殊性&#xff0c;效期管理至关重要&#xff0c;但该企业一直面临效期问题的困扰。在未使用WMS系统之前&#xff0c;其药品入库、存储、出库等环节的效期管理主要依赖人工记录与检查。库…...