[EN] MicroPython : pyb USB_HID/USB_VCP

This article discusses the use of USB HID capabilities of  STM32F411CEU6 via MicroPython in pyb class, our team was inspired by Dr. Rawat’s article MicroPython for STM32F411 Black Pill: Embedded Programming Style of Siriphokaphirom. So let’s start compiling and uploading the firmware (Read details from Ajarn Dr. Rawat’s article). We use the WeAct STM32F411CEU6 imitation board as shown in Figure 1. When installing MicroPython, there will be a pyb class to use.

Figure 1 Our board

Sawasdee (Hello)

Let’s start by running a Sawasdee program that reads the details of the board and it’s better to test it around to find prime numbers. The program code is as follows. The sample result is as shown in Figure 2.

import os
import gc
import sys
import time as tm
import machine as mc

gc.enable()
gc.collect()

def isPrime(x):
    i = 2
    while (i < x):
        if x%i == 0:
            return False
        i = i+1
    if (i == x):
        return True
    return False

def testPrimeNumber(maxN):
    counter = 0
    t0 = tm.ticks_ms() #tm.monotonic()
    for n in range(2, maxN):
        if isPrime(n):
            counter+=1
    t1 = tm.ticks_ms() #tm.monotonic()
    print("testPrineNumner({}) ... Found {} in {} msecs.".format(maxN,counter,abs(t1-t0)))


def show_hw_info():
    uname = os.uname()
    mem_total = gc.mem_alloc()+gc.mem_free()
    free_percent = "("+str((gc.mem_free())/mem_total*100.0)+"%)"
    alloc_percent = "("+str((gc.mem_alloc())/mem_total*100.0)+"%)"
    stat = os.statvfs('/flash')
    block_size = stat[0]
    total_blocks = stat[2]
    free_blocks  = stat[3]
    rom_total = (total_blocks * block_size)/1024
    rom_free = (free_blocks * block_size)/1024
    rom_usage = (rom_total-rom_free)
    rfree_percent = "("+str(rom_free/rom_total*100.0)+"%)"
    rusage_percent = "("+str(rom_usage/rom_total*100.0)+"%)"
    print("Platform ......:",sys.implementation[0])
    print("Version .......:",sys.version)
    print("Frequency")
    print("   CPU ........: {}Hz".format(mc.freq()[0]))
    print("   AHB ........: {}Hz".format(mc.freq()[1]))
    print("   APB1 .......: {}Hz".format(mc.freq()[2]))
    print("   APB2 .......: {}Hz".format(mc.freq()[3]))
    print("Memory")
    print("   total ......:",mem_total/1024,"KB")
    print("   usage ......:",gc.mem_alloc()/1024,"KB",alloc_percent)
    print("   free .......:",gc.mem_free()/1024,"KB",free_percent)
    print("ROM")
    print("   total ......:", rom_total,"KB" )
    print("   usage ......:", rom_usage,"KB",rfree_percent )
    print("   Free .......:", rom_free,"KB",rusage_percent )
    print("system name ...:",uname.sysname)
    print("node name .....:",uname.nodename)
    print("release .......:",uname.release)
    print("version .......:",uname.version)
    print("machine .......:",uname.machine)

if __name__=='__main__':
    show_hw_info()
    testPrimeNumber(2000)

Figure 2 Result from the program

From Figure 2, we can see that the MicroPython that we installed is the model 1.17-134-gcv99ca986 compiled on 2021-11-01, 85KB of SRAM is available, 4.609375KB is used, 80.375KB is left, and ROM is 42.0kb for data and programs.

If you want to read more in-depth information such as ID, DEVID, REVID, or memory location values, you can use the pyb class info() method as follows and will get an example as shown in Figure 3.

import pyb
print(pyb.info())
Figure 3 Result of pyb’s info()

As reported in Figure 3, MicroPython itself uses LFS or LittleFS to manage partitions and disks in the Flash ROM with 43008 bytes of usable space, which corresponds to the example ‘Sawasdee’. The values S, H, P1 and P2 are found to be the clock speed of the CPU, AHB, APB1 and APB2 and are single-threaded.

To check the list of modules supported by the board, run the following command.

help(“modules”)

A list of all available PyBoard classes will be displayed as shown in Figure 4.

Figure 4 microPython’s modules for STM32F411CEU6

The list of classes, methods, and constants within pyb is as follows:

object <module 'pyb'> is of type module
  __name__ -- pyb
  fault_debug -- <function>
  bootloader -- <function>
  hard_reset -- <function>
  info -- <function>
  unique_id -- <function>
  freq -- <function>
  repl_info -- <function>
  wfi -- <function>
  disable_irq -- <function>
  enable_irq -- <function>
  stop -- <function>
  standby -- <function>
  main -- <function>
  repl_uart -- <function>
  country -- <function>
  usb_mode -- <function>
  hid_mouse -- (1, 2, 4, 8, b'\x05\x01\t\x02\xa1\x01\t\x01\xa1\x00\x05\t\x19\x01)\x03\x15\x00%\x01\x95\x03u\x01\x81\x02\x95\x01u\x05\x81\x01\x05\x01\t0\t1\t8\x15\x81%\x7fu\x08\x95\x03\x81\x06\xc0\t<\x05\xff\t\x01\x15\x00%\x01u\x01\x95\x02\xb1"u\x06\x95\x01\xb1\x01\xc0')
  hid_keyboard -- (1, 1, 8, 8, b'\x05\x01\t\x06\xa1\x01\x05\x07\x19\xe0)\xe7\x15\x00%\x01u\x01\x95\x08\x81\x02\x95\x01u\x08\x81\x01\x95\x05u\x01\x05\x08\x19\x01)\x05\x91\x02\x95\x01u\x03\x91\x01\x95\x06u\x08\x15\x00%e\x05\x07\x19\x00)e\x81\x00\xc0')
  USB_HID -- <class 'USB_HID'>
  USB_VCP -- <class 'USB_VCP'>
  have_cdc -- <function>
  hid -- <function>
  millis -- <function>
  elapsed_millis -- <function>
  micros -- <function>
  elapsed_micros -- <function>
  delay -- <function>
  udelay -- <function>
  sync -- <function>
  mount -- <function>
  dht_readinto -- <function>
  Timer -- <class 'Timer'>
  RTC -- <class 'RTC'>
  Pin -- <class 'Pin'>
  ExtInt -- <class 'ExtInt'>
  pwm -- <function>
  servo -- <function>
  Servo -- <class 'Servo'>
  Switch -- <class 'Switch'>
  Flash -- <class 'Flash'>
  LED -- <class 'LED'>
  I2C -- <class 'I2C'>
  SPI -- <class 'SPI'>
  UART -- <class 'UART'>
  ADC -- <class 'ADC'>
  ADCAll -- <class 'ADCAll'>

And from all this list, In the article, we are only interested in the part of USB.

USB in pyb

What we’re interested in in this article is the implementation of STM32F411CEU6. Accessing this property requires a call to the pyb class and the following list of classes and functions will be found.

  • Class
    • USB_HID for ketboard or mouse
    • USB_VCP for using the USB port as a serial port (VCP = Virtual COMM Port) without using a converter module. USB-to-Serial
  • Function
    • usb_mode() for reading USB mode, which has 5 modes:
      • ‘VCP’ enable VCP mode or serial port virtual mode. Enables the board’s USB port as a serial communication port between the board and the connected devices. Also known as CDC mode.
      • ‘MSC’ run in MSC (Mass Storage Class) mode or act as virtual storage or known as Flash Drive
      • ‘VCP+MSC’ for VCP and MSC modes
      • ‘VCP+HID’ for VCP and HID (Human Interface Device) modes
      • ‘VCP+MSC+HID’ for VCP, MSC and HID modes
    • usb_mode([modestr, ]port=-1, vid=0xf055, pid=-1, msc=(), hid=pyb.hid_mouse, high_speed=False) to set the USB operation mode, but if you want to turn off the USB mode, set modestr=None.
      • port is the numeric value of the port number. Usually has values 0,1,2,3,… and if set to -1 will be the auto-received port number.
      • msc defines a device that operates in MSC mode. Usually set to () means no device, but if pyb.Flash() or pyb.SDCard() is specified, MSC is used from the Flash ROM memory or from the board’s SDCard port ( comes with PyB, but can connect additional circuits) or use both sources to be defined as msc=(pyb.flash(), pyb.SDCard())
      • hid defines the mode of operation of HID, usually set to mouse or pyb.hid_mouse. But if you want to change to a keyboard, set it to pyb.hid_keyboard
      • high_speed turns on USB HS mode by setting it to True
    • hid((btn,x,y,z)) for submit button and mouse movement in the x,y and z axes
    • have_cdc() returns True or False if the board’s USB is connected to a serial device.
  • The simulator property value is mouse or keyboard. This is the value used for the hid parameter of the usb_mode() command.
    • hid_mouse contains mouse HID
    • hid_keyboard contains keyboard HID

First, let’s test the USB mode readings with the following sample program.

import pyb

print("CDC/VCP support = {}".format(pyb.have_cdc()))
print("USB Mode = {}".format(pyb.usb_mode()))

When running the program, you will get an example of the working result as shown in Figure 5.

Figure 5 Result when read USB mode

Use VCP

Setup does not work in REPL mode or MicroPython run mode. Because the USB port must be used for communication and Flash ROM as storage. And when working in this mode, the USB_VCP part is also used. This makes the VCP output behave like a print statement, except the output must be a string. Therefore, a message “Hello, I’m” followed by the platform name could be written as follows:

import pyb
import math
import sys
from pyb import Pin

usb = pyb.USB_VCP()
usb.write("Hello, I'm {}\n".format(sys.platform))
usb.write("Press [UKEY] to exit!\n")

sw = Pin('PA0', Pin.IN)
while not (1-sw.value()):
    pyb.delay(20)
usb.write("End of Program.")

An example of the result is shown in Figure 6.

Figure 6 Result from pyb.USB_VCP.write()

keyboard simulator

For USB_VCP to perform full function Or change the USB mode, it must be modified in the boot.py file to change the behavior from boot, for example, it needs to run in VCP+HID mode to simulate itself as a keyboard. The code can be modified as follows:

import pyb
#pyb.usb_mode('VCP+MSC')
pyb.usb_mode('VCP+HID',hid=pyb.hid_keyboard)

Sending data can be done with the example of the following set of commands.

import pyb
hid = pyb.USB_HID()
buf = bytearray(8)
buf[2] = keyboard code
hid.send(buf) # 
buf[2] = 0
hid.send(buf) # 

For a 2-key transmission, it can be written as follows:

import pyb
hid = pyb.USB_HID()
buf = bytearray(8)
buf[0] = keycode1
buf[2] =  keycode2
hid.send( buf )
buf[0] = 0
buf[2] = 0
hid.send( buf )

keycode

KEY_NONE = 0x00
KEY_ERRORROLLOVER = 0x01
KEY_POSTFAIL = 0x02
KEY_ERRORUNDEFINED = 0x03
KEY_A = 0x04
KEY_B = 0x05
KEY_C = 0x06
KEY_D = 0x07
KEY_E = 0x08
KEY_F = 0x09
KEY_G = 0x0A
KEY_H = 0x0B
KEY_I = 0x0C
KEY_J = 0x0D
KEY_K = 0x0E
KEY_L = 0x0F
KEY_M = 0x10
KEY_N = 0x11
KEY_O = 0x12
KEY_P = 0x13
KEY_Q = 0x14
KEY_R = 0x15
KEY_S = 0x16
KEY_T = 0x17
KEY_U = 0x18
KEY_V = 0x19
KEY_W = 0x1A
KEY_X = 0x1B
KEY_Y = 0x1C
KEY_Z = 0x1D
KEY_1_EXCLAMATION_MARK = 0x1E
KEY_2_AT = 0x1F
KEY_3_NUMBER_SIGN = 0x20
KEY_4_DOLLAR = 0x21
KEY_5_PERCENT = 0x22
KEY_6_CARET = 0x23
KEY_7_AMPERSAND = 0x24
KEY_8_ASTERISK = 0x25
KEY_9_OPARENTHESIS = 0x26
KEY_0_CPARENTHESIS = 0x27
KEY_ENTER = 0x28
KEY_ESCAPE = 0x29
KEY_BACKSPACE = 0x2A
KEY_TAB = 0x2B
KEY_SPACEBAR = 0x2C
KEY_MINUS_UNDERSCORE = 0x2D
KEY_EQUAL_PLUS = 0x2E
KEY_OBRACKET_AND_OBRACE = 0x2F
KEY_CBRACKET_AND_CBRACE = 0x30
KEY_BACKSLASH_VERTICAL_BAR = 0x31
KEY_NONUS_NUMBER_SIGN_TILDE = 0x32
KEY_SEMICOLON_COLON = 0x33
KEY_SINGLE_AND_DOUBLE_QUOTE = 0x34
KEY_GRAVEACCENTANDTILDE = 0x35
KEY_COMMA_AND_LESS = 0x36
KEY_DOT_GREATER = 0x37
KEY_SLASH_QUESTION = 0x38
KEY_CAPSLOCK = 0x39
KEY_F1 = 0x3A
KEY_F2 = 0x3B
KEY_F3 = 0x3C
KEY_F4 = 0x3D
KEY_F5 = 0x3E
KEY_F6 = 0x3F
KEY_F7 = 0x40
KEY_F8 = 0x41
KEY_F9 = 0x42
KEY_F10 = 0x43
KEY_F11 = 0x44
KEY_F12 = 0x45
KEY_PRINTSCREEN = 0x46
KEY_SCROLLLOCK = 0x47
KEY_PAUSE = 0x48
KEY_INSERT = 0x49
KEY_HOME = 0x4A
KEY_PAGEUP = 0x4B
KEY_DELETE = 0x4C
KEY_END1 = 0x4D
KEY_PAGEDOWN = 0x4E
KEY_RIGHT_ARROW = 0x4F
KEY_LEFT_ARROW = 0x50
KEY_DOWN_ARROW = 0x51
KEY_UP_ARROW = 0x52
KEY_KEYPAD_NUM_LOCK_AND_CLEAR = 0x53
KEY_KEYPAD_SLASH = 0x54
KEY_KEYPAD_ASTERIKS = 0x55
KEY_KEYPAD_MINUS = 0x56
KEY_KEYPAD_PLUS = 0x57
KEY_KEYPAD_ENTER = 0x58
KEY_KEYPAD_1_END = 0x59
KEY_KEYPAD_2_DOWN_ARROW = 0x5A
KEY_KEYPAD_3_PAGEDN = 0x5B
KEY_KEYPAD_4_LEFT_ARROW = 0x5C
KEY_KEYPAD_5 = 0x5D
KEY_KEYPAD_6_RIGHT_ARROW = 0x5E
KEY_KEYPAD_7_HOME = 0x5F
KEY_KEYPAD_8_UP_ARROW = 0x60
KEY_KEYPAD_9_PAGEUP = 0x61
KEY_KEYPAD_0_INSERT = 0x62
KEY_KEYPAD_DECIMAL_SEPARATOR_DELETE = 0x63
KEY_NONUS_BACK_SLASH_VERTICAL_BAR = 0x64
KEY_APPLICATION = 0x65
KEY_POWER = 0x66
KEY_KEYPAD_EQUAL = 0x67
KEY_F13 = 0x68
KEY_F14 = 0x69
KEY_F15 = 0x6A
KEY_F16 = 0x6B
KEY_F17 = 0x6C
KEY_F18 = 0x6D
KEY_F19 = 0x6E
KEY_F20 = 0x6F
KEY_F21 = 0x70
KEY_F22 = 0x71
KEY_F23 = 0x72
KEY_F24 = 0x73
KEY_EXECUTE = 0x74
KEY_HELP = 0x75
KEY_MENU = 0x76
KEY_SELECT = 0x77
KEY_STOP = 0x78
KEY_AGAIN = 0x79
KEY_UNDO = 0x7A
KEY_CUT = 0x7B
KEY_COPY = 0x7C
KEY_PASTE = 0x7D
KEY_FIND = 0x7E
KEY_MUTE = 0x7F
KEY_VOLUME_UP = 0x80
KEY_VOLUME_DOWN = 0x81
KEY_LOCKING_CAPS_LOCK = 0x82
KEY_LOCKING_NUM_LOCK = 0x83
KEY_LOCKING_SCROLL_LOCK = 0x84
KEY_KEYPAD_COMMA = 0x85
KEY_KEYPAD_EQUAL_SIGN = 0x86
KEY_INTERNATIONAL1 = 0x87
KEY_INTERNATIONAL2 = 0x88
KEY_INTERNATIONAL3 = 0x89
KEY_INTERNATIONAL4 = 0x8A
KEY_INTERNATIONAL5 = 0x8B
KEY_INTERNATIONAL6 = 0x8C
KEY_INTERNATIONAL7 = 0x8D
KEY_INTERNATIONAL8 = 0x8E
KEY_INTERNATIONAL9 = 0x8F
KEY_LANG1 = 0x90
KEY_LANG2 = 0x91
KEY_LANG3 = 0x92
KEY_LANG4 = 0x93
KEY_LANG5 = 0x94
KEY_LANG6 = 0x95
KEY_LANG7 = 0x96
KEY_LANG8 = 0x97
KEY_LANG9 = 0x98
KEY_ALTERNATE_ERASE = 0x99
KEY_SYSREQ = 0x9A
KEY_CANCEL = 0x9B
KEY_CLEAR = 0x9C
KEY_PRIOR = 0x9D
KEY_RETURN = 0x9E
KEY_SEPARATOR = 0x9F
KEY_OUT = 0xA0
KEY_OPER = 0xA1
KEY_CLEAR_AGAIN = 0xA2
KEY_CRSEL = 0xA3
KEY_EXSEL = 0xA4
KEY_KEYPAD_00 = 0xB0
KEY_KEYPAD_000 = 0xB1
KEY_THOUSANDS_SEPARATOR = 0xB2
KEY_DECIMAL_SEPARATOR = 0xB3
KEY_CURRENCY_UNIT = 0xB4
KEY_CURRENCY_SUB_UNIT = 0xB5
KEY_KEYPAD_OPARENTHESIS = 0xB6
KEY_KEYPAD_CPARENTHESIS = 0xB7
KEY_KEYPAD_OBRACE = 0xB8
KEY_KEYPAD_CBRACE = 0xB9
KEY_KEYPAD_TAB = 0xBA
KEY_KEYPAD_BACKSPACE = 0xBB
KEY_KEYPAD_A = 0xBC
KEY_KEYPAD_B = 0xBD
KEY_KEYPAD_C = 0xBE
KEY_KEYPAD_D = 0xBF
KEY_KEYPAD_E = 0xC0
KEY_KEYPAD_F = 0xC1
KEY_KEYPAD_XOR = 0xC2
KEY_KEYPAD_CARET = 0xC3
KEY_KEYPAD_PERCENT = 0xC4
KEY_KEYPAD_LESS = 0xC5
KEY_KEYPAD_GREATER = 0xC6
KEY_KEYPAD_AMPERSAND = 0xC7
KEY_KEYPAD_LOGICAL_AND = 0xC8
KEY_KEYPAD_VERTICAL_BAR = 0xC9
KEY_KEYPAD_LOGIACL_OR = 0xCA
KEY_KEYPAD_COLON = 0xCB
KEY_KEYPAD_NUMBER_SIGN = 0xCC
KEY_KEYPAD_SPACE = 0xCD
KEY_KEYPAD_AT = 0xCE
KEY_KEYPAD_EXCLAMATION_MARK = 0xCF
KEY_KEYPAD_MEMORY_STORE = 0xD0
KEY_KEYPAD_MEMORY_RECALL = 0xD1
KEY_KEYPAD_MEMORY_CLEAR = 0xD2
KEY_KEYPAD_MEMORY_ADD = 0xD3
KEY_KEYPAD_MEMORY_SUBTRACT = 0xD4
KEY_KEYPAD_MEMORY_MULTIPLY = 0xD5
KEY_KEYPAD_MEMORY_DIVIDE = 0xD6
KEY_KEYPAD_PLUSMINUS = 0xD7
KEY_KEYPAD_CLEAR = 0xD8
KEY_KEYPAD_CLEAR_ENTRY = 0xD9
KEY_KEYPAD_BINARY = 0xDA
KEY_KEYPAD_OCTAL = 0xDB
KEY_KEYPAD_DECIMAL = 0xDC
KEY_KEYPAD_HEXADECIMAL = 0xDD
KEY_LEFTCONTROL = 0xE0
KEY_LEFTSHIFT = 0xE1
KEY_LEFTALT = 0xE2
KEY_LEFT_GUI = 0xE3
KEY_RIGHTCONTROL = 0xE4
KEY_RIGHTSHIFT = 0xE5
KEY_RIGHTALT = 0xE6
KEY_RIGHT_GUI = 0xE7

Mouse simulator

If you want to emulate yourself as a mouse, edit the boot.py as follows:

import pyb
pyb.usb_mode('VCP+HID',hid=pyb.hid_mouse)

To send data to simulate as a mouse, use the following commands.

import pyb
mouse = pyb.USB_HID()
hid.send((btn,  x,  y,  scroll))

Conclusion

From this article, it is found that a microcontroller board that uses the same family of ARM processors as the PyBoard is the prototype of the board for MicroPython. It can use the USB port that comes with the board in 3 modes: VCP, MSC and HID. The board provides a serial communication port, storage and user interface in virtual keyboard or mouse mode, so if adding a Gyro module, we can use the x,y,z values ​​from the module instead of x, y, the scroll of the mouse, our board can become a different input device, etc. Finally, have fun programming.

References

  1. Reawat Siriphokapirom. (2020).MicroPython for STM32F411 Black Pill: Embedded Programming Style
  2. MicroPython : pyb
  3. MicroPython : pyb -> USB_HID
  4. MicroPython : pyb -> USB_VCP
  5. [MicroPython] Use python to perform USB-HID test of BadUSB (include wireless control)

(C) 2020-2022, By Jarut Busarathid and Danai Jedsadathitikul
Updated 2022-01-12