目录
前言
硬件环境
USB协议
Digispark介绍
Attiny85介绍
开始前准备
1. 安装Arduino 兼容板设备文件
2. 安装下载驱动
模拟键盘
模拟鼠标
下载到开发板
前言
硬件环境
开发板名称 | MCU | Flash闪存大小 |
Digispark | Attiny85 | 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_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键 | 右 |
这里说一下左右是什么意思,这里给大家看一张键盘图
大家可以看到上图键布局中,左侧与右侧都有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功能检查代码是否有错
模拟鼠标
针对鼠标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演示