STM32—旋转编码器控制直流电机(标准库)

server/2024/10/20 10:31:58/

本文使用 KY-040旋转编码器 通过TC1508A电机驱动模块来控制直流电机正转和反转(Speed:0-100),代码部分基于标准库,使用定时器输出比较两个通道来控制PWM输出。

一、KY-040旋转编码器

下图为KY-040旋转编码器,它有5个引脚:CLK、DT、SW、VCC、GND

作者是这么理解的:CLK是时钟,DT是数据。这两个引脚输出的时序反映了旋转编码器所产生的中断信号。SW是编码器按键,设置GPIO为上拉输入,当编码器被按下时,SW输出低电平。VCC正极,GND负极。

CLK和DT相当于编码器时序的A相和B相,如上图,设置这两个引脚的GPIO为下拉输入,上升沿触发中断。当正时针旋转编码器时,红色笔迹显示,在CLK(TI1)触发中断时,DT(TI2)为低电平。绿色笔迹显示,当DT(TI2)触发中断时,CLK(TI1)为高电平。 

CLK引脚产生的中断处理函数如下:

DT引脚产生的中断处理函数如下: 

按下编码器可以来设置正反转,这里我使用了LED灯的亮灭来反映正反转的状态,正亮,反灭。每按下一次,灯的状态翻转。再通过检测灯的状态来控制正反转(这一步按自己设计代码来,就相当于按键控制LED灯。我这里将这段代码写在main函数里,因为需要初始化LED

 我的代码不够简练,作为小白什么都不懂,只知道到处碰壁。

Encoder.c:

#include "stm32f10x.h"                  // Device header
#include "Encoder.h" int16_t Encoder_Count;					//全局变量,用于计数旋转编码器的增量值/*** 函    数:旋转编码器初始化* 参    数:无* 返 回 值:无*/
void Encoder_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(Encoder_GPIO_CLK, ENABLE);		    //开启GPIOB的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;GPIO_InitStructure.GPIO_Pin = Encoder_GPIO_Pin_CLK | Encoder_GPIO_Pin_DT | Encoder_GPIO_Pin_SW;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(Encoder_GPIO_Port, &GPIO_InitStructure);						//将PB0和PB1引脚初始化为上拉输入/*AFIO选择中断引脚*/GPIO_EXTILineConfig(Encoder_GPIO_Port_Source, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚GPIO_EXTILineConfig(Encoder_GPIO_Port_Source, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚/*EXTI初始化*/EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;		//选择配置外部中断的0号线和1号线EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;		//指定外部中断线为下降沿触发EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2//即抢占优先级范围:0~3,响应优先级范围:0~3//此分组配置在整个工程中仅需调用一次//若有多个中断,可以把此代码放在main函数内,while循环之前//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;			//选择配置NVIC的EXTI0线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;			//选择配置NVIC的EXTI1线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;			//指定NVIC线路的响应优先级为2NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
}/*** 函    数:旋转编码器获取增量值* 参    数:无* 返 回 值:自上此调用此函数后,旋转编码器的增量值*/
int16_t Encoder_Get(void)
{/*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*//*在这里,也可以直接返回Encoder_Count但这样就不是获取增量值的操作方法了也可以实现功能,只是思路不一样*/int16_t Temp;Temp = Encoder_Count;Encoder_Count = 0;return Temp;
}/*** 函    数:EXTI0外部中断函数* 参    数:无* 返 回 值:无* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行*           函数名为预留的指定名称,可以从启动文件复制*           请确保函数名正确,不能有任何差异,否则中断函数将不能进入*/
void EXTI0_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line0) == SET)		//判断是否是外部中断0号线触发的中断{/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_CLK) == 1){if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_DT) == 0)		//下降沿触发中断,此时检测另一相电平,目的是判断旋转方向{Encoder_Count++;}}EXTI_ClearITPendingBit(EXTI_Line0);			//清除外部中断0号线的中断标志位}}///**
//  * 函    数:EXTI1外部中断函数
//  * 参    数:无
//  * 返 回 值:无
//  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
//  *           函数名为预留的指定名称,可以从启动文件复制
//  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
//  */
void EXTI1_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line1) == SET)		//判断是否是外部中断0号线触发的中断{if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_DT) == 1){if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_CLK) == 0)	//下降沿触发中断,此时检测另一相电平,目的是判断旋转方向{Encoder_Count--;			//此方向定义为反转,计数变量自减}}EXTI_ClearITPendingBit(EXTI_Line1);			//清除外部中断0号线的中断标志位}}

Encoder.h:

#ifndef __ENCODER_H
#define __ENCODER_H#define Encoder_GPIO_CLK      RCC_APB2Periph_GPIOD
#define Encoder_GPIO_Port     GPIOD
#define Encoder_GPIO_Pin_CLK  GPIO_Pin_0
#define Encoder_GPIO_Pin_DT   GPIO_Pin_1
#define Encoder_GPIO_Pin_SW   GPIO_Pin_2
#define Encoder_GPIO_Port_Source   GPIO_PortSourceGPIODvoid Encoder_Init(void);
int16_t Encoder_Get(void);#endif

二、TC1508电机驱动模块

  • IN1和IN2用来控制motor-A
  • IN3和IN4用来控制motor-B

 如下图,IN1输入PWM,配合IN2输入0,可实现直流电机正转;IN1输入0,配合IN2输入PWM,即可实现直流电机反转。PWM为调节速度所用。

于是在旋转编码器部分设置好后,再开始写PWM波形代码,PWM部分我使用的是定时器TIM3,TIM3有4个通道,我使用了 Channel1和Channel2 两个通道来分别输出PWM。

由于我的开发板上两个引脚PA6、PA7、PB4、PB5都被占用,所以使用TIM3完全重映像来设置PC6和PC7来输出IN1、IN2。

完全重映像代码

PWM设置两个通道的时候需要注意,先TIM_OC1Init,再GPIO_Init。如果顺序弄反,通道一和通道二无法对应PC6和PC7,我也不知道为什么。希望有大神在评论区解答一下,谢谢!

PWM_Init:

void PWM_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);TIM_InternalClockConfig(TIM3);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStruct.TIM_Period = 100-1;TIM_TimeBaseInitStruct.TIM_Prescaler = 720-1;TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);TIM_OCInitTypeDef TIM_OCInitStruct;TIM_OCStructInit(&TIM_OCInitStruct);TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStruct.TIM_Pulse = 0;TIM_OC1Init(TIM3, &TIM_OCInitStruct);TIM_OC2Init(TIM3, &TIM_OCInitStruct);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &GPIO_InitStruct);TIM_Cmd(TIM3, ENABLE);}

 然后连线,连线需要注意的是,TC1508A电机驱动模块的正负极容易接触不良。可以接完正负极,MOTORA连接电机后,通过一根线连接5v和IN1(或者IN2)测试电机是否正常启动。

主函数可以按自己想法来,作者这里的思路是:

1,在while之前,编码器初始化、PWM初始化。

2,while中,通过编码器转动产生中断获取速度的加减,通过按键中断翻转LED灯,再通过LED状态判断是正转还是反转。

3,如果是正转,通过TIM_SetCompare1(TIM3, Compare); 来设置速度,如果是反转,通过TIM_SetCompare2(TIM3, Compare); 来设置速度。

三、各模块代码部分

Encoder.c:

#include "stm32f10x.h"                  
#include "Encoder.h" int16_t Encoder_Count;					//全局变量,用于计数旋转编码器的增量值void Encoder_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(Encoder_GPIO_CLK, ENABLE);		//开启GPIOB的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;GPIO_InitStructure.GPIO_Pin = Encoder_GPIO_Pin_CLK | Encoder_GPIO_Pin_DT | Encoder_GPIO_Pin_SW;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(Encoder_GPIO_Port, &GPIO_InitStructure);						//将PB0和PB1引脚初始化为上拉输入/*AFIO选择中断引脚*/GPIO_EXTILineConfig(Encoder_GPIO_Port_Source, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚GPIO_EXTILineConfig(Encoder_GPIO_Port_Source, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚/*EXTI初始化*/EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;		//选择配置外部中断的0号线和1号线EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;		//指定外部中断线为下降沿触发EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2//即抢占优先级范围:0~3/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;			//选择配置NVIC的EXTI0线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;			//选择配置NVIC的EXTI1线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;			//指定NVIC线路的响应优先级为2NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
}int16_t Encoder_Get(void)
{/*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*//*在这里,也可以直接返回Encoder_Count但这样就不是获取增量值的操作方法了也可以实现功能,只是思路不一样*/int16_t Temp;Temp = Encoder_Count;Encoder_Count = 0;return Temp;
}void EXTI0_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line0) == SET)		//判断是否是外部中断0号线触发的中断{if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_CLK) == 1)  // 再次判断引脚电平,以避免抖动,其实没必要{if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_DT) == 0)		{Encoder_Count++;}}EXTI_ClearITPendingBit(EXTI_Line0);			//清除中断标志位}}void EXTI1_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line1) == SET)		 //判断是否是外部中断0号线触发的中断{if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_DT) == 1){if (GPIO_ReadInputDataBit(Encoder_GPIO_Port, Encoder_GPIO_Pin_CLK) == 0){Encoder_Count--;			//此方向定义为反转,计数变量自减  }}EXTI_ClearITPendingBit(EXTI_Line1);			//清除中断标志位}}

Encoder.h:

#ifndef __ENCODER_H
#define __ENCODER_H#define Encoder_GPIO_CLK      RCC_APB2Periph_GPIOD
#define Encoder_GPIO_Port     GPIOD
#define Encoder_GPIO_Pin_CLK  GPIO_Pin_0
#define Encoder_GPIO_Pin_DT   GPIO_Pin_1
#define Encoder_GPIO_Pin_SW   GPIO_Pin_2
#define Encoder_GPIO_Port_Source   GPIO_PortSourceGPIODvoid Encoder_Init(void);
int16_t Encoder_Get(void);#endif

PWM.c:

#include "stm32f10x.h"              
#include "PWM.h" void PWM_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);TIM_InternalClockConfig(TIM3);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStruct.TIM_Period = 100-1;TIM_TimeBaseInitStruct.TIM_Prescaler = 720-1;TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);TIM_OCInitTypeDef TIM_OCInitStruct;TIM_OCStructInit(&TIM_OCInitStruct);TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStruct.TIM_Pulse = 0;TIM_OC1Init(TIM3, &TIM_OCInitStruct);TIM_OC2Init(TIM3, &TIM_OCInitStruct);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &GPIO_InitStruct);TIM_Cmd(TIM3, ENABLE);}void TIM3_SetCompare1(uint16_t Compare)
{	TIM_SetCompare1(TIM3, Compare);		//设置CCR1的值
}void TIM3_SetCompare2(uint16_t Compare)
{	TIM_SetCompare2(TIM3, Compare);		//设置CCR1的值
}

PWM.h:

#ifndef __PWM_H
#define __PWM_Hvoid PWM_Init(void);
void TIM3_SetCompare1(uint16_t Compare);
void TIM3_SetCompare2(uint16_t Compare);#endif

main.c: 部分代码,因为作者主函数代码太多,删减了一部分:OLED显示、串口显示......

#include "stm32f10x.h"                  // Device header
#include "LED.h"
#include "Encoder.h"
#include "PWM.h"uint8_t i;			          //定义for循环的变量int16_t Num;			//定义待被旋转编码器调节的变量
int16_t Speed;			int main(void)
{/*模块初始化*/LED_Init();PWM_Init();Encoder_Init();		//旋转编码器初始化while (1){Num += Encoder_Get();				//获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上Speed = Num*10;if(Speed>100){Speed=100;}else if(Speed<0){Speed=0;}if (GPIO_ReadInputDataBit(Encoder_GPIO_Port,Encoder_GPIO_Pin_SW) == 0)			//读PB1输入寄存器的状态,如果为1,则代表按键1按下{Delay_ms(10);											//延时消抖while (GPIO_ReadInputDataBit(Encoder_GPIO_Port,Encoder_GPIO_Pin_SW) == 0);	//等待按键松手Delay_ms(10);											//延时消抖LED1_Turn();Speed=00;Num=0;}if((GPIO_ReadOutputDataBit(LED1_GPIO_Port, LED1_GPIO_Pin) == 1)){TIM3_SetCompare1(Speed);TIM3_SetCompare2(0);}else{TIM3_SetCompare1(0);TIM3_SetCompare2(Speed);}}
}

整体代码,需要根据自己的实际情况修改,比如不用重映像、不用LED等。


http://www.ppmy.cn/server/133311.html

相关文章

数据结构常考基础代码题-数组倒置

题目要求 将数组 (a1, a2, a3, ..., am, b1, b2, ..., bn) 转换成 (b1, b2, ..., bn, a1, a2, a3, ..., am)。 代码实现步骤 第一步&#xff1a;定义反转函数 根据题目中的“将数组中的元素顺序反转”&#xff0c;我们需要实现一个函数 Reverse&#xff0c;用于反转数组中从…

kafka脚本工具使用

如何定位kakfa消费端消息异常问题 查看主题查看消费者组查看消费者详情&#xff08;LAG: 消费者与最新消息的滞后程度(数字越大说明消费者处理消息的速度越慢)&#xff09; 进入docker容器&#xff0c;直接运行sh脚本即可 docker exec -it <containerName> /bin/bash或…

游戏盾真的能无视攻击吗?

在当今社会&#xff0c;网络游戏已成为人们娱乐休闲的重要组成部分。随着游戏行业的快速扩展&#xff0c;网络安全挑战也随之加剧。DDoS攻击、CC攻击等恶意手段频繁出现&#xff0c;给游戏运营商及玩家带来了重重困扰。幸运的是&#xff0c;游戏盾这一专为游戏领域设计的网络安…

C语言:c语言中‘ ‘空格与‘\0‘的区别

c语言中’ ‘空格与’\0’的区别 在C语言中&#xff0c;空格和\0是两个不同的字符&#xff0c;具有不同的作用和含义。 空格&#xff08;’ &#xff09;是一个可打印的字符&#xff08;可见字符&#xff09;&#xff0c;用于表示空白区域。它的ASCII值为32&#xff0c;主要用于…

读书读到NOBEL

最近在读陈逸鹤的《程序员的自我修养》这本书&#xff0c;里面有这么一段话&#xff1a; “远古时代的人们只能创造出用于猎捕的长矛&#xff0c;而今天借助来自各行各业人 们的智慧&#xff0c;我们可以制造出高铁、大型飞机&#xff0c;并探索宇宙。但要更进一步解决人类所面…

LeetCode 24 - 两两交换链表中的节点

题目描述 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 解题思路 交换链表中相邻节点的问题可以通过迭代或递归来解决。本…

沪尚茗居装修秘籍:嵌入式蒸烤箱,让厨房生活更精彩

在装修厨房时&#xff0c;选择一款合适的嵌入式蒸烤箱不仅能提升烹饪效率&#xff0c;还能为厨房增添一份现代感。沪尚茗居深知用户对厨房电器的需求&#xff0c;从实际出发&#xff0c;为用户推荐选购嵌入式蒸烤箱的实用技巧&#xff0c;让厨房生活更加美好。    首先&…

保护企业终端安全,天锐DLP帮助企业智能管控终端资产

为有效预防员工非法调包公司的软硬件终端资产&#xff0c;企业管理员必须建立高效的企业终端安全管控机制&#xff0c;确保能够即时洞察并确认公司所有软硬件资产的状态变化。这要求企业要有一套能够全面管理终端资产的管理系统&#xff0c;确保任何未经授权的资产变动都能被迅…