# Problem with Keypad exercise on STM32 (Nucleo-F103RB)

I try to learn STM32 MCU programming and at the beginning I'd like to mark, that't I'm totally begginer in it, so please forgive me some mistakes (also language mistakes).

So I'm using STM32F103RBT6 (Nucleo-64) Board and I wanted to implement an excercise with 4x3 Matrix Keypad as shown below:

When some key from this matrix is pressed it should be printed in program. After writing and testing code using tool 'SWV ITM Data Console' in STM32CubeIDE it prints over and over, like in some loop, characters #,3,5,6,9 without pressing actually any key on the keypad. When I press some other button like '1' or '2' it's also printed "between" metioned above characters. My program works the way, that every column is set as an Input and every row as Output. Columns are in Pull-up Mode (with internal pull-up resistors) and at the beginning all rows are in High state. Next I make in turn every row Low to check if any of 3 columns is Low and, if need be, print that one's character.I use PC0, PC1, PC2 and PC3 I/O Pins as output for Rows (PC0 - first row from the top, PC1 - 2nd, and so on...) and PA0, PA1, PA4 as Input for Columns (PA0 - 1st from the left, PA1 - 2nd and PA4 - 3rd). The checking of rows and columns is made in a while loop. Below is my main.c code (I had some trouble pasting it in one piece, forgive me):

#include <stdint.h>
#include <stdio.h>

void delay (void)
{
for(int i = 0; i < 300000; i++);
}

int main(void)
{

* 0x4002 1000 - 0x4002 13FF Reset and clock control RCC
* 0x4001 0800 - 0x4001 0BFF GPIO Port A
* 0x4001 1000 - 0x4001 13FF GPIO Port C
*/

//Clock for all used Pins
uint32_t volatile *const p_Clk_reg = (uint32_t*) (0x40021000 + 0x18);

// PAx Pins:
uint32_t volatile *p_PortA_Mode_reg = (uint32_t*) 0x40010800;
uint32_t volatile *p_PortA_In_reg = (uint32_t*) (0x40010800 + 0x08);
uint32_t volatile *p_Pullup_Pulldown_reg = (uint32_t*) 0x40010800;

//PCx Pins:
uint32_t volatile *p_PortC_Mode_reg = (uint32_t*) 0x40011000;
uint32_t volatile *p_PortC_Out_reg = (uint32_t*) (0x40010800 + 0x0C);

//Set clock signal for GPIOA/C:
*p_Clk_reg |= (0x5 << 2);
//------------------ Initialization for PAx Pins (COLUMNS) -----------------------
//1. Clear appropriate bits
// PA0:
*p_PortA_Mode_reg &= (~0xF);
// PA1:
*p_PortA_Mode_reg &= (~(0xF << 4));
// PA4:
*p_PortA_Mode_reg &= (~(0xF << 16));

//2a. Set the INPUT mode
//There's nothing to be done, because to set INPUT bits MODE[1..0] must be = "00"
//and above we set all appropriate bits as '0'(Is it OK?)

//2b. Set the PULL-UP Mode for PA0, PA1, PA4 (Inputs) - CNF[1..0] must be "10"
*p_Pullup_Pulldown_reg |= (1 << 3);
*p_Pullup_Pulldown_reg |= (1 << 7);
*p_Pullup_Pulldown_reg |= (1 << 19);
//----------------------------------------------------------------------
//------------------ Initialization for PCx Pins (ROWS) -----------------------
//1. Clear appropriate bits
// PC0:
*p_PortC_Mode_reg &= (~0xF);
// PC1:
*p_PortC_Mode_reg &= (~(0xF << 4));
// PC2:
*p_PortC_Mode_reg &= (~(0xF << 8));
// PC3:
*p_PortC_Mode_reg &= (~(0xF << 12));

//2. Set the Push-pull OUTPUT mode
// so from table 20. when there's CNF1_CNF0_MODE1_MODE0 it must be sequence "0001"
*p_PortC_Mode_reg |= (1); //PC0
*p_PortC_Mode_reg |= ( 1 << 4); //PC1
*p_PortC_Mode_reg |= (1 << 8); //PC2
*p_PortC_Mode_reg |= (1 << 12); //PC3
//----------------------------------------------------------------------
while(1)
{
// Make all rows as HIGH

*p_PortC_Out_reg |= (1); //PC0

*p_PortC_Out_reg |= (1 << 1); //PC1

*p_PortC_Out_reg |= (1 << 2); //PC2

*p_PortC_Out_reg |= (1 << 3); //PC3

//Make 1st row LOW (PC0)
*p_PortC_Out_reg &= (~1);

//Scan the columns
//check if 1st column (PA0) is LOW
if(!(*p_PortA_In_reg &= (1)))
{
//key '1' is pressed
delay();
printf("1\n");
}
//check if 2nd column (PA1) is LOW
if(!(*p_PortA_In_reg &= (1 << 1)))
{
//key '2' is pressed
delay();
printf("2\n");
}
//check if 3rd column (PA4) is LOW
if(!(*p_PortA_In_reg &= (1 << 4)))
{
//key '3' is pressed
delay();
printf("3\n");
}

// Make all rows as HIGH

*p_PortC_Out_reg |= (1); //PC0
*p_PortC_Out_reg |= (1 << 1); //PC1
*p_PortC_Out_reg |= (1 << 2); //PC2
*p_PortC_Out_reg |= (1 << 3); //PC3

//Make 2nd row LOW (PC1)
*p_PortC_Out_reg &= (~(1 << 1));

//Scan the columns
//check if 1st column (PA0) is LOW
if(!(*p_PortA_In_reg &= (1)))
{
//key '4' is pressed
delay();
printf("4\n");
}
//check if 2nd column (PA1) is LOW
if(!(*p_PortA_In_reg &= (1 << 1)))
{
//key '5' is pressed
delay();
printf("5\n");
}
//check if 3rd column (PA4) is LOW
if(!(*p_PortA_In_reg &= (1 << 4)))
{
//key '6' is pressed
delay();
printf("6\n");
}

// Make all rows as HIGH
*p_PortC_Out_reg |= (1); //PC0
*p_PortC_Out_reg |= (1 << 1); //PC1
*p_PortC_Out_reg |= (1 << 2); //PC2
*p_PortC_Out_reg |= (1 << 3); //PC3

//Make 3rd row LOW (PC2)
*p_PortC_Out_reg &= (~(1 << 2));

//Scan the columns
//check if 1st column (PA0) is LOW
if(!(*p_PortA_In_reg &= (1)))
{
//key '7' is pressed
delay();
printf("7\n");
}
//check if 2nd column (PA1) is LOW
if(!(*p_PortA_In_reg &= (1 << 1)))
{
//key '8' is pressed
delay();
printf("8\n");
}
//check if 3rd column (PA4) is LOW
if(!(*p_PortA_In_reg &= (1 << 4)))
{
//key '9' is pressed
delay();
printf("9\n");
}

// Make all rows as HIGH
*p_PortC_Out_reg |= (1); //PC0
*p_PortC_Out_reg |= (1 << 1); //PC1
*p_PortC_Out_reg |= (1 << 2); //PC2
*p_PortC_Out_reg |= (1 << 3); //PC3

//Make 4th row LOW (PC3)
*p_PortC_Out_reg &= (~(1 << 3));

//Scan the columns
//check if 1st column (PA0) is LOW
if(!(*p_PortA_In_reg &= (1)))
{
//key '*' is pressed
delay();
printf("*\n");
}
//check if 2nd column (PA1) is LOW
if(!(*p_PortA_In_reg &= (1 << 1)))
{
//key '0' is pressed
delay();
printf("0\n");
}
//check if 3rd column (PA4) is LOW
if(!(*p_PortA_In_reg &= (1 << 4)))
{
//key '#' is pressed
delay();
printf("#\n");
}

} // end of 'while' loop
}



And here I paste needed fragment from MCU Reference Manual (except for Memory Addresses, because that's in code comments at the beginning):

While debugging a code line by line I noticed that, when theres's a beginning of a while(1){..} loop, where are first 2 lines of *p_PortC_Out_reg |=... (for PC0 and PC1), except for *p_PortC_Out_reg, alos are being changed values of *p_PortA_In_reg () and I have no idea why is that and I thought if that's the source of a problem? I double checked if I connected all connectors the right way, so that's not an issue. I'm also not sure if I correctly used Input/Output Modes (Pull-up/Pull-down I mean) and I would be very greatful If any of You would have a look on that, because after several hours I'm out of ideas what's wrong and how to fix it.

• If you are a beginner, why start from the deep end of the pool and wiggle each and every bit of the registers? Normally, one would start with CubeMX to configure the clocks and peripherals and use the HAL to set IO pins. The headers even provide register names and bits, so you don't have to use magic numbers in the code. Did you use the debugger to single step the code and find out if IO pins are set up correctly? Feb 21 '21 at 15:09
• Did you use the debugger to single step the code and find out if IO pins are set up correctly? - While debugging the code I turned on Window -> Show view -> variables, and then step by step I analsyed the value of each variable. How can I find out using Debugger if IO pins are set up correctly? I would be very greatful for any help/advice.
– ABC
Feb 21 '21 at 16:09

I think you never write to Port A ODR to turn on pull-ups instead of pull-downs.

The Port C pins are also push-pull outputs, so pressing two buttons that are on different Port C pins will short circuit the IO pins together, which is not what you want.

And it makes little sense to have the &= read-modify-write operation in the if statements that read the Port A input register.

• I thought that setting appropriate bits due to Table 20. that I also posted make the pull-up... So for example when I want "General purpose Output Pull-up " it would be like "0001" (CNF1_CNF0_MODE1_MODE0) and then I set ''1" like in section 9.2.4. Am I wrong? Could you explain that if you can spare couple mintues on me?
– ABC
Feb 21 '21 at 16:03
• Could you advise me also with the last section of you comment?
– ABC
Feb 21 '21 at 16:55
• No, table 9.2.4 clearly says you need CNF and MODE regs correctly set up to use the pin as input with internal pulls, and PxODR will the select if you want the pin to be pulled up or down. I sincerely suggest any beginner to simply use the HAL provided by ST, or at least copy-paste the relevant parts, or otherwise you will be debugging a week for a single IO pin setup and toggling it (and another couple of days to learn debugging) instead of actually making what you want. Feb 21 '21 at 17:36