目录

前言 

硬件环境

USB协议

Digispark介绍

Attiny85介绍

开始前准备

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

 2. 安装下载驱动

模拟键盘

模拟鼠标

下载到开发板


前言 

硬件环境

开发板名称

MCU

Flash闪存大小

Digispark

Attiny85

6KB

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

arduino android 模拟键盘_stm32

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

arduino android 模拟键盘_Arduino_02

 Tools-Board:"Arduino Uno"-Boards Manager

arduino android 模拟键盘_Digispark_03

 2. 安装下载驱动

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

arduino android 模拟键盘_Digispark_04

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

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

arduino android 模拟键盘_#define_05

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

模拟键盘

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

arduino android 模拟键盘_#define_06

 模拟键盘我们可以使用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_LEFT

CONTROL键


MOD_SHIFT_LEFT

SHIFT键


MOD_ALT_LEFT

ALT键


MOD_GUI_LEFT

Windows键


MOD_CONTROL_RIGHT

CONTROL键


MOD_SHIFT_RIGHT

SHIFT键


MOD_ALT_RIGHT

ALT键


MOD_GUI_RIGHT

Windows键


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

arduino android 模拟键盘_Digispark_07

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

键定义

作用

键定义

作用

KEY_A

A键

KEY_B

B键

KEY_C

C键

KEY_D

D键

KEY_E

E键

KEY_F

F键

KEY_G

G键

KEY_H

H键

KEY_I

I键

KEY_J

J键

KEY_K

K键

KEY_L

L键

KEY_M

M键

KEY_N

N键

KEY_O

O键

KEY_P

P键

KEY_Q

Q键

KEY_R

R键

KEY_S

S键

KEY_U

U键

KEY_V

V键

KEY_W

W键

KEY_X

X键

KEY_Z

Z键

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功能检查代码是否有错

arduino android 模拟键盘_Arduino_08

模拟鼠标

针对鼠标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的驱动以及开发板是插在电脑上的

arduino android 模拟键盘_Digispark_09

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

arduino android 模拟键盘_#define_10

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

arduino android 模拟键盘_Arduino_11

 上传完成会出现如下画面

arduino android 模拟键盘_模拟键盘鼠标_12

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

视频演示


Digispark演示