This tutorial was extracted from Erich Styger blog http://mcuoneclipse.wordpress.com with his agreement.
I miss my old DELL laptop. Ok, the new one I received from IT services is not bad. It is faster and has a better screen. But I’m not really happy with the new keyboard. With the previous keyboard I was able to do a ‘PrtnScrn’ with a single key press. With the new one I need to press Fn + PrntScrn. And this is impossible to do with one hand:
Yes, I have two hands . But many times I need to do ‘print screen’ while having my other hand on the mouse .What else can I do?
Exploring Options
Of course there are several options:
- Call someone in my office and say “hey, can you give me a helping hand?”. Works, but my co-workers will hate me for this.
- Reconfigure my laptop keyboard mapping. Doable, but then the writing on the keyboard does not match the reality any more which can be confusing.
- Remapping the print screen functionality in SnagIt (the screen capture tool I’m using). That would be too easy
- Using the FRDM-KL25Z board as USB keyboard
As you might have guessed from this post title: I’m going for option 4 .
This gives me the added value that I can do anything I want: having my own shortcuts, doing sequences of key press actions, and so on.
In the next steps I explain how to turn the FRDM-KL25Z into a USB HID Keyboard device with CodeWarrior for MCU10.4 and Processor Expert Components. A link to the project is posted at the end of this article.
Processor Expert Components
Make sure you have the latest Processor Expert components loaded (zip file attached to this post). Instructions how to load the components is explained in this post.
Creating CodeWarrior KL25Z Project
Create a new Processor Expert project in CodeWarrior using the menu File > New > BareBoard Project:
- Provide a name for the project
- Select the MKL25Z128 under Kinetis L Series > KL2x > KL25Z
- Select OpenSDA as connection
- In ‘Language and Build Tools Options’, the No I/O option can be selected for reduced footprint
- Select Processor Expert under Rapid Application Development
- Finished
USB Clock Settings
The USB clock needs to be set at 48 MHz. For this, I open the Inspector on the Cpu:
- Enable ‘System oscillator 0′. This enables it to use the 8 MHz external crystal:
- Set the MCG clock settings to PEE (PLL Engaged External) with 96 MHz:
- Set the core clock to 48 MHz and the bus clock to 24 MHz:
With this I have properly configured the microcontroller to work for USB.
Adding USB Stack
The next step is to add the USB stack. I have created the FSL_USB_Stack Processor Expert component which is a wrapper for the Freescale bare metal USB stack. This USB stack can be used as well with an RTOS like FreeRTOS too. The FSL_USB_Stack component now supports HID, CDC and MSD device classes.
From the Components Library view, I add the component to my project (double-click on the component):
By default, it is added for CDC. So I need to configure it for USB HID Keyboard device class.
I select the FSL_USB_Stack and configure it:
- USB is using Init_USB_OTG_VAR0: this one is used for Kinetis/ARM.
- Device Class: HID Keyboard Device.
- In the HID Keyboard field, I choose FSL_USB_HID_Keyboard_Device.
FSL_USB_Stack Configured for USB HID Keyboard
Next, I select the Init_USB_OTG component and configure it:
- Enabled clock gate (otherwise the USB module is not clocked).
- Set PLL/FLL as clock source (has to be 48 MHz, that’s why we configured the CPU clock in the previous step for 48 MHz).
Init Component Configured
Finally, configure the FSL_USB_HID_Keyboard_Device CPU:
Now all the error markers should disappear as I finished configuring the component.
FSL_USB_HID_Keyboard_Device Component
The component has two more settings which are used to report the device to the host:
The name is used in the Windows Device manager:
The component features a ring buffer which is used to store keyboard events to be sent later:
The buffer entries are 16bit each (2 bytes), because a keyboard event has one byte for the ‘modifier’ (e.g. SHIFT pressed) and the key itself (e.g. ‘a’). More about this later. By default the buffer is set up for 16 entries: so I can buffer up to 16 items (or key changes). If you always send just one key, then you can reduce this number to a lower number, say 4 (to be safe).
USB HID Keyboard Protocol
The HID Keyboard device has to send a report to the host. That report is an array of 8 bytes describing the current key status:
- Byte 0: Modifier byte which encodes CTRL, SHIFT, ALT and GUI keys (8 bits are defined for this)
- Byte 1: unused
- Byte 2-7: 6 bytes with key codes. With this it is possible to report up 6 plus the modifier keys pressed at the same time.
Typically only Byte 0 and Byte 2 are used. So for example to send a USB HID report that ‘a’ is pressed, I send
Byte 0: 0x00 (no modifier) Byte 2: 0x04 (is the code for key 'a') all other bytes are 0x00
To send the a report that ‘A’ is pressed (which is SHIFT+a):
Byte 0: 0x02 (left SHIFT key pressed) Byte 2: 0x04 (is the code for key 'a') all other bytes are 0x00
It is important to note that these reports are ‘keys are down’ reports. And as long as I do not send a new report, the last sent report is still active (or key pressed). So I need to send a message for the ‘release’ event, which is a report with all bytes zero:
Byte 0: 0x00 (no modifier key pressed) Byte 2: 0x00 (no key pressed) all other bytes are 0x00
The Key Usage codes are documented in chapter 10 of the HID Usage Tables of the USB Standard Document.
The FSL_USB_HID_Keyboard_Device Processor Expert component I have implemented makes it really easy to use.
FSL_USB_HID_Keyboard_Device Interface
The component offers the following methods and events:
- App_Task() needs be called periodically. With this the elements from the ring buffer are sent to the USB bus. Call this method after you have used any of the Send methods. This routine returns ERR_OK if the device has been enumerated (is connected to the host).
- SendStr() can be used to send an ASCII string. This method translates the string into USB HID messages and places them into the ring buffer to be processed by App_Task().
- SendChar() is the same as SendStr(), but sends a single character only.
- Send() is used to send native USB HID code. As for the other Send routines, it places the item into the buffer to be processed later by
App_Task()
.
Examples how to use it:
for
(;;) {
if
(HIDK1_App_Task()==ERR_OK) {
/* run the USB application task: this will send the buffer */
/* ok: we are connected! */
(
void
)HIDK1_SendStr((unsigned
char
*)
"Hello!"
);
/* send a string */
}
}
I’m ignoring here the return code of SendStr(). It would return ERR_TXFULL in case the ring buffer is not able to store the string. In that case either increase the buffer size, or send smaller strings and call
App_Task()
in between.
Once the device is connected and has finished enumeration, App_Task()
will return ERR_OK
. Then the applications places the string “Hello!” in the ring buffer, which is sent to the host in the nextApp_Task()
call, as if would have typed it on the keyboard.
It happened to me that such test code writes text to my host machine, potentially overwriting my stuff. I recommend to have a notepad window open (with active focus) so the board can write into that space instead overwriting your sources in the editor
you might get a ‘+’ character instead of the ‘!’. This is because my driver does not support different keyboard layouts and mappings. Contributions are more than welcome
To send a single ASCII character is simple:
(
void
)HIDK1_SendChar(
'A'
);
/* send the A character */
Knowing the USB CDC protocol, it cannot send just the ‘A’ character. USB CDC defines the ‘a’ (and ‘A’) keyboard character with the value 0×04:
#define KEY_A 0x04
The information if it is ‘a’ or ‘A’ is encoded as ‘modifier’: if the SHIFT key is pressed or not. And there are several modifier flags available in the header file of the component:
#define MODIFERKEYS_NONE 0x00
#define MODIFERKEYS_LEFT_CTRL 0x01
#define MODIFERKEYS_LEFT_SHIFT 0x02
#define MODIFERKEYS_LEFT_ALT 0x04
#define MODIFERKEYS_LEFT_GUI 0x08
#define MODIFERKEYS_RIGHT_CTRL 0x10
#define MODIFERKEYS_RIGHT_SHIFT 0x20
#define MODIFERKEYS_RIGHT_ALT 0x40
#define MODIFERKEYS_RIGHT_GUI 0x80
So to send ‘A’, I need to send MODIFERKEYS_LEFT_SHIFT
and KEY_A
.
This information is put together by SendChar()
using the hidKeyCode
table:
byte HIDK1_SendChar(byte ch)
{
if
(ch&0x7F) {
/* only handle 0x00..0x7F */
Tx2_Put(hidKeyCode[ch]);
/* put 16bit value (modifier|code) into buffer */
}
Tx2_Put((MODIFERKEYS_NONE<<8)|KEY_NONE);
/* send release message */
return
ERR_OK;
}
Note that the above code uses a second Put()
with MODIFIERKEYS_NONE
and KEY_NONE
which is a ‘keys release’ message (actually it says ‘no keys pressed’). The HID protocol sends the actual state of a key. So if I send the information ‘the A key is pressed’ the host assumes that I still have that key pressed until I send another message about the new key. If I would not send that release message, the host assumes that the key ‘A’ is still pressed, and repeats the character. That’s why I send a ‘release’ message right afterwards to tell the host that all keys have been released.
SendChar()
and SendStr()
perform automatic ASCII to HID code translation. If I want to send HID codes directly, I can use the method Send()
of the component:
byte HIDK1_Send(byte modifier, byte key)
{
return
Tx2_Put((modifier<<8)|key);
/* put 16bit value (modifier|code) into buffer */
}
So for example if I want to send my ‘print screen’ action, I do it wit:
HIDK1_Send(MODIFERKEYS_NONE, KEY_PRINTSCREEN);
HIDK1_Send(MODIFERKEYS_NONE, KEY_NONE);
/* release key */
And if I need CTRL+ALT+DELETE, then it is
HIDK1_Send(MODIFERKEYS_LEFT_CTRL|MODIFERKEYS_RIGHT_ALT, KEY_DELETE);
HIDK1_Send(MODIFERKEYS_NONE, KEY_NONE);
/* release key */
The Application
Now I have everything in place. What I want is an application which sends the ‘Print Screen’ key event to the host with a single button press. For this I re-use the reset button of my FRDM-KL25Z (see this post how to do this). Then to add LEDs to show the status of the USB enumeration (see this post how to add LED’s). Adding some mechanics for LED blinking and button debouncing, and voilà:
for
(;;) {
WAIT1_Waitms(10);
cnt++;
if
(SW1_GetVal()==0) {
/* button pressed */
WAIT1_Waitms(100);
/* wait for debouncing */
if
(SW1_GetVal()==0) {
/* still pressed */
/* send print screen */
HIDK1_Send(MODIFERKEYS_NONE, KEY_PRINTSCREEN);
HIDK1_Send(MODIFERKEYS_NONE, KEY_NONE);
/* release key */
}
while
(SW1_GetVal()==0) {}
/* wait until button is released */
}
if
(HIDK1_App_Task()==ERR_OK) {
/* run the USB application task: this will send the buffer */
if
((cnt%100)==0) {
LEDR_Off();
LEDG_Neg();
/* blink green LED if connected */
}
}
else
{
if
((cnt%200)==0) {
LEDG_Off();
LEDR_Neg();
/* blink red LED if not connected */
}
}
}
The red RGB LED blinks if not connected to the host, and the green one if it is connected as USB HID Keyboard. And when I press the reset button, it sends the ‘Print Screen’ key to the host. Finally I can do ‘print screen’ with my left hand while having my right hand on the (FRDM-KL25Z) keyboard :mrgeen:
Summary
With the creation of the USB HID Keyboard Device Processor Expert component, I have turned my FRDM-KL25Z init a generic USB keyboard device. With a simple button press I can send any keyboard actions to my laptop, making such as ‘print screen’ a single button press again. So it makes it a great device even for users with disabilities. And my ‘disability’ is solved now too: I get a print screen with the press of single button :
Other ideas I have in mind: I thinking of turning the FRDM board into a wireless presenter. All what I need on top of this is either Bluetooth (but this would need to be a Bluetooth HID Keyboard), or maybe just using two FRDM boards: one is the keyboard dongle, and the other is sending data over the wireless communication channels. Still thinking about the best solution, and ideas are welcome.
The project created above (along with the components) are available in attachement to this post. While it does not support international keyboard mappings, that project has been very useful for me. I hope it is useful for you too.