1
\$\begingroup\$

I am trying to implement a keyboard with STM32 controller (since I managed to burn all my ATMegas and STM32 was all I have).

It uses two matrices of keys (it was supposed to be a split keyboard, but my experiments with I2C protocol ended up with 4 dead MCUs) as shown on a schematic.

schematic

For the schematic, disregard the power supply - this is just a prototype and I am using blue pill board, powered by the USB.

My prototype also has two separate connections to each set of rows - for each half of the keyboard.

To prevent short-circuit on row output pins when multiple keys in the same column are pressed, I have added diodes to the row outputs.

The 1K pull-down resistors on each column pin are supposed to provide the "default LOW value" for the pins to prevent scan mistakes.

The way I scan the keys is:

  • set LOW on all the row pins except the one being scanned (which is set to HIGH)
  • read the status of all the column pins
  • map the combination of currentRowIndex and highColumnIndex to the map of key codes
  • form a HID USB report and send it over the USB
  • wait for 5 ms (could be 50, but that's less relevant, in my opinion)

The MCU code is as follows:

#include "main.h"
#include "usb_device.h"

#include "hid_keycodes.h"

extern USBD_HandleTypeDef hUsbDeviceFS;

typedef struct
{
  uint8_t MODIFIER;
  uint8_t RESERVER;
  uint8_t KEYCODE1;
  uint8_t KEYCODE2;
  uint8_t KEYCODE3;
  uint8_t KEYCODE4;
  uint8_t KEYCODE5;
  uint8_t KEYCODE6;
} KeyboardReport;

typedef struct
{
  uint8_t PORT;
  uint8_t PIN;
} KeyAddress;

/*
 * Workman keyboard layout:
 *
 * Q D R W B    J F U P ;
 * A S H T G    Y N E O I
 * Z X M C V    K L , . /
 */

uint8_t LEFT_KEY_MAP[] = {
    KEY_Q, KEY_D, KEY_R, KEY_W, KEY_B,
    KEY_A, KEY_S, KEY_H, KEY_T, KEY_G,
    KEY_Z, KEY_X, KEY_M, KEY_C, KEY_V,
    KEY_SPACE, KEY_LEFTSHIFT, KEY_LEFTCTRL, KEY_LEFTALT, KEY_LEFTMETA
};

uint8_t RIGHT_KEY_MAP[] = {
    KEY_J, KEY_F, KEY_U, KEY_P, KEY_SEMICOLON,
    KEY_Y, KEY_N, KEY_E, KEY_O, KEY_I,
    KEY_K, KEY_L, KEY_COMMA, KEY_DOT, KEY_SLASH,
    KEY_BACKSPACE, KEY_RIGHTSHIFT, KEY_RIGHTCTRL, KEY_RIGHTALT, KEY_ENTER
};

void SystemClock_Config(void);
static void MX_GPIO_Init(void);

extern uint8_t USBD_HID_SendReport(USBD_HandleTypeDef  *pdev,
    uint8_t *report,
    uint16_t len);

void processKey(uint8_t* pressedKeys, uint8_t* pNumPressedKeys, uint8_t* pModifier, uint8_t* pKeyMap, uint8_t keyIndex, uint8_t isPressed)
{
  uint8_t key = pKeyMap[keyIndex];

  if (isPressed)
  {
    if (key == KEY_LEFTALT)
    {
      *pModifier |= KEY_MOD_LALT;
    }
    else if (key == KEY_LEFTSHIFT)
    {
      *pModifier |= KEY_MOD_LSHIFT;
    }
    else if (key == KEY_LEFTCTRL)
    {
      *pModifier |= KEY_MOD_LCTRL;
    }
    else if (*pNumPressedKeys < 3)
    {
      pressedKeys[(*pNumPressedKeys)++] = key;
    }
  }
}

void sendHIDReport(uint8_t* pressedKeys, uint8_t numPressedKeys, uint8_t modifier)
{
  KeyboardReport report = {
      .MODIFIER = 0,
      .KEYCODE1 = 0,
      .KEYCODE2 = 0,
      .KEYCODE3 = 0,
      .KEYCODE4 = 0,
      .KEYCODE5 = 0,
      .KEYCODE6 = 0
  };

  report.MODIFIER = modifier;

  if (numPressedKeys > 0)
  {
    report.KEYCODE1 = pressedKeys[0];
  }

  if (numPressedKeys > 1)
  {
    report.KEYCODE2 = pressedKeys[1];
  }

  if (numPressedKeys > 2)
  {
    report.KEYCODE3 = pressedKeys[2];
  }

  if (numPressedKeys > 3)
  {
    report.KEYCODE4 = pressedKeys[3];
  }

  if (numPressedKeys > 4)
  {
    report.KEYCODE5 = pressedKeys[4];
  }

  if (numPressedKeys == 6)
  {
    report.KEYCODE6 = pressedKeys[5];
  }

  USBD_HID_SendReport(&hUsbDeviceFS, &report, sizeof(KeyboardReport));
}

int main(void)
{
  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();
  MX_USB_DEVICE_Init();

  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);

  HAL_Delay(5);

  while (1)
  {
    // convert pressed keys to report
    uint8_t pressedKeys[] = { 0, 0, 0, 0, 0, 0 };
    uint8_t numPressedKeys = 0;
    uint8_t modifier = 0;

    // read left side state
    // set HIGH on first row
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

    // read which column pins have HIGH set by pressing the button
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (0 * 5) + 0, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (0 * 5) + 1, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (0 * 5) + 2, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (0 * 5) + 3, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (0 * 5) + 4, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_SET);

    // set HIGH on second row
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

    // read which column pins have HIGH set by pressing the button
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (1 * 5) + 0, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (1 * 5) + 1, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (1 * 5) + 2, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (1 * 5) + 3, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (1 * 5) + 4, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_SET);

    // set HIGH on third row
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);

    // read which column pins have HIGH set by pressing the button
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (2 * 5) + 0, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (2 * 5) + 1, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (2 * 5) + 2, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (2 * 5) + 3, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, LEFT_KEY_MAP, (2 * 5) + 4, HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_SET);

    // ========================

    // set HIGH on first row
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);

    // read which column pins have HIGH set by pressing the button
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (0 * 5) + 0, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (0 * 5) + 1, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (0 * 5) + 2, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (0 * 5) + 3, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (0 * 5) + 4, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_SET);

    // set HIGH on second row
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);

    // read which column pins have HIGH set by pressing the button
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (1 * 5) + 0, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (1 * 5) + 1, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (1 * 5) + 2, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (1 * 5) + 3, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (1 * 5) + 4, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_SET);

    // set HIGH on third row
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);

    // read which column pins have HIGH set by pressing the button
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (2 * 5) + 0, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (2 * 5) + 1, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (2 * 5) + 2, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (2 * 5) + 3, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) == GPIO_PIN_SET);
    processKey(pressedKeys, &numPressedKeys, &modifier, RIGHT_KEY_MAP, (2 * 5) + 4, HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_SET);

    sendHIDReport(pressedKeys, numPressedKeys, modifier);

    HAL_Delay(5);
  }
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL6;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USB;
  PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_PLL;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8, GPIO_PIN_RESET);

  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8, GPIO_PIN_RESET);

  /*Configure GPIO pins : PA0 PA1 PA2 PA3
                           PA4 */
  GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
                          |GPIO_PIN_4;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pins : PA5 PA6 PA7 PA8 */
  GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pins : PB0 PB1 PB3 PB4
                           PB5 */
  GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_3|GPIO_PIN_4
                          |GPIO_PIN_5;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /*Configure GPIO pins : PB6 PB7 PB8 */
  GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

There are few bad code design choices made, mostly caused by the ease of debugging and the weird constant memory locations (e.g. pin addresses are powers of 2 in groups of 4), preventing me from using some pointer arithmetic to use the for loops efficiently.

I have built a prototype on a breadboard and it works for the most part, but there is the issue of wrong keypress reports sent to a PC:

  • immediately after powering up it keeps sending the semicolon symbol
  • occasionally it starts sending random keystrokes (actually something like keys being stuck) despite no key is pressed

I have a suspicion that this might be caused by some loose connections (I have a lot of jumping wires connected to one another to make the prototype at least somewhat less messy):

prototype

I also assume there might be a resistor added after each diode to limit the current on the input pins.

Least likely, there might be some noise from other pins, static electricity or even the USB port itself.

Q: What might be causing those random keypresses?

Also, would really appreciate any critique towards the hardware design.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Notice the semicolon button uses a row and a column pin that are next to each other, so a high going edge can capacitively couple from row to that column pin. It might mean the pull-down resistor is not properly connected. Check the connections. Also the MCU has built in pull resistors, they might help if external resistor is missing. Also you don't give any time after setting the column pin for the signals to settle, have you tried a delay between setting columns and reading the rows? \$\endgroup\$
    – Justme
    Nov 28, 2021 at 10:10
  • \$\begingroup\$ Wow, thanks! For the random key registration it was some loose resistor contacts indeed - my prototype hanging in the air does not really allow for much robustness there. On the other hand, the startup issue did not really resolve itself - might want to add some transistor-driven discharge mechanism for the startup time or solve it by spacing out components on the PCB. \$\endgroup\$
    – shybovycha
    Nov 28, 2021 at 10:27
  • \$\begingroup\$ Other than that - do you see any major issues with such design? Want to make fewer mistakes before I put it into PCB \$\endgroup\$
    – shybovycha
    Nov 28, 2021 at 10:28
  • \$\begingroup\$ What if the button has bad soldering or wiring mistake, and the MCU really sees that button pressed all the time? Regarding the major issues, do you mean only matrix scanning or the MCU circuit? The MCU circuit is terrible and won't work for many reasons, but I did not mention because you said it is work in progress and should be ignored. \$\endgroup\$
    – Justme
    Nov 28, 2021 at 10:41
  • \$\begingroup\$ I can think about bad soldering all over the place (the back of those keyboard matrices is just a hot mess), but if there was a short circuit, the button would be seen as pressed down all the time, not just randomly, neither would it disappear after pressing any button after start up. I would love to hear all your thoughts on both matrix scanning and why MCU circuitry won't work. \$\endgroup\$
    – shybovycha
    Nov 28, 2021 at 23:19

1 Answer 1

1
\$\begingroup\$

The button being constantly held down or having random button presses indicates there is capacitive coupling between wires and the resistors might have bad connections so floating wires can go high via the capacitance, or the time between setting a pin high and having the resistor discharge the capacitively coupled charges is not long enough.

Constantly pushed button can also indicate a short circuit so the MCU will see the button pressed even without capacitive coupling, and is easy to test with multimeter or code that only focuses on one button.

You don't need any extra hardware to deal with capacitive coupling. You have an MCU so all the IO pins and the timing how to operate the IO pins can be under control of the MCU.

\$\endgroup\$
1
  • \$\begingroup\$ With a bit of effort, managed to eliminate floating pins as the cause of stuck letter at startup. In the end figured out the cause - had to move the HID report variable outside of a function (which, apparently, made use of a stack) to the program initialization section. \$\endgroup\$
    – shybovycha
    Nov 30, 2021 at 10:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.