nrf52840是一款低功耗的蓝牙芯片,它支持USB HID,可以作为一个HID Keyboard进行使用。
USB HID(Human Interface Device)是一种USB设备类别,它定义了常见的人机界面设备,如键盘、鼠标、游戏手柄等。HID Keyboard是指通过USB接口连接的键盘设备,与普通的键盘相比,它可以通过程序控制模拟键盘输出。
在nrf52840上实现USB HID Keyboard的关键是使用USB Device Stack,该协议栈提供了对USB HID协议的支持。实现步骤包括:初始化USB协议栈、注册USB HID设备、注册USB endpoint以及设置和处理USB HID Report。
需要注意的是,nrf52840的USB接口只支持USB 2.0 Full Speed(12 Mbps),如果需要更高的传输速率,则需要使用蓝牙连接。
我使用Nordic 52840的usb hid keyboard实现数据输出,实际键盘上的每个按键按下和释放都会是8个字节的数据。
Byte0 | reserve | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
下表描述的是特殊功能键Byte0里面的每个bit所代表的含义,也就是平常所说的modifier
bit位 | 按键名 | 按下 | 释放 | Byte0值 |
bit0 | Left Control | 1 | 0 | 0x01 |
bit1 | Left Shift | 1 | 0 | 0x02 |
bit2 | Left Alt | 1 | 0 | 0x04 |
bit3 | Left GUI | 1 | 0 | 0x08 |
bit4 | Right Control | 1 | 0 | 0x10 |
bit5 | Right Shift | 1 | 0 | 0x20 |
bit6 | Right Alt | 1 | 0 | 0x40 |
bit7 | Right GUI | 1 | 0 | 0x80 |
Byte1保留,普通按键定义在Byte2–Byte7内
举个简单的例子:在CapsLock没有打开的情况下,需要输出字符大写A,那么该按键按下时候发出的数据结构为:
0x02 0x00 0x04 0x00 0x00 0x00 0x00 0x00(A)
按键释放的时候:
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00(全部弹起)
这就是1个A发送时按键按下和弹起的时候主机所收到的数据。(keyboard表可以参照下面这位大佬发的)
USB协议中HID设备描述符以及键盘按键值对应编码表_usb hid键盘码表的完整列表。-CSDN博客
那么我需要实现的由两种按下和释放的形式
如:1asKW93(接收到数据字符的ASCII转换成keyboar对应的modifier和scan_key之后存放在send_data[MAX_SIZE]数组里面)
由于八个字节里面每个按键都有1个对应的Modifier和键值,我就把这个的modifier和键值存放在1个结构体里面
typedef unsigned char uint8_t;
#define MAX_SIZE 1024
typedef struct
{
uint8_t modifier;//特殊键 byte0
uint8_t keynum;//普通键
}single_keyboard;
//定义了1个存放由ASCII码值转换成hid_keyboard的需要发送的所有键的数组
single_keyboard send_data[MAX_SIZE];
//设置modifier的状态的函数 norid例程官方提供
ret_code_t app_usbd_hid_kbd_modifier_state_set(app_usbd_hid_kbd_t const * p_kbd,
app_usbd_hid_kbd_modifier_t modifier,
bool state)
{
app_usbd_hid_kbd_ctx_t * p_kbd_ctx = hid_kbd_ctx_get(p_kbd);
bool actual_state = (p_kbd_ctx->rep.modifier & modifier) != 0;
if (actual_state == state)
{
/*Modifier has already the same state*/
return NRF_SUCCESS;
}
app_usbd_hid_access_lock(&p_kbd_ctx->hid_ctx);
if (state)
{
p_kbd_ctx->rep.modifier |= modifier;
}
else
{
p_kbd_ctx->rep.modifier &= ~modifier;
}
app_usbd_hid_access_unlock(&p_kbd_ctx->hid_ctx);
if (app_usbd_hid_trans_required(&p_kbd_ctx->hid_ctx))
{
/*New transfer need to be triggered*/
return hid_kbd_transfer_set(p_kbd);
}
return NRF_SUCCESS;
}
//发送scan_key的函数 Nordic例程官方提供
ret_code_t app_usbd_hid_kbd_key_control(app_usbd_hid_kbd_t const * p_kbd,
app_usbd_hid_kbd_codes_t key,
bool press)
{
app_usbd_hid_kbd_ctx_t * p_kbd_ctx = hid_kbd_ctx_get(p_kbd);
uint8_t * destination = NULL;
if (press)
{
for (size_t i = 0; i < ARRAY_SIZE(p_kbd_ctx->rep.key_table); ++i)
{
if (p_kbd_ctx->rep.key_table[i] == key)
{
/*Already pressed*/
return NRF_SUCCESS;
}
if ((destination == NULL) && (p_kbd_ctx->rep.key_table[i] == 0))
{
destination = &p_kbd_ctx->rep.key_table[i];
}
}
if (destination == NULL)
{
return NRF_ERROR_BUSY;
}
}
else
{
/*Find if key is pressed*/
for (size_t i = 0; i < ARRAY_SIZE(p_kbd_ctx->rep.key_table); ++i)
{
if (p_kbd_ctx->rep.key_table[i] == key)
{
destination = &p_kbd_ctx->rep.key_table[i];
break;
}
}
if (destination == NULL)
{
/*Key hasn't been pressed*/
return NRF_SUCCESS;
}
}
/*Save destination*/
app_usbd_hid_access_lock(&p_kbd_ctx->hid_ctx);
*destination = press ? key : 0;
app_usbd_hid_access_unlock(&p_kbd_ctx->hid_ctx);
if (app_usbd_hid_trans_required(&p_kbd_ctx->hid_ctx))
{
/*New transfer need to be triggered*/
return hid_kbd_transfer_set(p_kbd);
}
return NRF_SUCCESS;
}
//按下时发送键值和modifier/释放时释放键值不释放modifier
ret_code_t usbd_hid_signal_key_send(app_usbd_hid_kbd_t const * p_kbd,
single_keyboard key,
bool press)
{
app_usbd_hid_kbd_ctx_t * p_kbd_ctx = hid_kbd_ctx_get(p_kbd);
memset(p_kbd_ctx->rep.key_table,0,sizeof(p_kbd_ctx->rep.key_table));
/*Save destination*/
app_usbd_hid_access_lock(&p_kbd_ctx->hid_ctx);
if(press)
{
p_kbd_ctx->rep.key_table[0] = key.keynum;
p_kbd_ctx->rep.modifier = key.modifier;
}
else
{
p_kbd_ctx->rep.modifier = key.modifier;//按键释放的时候保存其键值
}
app_usbd_hid_access_unlock(&p_kbd_ctx->hid_ctx);
if (app_usbd_hid_trans_required(&p_kbd_ctx->hid_ctx))
{
//一般除了第一次发送,其他情况不会走这,而是走hid中断的那边
/*New transfer need to be triggered*/
return hid_kbd_transfer_set(p_kbd);
}
return NRF_SUCCESS;
}
/*方案一:无论需要实现什么字符发送,都是按下后立刻释放,如在CapsLock关闭的情况下,需要显示XY,那么按下和释放的顺序就是shift⬇x⬇shift⬆x⬆shift⬇y⬇shift⬆y⬆*/
void main()
{
int data_length = 0;
data_length = ascii_to_hidkbd("1asKW93",7);//data_length = 7
do
{
if(可以按下按键)
{
app_usbd_hid_kbd_modifier_state_set(&hid_keboard_instance,send_data[i].modifier,true);
app_usbd_hid_kbd_key_control(&hid_keboard_instance,send_data[i].keynum,true);
}
else if(可以释放按键)
{
app_usbd_hid_kbd_modifier_state_set(&hid_keboard_instance,send_data[i].modifier,false);
app_usbd_hid_kbd_key_control(&hid_keboard_instance,send_data[i].keynum,false);
}
}
while(i<data_length)
}
/*方案二:如果下一个需要发送的数据modifier和当前发送的数据的modifier相同,那么这个特殊键不释放,只释放普通键,如在CapsLock关闭的情况下,需要显示XY,那么按下和释放的顺序就是shift⬇x⬇x⬆y⬇shift⬆y⬆*/
void main()
{
int data_length = 0;
data_length = ascii_to_hidkbd("1asKW93",7);//data_length = 7
do
{
if(可以按下按键)
{
app_usbd_hid_kbd_modifier_state_set(&hid_keboard_instance,send_data[i].modifier,true);
app_usbd_hid_kbd_key_control(&hid_keboard_instance,send_data[i].keynum,true);
}
else if(可以释放按键)
{
usbd_hid_signal_key_send(&hid_keboard_instance,send_data[i],false);
}
}
while(i<data_length)
/*释放最后一个键*/
app_usbd_hid_kbd_modifier_state_set(&hid_keboard_instance,send_data[i-1].modifier,false);
app_usbd_hid_kbd_key_control(&hid_keboard_instance,0x00,false);//发送1个空包
}
A字符:
按下时发送:
0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x02 0x00 0x04 0x00 0x00 0x00 0x00 0x00
释放时发送:
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
a字符:
按下时发送:0x00 0x00 0x04 0x00 0x00 0x00 0x00 0x00
释放时发送:0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
在CTRL+X模式打开的情况下CTRL+F:
按下时发送:0x01 0x00 0x09 0x00 0x00 0x00 0x00 0x00
释放时发送:0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
数字1:
按下时候发送:0x00 0x00 0x1E 0x00 0x00 0x00 0x00 0x00
释放时发送:0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
疑惑:为什么发送一串字符串"adAoFWx81"的时候只有在发送A的时候会先单独发送shift的键值包0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x00之后再发送0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x00,但是在发送F字符的时候只发送0x02 0x00 0x09 0x00 0x00 0x00 0x00 0x00,希望由看到的大佬可以指点一下,万分感谢!!!!!!