ARM Cortex/STM32/"BluePill" port configuration

Thread Starter

usernamehere

Joined Nov 22, 2017
18
Hello All,

FIXED AND EDITED TO SHOW THE FIX....

Background:
Pin B10 is configured to turn an LED on and A8 is configured to receive input when the button/switch is pressed.

The following C code does not work:

Code:
int main(void) {

    //Setting Ports A and B as active:
    RCC->APB2ENR = (0x3<<0);
    Defining GPIOA as input on pin 8 by passing 1 into CNF8(2):
    GPIOA->CRH = (0x1<<2);
    //Defining GPIOB as an output on pin 10 by passing 10 into MODE10(8):
    GPIOB->CRH = (0x2<<8);

while(1){
   // Using IDR to read when the button is pressed from A8:
    if(GPIOA->IDR & (1<<8))
   // resetting pin B10:
        GPIOB->BSRR = (1<<26);
    else
   // setting 1 into pin B10:
        GPIOB->BSRR = (1<<10);
   }
}
The following code does work:

Code:
int main(void) {

    RCC->APB2ENR = (0xC<<0);
    GPIOA->CRH = (0x8<<0);
    GPIOB->CRH = (0x2<<8);

while(1){ 
 
    if(GPIOA->IDR & (1<<8))
        GPIOB->BSRR = (1<<26);
    else
        GPIOB->BSRR = (1<<10);

 
    }
}
The APB2ENR need "C" hex (=1100) passed into it and GPIOA->CRH needs "8" hex (=1000) passed into it. This will turn LED to ON when button is pressed.
Device is STM32F103
 
Last edited:

MrChips

Joined Oct 2, 2009
30,708
The purpose of using library functions is to make your code portable.
As written, it is not portable. Hence, why don't you make your code obvious by replacing the bit-shift operations with literal constants.

Code:
int main(void)
{
    RCC->APB2ENR |= 0xC;
  //GPIOA->CRH   |= 0x8;      not needed by default
    GPIOB->CRH   |= 0x0200;

while(1)
   {
    if(GPIOA->IDR & 0x0100)
        GPIOB->BSRR = (1<<26);  // leave this since we want to set bit-26
      else
        GPIOB->BSRR = (1<<10); // set bit-10
   }
}
 

mckenney

Joined Nov 10, 2018
125
I give the OP a partial pass, since the CRH and BSRR defines are useless to the point of counter-productive.

Some things could be done, though:
Code:
#define LED 10   // PB10
#define BUTTON 8 // PA8
int main(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN; // Power up GPIOA/B
  //GPIOA->CRH   |= 0x8;      not needed by default
    GPIOB->CRH   |= 0x02 << (4*(LED-8));  // Output, 2MHz

while(1)
   {
    if(GPIOA->IDR & (1 << BUTTON))    // button released?
        GPIOB->BSRR = (1<<(LED+16));  //  Turn off PB10
      else                                                   // button pushed?
        GPIOB->BSRR = (1<<(LED+00)); // Turn on PB10
   }
}
 

Thread Starter

usernamehere

Joined Nov 22, 2017
18
Thank you for the replies guys.

The purpose of using library functions is to make your code portable.
As written, it is not portable. Hence, why don't you make your code obvious by replacing the bit-shift operations with literal constants.

Code:
int main(void)
{
    RCC->APB2ENR |= 0xC;
  //GPIOA->CRH   |= 0x8;      not needed by default
    GPIOB->CRH   |= 0x0200;

while(1)
   {
    if(GPIOA->IDR & 0x0100)
        GPIOB->BSRR = (1<<26);  // leave this since we want to set bit-26
      else
        GPIOB->BSRR = (1<<10); // set bit-10
   }
}
Is the GPIO on A set to input by default? it does work without declaring anything into that register. I have been trying to work without libraries just so I can understand how the MCU works better and learn the core concept of manipulating registers.


I give the OP a partial pass, since the CRH and BSRR defines are useless to the point of counter-productive.

Some things could be done, though:
Code:
#define LED 10   // PB10
#define BUTTON 8 // PA8
int main(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN; // Power up GPIOA/B
  //GPIOA->CRH   |= 0x8;      not needed by default
    GPIOB->CRH   |= 0x02 << (4*(LED-8));  // Output, 2MHz

while(1)
   {
    if(GPIOA->IDR & (1 << BUTTON))    // button released?
        GPIOB->BSRR = (1<<(LED+16));  //  Turn off PB10
      else                                                   // button pushed?
        GPIOB->BSRR = (1<<(LED+00)); // Turn on PB10
   }
}
Is there another way other than using the CRH register to set input/output modes for pins?
I thought we are meant to use BSRR in order to alter state of one pin only, I could use ODR but that sets/resets states of all A or B or C pins.

The next thing I am going to try is to hold the value of the switch/button so that if pressed, the LED stays on.
 

mckenney

Joined Nov 10, 2018
125
Is the GPIO on A set to input by default?
Yes. Per RM0008 section 9.2.1/.2, the CRx registers are filled with 4-s (=input pin) at reset.
Is there another way other than using the CRH register to set input/output modes for pins?
No, that's the way you do it. When I started with the STM32 some years, ago, one of the first things I did was create a set of macros/inline functions to "tame" those registers (since I couldn't keep it all straight).
I thought we are meant to use BSRR in order to alter state of one pin only, I could use ODR but that sets/resets states of all A or B or C pins.
Yes, you should use the BSRR (or BSRRL/H on some variants). If you code carefully, you can set the bit(s) you want directly in the ODR, but it will never be interrupt-safe.I suggest that what I wrote is readable enough without going for baroque.
-------
My comment about the defines was that it's hard to tie them to your abstract item ("LED", "BUTTON") without creating a whole bunch of derived definitions which clutter the program and anyway always have to be changed in tandem (by going back and slogging through the book). In ST's defense, I don't know that I could have done better, it's just a problem given how the registers are constructed.

If I were doing this, I would add a "#define LED_PORT GPIOB" (and so on) as well.
 
Last edited:

mckenney

Joined Nov 10, 2018
125
(a bunch of quote with no content)
The forum software posted that without asking, and now there's no "Edit" button. Sorry. I'll get the hang of this eventually.

Mod edit: No problem. Post with no added content deleted.

(Edit: mckenney now says: Wow, that was quick. Thanks.)
 
Last edited:

Thread Starter

usernamehere

Joined Nov 22, 2017
18
thanks again mckenney.

I have started using what you and mrchips suggested although I am still using (HEX<<X) to shift and pass because it is getting a bit confusing for me otherwise, since I have just started to learn, I want to remember what I did and then optimise later.

Code:
#define LED 10   // PB10
#define BUTTON 8 // PA8
uint16_t isled_on = 0;

int main(void)
{
    RCC->APB2ENR|= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
    GPIOB->CRH|=(0x2<<8);

while(1)
   {
    if(GPIOA->IDR & (1 << BUTTON) && (isled_on == 1))  {
        GPIOB->BSRR = (1<<(26));
                isled_on = 0;
        }
      else if (GPIOA->IDR & (1 << BUTTON) && (isled_on == 0))   {                                              
        GPIOB->BSRR = (1<<(10));
                isled_on = 1;
   }
    }
}
The above code is meant to hold the value of isled_on to 1 or 0 depending on whether the button has been pressed or not. I am trying to keep the LED on once the button is pressed and turning it off if the button is pressed again. Before LED will turn on or off simply based on releasing the button.
After writing the above code to the MCU, now its a luck of the draw whether the LED turns off or not, I have to press the button for a certain amount of time in order for the LED to stay on or stay off. I tried to add a delay but that does not work either. What am I doing wrong?

isnt "if(GPIOA->IDR & (1 << BUTTON)" essentially saying "if A8 == 1" then do something? or is a value other than 1 returned when the button is pressed?
 

MrChips

Joined Oct 2, 2009
30,708
When interrogating pushbutton switches you must have a switch debounce technique.
For the moment, forget about writing code. You have to come up with an algorithm similar to the following:

1. read the switch
2. wait 50ms
3. read the switch again
4. if the last two readings do not agree, go back to step 2.
5. accept and continue.
 

Thread Starter

usernamehere

Joined Nov 22, 2017
18
When interrogating pushbutton switches you must have a switch debounce technique.
For the moment, forget about writing code. You have to come up with an algorithm similar to the following:

1. read the switch
2. wait 50ms
3. read the switch again
4. if the last two readings do not agree, go back to step 2.
5. accept and continue.
Thanks. I tried what you said and initially this was my logic:

If button is pressed then
delay(5000)
check again if button is pressed then
turn LED on

but it did not work so I found some code on that does work but I have some questions:

Code:
void check_button(void){
btn_position = (GPIOA->IDR&(1<<BUTTON)) == (1<<BUTTON);

    if (btn_position != last_position){
   
        count++;
   
            if (count >= 4){
                last_position = btn_position;
                //init_state = curr_state;
                if (btn_position != 0)
                    {
               
                    isbtnhigh = 1;
                    count = 0;
               
                }
            }
        }
        else
                {
                    count = 0;
                }
}
MAIN LOOP:

check_button();

if (isbtnhigh)
        {
            // Clear flag
            isbtnhigh = 0;
       
            GPIOB->ODR ^= (1<<(10));
        
        }
What I don't understand is that this code only works if I compare the current button position to a 0 value (last_position is 0 initially). If I am making a comparison using the following:

btn_position = (GPIOA->IDR&(1<<BUTTON)) == (1<<BUTTON);

then after reading the IDR register I am asking if the comparison of IDR register is equal to "0000 1000 0000" which should be the case if the button on pin 8 is pressed because I think:

0000 1000 0000 AND 0000 1000 0000 will return 0000 1000 0000

but then if I change line #4 from if (btn_position != last_position) to if (btn_position == 1) then the code does not work, the whole thing goes back to behaving in the same way as before where the LED will unpredictably stay on or off depending on how long and when the button was pressed. Why is this?

I also have to use the ODR register instead of BSRR because the BSRR does not work in this case.

Thank you.
 

mckenney

Joined Nov 10, 2018
125
IDR register is equal to "0000 1000 0000" which should be the case if the button on pin 8 is pressed because I think:
You were describing a button which shorts to GND when pushed (most are wired like that), so it shows as low (=0) while it is held down.

You may find the number 4 to be a bit short -- some switches bounce for milliseconds (a few thousand CPU clocks).
 

Thread Starter

usernamehere

Joined Nov 22, 2017
18
You were describing a button which shorts to GND when pushed (most are wired like that), so it shows as low (=0) while it is held down.

You may find the number 4 to be a bit short -- some switches bounce for milliseconds (a few thousand CPU clocks).
You were right, bumping the counter to something large works well.
Do you think I can use the 5v off the MCU to power a small motor using a transistor or will that fry the MCU?
 

mckenney

Joined Nov 10, 2018
125
Do you think I can use the 5v off the MCU to power a small motor using a transistor or will that fry the MCU?
The STM32 is 3V, so the 5V pin is presumably coming directly off the USB, i.e. from your PC's port. That's 500mA (minus whatever the board uses). You can control the motor using an MCU pin and a 3V-compatible interface (H-bridge, RC servo controller).
 

Thread Starter

usernamehere

Joined Nov 22, 2017
18
The STM32 is 3V, so the 5V pin is presumably coming directly off the USB, i.e. from your PC's port. That's 500mA (minus whatever the board uses). You can control the motor using an MCU pin and a 3V-compatible interface (H-bridge, RC servo controller).
Ok thanks, I will look into H-bridges :)
 
Top