Introduction: So, You Load STM32duino Bootloader in Your "Blue Pill"...So What Now?

If you already read my instructables explaining how load STM32duino bootloader or any other similar documentation, you try load code example and....may be nothing happens at all.

Problem is, many, if not all examples for "Generic" STM32 will not work out of the box. It will be necessary minor changes to get then work in your STM32 "Blue Pill" board.

I will select 4 code examples to explain what needs to change and why. Codes are: "BlinkWithoutDelay", "Fading", "Dimmer" and "AnalogInSerial".

Note I did NOT code anything. I just issue minor changes in codes created by:

  • David A. Mellis and late modified by Tom Igoe, Marti Bolivar and some cases by Scott Fitzgerald
  • Tom Igoe and late modified by Bryan Newbold

So, I prefer keep the author names even in codes I modify, keeping the creation credit.

Step 1: Pins and Pins....Why Code Is Not Work?

Lets take a look in STM32 "Blue Pill" pin out. Note pins are identify as PA1 or PC2....something like that.

If you take a look in, for example, "BlinkWithoutDelay" code example, pin is declared as "33"....Why?

I suspect that's because Mr. Marti Bolivar ported this code for MAPLE board.

I think it was not his intention let code compatible to "Blue Pill" boards.

Maple and Maple mini board pins are numeric declared, like Arduino, although they use numbers like 33, 24 and some like this.

I said code was not work? My mistake. Code compile without any error and upload correctly to "Blue Pill", so, is my opinion it is indeed working, but using a GPIO output we are not expect. May be not even available.

So little changes are necessary in code to it work as expected.

Step 2: Let's "define" Some Pins....

It is a good code practice declare resources as easy identify or meaning variables or constant's. It will let you code easier to understand and troubleshooting.

I used declare Arduino pins like this:

"...

const int ledPin =13;

..."

If you do like me, maybe you are asking yourself: "How do I can declare pins with names like PC13???"

The answer is: Use "#define" C statement.

So, according pinout draw, PC13 is the pin we have on board LED in "BluePill". To use it, I would declare like this, just after libraries definition (#include...) and before anything else:

"...

#define LedPin PC13

..."

Note there is NO ";" line termination, NOR "=" assignment.

Compare both codes. One is the original example loaded from IDE. Second is the one I did some adjust to work with "BluePill".

I strongly recommend declare all pins you intend to use in code. Even those intend to use as ADC input (more about it later).

This will make your life easy.

Step 3: PinMode()...How You Will Use Your Pins...

Before continue, lets understand PinMode() funtion.

Like Arduino, STM32 pin's have multiple functions. Simplest way to select one or other is using pinMode() statement.

Arduino have only 3 modes available, INPUT, OUTPUT, or INPUT_PULLUP.

STM32, in other hand have many flavors of pinMode(). They are:

  • OUTPUT -Basic digital output: when the pin is HIGH, the voltage is held at +3.3v (Vcc) and when it is LOW, it is pulled down to ground.
  • OUTPUT_OPEN_DRAIN -In open drain mode, the pin indicates “low” by accepting current flow to ground and “high” by providing increased impedance.
  • INPUT_ANALOG -This is a special mode for when the pin will be used for analog (not digital) reads. Enables ADC conversion to be performed on the voltage at the pin.
  • INPUT_PULLUP -The state of the pin in this mode is reported the same way as with INPUT, but the pin voltage is gently “pulled up” towards +3.3v.
  • INPUT_PULLDOWN -The state of the pin in this mode is reported the same way as with INPUT, but the pin voltage is gently “pulled down” towards 0v.
  • INPUT_FLOATING -Synonym for INPUT.
  • PWM -This is a special mode for when the pin will be used for PWM output (a special case of digital output).
  • PWM_OPEN_DRAIN -Like PWM, except that instead of alternating cycles of LOW and HIGH, the voltage on the pin consists of alternating cycles of LOW and floating (disconnected).

(note: extracted from http://docs.leaflabs.com/static.leaflabs.com/pub/leaflabs/maple-docs/latest/lang/api/pinmode.html#lang-pinmode )

I just open this parenthesis because when you start create your own code, be careful to use correct pinMode() for your need.

Step 4: AnalogWrite() Versus PwmWrite()...Analog Output in 2 Flavors

Before use "Blue Pill" GPIO pins it is necessary declare its behavior, i.e., how it will works. That is exactly what pinMode() function does.

So, lets focus now how correct set a analog output. It can be declared either as OUTPUT mode or PWM mode.

Same way, Analog values can be attribute to GPIO in 2 ways: analogWrite() or pwmWrite(), BUT, analogWrite() WILL only work if pinMode()= OUTPUT. In other hand, pwmWrite() WILL only work if pinMode()=PWM.

Lets take PA0, for example: it is a analog/pwm output candidate.

analogWrite(): this declare this way:

"....

#define ledPin PA0

...

pinMode(ledPin, OUTPUT);

...

analogWrite(ledPin, <number>);

......"

where number must be between 0 and 255, like Arduino. Actually, it is backwards compatible to Arduino.

pwmWrite(): declare this way:

"...

#define ledPin PA0

...

pinMode(ledPin, PWM);

...

pwmWrite(ledPin, <number.>);

...."

Where number must be between 0~65535, a resolution much higher than Arduino.

In images is possible to compare between 2 codes. You also can see original code.

Step 5: STM32 Serial Communication

Let's see how is arranged USART interfaces in STM32. Yes, interfaces in plural.....

"Blue Pill" has 3 USART's (RX/ TX 1~3), and, if you are using a bootloader allows you use USB, it is not connected to any of then.

Depending you are using or not USB, you need to declare serial port in one or other way in your code.

Case 1: Using USB:

This way, sketch's are downloaded directly via USB. No need to move BOOT0 jumper to 1 position and back to 0.

In this case, any time you declare "Serial" with no index, means communication via USB.

So, Serial1, means TX/ RX 1 (Pins PA9 and PA10) ; Serial2, means TX/ RX 2 (pins PA2 and PA3) and Serial 3 means TX/ RX 3 (Pins PA10 and PA11).

This is the way we are working with. I will present changes in examples for this way of coding.

Another thing: "Serial USB" does not need to initialize. In other words, "...Serial.begin(15200);" is not necessary.

It is possible call any Serial function (Serial.read(), Serial.write(), etc) without any initialization.

If by some reason it is present in code, compiler will ignore it.

Case 2: Using TTL seria to USB adapter:

In this way, bootloader doesn't support native STM32 USB communication, so you need a USB to serial adapter connected to TX/ RX 1 (pin PA9 and PA10) to upload sketch's.

In this case, any time "Serial" with no index is code, means TX/ RX1 (port used to upload the code). So on, Serial1 refers to TX/ RX 2 (pins PA2 and PA3) and Serial2 refers toTX/ RX 3 (Pins PA10 and PA11). No Serial3 available.

Step 6: Passing a Value to Microcontroller

Dimmer example is simple way to show how pass a value to microcontroller.

It suppose to pass a value from 0 to 255 to control LED brightness.

It will NOT work as expected in Blue Pill due:

  1. To use pwmWrite() function, pinMode() MUST be declared as PWM mode.
  2. You will never get whole 3 digits number. Serial.read() function captures just buffer content, which is a "BYTE". if you type "100" and press "enter", only last "0" will be capture from buffer. And its value will be "48" (decimal ASCII value for "0"). If intend to issue value "100", it is necessary to type "d". So, it is correct to say it will convert a ASCII symbol decimal value in LED brightness, right??....Well, a sort of...
  3. Problem, map values directly from Serial.read() function is a trick action. It is almost certain get unexpected values. Better approach is storage buffer content in a temporary variable and THAN map it.

Like I explain before in item 2, code I introduce changes will allow enter a ASCII symbol and this will control LED brightness based in its ASCII decimal value...for example, "space" is value 32 (actually is lowest printable character you can enter) and "}" is possible the highest one (value 126). Other character's are non printable one, so terminal will not understand or they are possible a compound of character (like "~" is a dead key in my keyboard and will not work correctly). This means, this compound character, when enter in terminal, will send character itself and something else. Usually a non printable one. And is this last one code will capture.
Also, keep in mind your Terminal, in this case, should NOT send neither "Carriage Return" nor "Line Feed". You must pay attention to this to code work correctly.

If you fell it is little confusing, it get worst.....

Step 7: And If I Would Like to Type Three Digits.... or Even More??

Receive multiple character from a serial communication is not a simple task.

Serial buffer is FIFO byte pile of chars. Any time Serial.read() function is calling, first char sent is removed from pile and stored in some where else. Usually a char variable in code. Note, depend on hardware, usually there is a timeout for how log buffer can keep information.

If you intend to enter more than one digit via serial, you will must "compose" a string character by character, as they get in to UART buffer.

This means cycling read each buffer char, store in a temp variable, load it in first position of a string array, move to next position and start over, until... well, depends application. There are 2 ways to end cycle:

  1. Using some "end mark" character, like "carriage Return" or "Line Feed". As soon "end Mark" char is found, loop ends up.
  2. Alternatively, number of characters in string chain can be limited, so do, number of interactive cycles. When it reaches the limit, lets say, 4, acquire routine finishes by it self.

Lets take a look in a simple example how do this:

  • Set a "end" char, like '\n' (this means line feed ASCII char).
  • looping meanwhile Serial.available() is true
  • storing Serial.read() result in a temporary char variable. Remember: as soon Serial.read() actually "reads" buffer, it is clean and next character loads in it.
  • increment a string variable with this char
  • If last char is "end" exit the loop.

Usually, routine for get serial character array looks like picture.

It was based in a extensive adaptation of Mr. David A. Mellis original code.

Fell free to use and test it. Remember: values MUST be enter in 3 digits format.

This it for now.I will not extend myself in additional serial communication details. It is too complex to cover here and it deserves it own Intructables.

I hope it help you use examples in Blue Pill and give you some enlightenment how correct code for this little board.

See you around in other instructable.