/*
*********************************************************************************************************
*
*   模块名称 : 独立按键驱动模块 (外部输入IO)
*   文件名称 : bsp_key.c
*   版    本 : V1.3
*   说    明 : 扫描独立按键,具有软件滤波机制,具有按键FIFO。可以检测如下事件:
*               (1) 按键按下
*               (2) 按键弹起
*               (3) 长按键
*               (4) 长按时自动连发
*
*   修改记录 :
*       版本号  日期        作者     说明
*       V1.0    2020.02.27  armfly  KEY0、KEY1 和 KEY2 是低电平有效的,
*                                   而 KEY_UP 是高电平有效的,并且外部
*                                   都没有上下拉电阻,所以,需要在 STM32F4 内部设置上下拉。
*
*
*
*********************************************************************************************************
*/#include "bsp.h"/*正点原子探索者 按键口线分配:KEY0 键      : PE4   (低电平表示按下)KEY1 键     : PE3   (低电平表示按下)KEY2 键     : PE2   (低电平表示按下)KEY_UP 键   : PA0   (高电平表示按下)*/#define HARD_KEY_NUM     4                   /* 实体按键个数 */
#define KEY_COUNT           (HARD_KEY_NUM + 2) /* 4个独立建 + 2个组合按键 *//* 使能GPIO时钟 */
#define ALL_KEY_GPIO_CLK_ENABLE() { \__HAL_RCC_GPIOA_CLK_ENABLE();  \__HAL_RCC_GPIOE_CLK_ENABLE();  \};/* 依次定义GPIO */
typedef struct
{GPIO_TypeDef* gpio;uint16_t pin;uint8_t ActiveLevel;   /* 激活电平 */
}X_GPIO_T;/* GPIO和PIN定义 */
static const X_GPIO_T s_gpio_list[HARD_KEY_NUM] = {{GPIOE, GPIO_PIN_4, 0}, /* KEY0 */{GPIOE, GPIO_PIN_3, 0},   /* KEY1 */{GPIOE, GPIO_PIN_2, 0},   /* KEY2 */{GPIOA, GPIO_PIN_0, 1},   /* KEY_UP */
};  /* 定义一个宏函数简化后续代码 判断GPIO引脚是否有效按下
*/
static KEY_T s_tBtn[KEY_COUNT] = {0};  /* 按键对应的全局变量结构体 */
static KEY_FIFO_T s_tKey;       /* 按键FIFO变量,结构体 */static void bsp_InitKeyVar(void);
static void bsp_InitKeyHard(void);
static void bsp_DetectKey(uint8_t i);#define KEY_PIN_ACTIVE(id) /*
*********************************************************************************************************
*   函 数 名: KeyPinActive
*   功能说明: 判断按键是否按下
*   形    参: 无
*   返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)
*********************************************************************************************************
*/
static uint8_t KeyPinActive(uint8_t _id)
{uint8_t level;if ((s_gpio_list[_id].gpio->IDR & s_gpio_list[_id].pin) == 0){level = 0;}else{level = 1;}if (level == s_gpio_list[_id].ActiveLevel){return 1;}else{return 0;}
}/*
*********************************************************************************************************
*   函 数 名: IsKeyDownFunc
*   功能说明: 判断按键是否按下。单键和组合键区分。单键事件不允许有其他键按下。
*   形    参: 无
*   返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)
*********************************************************************************************************
*/
static uint8_t IsKeyDownFunc(uint8_t _id)
{/* 实体单键 */if (_id < HARD_KEY_NUM){uint8_t i;uint8_t count = 0;uint8_t save = 255;/* 判断有几个键按下 */for (i = 0; i < HARD_KEY_NUM; i++){if (KeyPinActive(i)) {count++;save = i;}}if (count == 1 && save == _id){return 1;  /* 只有1个键按下时才有效 */}      return 0;}/* 组合键 K0K1 */if (_id == HARD_KEY_NUM + 0){if (KeyPinActive(KID_K0) && KeyPinActive(KID_K1)){return 1;}else{return 0;}}/* 组合键 K1KU */if (_id == HARD_KEY_NUM + 1){if (KeyPinActive(KID_K1) && KeyPinActive(KID_KU)){return 1;}else{return 0;}}return 0;
}/*
*********************************************************************************************************
*   函 数 名: bsp_InitKey
*   功能说明: 初始化按键. 该函数被 bsp_Init() 调用。
*   形    参:  无
*   返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitKey(void)
{bsp_InitKeyVar();      /* 初始化按键变量 */bsp_InitKeyHard();     /* 初始化按键硬件 */
}/*
*********************************************************************************************************
*   函 数 名: bsp_InitKeyHard
*   功能说明: 配置按键对应的GPIO
*   形    参:  无
*   返 回 值: 无
*********************************************************************************************************
*/
static void bsp_InitKeyHard(void)
{   GPIO_InitTypeDef gpio_init;uint8_t i;/* 第1步:打开GPIO时钟 */ALL_KEY_GPIO_CLK_ENABLE();/* 第2步:配置所有的按键GPIO为浮动输入模式(实际上CPU复位后就是输入状态) */gpio_init.Mode = GPIO_MODE_INPUT;              /* 设置输入 */gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;  /* GPIO速度等级 */for (i = 0; i < HARD_KEY_NUM; i++){if(i<3)                                              /* KEY0 KEY1 KEY2 */{gpio_init.Pin = s_gpio_list[i].pin;gpio_init.Pull = GPIO_PULLUP;                 /* 上拉电阻 */HAL_GPIO_Init(s_gpio_list[i].gpio, &gpio_init);   }else                                               /* KEY_UP */{gpio_init.Pin = s_gpio_list[i].pin;gpio_init.Pull = GPIO_PULLDOWN;                 /* 下拉电阻 */HAL_GPIO_Init(s_gpio_list[i].gpio, &gpio_init);         }}
}/*
*********************************************************************************************************
*   函 数 名: bsp_InitKeyVar
*   功能说明: 初始化按键变量
*   形    参:  无
*   返 回 值: 无
*********************************************************************************************************
*/
static void bsp_InitKeyVar(void)
{uint8_t i;/* 对按键FIFO读写指针清零 */s_tKey.Read = 0;s_tKey.Write = 0;s_tKey.Read2 = 0;/* 给每个按键结构体成员变量赋一组缺省值 */for (i = 0; i < KEY_COUNT; i++){s_tBtn[i].LongTime = KEY_LONG_TIME;           /* 长按时间 0 表示不检测长按键事件 */s_tBtn[i].Count = KEY_FILTER_TIME / 2;      /* 计数器设置为滤波时间的一半 */s_tBtn[i].State = 0;                            /* 按键缺省状态,0为未按下 */s_tBtn[i].RepeatSpeed = 0;                        /* 按键连发的速度,0表示不支持连发 */s_tBtn[i].RepeatCount = 0;                        /* 连发计数器 */}/* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */}/*
*********************************************************************************************************
*   函 数 名: bsp_PutKey
*   功能说明: 将1个键值压入按键FIFO缓冲区。可用于模拟一个按键。
*   形    参:  _KeyCode : 按键代码
*   返 回 值: 无
*********************************************************************************************************
*/
void bsp_PutKey(uint8_t _KeyCode)
{s_tKey.Buf[s_tKey.Write] = _KeyCode;if (++s_tKey.Write  >= KEY_FIFO_SIZE){s_tKey.Write = 0;}
}/*
*********************************************************************************************************
*   函 数 名: bsp_GetKey
*   功能说明: 从按键FIFO缓冲区读取一个键值。
*   形    参: 无
*   返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bsp_GetKey(void)
{uint8_t ret;if (s_tKey.Read == s_tKey.Write){return KEY_NONE;}else{ret = s_tKey.Buf[s_tKey.Read];if (++s_tKey.Read >= KEY_FIFO_SIZE){s_tKey.Read = 0;}return ret;}
}/*
*********************************************************************************************************
*   函 数 名: bsp_GetKey2
*   功能说明: 从按键FIFO缓冲区读取一个键值。独立的读指针。
*   形    参:  无
*   返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bsp_GetKey2(void)
{uint8_t ret;if (s_tKey.Read2 == s_tKey.Write){return KEY_NONE;}else{ret = s_tKey.Buf[s_tKey.Read2];if (++s_tKey.Read2 >= KEY_FIFO_SIZE){s_tKey.Read2 = 0;}return ret;}
}/*
*********************************************************************************************************
*   函 数 名: bsp_GetKeyState
*   功能说明: 读取按键的状态
*   形    参:  _ucKeyID : 按键ID,从0开始
*   返 回 值: 1 表示按下, 0 表示未按下
*********************************************************************************************************
*/
uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID)
{return s_tBtn[_ucKeyID].State;
}/*
*********************************************************************************************************
*   函 数 名: bsp_SetKeyParam
*   功能说明: 设置按键参数
*   形    参:_ucKeyID : 按键ID,从0开始
*           _LongTime : 长按事件时间
*            _RepeatSpeed : 连发速度
*   返 回 值: 无
*********************************************************************************************************
*/
void bsp_SetKeyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t  _RepeatSpeed)
{s_tBtn[_ucKeyID].LongTime = _LongTime;            /* 长按时间 0 表示不检测长按键事件 */s_tBtn[_ucKeyID].RepeatSpeed = _RepeatSpeed;            /* 按键连发的速度,0表示不支持连发 */s_tBtn[_ucKeyID].RepeatCount = 0;                     /* 连发计数器 */
}/*
*********************************************************************************************************
*   函 数 名: bsp_ClearKey
*   功能说明: 清空按键FIFO缓冲区
*   形    参:无
*   返 回 值: 按键代码
*********************************************************************************************************
*/
void bsp_ClearKey(void)
{s_tKey.Read = s_tKey.Write;
}/*
*********************************************************************************************************
*   函 数 名: bsp_DetectKey
*   功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。
*   形    参: IO的id, 从0开始编码
*   返 回 值: 无
*********************************************************************************************************
*/
static void bsp_DetectKey(uint8_t i)
{KEY_T *pBtn;pBtn = &s_tBtn[i];if (IsKeyDownFunc(i))       /* 是否有按键i按下 */{if (pBtn->Count < KEY_FILTER_TIME){pBtn->Count = KEY_FILTER_TIME;}else if(pBtn->Count < 2 * KEY_FILTER_TIME){pBtn->Count++;}else{if (pBtn->State == 0){pBtn->State = 1;/* 发送按钮按下的消息 */bsp_PutKey((uint8_t)(3 * i + 1));}if (pBtn->LongTime > 0){if (pBtn->LongCount < pBtn->LongTime){/* 发送按钮持续按下的消息 */if (++pBtn->LongCount == pBtn->LongTime){/* 键值放入按键FIFO */bsp_PutKey((uint8_t)(3 * i + 3));}}else{if (pBtn->RepeatSpeed > 0){if (++pBtn->RepeatCount >= pBtn->RepeatSpeed){pBtn->RepeatCount = 0;/* 常按键后,每隔10ms发送1个按键 */bsp_PutKey((uint8_t)(3 * i + 1));}}}}}}else{if(pBtn->Count > KEY_FILTER_TIME){pBtn->Count = KEY_FILTER_TIME;}else if(pBtn->Count != 0){pBtn->Count--;}else{if (pBtn->State == 1){pBtn->State = 0;/* 发送按钮弹起的消息 */bsp_PutKey((uint8_t)(3 * i + 2));}}pBtn->LongCount = 0;pBtn->RepeatCount = 0;}
}/*
*********************************************************************************************************
*   函 数 名: bsp_DetectFastIO
*   功能说明: 检测高速的输入IO. 1ms刷新一次
*   形    参: IO的id, 从0开始编码
*   返 回 值: 无
*********************************************************************************************************
*/
static void bsp_DetectFastIO(uint8_t i)
{KEY_T *pBtn;pBtn = &s_tBtn[i];if (IsKeyDownFunc(i)){if (pBtn->State == 0){pBtn->State = 1;/* 发送按钮按下的消息 */bsp_PutKey((uint8_t)(3 * i + 1));}if (pBtn->LongTime > 0){if (pBtn->LongCount < pBtn->LongTime){/* 发送按钮持续按下的消息 */if (++pBtn->LongCount == pBtn->LongTime){/* 键值放入按键FIFO */bsp_PutKey((uint8_t)(3 * i + 3));}}else{if (pBtn->RepeatSpeed > 0){if (++pBtn->RepeatCount >= pBtn->RepeatSpeed){pBtn->RepeatCount = 0;/* 常按键后,每隔10ms发送1个按键 */bsp_PutKey((uint8_t)(3 * i + 1));}}}}}else{if (pBtn->State == 1){pBtn->State = 0;/* 发送按钮弹起的消息 */bsp_PutKey((uint8_t)(3 * i + 2));}pBtn->LongCount = 0;pBtn->RepeatCount = 0;}
}/*
*********************************************************************************************************
*   函 数 名: bsp_KeyScan10ms
*   功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用,10ms一次
*   形    参: 无
*   返 回 值: 无
*********************************************************************************************************
*/
void bsp_KeyScan10ms(void)
{uint8_t i;for (i = 0; i < KEY_COUNT; i++){bsp_DetectKey(i);}
}/*
*********************************************************************************************************
*   函 数 名: bsp_KeyScan1ms
*   功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用,1ms一次.
*   形    参: 无
*   返 回 值: 无
*********************************************************************************************************
*/
void bsp_KeyScan1ms(void)
{uint8_t i;for (i = 0; i < KEY_COUNT; i++){bsp_DetectFastIO(i);}
}/***************************** 安富莱电子 www.armfly.com (END OF FILE) *********************************//* 例程 *//* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
//  uint8_t ucKeyCode;      /* 按键代码 */
//  while(1)
//  {//      ucKeyCode = bsp_GetKey();  /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
//      if (ucKeyCode != KEY_NONE)
//      {//          switch (ucKeyCode)
//          {//              case KEY_DOWN_K0:           /* K0键按下 */
//                  bsp_LedToggle(0);
//                  break;
//              case KEY_DOWN_K1:           /* K0键按下 */
//                  bsp_LedToggle(1);
//                  break;
//              case SYS_DOWN_K1KU:         /* K1KU键按下 */
//                  bsp_LedOff(1);
//                  bsp_LedOff(0);
//                  break;
//              default:
//                  /* 其它的键值不处理 */
//                  break;
//          }
//
//      }
//  }
/*
*********************************************************************************************************
*
*   模块名称 : 按键驱动模块
*   文件名称 : bsp_key.h
*   版    本 : V1.0
*   说    明 : 头文件
*
*   Copyright (C), 2013-2014, 安富莱电子 www.armfly.com
*
*********************************************************************************************************
*/#ifndef __BSP_KEY_H
#define __BSP_KEY_H/* 根据应用程序的功能重命名按键宏 */#define KEY_DOWN_K0     KEY_1_DOWN      /*  KEY0  键 */
#define KEY_UP_K0       KEY_1_UP
#define KEY_LONG_K0     KEY_1_LONG#define KEY_DOWN_K1       KEY_2_DOWN      /*  KEY1  键 */
#define KEY_UP_K1       KEY_2_UP
#define KEY_LONG_K1     KEY_2_LONG#define KEY_DOWN_K2       KEY_3_DOWN      /*  KEY2  键 */
#define KEY_UP_K2       KEY_3_UP
#define KEY_LONG_K2     KEY_3_LONG#define KEY_DOWN_KU       KEY_4_DOWN      /* KEY_UP 键 */
#define KEY_UP_KU       KEY_4_UP
#define KEY_LONG_KU     KEY_4_LONG#define SYS_DOWN_K0K1 KEY_5_DOWN      /* K0 K1 组合键 */
#define SYS_UP_K0K1     KEY_5_UP
#define SYS_LONG_K0K1   KEY_5_LONG#define SYS_DOWN_K1KU KEY_6_DOWN      /* K1 KU 组合键 */
#define SYS_UP_K1KU     KEY_6_UP
#define SYS_LONG_K1KU   KEY_6_LONG/* 按键ID, 主要用于bsp_KeyState()函数的入口参数 */
typedef enum
{KID_K0 = 0,KID_K1,KID_K2,KID_KU,
}KEY_ID_E;/*按键滤波时间50ms, 单位10ms。只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件
*/
#define KEY_FILTER_TIME   5
#define KEY_LONG_TIME     100           /* 单位10ms, 持续1秒,认为长按事件 *//*每个按键对应1个全局的结构体变量。
*/
typedef struct
{/* 下面是一个函数指针,指向判断按键是否按下的函数 */uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */uint8_t  Count;           /* 滤波器计数器 */uint16_t LongCount;     /* 长按计数器 */uint16_t LongTime;       /* 按键按下持续时间, 0表示不检测长按 */uint8_t  State;         /* 按键当前状态(按下还是弹起) */uint8_t  RepeatSpeed; /* 连续按键周期 */uint8_t  RepeatCount;   /* 连续按键计数器 */
}KEY_T;/*定义键值代码, 必须按如下次序定时每个键的按下、弹起和长按事件推荐使用enum, 不用#define,原因:(1) 便于新增键值,方便调整顺序,使代码看起来舒服点(2) 编译器可帮我们避免键值重复。
*/
typedef enum
{KEY_NONE = 0,         /* 0 表示按键事件 */KEY_1_DOWN,               /* 1键按下 */KEY_1_UP,             /* 1键弹起 */KEY_1_LONG,               /* 1键长按 */KEY_2_DOWN,               /* 2键按下 */KEY_2_UP,             /* 2键弹起 */KEY_2_LONG,               /* 2键长按 */KEY_3_DOWN,               /* 3键按下 */KEY_3_UP,             /* 3键弹起 */KEY_3_LONG,               /* 3键长按 */KEY_4_DOWN,               /* 4键按下 */KEY_4_UP,             /* 4键弹起 */KEY_4_LONG,               /* 4键长按 *//* 组合键 */KEY_5_DOWN,              /* 5键按下 */KEY_5_UP,             /* 5键弹起 */KEY_5_LONG,               /* 5键长按 */KEY_6_DOWN,               /* 6键按下 */KEY_6_UP,             /* 6键弹起 */KEY_6_LONG,               /* 6键长按 */}KEY_ENUM;/* 按键FIFO用到变量 */
#define KEY_FIFO_SIZE   10
typedef struct
{uint8_t Buf[KEY_FIFO_SIZE];        /* 键值缓冲区 */uint8_t Read;                    /* 缓冲区读指针1 */uint8_t Write;                 /* 缓冲区写指针 */uint8_t Read2;                  /* 缓冲区读指针2 */
}KEY_FIFO_T;/* 供外部调用的函数声明 */
void bsp_InitKey(void);
void bsp_KeyScan10ms(void);
void bsp_PutKey(uint8_t _KeyCode);
uint8_t bsp_GetKey(void);
uint8_t bsp_GetKey2(void);
uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID);
void bsp_SetKeyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t  _RepeatSpeed);
void bsp_ClearKey(void);#endif/***************************** 安富莱电子 www.armfly.com (END OF FILE) *********************************/

stm32F407按键例程安富莱相关推荐

  1. 2.8 STM32_按键扫描_安富莱

    1.程序优点 扩展性非常强,功能比较齐全: bsp_key按键驱动程序用于扫描独立按键,具有软件滤波机制,采用FIFO机制保存键值.可以检测如下事件: 按键按下. 按键弹起. 长按键. 长按时自动连发 ...

  2. 《安富莱嵌入式周报》第295期:世界杯球员和足球实时跟踪,开源手持矢量网络分析仪,自制柔性电容式传感器,IAR加强对VSCode支持、索尼早期PSX的光驱模拟器

    往期周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - P ...

  3. 细数STM32开发板有哪些,官方板/正点原子/野火/安富莱等

    首先列举ST官方开发板.这些开发板功能由简到繁,价格也是.Nucleo板最便宜,而且自带ST-LINK/V2-1.评估板带有更多外设模块,比较方便开发.STM32中文官网上有很多开发板资源. STM3 ...

  4. 安富莱C语言编码规范

    本文为博主根据安富莱电子论坛的帖子整理而成,如有侵权,可联系删除 目录 1--文件与目录 2--排版 3 --注释 4 --可读性 5--变量.结构.常量.宏 6--函数 7.完整pdf文档下载链接 ...

  5. STM32开发,串口和PC机通信(串口中断、FIFO机制),安富莱+正点原子程序合并

    STM32开发,串口和PC机通信(串口中断.FIFO机制),安富莱+正点原子程序合并 1 概述 1.1 资源概述 1.2 实现功能 2 软件实现 2.1实现步骤 2.2 main()函数代码 2.3 ...

  6. 【安富莱】RTX嵌入式操作系统教程发布,支持F103,F407和F429,含81个配套例程(2017-10-17)...

    前言说明: 1. 首先感谢大家对我们安富莱电子一年来的支持,2016年我们会再接再厉推出更好的教程. 2. 估计也有网友会问RTX的优势在那里,针对这个问题,教程中第一章分为6条专门回答了这个问题,有 ...

  7. 《安富莱嵌入式周报》第269期:2022.06.06--2022.06.12

    往期周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - P ...

  8. 《安富莱嵌入式周报》第280期:支持在线仿真编程的网页版电子开发,CAN总线防攻击实现,BigFAT 规范打破了 FAT 每个文件 4GB 的限制

    往期周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - P ...

  9. 《安富莱嵌入式周报》第266期:真正模拟DA神的威力,全开源nV级测量仪表挑战赛结束,欣赏震撼设计过程

    往期周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - P ...

最新文章

  1. Java 客户端界面功能:停止当前操作
  2. Set Up a Simple Knowledge Base
  3. Java jdbctemplate赋值_Spring 之 Spring JdbcTemplate的使用
  4. 制作Slider组件
  5. 深入理解Java:SimpleDateFormat安全的时间格式化 ;
  6. Python对json数据的操作(香烟示例)
  7. java正则表达式提取需要的字符并放入数组
  8. 用什么的SQL语句来查的一个表中有相同的记录条数
  9. Lobooi个人作业:阅读与准备作业
  10. 商业级激光打标源代码 因为已经开发出更强大的激光打标系统。改改就可以应用到自已的系统中去。
  11. C51与MDK共存 Keil5安装教程 WIN10 亲测可用
  12. matlab 离散求极值,Matlab离散数据点,求极值,有现成的函数吗
  13. [shell][原创]shell脚本遍历文件夹下所有文件
  14. 微信公众号 语音转文字api_微信语音快速转文字功能,方便实用
  15. Foxmail设置的学习
  16. 怎么做好备件管理?备件管理系统都包括哪些功能模块?
  17. 使用缓存django、redis
  18. 中山大学2021级研究生学术道德规范在线考试学习资料
  19. LuoguP4313 BZOJ3894 文理分科——最小割
  20. 产品和技术解决方案_成功的产品负责人挑战技术解决方案

热门文章

  1. C语言第六章函数实验报告,第六章实验报告
  2. python如何计算概率事件_145、Python实现概率分布
  3. Android Wifi移植
  4. 每一个圣人都有一个过去
  5. 正版-WPS下载地址(免费)
  6. [附源码]计算机毕业设计springboot动物保护协会网站
  7. mac上安装R和RStudio
  8. L型与R型思维的特征
  9. Linux指定网卡名称
  10. oracle 11g duplicate database基于备份复制数据库(五)