Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C

Skeleton of I/O Device Implementation on STM32 Board

Rate me:
Please Sign up or sign in to vote.
5.00/5 (9 votes)
16 Jul 2021CPOL3 min read 11.9K   13   2
An overview of what STM32 development boards have to offer
The HID protocol serves as a foundational element for USB Input devices such as mice, keyboards, and hobby electronic project. In this article, we'll delve into the configuration of a STM32 board to serve as a starting point for creating various USB Input devices, as previously mentioned.

Prerequisite

To get started, you'll need to install STM32CubeIDE and STM32CubeProg, after that you'll need to obtain an ST-LINK V2 device along with an STM32F411CEU6 board, commonly known as Black Pill Development Board.

Feature richness and performance comparison:

  • Black pill > Blue/Red/Purple pill > Green pill > Arduino boards

Image 1

Listing of Essential API's Functions

GPIO_PIN_SET and GPIO_PIN_RESET names come from SR Flip-flops (learnabout-electronics.org)

  • HAL_GPIO_WritePin()
    C++
    void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin,
                           GPIO_PinState PinState);
    
    HAL_GPIO_WritePin(GPIOE, LED1_Pin, GPIO_PIN_SET);      // LED1 is turned on 
    HAL_GPIO_WritePin(GPIOE, LED1_Pin, GPIO_PIN_RESET);    // LED1 is turned off 
  • HAL_GPIO_ReadPin()
    C++
    GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
    
    GPIO_PinState LED1_PinState = HAL_GPIO_ReadPin(GPIOE, LED1_Pin);
    HAL_GPIO_WritePin(GPIOE, LED2_Pin, LED1_PinState);
    HAL_GPIO_WritePin(GPIOE, LED3_Pin, LED1_PinState);
  • HAL_Delay()
    C++
    void    HAL_Delay(uint32_t Delay);
    
    HAL_Delay(500);    // Wait 500 ms
  • Introduction to USB with STM32

Bootloader Programming

Inside STM32CubeIDE, create a new project with STM32F411CEU6 as Commerical Part Number

Image 2

Image 3

Then perform these actions in sequence:

  1. Next
  2. Project naming
  3. Finish
  4. Wait for the software packages download

You are now seeing the MCU chosen:

Image 4

To be able to keep an eye on normal or programming mode, a LED can help that out.

Left click on the PB10 pin and choose GPIO_Output and in System Core tab, select the said pin and set its output level at high.

Image 5

Image 6

On the electronic board, physically attach a LED to B10 pin and ground it.

Then we have to setup a clock device (System Core tab)

Image 7

After that, we can setup the USB feature inside Connectivity and Middleware tabs

Image 8

Image 9

We choose Custom HID class rather HID class since Custom HID class offer the possibility to read HID packet.

Switch on Clock Configuration tab and click on Resolve Clock Issues, then Ctrl-S to generate the code

Image 10

Click near the hammer to choose Release in order to generate the binary file in elf format.

Image 11

Grab your STM32 board and wire its back pins to the corresponding pins of ST-LINK V2:

  • 3.3V to 3.3V
  • SWCLK to SWCLK
  • GND to GND
  • SWDIO to SWDIO

Once finished, connect the ST-LINK V2 to PC.

Inside STM32CubeProgrammer click on Connect button and on + button to access to Open File.

Image 12

Select the *.elf file inside Release folder of the created STM32CubeIDE project then click on Download button, then plug the STM32 board on PC via the USB-C port.

To see the newly created device appear on windows, you just need to press NRST button onboard, you should see this, there is an error because the HID report descriptor has not been set.

Image 13

Firmware Programming

Back in STM32CubeIDE, you would need to modify some data to prepare the device.

Inside \USB_DEVICE\App\usbd_desc.c

Update these defines with these new values:

C++
#define USBD_VID                        0x0001
#define USBD_PID_FS                     0x0001
#define USBD_LANGID_STRING              1033
#define USBD_MANUFACTURER_STRING        "Manufacturer Name"
#define USBD_PRODUCT_STRING_FS          "Product Name"
#define USBD_CONFIGURATION_STRING_FS    "Configuration Name"
#define USBD_INTERFACE_STRING_FS        "Interface Name"

Inside \USB_DEVICE\App\usbd_custom_hid_if.c

Replace CUSTOM_HID_ReportDesc_FS[] with:

enum HID_Helper
{
    IN_REPORT_ID     = 0x02,
    IN_REPORT_SIZE   = 8,       // Bit size of one HID packet
    IN_REPORT_COUNT  = 2,       // Number of HID packets

    OUT_REPORT_ID    = 0x01,
    OUT_REPORT_SIZE  = 8,       // Bit size of one HID packet
    OUT_REPORT_COUNT = 2,       // Number of HID packets
};

// Set USBD_CUSTOM_HID_REPORT_DESC_SIZE to 33
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE]
__ALIGN_END =
{
    0x06, 0x00, 0xFF,           //  USAGE_PAGE (Vendor Specific)
    0x09, 0x01,                 //  USAGE (1)
    0xA1, 0xFF,                 //  COLLECTION (Vendor Specific)

    0x15, 0x00,                 //      LOGICAL_MINIMUM (0)
    0x26, 0xFF, 0x00,           //      LOGICAL_MAXIMUM (255 possibilities = 2 ^ 8 bits)

    0x85, IN_REPORT_ID,         //      REPORT_ID (2)
    0x75, IN_REPORT_SIZE,       //
    0x95, IN_REPORT_COUNT,      //
    0x09, 0x00,                 //      USAGE (0)
    0x81, 0x00,                 //      INPUT (Data,Ary,Abs)

    0x85, OUT_REPORT_ID,        //      REPORT_ID (1)
    0x75, OUT_REPORT_SIZE,      //
    0x95, OUT_REPORT_COUNT,     //
    0x09, 0x00,                 //      USAGE (0)
    0x91, 0x00,                 //      OUTPUT (Data,Ary,Abs)

    0xC0                        //  END_COLLECTION
};

To learn more about HID Report Descriptor, you can explore Human Interface Devices (HID) Information | USB-IF

Inside \Middlewares\ST\STM32_USB_Device_Library\Class\CustomHID\Src\usbd_customhid.c

Add the following code after USBD_CUSTOM_HID_SendReport():

uint8_t USBD_CUSTOM_HID_SendReport(USBD_HandleTypeDef* pdev, uint8_t* report, uint16_t len)
{
    //
    // ...
    //
}

extern  USBD_HandleTypeDef hUsbDeviceFS;
void    HID_Send(uint8_t* Data, uint16_t Size)
{
    USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, Data, Size);
}

Add the following code after USBD_CUSTOM_HID_ReceivePacket():

uint8_t USBD_CUSTOM_HID_ReceivePacket(USBD_HandleTypeDef *pdev)
{
    //
    // ...
    //
}

// Set USBD_CUSTOMHID_OUTREPORT_BUF_SIZE to 3
void    HID_Read(uint8_t* Data)
{
    USBD_HandleTypeDef*             pdev = &hUsbDeviceFS;
    USBD_CUSTOM_HID_HandleTypeDef*  hhid;

    hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassDataCmsit[pdev->classId];

    memcpy(Data, hhid->Report_buf, USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
}

Now we're ready to start programming the firmware (also called App in this IDE).

Inside \Core\Src\main.c

Replace  main() by this:

C++
enum       HID_Helper
{
    RID_SIZE         = 1,    // Report ID is 1 byte size

    IN_REPORT_COUNT  = 2,
    OUT_REPORT_COUNT = 2,

    IN_REPORT_ID     = 0x02,
    OUT_REPORT_ID    = 0x01,
};

uint8_t HostOutBuffer  [RID_SIZE + OUT_REPORT_COUNT];
uint8_t HostInBuffer   [RID_SIZE + IN_REPORT_COUNT ];

uint8_t DeviceOutBuffer[RID_SIZE + OUT_REPORT_COUNT];
uint8_t DeviceInBuffer [RID_SIZE + IN_REPORT_COUNT ];

int        main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USB_DEVICE_Init();

    while (1)
    {
        //
        // Copy data sent by the PC
        //
        HID_Read(DeviceOutBuffer);

        //
        // Build IN buffer with the data
        //
        DeviceInBuffer[0] = IN_REPORT_ID;
        DeviceInBuffer[1] = DeviceOutBuffer[1];
        DeviceInBuffer[2] = DeviceOutBuffer[2];

        //
        // and send it back to PC for feedback purpose
        //
        HID_Send(DeviceInBuffer, RID_SIZE + IN_REPORT_COUNT);


        HAL_GPIO_WritePin(GPIOE, LED1_Pin, GPIO_PIN_SET);
        GPIO_PinState LED1_PinState = HAL_GPIO_ReadPin(GPIOE, LED1_Pin);
        HAL_GPIO_WritePin(GPIOE, LED2_Pin, LED1_PinState);
        HAL_GPIO_WritePin(GPIOE, LED3_Pin, LED1_PinState);

        HAL_Delay(500);

        HAL_GPIO_WritePin(GPIOE, LED1_Pin, GPIO_PIN_RESET);
        LED1_PinState = HAL_GPIO_ReadPin(GPIOE, LED1_Pin);
        HAL_GPIO_WritePin(GPIOE, LED2_Pin, LED1_PinState);
        HAL_GPIO_WritePin(GPIOE, LED3_Pin, LED1_PinState);

        HAL_Delay(500);
    }
}

Now, we possess an STM32 board capable of acquiring data that can be configured from a PC, also known as the host device, and has the ability to transmit this data back to the host device.

Finally, we can rebuild and upload the new binary file to the STM32 board.

And for being able to interact with this board, we can use two tools.

A USB sniffer such as Busdog and a USB HID Communication Tool

FS means Full-Speed for USB spec:

LS  =  Low Speed  =   1.5 Mbps
FS  = Full Speed  =  12   Mbps
HS  = High Speed  = 480   Mbps
SS  = SuperSpeed  =   5   Gbps
SS+ = SuperSpeed+ =  10   Gbps
SS+ = SuperSpeed+ =  20   Gbps

History

  • 16th July, 2021 : Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA16-Jul-21 2:04
professionalȘtefan-Mihai MOGA16-Jul-21 2:04 
GeneralRe: My vote of 5 Pin
Orphraie16-Jul-21 2:25
Orphraie16-Jul-21 2:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.