详解如何基于Arduino兼容板Digispark实现虚拟键盘与鼠标

Posted 17岁boy想当攻城狮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解如何基于Arduino兼容板Digispark实现虚拟键盘与鼠标相关的知识,希望对你有一定的参考价值。

目录

前言 

硬件环境

USB协议

Digispark介绍

Attiny85介绍

开始前准备

1. 安装Arduino 兼容板设备文件

 2. 安装下载驱动

模拟键盘

模拟鼠标

下载到开发板


前言 

理论知识:什么是Arduino?Arduino的起源与架构简述_17岁boy的博客-CSDN博客

硬件环境

开发板名称

MCU

Flash闪存大小

DigisparkAttiny85

6KB

这里选择Digispark的原因是Digispark兼容Arduino,并且提供了一套针对键盘的库函数(其实这些库函数都是基于Arduino提供的函数实现的),并且它的接口形式就是USB形式的,方便使用,也不需要外接USB线

USB协议

是一种通讯协议,用于特定硬件,可以基于此协议模拟键盘、鼠标任何一款是USB设备的硬件,它仅需两根PIN脚就可以完成通讯,它有两种类型,一种是USB输入设备,一种是USB输出设备

在开始时计算机并不知道你插入的USB是键盘还是鼠标,这个是无法识别的,操作系统只能通过协议识别出你是输入设备还是输出设备,当你是输入设备时后续的通讯就要通过输入协议来工作,这个时候可以根据协议头来识别你输入的是什么,如这个时候发送键盘协议那么操作系统就认为你是键盘,你输入鼠标协议那么操作系统就会认为你是鼠标,当你是输出设备时只能有操作系统给你发送协议

Digispark介绍

Digispark是基于Attiny85 MCU实现的一个迷你开发板,它提供的功能非常有限,它的目的就是为了实现虚拟USB,所以它的总线接口以USB的规范来接的,所以我们只需要按照USB通讯方式进行通讯就可以模拟任何一款USB设备,它的硬件架构都是按照USB框架制作的,在开始时它就会给操作系统发送默认的消息,来证明它是一个输入设备

Attiny85介绍

Attiny85是Atmel公司开发的一款迷你MCU,引脚仅8个,但是成本非常廉价,售价是2美刀,8个引脚对于USB设备已经足够了,因为USB只需要2根引脚就可以进行通讯

开始前准备

1. 安装Arduino 兼容板设备文件

首先需要在Arduino上安装Digispark兼容板的描述文件:http://digistump.com/package_digistump_index.json 添加到描述文件上

File-Preferences

 Tools-Board:"Arduino Uno"-Boards Manager

 2. 安装下载驱动

驱动下载地址:Releases · digistump/DigistumpArduino · GitHub

 Digis目前只提供了Windows上的驱动安装文件,如果是别的系统,可以下载Source code源码编译生成

根据你当前的系统位数选择对应的型号安装:

 安装完成后重启电脑就可以使用下载驱动了

模拟键盘

在开始之前选中你的板子为Digispark,选中之后Arduino会自动更改引脚地址以及加载设备附带的开发库

 模拟键盘我们可以使用Digispark提供的DigiKeyboard库,只需要在代码中包含头文件就可以了

#include "DigiKeyboard.h"

其实这些库都是C语言写的,我们可以打开它的源文件看一下:

(路径:你的Arduino安装路径\\Arduino15\\packages\\digistump\\hardware\\avr\\1.6.7\\libraries\\DigisparkKeyboard\\DigiKeyboard.h)

/*
 * Based on Obdev's AVRUSB code and under the same license.
 *
 * TODO: Make a proper file header. :-)
 * Modified for Digispark by Digistump
 */
#ifndef __DigiKeyboard_h__
#define __DigiKeyboard_h__
 
#include <Arduino.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/delay.h>
#include <string.h>
 
#include "usbdrv.h"
#include "scancode-ascii-table.h"
 
// TODO: Work around Arduino 12 issues better.
//#include <WConstants.h>
//#undef int()
 
typedef uint8_t byte;
 
 
#define BUFFER_SIZE 2 // Minimum of 2: 1 for modifiers + 1 for keystroke
 
 
static uchar    idleRate;           // in 4 ms units

其实Arduino就是用Java写了一个解释器,然后这个解释器去调用C/C++语言编译器去编译库,和Makefile一样,所以Arduino的编译速度会慢许多

可以在往下看看,可以看到许多按键宏

/* Keyboard usage values, see usb.org's HID-usage-tables document, chapter
 * 10 Keyboard/Keypad Page for more codes.
 */
#define MOD_CONTROL_LEFT    (1<<0)
#define MOD_SHIFT_LEFT      (1<<1)
#define MOD_ALT_LEFT        (1<<2)
#define MOD_GUI_LEFT        (1<<3)
#define MOD_CONTROL_RIGHT   (1<<4)
#define MOD_SHIFT_RIGHT     (1<<5)
#define MOD_ALT_RIGHT       (1<<6)
#define MOD_GUI_RIGHT       (1<<7)
 
#define KEY_A       4
#define KEY_B       5
#define KEY_C       6
#define KEY_D       7
#define KEY_E       8
#define KEY_F       9
#define KEY_G       10
#define KEY_H       11
#define KEY_I       12
#define KEY_J       13
#define KEY_K       14
#define KEY_L       15
#define KEY_M       16
#define KEY_N       17
#define KEY_O       18
#define KEY_P       19
#define KEY_Q       20
#define KEY_R       21
#define KEY_S       22
#define KEY_T       23
#define KEY_U       24
#define KEY_V       25
#define KEY_W       26
#define KEY_X       27
#define KEY_Y       28
#define KEY_Z       29
#define KEY_1       30
#define KEY_2       31
#define KEY_3       32
#define KEY_4       33
#define KEY_5       34
#define KEY_6       35
#define KEY_7       36
#define KEY_8       37
#define KEY_9       38
#define KEY_0       39
 
#define KEY_ENTER   40
 
#define KEY_SPACE   44
 
#define KEY_F1      58
#define KEY_F2      59
#define KEY_F3      60
#define KEY_F4      61
#define KEY_F5      62
#define KEY_F6      63
#define KEY_F7      64
#define KEY_F8      65
#define KEY_F9      66
#define KEY_F10     67
#define KEY_F11     68
#define KEY_F12     69
 
#define KEY_ARROW_LEFT 0x50

在往下看看就能看到它的类封装函数,它的库代码是使用C++编写而成

class DigiKeyboardDevice : public Print {
 public:
  DigiKeyboardDevice () {
    cli();
    usbDeviceDisconnect();
    _delay_ms(250);
    usbDeviceConnect();
 
 
    usbInit();
       
    sei();
 
    // TODO: Remove the next two lines once we fix
    //       missing first keystroke bug properly.
    memset(reportBuffer, 0, sizeof(reportBuffer));     
    usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
  }
     
  void update() {
    usbPoll();
  }
     
    // delay while updating until we are finished delaying
    void delay(long milli) {
        unsigned long last = millis();
      while (milli > 0) {
        unsigned long now = millis();
        milli -= now - last;
        last = now;
        update();
      }
    }
   
  //sendKeyStroke: sends a key press AND release
  void sendKeyStroke(byte keyStroke) {
    sendKeyStroke(keyStroke, 0);
  }
 
  //sendKeyStroke: sends a key press AND release with modifiers
  void sendKeyStroke(byte keyStroke, byte modifiers) {
    sendKeyPress(keyStroke, modifiers);
    // This stops endlessly repeating keystrokes:
    sendKeyPress(0,0);
  }
 
  //sendKeyPress: sends a key press only - no release
  //to release the key, send again with keyPress=0
  void sendKeyPress(byte keyPress) {
    sendKeyPress(keyPress, 0);
  }
 
  //sendKeyPress: sends a key press only, with modifiers - no release
  //to release the key, send again with keyPress=0
  void sendKeyPress(byte keyPress, byte modifiers) {
    while (!usbInterruptIsReady()) {
      // Note: We wait until we can send keyPress
      //       so we know the previous keyPress was
      //       sent.
        usbPoll();
        _delay_ms(5);
    }
     
    memset(reportBuffer, 0, sizeof(reportBuffer));
         
    reportBuffer[0] = modifiers;
    reportBuffer[1] = keyPress;
     
    usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
  }
   
  size_t write(uint8_t chr) {
    uint8_t data = pgm_read_byte_near(ascii_to_scan_code_table + (chr - 8));
    sendKeyStroke(data & 0b01111111, data >> 7 ? MOD_SHIFT_RIGHT : 0);
    return 1;
  }
     
  //private: TODO: Make friend?
  uchar    reportBuffer[2];    // buffer for HID reports [ 1 modifier byte + (len-1) key strokes]
  using Print::write;
};

可以看到每个函数的原型,以及作用都写了出来,那么我们就可以根据这些信息来写我们的代码了

其中我们不需要声明我们自己的类的,因为在头文件中可以看到这一条代码

DigiKeyboardDevice DigiKeyboard = DigiKeyboardDevice();

我们可以在Arduino里直接使用DigiKeyboard这个变量来完成函数调用

这里我将函数整理了一下:

API

函数原型

作用

返回值

void delay(long milli) 延迟,毫秒为单位
void sendKeyStroke(byte keyStroke)发送按键并释放
void sendKeyStroke(byte keyStroke, byte modifiers) 发送带有关联按键和释放,第一个参数是主键,第二个键是按住的键
void sendKeyPress(byte keyPress)发送按键但不释放,若要释放在次调用此函数并且参数keyPress=0
void sendKeyPress(byte keyPress, byte modifiers)发送带有关联案件,但不释放,若要释放在次调用此函数并且参数keyPress=0

按键

键定义

作用

方向

MOD_CONTROL_LEFTCONTROL键
MOD_SHIFT_LEFTSHIFT键
MOD_ALT_LEFTALT键
MOD_GUI_LEFTWindows键
MOD_CONTROL_RIGHTCONTROL键
MOD_SHIFT_RIGHTSHIFT键
MOD_ALT_RIGHTALT键
MOD_GUI_RIGHTWindows键

这里说一下左右是什么意思,这里给大家看一张键盘图

大家可以看到上图键布局中,左侧与右侧都有Alt与Ctrl键,这个在键盘上功能是一样的,但是硬件键代码不一样,所以有左右区分

键定义

作用

键定义

作用

KEY_AA键
KEY_BB键
KEY_CC键
KEY_DD键
KEY_EE键
KEY_FF键
KEY_GG键
KEY_HH键
KEY_II键
KEY_JJ键
KEY_KK键
KEY_LL键
KEY_MM键
KEY_NN键
KEY_OO键
KEY_PP键
KEY_QQ键
KEY_RR键
KEY_SS键
KEY_UU键
KEY_VV键
KEY_WW键
KEY_XX键
KEY_ZZ键
KEY_1数字1键
KEY_2数字2键
KEY_3数字3键
KEY_4数字4键
KEY_5数字5键
KEY_6数字6键
KEY_7数字7键
KEY_8数字8键
KEY_9数字9键
KEY_0数字0键

在setup函数里写我们的驱动代码,写一个简单的调用,就是打开文本记事本然后输入一行hello word

// put your setup code here, to run once:
  DigiKeyboard.delay(2000);
  DigiKeyboard.sendKeyStroke(MOD_GUI_LEFT);
  DigiKeyboard.delay(300);
  DigiKeyboard.println("hello word");

在Linux上调用终端也很简单,调用终端的快捷键是ctrl+alt+t

DigiKeyboard.sendKeyStroke(KEY_R, MOD_CONTROL_LEFT | MOD_ALT_LEFT);

调出终端后你就可以随意模拟键盘输入命令了

完整代码:

#include "DigiKeyboard.h"
void setup() {
  // put your setup code here, to run once:
  DigiKeyboard.delay(2000);
  DigiKeyboard.sendKeyStroke(MOD_GUI_LEFT);
  DigiKeyboard.delay(300);
  DigiKeyboard.println("hello word");
}
 
void loop() {
  // put your main code here, to run repeatedly:
 
}

USB设备可以在Bios上运行,因为BIOS自带USB驱动,可以在任何支持USB驱动的设备上运行,也就是说不需要操作系统也一样可以跑

我们也可以不使用digikeyboard提供的键盘库,我们可以使用Arduino提供的引脚控制函数来完成模拟键盘输入,因为Digispark里面有一个小代码,它在启动时会自动与目标设备交互,把自己识别为一个USB输入设备,那么剩下的就是通过引脚发送数据了,这个方面需要去看一下Digispark开发文档中PIN脚的对应关系,然后输出对应的协议就可以了。

在写好代码以后可以通过verify功能检查代码是否有错

模拟鼠标

针对鼠标Digispark提供了DigiMouse库,可以使用这个库来完成模拟鼠标的操作

(文件路径:你的Arduino安装路径\\Arduino15\\packages\\digistump\\hardware\\avr\\1.6.7\\libraries\\DigisparkMouse\\DigiMouse.h) 

API

函数原型

作用

返回值

void delay(long milli)延迟,毫秒为单位
void moveX(char deltaX)移动X坐标
void moveY(char deltaY)移动Y坐标
void scroll(char deltaS)滚动齿轮
void move(char deltaX, char deltaY, char deltaS)控制鼠标
void begin()开始鼠标监听,这个函数必须首先调用

这里说一下为什么鼠标要先调用begin函数,而键盘不用,这个原因是因为键盘在按下按键时是向目标设备发送键代码,而鼠标不一样,鼠标是每次移动鼠标时鼠标硬件会将传感器的数据写入到寄存器中等待被读取,begin函数就是告诉操作系统你要主动来读取寄存器的值,上述的API都是修改寄存器的值,这里我们可以拆开moveX函数看一下

void moveX(char deltaX) {
        if (deltaX == -128) deltaX = -127;
        last_built_report[1] = *(reinterpret_cast<unsigned char *>(&deltaX));
    }
     
void moveY(char deltaY) {
        if (deltaY == -128) deltaY = -127;
        last_built_report[2] = *(reinterpret_cast<unsigned char *>(&deltaY));
    }
void scroll(char deltaS)    {
        if (deltaS == -128) deltaS = -127;
        last_built_report[3] = *(reinterpret_cast<unsigned char *>(&deltaS)); 
    }

可以看到上面代码中last_built_report是一个数组,X、Y、齿轮值都由一个数组下标控制,这个数组里保存了X、Y、齿轮寄存器的地址,所以每次设置它都在设置对应寄存器的值

 而我们拆开键盘API看一下

void sendKeyStroke(byte keyStroke) {
    sendKeyStroke(keyStroke, 0);
  }

可以看到内部调用了sendKeyStroke这个API来发送键代码

DigiMouse头文件中也实例化了一个变量,方便我们调用

// create the global singleton DigiMouse
DigiMouseDevice DigiMouse = DigiMouseDevice();

下面一段代码演示让鼠标移动的代码

鼠标会以500毫秒间隔慢慢从x为0的坐标移动到10000

DigiMouse.begin();
for (int x = 0; x < 1000; x++) {
      DigiMouse.moveX(x);
      DigiMouse.delay(500);
    }

完整代码:

#include "DigiMouse.h"
void setup() {
  // put your setup code here, to run once:
  DigiMouse.begin();
  for (int x = 0; x < 1000; x++) {
      DigiMouse.moveX(x);
      DigiMouse.delay(500);
    }
}
 
void loop() {
  // put your main code here, to run repeatedly:
 
}

下载到开发板

使用upload功能即可,在下载之前需要确保电脑安装了Digispark的驱动以及开发板是插在电脑上的

Digispark下载到开发板有一个注意事项,就是下载时需要你进行热拔插一次,这个原因是因为Digispark只有一个接口

这个接口要用来做下载接口与USB通讯接口,Digispark的设计不支持同时进行,所以Digispark的引导代码会先启动前五秒一直闪烁上面的LED灯,表示当前是处于下载状态,可以通过上位机进行下载,若五秒内没有下载则转入执行Flash闪存代码,所以在下载时Arduino在调用驱动时会提示你在60秒内热拔插一次

 上传完成会出现如下画面

 传输完成之后你就可以将Digispark板子拔掉了,然后插入等待五秒后会自动执行你编写的代码

视频演示

Digispark演示

以上是关于详解如何基于Arduino兼容板Digispark实现虚拟键盘与鼠标的主要内容,如果未能解决你的问题,请参考以下文章

详解如何基于Arduino兼容板Teensy LC实现虚拟键盘与鼠标

详解如何基于Arduino兼容板Teensy LC实现虚拟键盘与鼠标

badusb-digispark-ATTINY85

基于Arduino框架下VSCode PlatformIO一个项目配置两种不同开发板的兼容模式

Arduino--开发板简介(一)

Digispark(ATTINY85) 微型开发板驱动安装与开发环境配置教程