STM32实现按键单击、双击、长按、连按功能,使用状态机,无延时,不阻塞

常见的按键判定程序,如正点原子按键例程,只能判定单击事件,对于双击、长按等的判定逻辑较复杂,且使用main函数循环扫描的方式,容易被阻塞,或按键扫描函数会阻塞其他程序的执行。使用定时器设计状态机可以规避这一问题。

本文在博主 老子姓李! 程序的基础上添加连按功能,整合双击功能并补充双击二次按下消抖,添加可调消抖时间,扩展为多按键,修改在定时器运行状态机,在主函数判断,并尽可能多的添加注释方便阅读。。
参考博主文章【源码详解-按键状态机-简洁易懂】1.单个按键实现短按长按的功能(基于STM32)

功能介绍

本程序功能:
使用定时器状态机实现按键单击、双击、长按、连按功能。消抖时间可调,长按时间可调,双击判定时间可调,连按单击间隔可调。无延时不阻塞,稳定触发。移植只需修改读IO函数,结构体初始化和宏定义时间参数即可。

注:

  1. 在定时器状态机判定产生事件标志,在主函数处理并清除事件标志。
  2. 单击、长按、双击事件为抬起时触发。
  3. 使能双击会稍微滞后单击事件的判定,可以选择是否使能双击判定。
  4. 可选择是否使能长按判定。
  5. 连按功能是指长按一定时间不松后,间隔较短时间多次触发快速单击事件,松开后结束。“快速单击”为单独事件,不与单击、长按事件冲突。

代码

头文件 my_key.h

#ifndef ___MY_KEY_H__
#define ___MY_KEY_H__

#define KEY_DEBOUNCE_TIME 10      //消抖时间
#define KEY_LONG_PRESS_TIME 500   //长按判定时间
#define KEY_QUICK_CLICK_TIME 100  //连按时间间隔
#define KEY_DOUBLE_CLICK_TIME 200 //双击判定时间

#define KEY_PRESSED_LEVEL 0 //按键被按下时的电平
#define TOTAL_KEYS 5        //物理按键数量

//按键事件
typedef enum
{
  KEY_Event_Null,        //空事件
  KEY_Event_SingleClick, //单击
  KEY_Event_LongPress,   //长按
  KEY_Event_QuickClick,  //连击
  KEY_Event_DoubleClick, //双击
} KEY_EventList_TypeDef;

//按键动作
typedef enum
{
  KEY_Action_Press,   //按住
  KEY_Action_Release, //松开
} KEY_Action_TypeDef;

//按键状态
typedef enum
{
  KEY_Status_Idle,             //空闲
  KEY_Status_Debounce,         //消抖
  KEY_Status_ConfirmPress,     //确认按下
  KEY_Status_ConfirmPressLong, //确认长按
  KEY_Status_WaitSecondPress,  //等待再次按下
  KEY_Status_SecondDebounce,   //再次消抖
  KEY_Status_SecondPress,      //再次按下
} KEY_StatusList_TypeDef;

// 按键引脚的电平
typedef enum
{
  KKEY_PinLevel_Low,
  KEY_PinLevel_High,
  KEY_PinLevel_Unknow,
} KEY_PinLevel_TypeDef;

//按键配置
typedef struct
{
  uint8_t KEY_Label;                 //按键标号
  uint16_t KEY_Count;                //按键按下计时
  KEY_Action_TypeDef KEY_Action;     //按键动作,按下或释放
  KEY_StatusList_TypeDef KEY_Status; //按键状态
  KEY_EventList_TypeDef KEY_Event;   //按键事件
} KEY_Configure_TypeDef;

extern KEY_Configure_TypeDef KeyConfig[TOTAL_KEYS];
extern KEY_EventList_TypeDef key_event[TOTAL_KEYS];
extern uint8_t double_click_mode;
extern uint8_t long_press_mode;

void KEY_ReadStateMachine(KEY_Configure_TypeDef *KeyCfg);

#endif

源文件 my_key.c

#include "my_key.h"

//单独封装函数方便移植
static KEY_PinLevel_TypeDef KEY_ReadPin(uint8_t key_label)
{
  switch (key_label)
  {
  case 1:
    return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(K1_GPIO_Port, K1_Pin);
  case 2:
    return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(K2_GPIO_Port, K2_Pin);
  case 3:
    return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(K3_GPIO_Port, K3_Pin);
  case 4:
    return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(K4_GPIO_Port, K4_Pin);
  case 5:
    return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(K5_GPIO_Port, K5_Pin);
  // case X:
  //   return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(KX_GPIO_Port, KX_Pin);
  }
  return KEY_PinLevel_Unknow;
}

KEY_Configure_TypeDef KeyConfig[TOTAL_KEYS] = {
    {1, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
    {2, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
    {3, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
    {4, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
    {5, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
    // {X, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
};

KEY_EventList_TypeDef key_event[TOTAL_KEYS] = {KEY_Event_Null};
uint8_t double_click_mode = 0; //双击模式
uint8_t long_press_mode = 1;   //长按模式
//按键状态处理
void KEY_ReadStateMachine(KEY_Configure_TypeDef *KeyCfg)
{
  //按键动作读取
  if (KEY_ReadPin(KeyCfg->KEY_Label) == KEY_PRESSED_LEVEL)
  {
    KeyCfg->KEY_Action = KEY_Action_Press;
  }
  else
  {
    KeyCfg->KEY_Action = KEY_Action_Release;
  }

  //状态机
  switch (KeyCfg->KEY_Status)
  {
  //状态:空闲
  case KEY_Status_Idle:
    if (KeyCfg->KEY_Action == KEY_Action_Press) //动作:按下
    {
      KeyCfg->KEY_Status = KEY_Status_Debounce; //状态->消抖
      KeyCfg->KEY_Event = KEY_Event_Null;       //事件->无
    }
    else //动作:默认动作,释放
    {
      KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲
      KeyCfg->KEY_Event = KEY_Event_Null;   //事件->无
    }
    break;

  //状态:消抖
  case KEY_Status_Debounce:
    if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_DEBOUNCE_TIME)) //动作:消抖时间已到,保持按下
    {
      KeyCfg->KEY_Count = 0;                        //计数清零
      KeyCfg->KEY_Status = KEY_Status_ConfirmPress; //状态->确认按下
      KeyCfg->KEY_Event = KEY_Event_Null;           //事件->无
    }
    else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_DEBOUNCE_TIME)) //动作:时间未到,保持按下
    {
      KeyCfg->KEY_Count++;                      //消抖计数
      KeyCfg->KEY_Status = KEY_Status_Debounce; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;       //事件->无
    }
    else //动作:消抖时间未到,释放,判定为抖动
    {
      KeyCfg->KEY_Count = 0;                //计数清零
      KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲
      KeyCfg->KEY_Event = KEY_Event_Null;   //事件->无
    }
    break;

  //状态:确认按下
  case KEY_Status_ConfirmPress:
    if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_LONG_PRESS_TIME)) //动作:长按时间已到,保持按下
    {
      KeyCfg->KEY_Count = KEY_QUICK_CLICK_TIME;         //计数置数,生成第一次连按事件
      KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->确认长按
      KeyCfg->KEY_Event = KEY_Event_Null;               //事件->无
    }
    else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_LONG_PRESS_TIME)) //动作:时间未到,保持按下
    {
      KeyCfg->KEY_Count++;                          //长按计数
      KeyCfg->KEY_Status = KEY_Status_ConfirmPress; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;           //事件->无
    }
    else //动作:长按时间未到,释放
    {
      if (double_click_mode) //双击模式
      {
        KeyCfg->KEY_Count = 0;                           //计数清零
        KeyCfg->KEY_Status = KEY_Status_WaitSecondPress; //状态->等待再按
        KeyCfg->KEY_Event = KEY_Event_Null;              //事件->无
      }
      else
      {
        KeyCfg->KEY_Count = 0;                     //计数清零
        KeyCfg->KEY_Status = KEY_Status_Idle;      //状态->空闲
        KeyCfg->KEY_Event = KEY_Event_SingleClick; //事件->单击****
      }
    }
    break;

  //状态:确认长按
  case KEY_Status_ConfirmPressLong:
    if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_QUICK_CLICK_TIME)) //动作:连按时间已到,保持按下
    {
      KeyCfg->KEY_Count = 0;                            //计数清零
      KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_QuickClick;         //事件->连按****
    }
    else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_QUICK_CLICK_TIME)) //动作:时间未到,保持按下
    {
      KeyCfg->KEY_Count++;                              //连按计数
      KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;               //事件->无
    }
    else //动作:长按下后释放
    {
      if (long_press_mode) //长按模式
      {
        KeyCfg->KEY_Count = 0;                   //计数清零
        KeyCfg->KEY_Status = KEY_Status_Idle;    //状态->空闲
        KeyCfg->KEY_Event = KEY_Event_LongPress; //事件->长按****
      }
      else //非长按模式
      {
        KeyCfg->KEY_Count = 0;                     //计数清零
        KeyCfg->KEY_Status = KEY_Status_Idle;      //状态->空闲
        KeyCfg->KEY_Event = KEY_Event_SingleClick; //事件->单击****
      }
    }
    break;

  //状态:等待是否再次按下
  case KEY_Status_WaitSecondPress:
    if ((KeyCfg->KEY_Action != KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_DOUBLE_CLICK_TIME)) //动作:双击等待时间已到,保持释放
    {
      KeyCfg->KEY_Count = 0;                     //计数清零
      KeyCfg->KEY_Status = KEY_Status_Idle;      //状态->空闲
      KeyCfg->KEY_Event = KEY_Event_SingleClick; //事件->单击****
    }
    else if ((KeyCfg->KEY_Action != KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_DOUBLE_CLICK_TIME)) //动作:时间未到,保持释放
    {
      KeyCfg->KEY_Count++;                             //双击等待计数
      KeyCfg->KEY_Status = KEY_Status_WaitSecondPress; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;              //事件->无
    }
    else //动作:双击等待时间内,再次按下
    {
      KeyCfg->KEY_Count = 0;                          //计数清零
      KeyCfg->KEY_Status = KEY_Status_SecondDebounce; //状态->再次消抖
      KeyCfg->KEY_Event = KEY_Event_Null;             //事件->无
    }
    break;

  //状态:再次消抖
  case KEY_Status_SecondDebounce:
    if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_DEBOUNCE_TIME)) //动作:消抖时间已到,保持按下
    {
      KeyCfg->KEY_Count = 0;                       //计数清零
      KeyCfg->KEY_Status = KEY_Status_SecondPress; //状态->确认再次按下
      KeyCfg->KEY_Event = KEY_Event_Null;          //事件->无
    }
    else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_DEBOUNCE_TIME)) //动作:时间未到,保持按下
    {
      KeyCfg->KEY_Count++;                            //消抖计数
      KeyCfg->KEY_Status = KEY_Status_SecondDebounce; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;             //事件->无
    }
    else //动作:消抖时间未到,释放,判定为抖动
    {
      KeyCfg->KEY_Count = 0;                //计数清零
      KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲
      KeyCfg->KEY_Event = KEY_Event_Null;   //事件->无
    }
    break;

  //状态:再次按下
  case KEY_Status_SecondPress:
    if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_LONG_PRESS_TIME)) //动作:长按时间已到,保持按下
    {
      if (long_press_mode) //长按模式
      {
        KeyCfg->KEY_Count = 0;                            //计数清零
        KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->确认长按
        KeyCfg->KEY_Event = KEY_Event_SingleClick;        //事件->先响应单击
      }
      else //非长按模式
      {
        KeyCfg->KEY_Count = 0;                       //计数清零
        KeyCfg->KEY_Status = KEY_Status_SecondPress; //状态->维持
        KeyCfg->KEY_Event = KEY_Event_Null;          //事件->无
      }
    }
    else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_LONG_PRESS_TIME)) //动作:长按时间未到,保持按下
    {
      KeyCfg->KEY_Count++;                         //计数
      KeyCfg->KEY_Status = KEY_Status_SecondPress; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;          //事件->无
    }
    else //动作:按下后释放
    {
      KeyCfg->KEY_Count = 0;                     //计数清零
      KeyCfg->KEY_Status = KEY_Status_Idle;      //状态->空闲
      KeyCfg->KEY_Event = KEY_Event_DoubleClick; //事件->双击
    }
    break;
  }

  if (KeyCfg->KEY_Event != KEY_Event_Null) //事件记录
    key_event[KeyCfg->KEY_Label] = KeyCfg->KEY_Event;
}

定时器中断和主函数调用
中断周期为1ms

uint32_t tim_cnt = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == htim1.Instance)
  {
    tim_cnt++;
    if (tim_cnt % 1 == 0) // 1ms
    {
      KEY_ReadStateMachine(&KeyConfig[KEY_1]);
      KEY_ReadStateMachine(&KeyConfig[KEY_2]);
      KEY_ReadStateMachine(&KeyConfig[KEY_3]);
      KEY_ReadStateMachine(&KeyConfig[KEY_4]);
      KEY_ReadStateMachine(&KeyConfig[KEY_5]);
    }
  }
}

//调用
int main(void)
{
  long_press_mode = 1;	 //使能长按模式
  double_click_mode = 1; //使能双击模式

  while (1)
  {
    if (key_event[KEY_1] == KEY_Event_SingleClick) //单击
    {
      something1();
    }
    if (key_event[KEY_2] == KEY_Event_LongPress) //长按
    {
      something2();
    }
    if ((key_event[KEY_3] == KEY_Event_QuickClick) || (key_event[KEY_3] == KEY_Event_SingleClick)) //连按
    {
      something3();
    }
    if (key_event[KEY_4] == KEY_Event_DoubleClick) //双击
    {
      something4();
    }
    memset(key_event, KEY_Event_Null, sizeof(key_event)); //清除事件
  }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

仅1月出刊:计算机科学类知网检索普刊

【欧亚科睿学术】 Journal of Computer Science and Electrical Engineering 《计算机科学与电气工程杂志》是一份同行评审期刊&#xff0c;发表计算机科学和电气工程几个领域的原创研究文章和综述文章。 它由UPUBSCIENCE出版社出版。它支持开放获取政策&#xff0c;即让所有…

vmdk to vhdx 虚拟磁盘格式转换qemu-img

qemu-img是创建、转换、修改磁盘映像的工具&#xff0c;我们可以用它非常方便的转换虚拟磁盘格式&#xff0c;比如在vmdk、vhdx、qcow2、vdi之间相互转换&#xff0c;它在流行的Linux、macOS、Windows平台上都发布有对应的版本。 本文介绍的是Windows版本&#xff0c;它支持下图…

【STM32入门教学】——串口、定时器与参考资料

机器人工程系列文章目录 这里罗列了系列文章链接 概念总述 STM入门教学 还没写完组里急用 文章目录 机器人工程系列文章目录概念总述STM入门教学 前言串口串口的概念cubemxkeil5实物实验关于cubemx生成逻辑printf升级usart.cmain.hretarget.c 定时器定时器的概念cubemxkeil5…

IDEA中使用Maven打包及碰到的问题

1. 项目打包 IDEA中&#xff0c;maven打包的方式有两种&#xff0c;分别是 install 和 package &#xff0c;他们的区别如下&#xff1a; install 方式 install 打包时做了两件事&#xff0c;① 将项目打包成 jar 或者 war&#xff0c;打包结果存放在项目的 target 目录下。…

医疗器械FDA | 医疗器械软件如何做源代码审计?

医疗器械网络安全测试https://link.zhihu.com/?targethttps%3A//www.wanyun.cn/Support%3Fshare%3D24315_ea8a0e47-b38d-4cd6-8ed1-9e7711a8ad5e 医疗器械源代码审计是一个确保医疗器械软件安全性和可靠性的重要过程。以下是医疗器械源代码审计的主要步骤和要点&#xff0c;以…

MIX OTP——依赖项和总体项目

在本章中&#xff0c;我们将讨论如何管理 Mix 中的依赖项。 我们的 kv 应用程序已经完成&#xff0c;现在是时候实现处理我们在第一章中定义的请求的服务器了&#xff1a; 但是&#xff0c;我们不会向 kv 应用程序添加更多代码&#xff0c;而是将 TCP 服务器构建为另一个应用程…

ROS2 rosbag2记录仪

rosbag2类似于行车记录仪&#xff0c;录制一段话题数据&#xff0c;录制完成后可以多次发布出来进行测试和实验&#xff0c;也可以将话题数据分享给别人用于验证算法等。 1.启动talker服务 ros2 run demo_nodes_cpp talker 2.记录话题数据 chatter ros2 bag record /chatte…

数据库操作-DML和DQL

DML DML英文全称是Data Manipulation Language(数据操作语言)&#xff0c;用来对数据库中表的数据记录进行增、删、改操作。 添加数据&#xff08;INSERT&#xff09; 1.指定字段添加数据&#xff1a; insert into 表名 ( 字段名 1, 字段名 2) values ( 值 1, 值 2); 2…

O2OA(翱途)开发平台 V9.1 即将发布,更安全、更高效、更开放

尊敬的O2OA(翱途)平台合作伙伴、用户以及亲爱的开发小伙伴们&#xff0c;O2OA(翱途)平台 V9.1将于7月3日正式发布&#xff0c;届时欢迎大家到O2OA官网部署下载及体验最新版本。新版本我们在如下方面做了更大的努力&#xff1a; 1.扩展数据库兼容性和功能范围&#xff1a;在O2OA…

[SwiftUI 开发] 嵌套的ObservedObject中的更改不会更新UI

1. 发生问题的demo 业务逻辑代码 class Address: ObservableObject {Published var street "123 Apple Street"Published var city "Cupertino" }class User: ObservableObject {Published var name "Tim Cook"Published var address Addr…

使用Python绘制动态螺旋线:旋转动画效果

文章目录 引言准备工作前置条件 代码实现与解析导入必要的库初始化Pygame绘制螺旋线函数主循环 完整代码 引言 螺旋线是一个具有美学和数学魅力的图形。通过编程&#xff0c;我们可以轻松创建动态旋转的螺旋线动画。在这篇博客中&#xff0c;我们将使用Python和Pygame库来实现…

XTDrone-固定翼无人机编队跟踪无人车-配置教程

配置使用ROS版本为Neotic 1 配置 1.1 加载固定翼无人机编队跟踪控制工程文件 cp -r ~/XTDrone/coordination/fixed_wing_formation_control ~/catkin_ws/src 1.2 加载一些用到的功能包 sudo apt-get install ros-noetic-serial #根据自己的ROS版本修改 sudo apt-get insta…

试用笔记之-免费的汇通餐饮管理软件

首先下载免费的汇通餐饮管理软件&#xff1a; http://www.htsoft.com.cn/download/htcanyin.exe 安装后的图标 登录软件&#xff0c;默认没有密码 汇通餐饮管理软件主界面 汇通餐饮软件前台系统 点菜

synchronized用法解析

锁的意义&#xff1a; 比如我跟我老弟要用电脑&#xff0c;我想学java&#xff0c;他想拿电脑打LOL&#xff0c;如果我敲java代码敲的正嗨皮&#xff0c;他突然把电脑抢了过去&#xff0c;代码还没保存&#xff0c;就被他拿去打LOL了&#xff0c;很✓8&#xff0c;那么如何解决…

【Arduino】XIAOFEIYU实验ESP32实验热敏电阻(图文)

今天XIAOFEIYU来实验一下ESP32使用热敏电阻传感器。 热敏电阻具有测试灵敏&#xff0c;测试范围大的特点&#xff0c;具有广泛的使用范围。常温器件适用于-55℃&#xff5e;315℃&#xff0c;高温器件适用温度高于315℃&#xff08;目前最高可达到2000℃&#xff09;&#xff…

[图解]SysML和EA建模住宅安全系统-07-to be块定义图

1 00:00:00,180 --> 00:00:06,820 我们来看&#xff0c;这是之前的那张图&#xff0c;现有的 2 00:00:08,290 --> 00:00:09,160 我们怎么做 3 00:00:09,170 --> 00:00:11,280 你看&#xff0c;我们之前 4 00:00:11,290 --> 00:00:15,600 在现状&#xff0c;as i…

Android AlertDialog对话框

目录 AlertDialog对话框普通对话框单选框多选框自定义框 AlertDialog对话框 部分节选自博主编《Android应用开发项目式教程》&#xff08;机械工业出版社&#xff09;2024.6 在Android中&#xff0c;AlertDialog弹出对话框用于显示一些重要信息或者需要用户交互的内容。 弹出…

GeoServer改造Springboot源码十(样式管理设计)

GeoServer改造Springboot源码一&#xff08;公共部分&#xff09; GeoServer改造Springboot源码二&#xff08;数据源管理设计&#xff09; GeoServer改造Springboot源码三&#xff08;数据源管理代码&#xff09; GeoServer改造Springboot源码四&#xff08;图层管理设计&a…

【知识】DGL中graph默认的稀疏矩阵格式和coo格式不对的坑

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 先给结论 源码解读 代码验证 网上没找到相关的讨论&#xff0c;因此只能从源码上一步步查。 先给结论 对于自己使用dgl.graph接口创建的图&am…

20240702在vmware17.5虚拟机中让ubuntu22.04使用主机的代理上网

20240702在vmware17.5虚拟机中让ubuntu22.04使用主机的代理上网 2024/7/2 14:41 百度&#xff1a;vmware 虚拟机 使用主机代理 上网 https://blog.csdn.net/nomoremorphine/article/details/138738065?utm_mediumdistribute.pc_relevant.none-task-blog-2~default~baidujs_ba…