STM32G070 Microcontroller Development Board for Tinkerers - Part 2

So I got the PCBs from OSH park. I got three PCBs (same design) for $9.99 USD. I also ordered enough parts to populate the 3 boards for under $20 CAD including some right angle headers. After a couple of hours of soldering, the three boards were completely soldered and (partly) tested! Two of them are shown in the figure below.

Populated STM32G070 Development board

Getting The Necessary Software Tools

The next step is to program the boards with a blinky program. The following development tools need to be downloaded from the ST Microelectronics site:

  • STM32CubeIDE - This is a free Eclipse-based integrated development environment (IDE) provided by ST Micro.

  • STM32CubeProg - This is a programmer tool that can program STM32 microcontrollers via SWD/ST-Link and serial bootloader.

  • STM32CubeG0 - This includes all necessary libraries and header files for programming the STM32G070 microcontroller. This package can be downloaded internally via the STM32CubeIDE when wanting to program the microcontroller with the STM32Cube HAL libraries. Since we will take the register-based approach we will only be needing a few header files and possibly a few additional files from this package, so it will be handy to have.

And The Hardware!

As far as hardware goes, a ST-link/SWD programmer is needed. Either the official $35 ST-linkV2 programmer/ debugger or a $2 ST-linkV2 clone will do. I highly recommend buying an official ST-linkV2 to support the vendor.

A USB to serial dongle / breakout board will also come in handy.

Be sure to have a look at the recently released $9.90 ST-linkV3Mini. Not only is this an official ST SWD/STlink programmer, it also has a built-in USB to serial VCP port. And that that price, you really can’t go wrong buying it.

Get the Documentation!

The necessary STM32G070 documentation is also required. The datasheet outlines the capabilities, pinouts and electrical characteristics of the microcontroller. The reference manual provides more detailed information about how the peripherals function and lists all peripheral registers. A lot of time will be spent looking at this reference manual.

The errata sheet lists silicon problems and workarounds. Finally the programming manual is a manual for the Cortex M0+ CPU implementation in the STM32G070. It documents the CPU’s programming model, built-in interrupt mechanisms, buses, registers, e.t.c.

Let’s Start!

  • Create a new STM32 project in the STM32CubeIDE.
  • In the Target Selection dialog, select the the STM32G070KB microcontroller and click next.

Target Selection

  • In the project setup dialog, give your project a name, say blinky. Select C for the targeted language and Empty for targeted project type. Click finish.

Project Setup

  • We now have an almost completely setup project! Extract the STM32CubeG0 package folder into the IDE’s workspace folder. In the STM32CubeIDE go to Properties->C/C++ Build->Settings and add the following directories to the include paths under the MCU GCC Assembler and MCU GCC compiler sections as shown in the two figures below:

    $HOME/STM32CubeIDE/workspace_1.0.2/STM32Cube_FW_G0_V1.3.0/Drivers/CMSIS/Include
    $HOME/STM32CubeIDE/workspace_1.0.2/STM32Cube_FW_G0_V1.3.0/Drivers/CMSIS/Device/ST/STM32G0xx/Include
    

Include path for MCU GCC Assembler

Include path for MCU GCC Compiler

This gives our project/program access to all of the register definitions found in the stm32g070xx.h file provided by ST Micro in the STM32CubeG0 package.

Because dummy delays that waste cycles will be used, all compiler optimization must be disabled by specifying the O0 flag under MCU GCC Compiler->Optimization.

Disable Optimization

Now copy and paste the following program into the main.c file:

#include <stm32g070xx.h>

void customDelay(unsigned int inner, unsigned int outer);

int main(void)
{
	RCC->IOPENR |= (1 << 2)  ; // send clock to GPIOC
	GPIOC->MODER |= (1 << 12) ;GPIOC->MODER &= ~(1 << 13) ; // make PC6 output
	GPIOC->OSPEEDR |= ((1 << 12) | (1 << 13));
	for(;;){
		GPIOC->ODR |= ( 1 << 6 );
		customDelay(1000,1000);
		GPIOC->ODR &= ~( 1 << 6 );
		customDelay(1000,1000);

	}
}

void customDelay(unsigned int inner, unsigned int outer){
	for(int i = 0 ; i < outer; i++){
		for(int j = 0 ; j < inner ; j++){}
	}
}
  • Compile the program. If all goes well you should have no errors!

Blinky Walkthrough

To understand this program you will need to have a good understanding of bit manipulation in C. If you do not know much about bit manipulation in C, or need a bit of review, have a look at my earlier post on bit manipulation in C.

The objective of this program is to blink the LED on our development board. This LED is tied to pin PC6 on the STM32G070 microcontroller which is present on the GPIOC GPIO port.

RCC_IOPENR Register

RCC->IOPENR |= (1 << 2)  ;
  • The first line of code sets bit 2 in the RCC_IOPENR register. This has the effect to enabling the GPIOC port and sending it a clock signal.

GPIOx_MODER Register

GPIOC->MODER |= (1 << 12) ;GPIOC->MODER &= ~(1 << 13) ; // make PC6 output
  • Once the GPIOC port is enabled, the direction of pin PC6 needs to be set to output. This is accomplished by setting bit 12 and clearing bit 13 in the GPIOC_MODER register. Because the reset value for the bits in this register are not all zeroes, it is necessary to explicitly clear bit 13.

GPIOx_OTYPER Register

  • The GPIOC_OTYPER register determines whether PC6 is a push-pull output or an output drain output. Since PC6 needs to be a push-pull output, bit 6 in this register needs to be cleared. Because all bits in this register are zeroes by default (after reset), this was not explicitly required. The push-pull behavior is the default for all pins.

GPIOx_OSPEEDR Register

GPIOC->OSPEEDR |= ((1 << 12) | (1 << 13));
  • The GPIOC_OSPEED Register, controls the switching speed of the I/O. Reducing switching speed means that the rise/fall times of the IO is longer. It also reduces power consumption. Increasing switching speed gives us faster rise/fall times but increases power consumption. Since power consumption is not a current concern, set the switching speed for PC6 to very high. This is accomplished by setting both bits 12 and 13 in the GPIOC_OSPEEDR register.

GPIOx_ODR Register

GPIOC->ODR |= ( 1 << 6 );
customDelay(1000,1000);
GPIOC->ODR &= ~( 1 << 6 );
customDelay(1000,1000);

Finally to set PC6 to a high or a low state, set or clear bit 6 in the GPIOC_ODR register as shown above.

Notice that once the PC6 pin is configured, the program enters an endless for loop and keeps setting and clearing the state of the PC6 pin via setting and clearing bit 6 in the GPIOC_ODR register. A CPU cycle delay provides a brief delay between the setting and the clearing, so that the LED blinking can happen slowly enough to be visible. Using such a delay strategy is not ideal because determining the exact length of the delay can be difficult. Also the compiler will usually identify this as useless instructions and try to optimize it out. This is why optimization had to be disabled. A better approach would be to use some sort of timer to accurately generate delays, but this is adequate for now.

Identifying bits in their registers by their bit position can make programs hard to interpret. Giving these bit positions names that make sense can in some instances make the code more readable. After using the bit definitions available in the stm32g070xx.h header file, the program becomes:

#include <stm32g070xx.h>

void customDelay(unsigned int inner, unsigned int outer);

int main(void)
{
	RCC->IOPENR |= RCC_IOPENR_GPIOCEN   ; // send clock to GPIOC
	GPIOC->MODER &= ~GPIO_MODER_MODE6_Msk;
	GPIOC->MODER |=(1 << GPIO_MODER_MODE6_Pos);

	GPIOC->OSPEEDR |= GPIO_OSPEEDR_OSPEED6;
	for(;;){
		GPIOC->ODR |= GPIO_ODR_OD6 ;
		customDelay(1000,1000);
		GPIOC->ODR &= ~GPIO_ODR_OD6 ;
		customDelay(1000,1000);

	}
}

void customDelay(unsigned int inner, unsigned int outer){
	for(int i = 0 ; i < outer; i++){
		for(int j = 0 ; j < inner ; j++){}
	}
}

I’m not sure I like these bit definitions but this will have to do for now.

After connecting the ST-Link hardware to the board and PC, and programming the microcontroller with the STM32CubeProgrammer, we got ourselves a blinking LED! Mission accomplished!

Hello blinky!

Bit Manipulation in C

While waiting for the PCBs and parts required to build the STM32G070 Microcontroller development board, I figured I’d post another entry summarizing how to perform bit manipulation in the C programming language. Bit manipulation is an important topic in embedded development. Those that want to program microcontrollers and configure their peripherals at the register level will need to know how to write binary values into registers as well as set, clear, toggle and read bits in registers. so let’s get started!

Bitwise operators

Let us first start by introducing the bitwise operators:

  • & - bitwise AND operator; AND’s two values at the binary level
  • | - bitwise OR operator; OR’s two values at the binary level
  • ^ - bitwise XOR operator; XOR’s two values at the binary level
  • ~ - bitwise one’s complement operator; flips all of the bits of a value at the binary level. This is a unary operator i.e. it operates on one value at a time.
  • >> - bitwise right shift operator; shifts a value to the right at the bit level by a specified number of bits.
  • << - bitwise left shift operator; shifts a value to the left at the bit level by a specified number of bits.

Consider the code snippet below. It declares three unsigned 8 bit integer variables; a, b & y. And initializes a to 200 and b to 56.

uint8_t a = 200;
uint8_t b = 56;
uint8_t y;

Performing a bitwise AND between a and b, results in 8.

y = a & b;

Bitwise AND example

Performing a bitwise OR between a and b, results in 248.

y = a | b;

Bitwise OR example

Performing a bitwise XOR between a and b, results in 240.

y = a ^ b;

Bitwise XOR example

Performing a bitwise right shift by 3 bits on a, results in 25.

y = a >> 3;

Performing a bitwise left shift by 3 bits on a, results in 64.

y = a << 3;

Shifting examples

It is important to note that bit shifts can also facilitate multiplication and division operations by literals that are powers of two. For example, to multiply a variable a by 8 (23) one can bit shift the variable a by 3 (power of two equivalent to eight) times to the left.

 y = a * 8;   /*is equivalent to    y = a << 3;*/

To divide (integer division) a variable a by 8 (23), one can bit shift the variable a by 3 times to the right.

 y = a / 8;     /*is equivalent to    y = a >> 3;*/

To generalize this

y = x * 2^z is equivalent to y = x << z
y = x / 2^z is equivalent to y = x >> z

All bitwise operations covered so far can be verified by compiling and running the simple C program provided below with gcc or your favourite c compiler:

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

int main (void) {
    uint8_t a,b,c,y;
    a = 200;
    b = 56;
    c = 16;
    y = 0;

    y = ~200;
    printf(" The one's complement (NOT) of 200 is %i \n",(unsigned int)y);
    y = a & b;
    printf(" Bitwise ANDING of 200 and 56 gives %i \n",(unsigned int)y);
    y = a | b;
    printf(" Bitwise ORING of 200 and 56 gives %i \n",(unsigned int)y);
    y = a ^ b;
    printf(" Bitwise XORING of 200 and 56 gives %i \n",(unsigned int)y);
    y = a << 3;
    printf(" Bitwise left shift by 3 of the value 200 gives %i \n",(unsigned int)y);
    y = a >> 3;
    printf(" Bitwise right shift by 3 of the value 200 gives %i \n",(unsigned int)y);

    printf(" Multiplying the value 16 by 8 (2^3) gives %i. Bit shifting the value 16 by 3 bits to the left also gives %i \n",(unsigned int)c*8, (unsigned int) c<<3);
    printf(" Dividing the value 16 by 8 (2^3) gives %i. Bit shifting the value 16 by 3 bits to the right also gives %i \n",(unsigned int)c/8, (unsigned int) c>>3);
    return 0;
}

A few more necessary basics

In C, when we want to perform an operation on a variable and store the result back into that same variable, the shorthand notation can be used:

 x = x <op> y   is equivalent to x <op>= y
 x = x  +   y   is equivalent to x  +=   y
 x = x  -   y   is equivalent to x  -=   y
 x = x  *   y   is equivalent to x  *=   y
 x = x  /   y   is equivalent to x  /=   y

Shorthand notation also works for bitwise operators:

 x = x  &   y   is equivalent to x  &=   y
 x = x  |   y   is equivalent to x  |=   y
 x = x  ^   y   is equivalent to x  ^=   y
 x = x  >>   y   is equivalent to x >>=  y
 x = x  <<   y   is equivalent to x >>=  y

In C, an integer number can be represented in decimal, binary, hexadecimal and octal formats through the use of prefixes.

  • To represent a number in decimal (base 10) no prefix is needed, i.e.
x = 255; //decimal number 255
  • To represent a binary (base 2), precede it with a 0b, i.e.
x = 0b11111111;
  • To represent a number in hexadecimal (base 16), precede it with a 0x, i.e.
x = 0xff;
  • To represent a number in octal (base 8), precede it with 0, i.e.
x = 0377;

It doesn’t matter what base representation is to the program or the computer, since all numbers end up being processed in binary. Typing out literals in bases other than decimal however can make understanding the program a bit easier for the programmer in some cases.

Another way to represent a number is to use the left shift 1 by an arbitrary number of bits approach. This is known as mask notation. Consider the number 8. In binary, number 8 is represented as 0b00001000. well that is basically a ‘1’ in the third position (start counting from 0 from right to left). Therefore number 8 can also be represented by left shifting one by three positions i.e. (1 << 3).

/******************************************************************************
x =   1; is equivalent to x = 0b00000001; which is equivalent to x = (1 << 0);
x =   2; is equivalent to x = 0b00000010; which is equivalent to x = (1 << 1);
x =   4; is equivalent to x = 0b00000100; which is equivalent to x = (1 << 2);
x =   8; is equivalent to x = 0b00001000; which is equivalent to x = (1 << 3);
x =  16; is equivalent to x = 0b00010000; which is equivalent to x = (1 << 4);
x =  32; is equivalent to x = 0b00100000; which is equivalent to x = (1 << 5);
x =  64; is equivalent to x = 0b01000000; which is equivalent to x = (1 << 6);
x = 128; is equivalent to x = 0b10000000; which is equivalent to x = (1 << 7);
******************************************************************************/

Using this mask notation works very nicely for numbers that are powers of two, since these numbers consist of a single ‘1’ value at different bit positions. What if a number that was not a power of two; say something like 14 or 25 is to be represented in mask notation ?

Number 14 is equivalent to 8+4+2, or in binary 0b00001110. This can be written as:

 (1 << 3) | (1 << 2) | (1 << 1);

Number 25 is equivalent to 16+8+1 or in binary 0b00011001. This can be written as:

(1 << 4) | (1 << 3) | (1 << 0);

So basically any number can be represented in this mask representation be either a left shift of 1, or by bitwise ORing multiple left shifts of ‘1’ !

We’re Now Ready To Manipulate Them Bits!

Armed with the above knowledge, bit manipulation is a breeze. There are four main bit manipulation operations to master:

  • Setting one or more bits in a register.
  • Clearing one or more bits in a register.
  • Toggling one or more bits in a register.
  • Reading the value of a particular bit within a register.

For the rest of this entry variables will be referred to as registers. Both refer to locations in memory where data is stored. The term register is typically used to refer to memory locations whose data configures peripherals such as General Purpose Input Output(GPIO) , Universal Asynchronous Receiver Transmitter (UART) e.t.c.

Setting one or more bits in a register

The goal here is to set one or more particular bit(s) in a register, without disturbing the state of the other bits that are not of interest. To achieve this, OR the current content of the register with a mask value with only one(s) in the bit location(s) to be set and zeroes elsewhere.

For example, if the value of a register (or variable) a is 200. To set bit x in that register, OR a with a mask value of 2x.

To further elaborate, If the goal is to set bit 5 in register a, the mask is 25 = 32. The set operation then becomes:

a= a | 32;  

which is equivalent to

a |= 32;

in shorthand notation. Alternatively 32 can be written as (1 << 5) in C. This mask notation is easier to read and immediately indicates to the reader/programmer which bit is being set.

a  |= (1 << 5);

Setting a bit in a register

In summary to set a bit x in register reg, use the following expression:

reg |= (1 << x);

What if the programmer needs to set more than one bit in register reg, say bits x, y and z ?

reg |= ((1 << x) | (1 << y) | (1 << z));

Clearing one or more bits in a register

The goal here is to clear one or more particular bit(s) in a register, without disturbing the state of the other bits that are not of interest. To achieve this, AND the current content of the register with a mask value with zero(es) in the bit location(s) that we want to clear and ones elsewhere.

For example, if the value of register (or variable) a is 200. To clear bit x in that register, the content of register a is ANDed with a mask that is the inverted (one’s complement) value of 2x.

To further elaborate, if the goal is to clear bit 6. the mask becomes the one’s complement of 26 = 64 which results in 191. Therefore to clear bit 6 in register a:

a= a & 191 ;  

which is equivalent to

a &= 191;

in shorthand notation. Alternatively 191 can be written as ~(1 << 6) in C. This mask notation indicates to the reader/programmer which bit is being cleared without having to resort to doing decimal to binary conversion.

a  &= ~(1 << 6);

Clearing a bit in a register

In summary, to clear a bit x in register reg use the following expression:

reg &= ~(1 << x);

What if the programmer needs to clear more than one bit in register reg, say bits x, y and z ?

reg &= ~((1 << x) | (1 << y) | (1 << z));

Toggling one or more bits in a register

The goal here is to toggle one or more particular bit(s) in a register, without disturbing the state of the other bits that are not of interest. To achieve this, XOR the current content of the register with a mask value with one(s) in the bit locations to be toggled and zeroes elsewhere.

For example if the value of a register a is 200, to toggle bit ‘x’ in that register then one will need to XOR a with a mask value of 2x.

To further elaborate, If the goal is to toggle bit 5 in register a, the mask is 25 = 32. The set operation then becomes:

a= a ^ 32;  

which is equivalent to

a ^= 32;

in shorthand notation. Alternatively 32 can be written as (1 << 5) in C.

a  ^= (1 << 5);

In summary, to toggle a bit x in register reg use the following expression:

reg ^= (1 << x);

What if the programmer needs to toggle more than one bit in register reg, say bits x, y and z ?

reg ^= ((1 << x) | (1 << y) | (1 << z));

Read the value of a bit in a register

To read the value of bit x in register a, simply AND a with a mask value of 2x (i.e. all bit positions are 0’s except position x). If the result is 0 then bit x in register a was 0(cleared). If the result is non-zero, then bit x in register a was 1(set).

For example to read the state of bit 5 in register a whose content is 200, AND a with 25 = 32 = (1 << 5). Because the fifth bit in a is zero, the result of this operation will also be zero as shown in the figure below.

Reading the state of a bit in a register

To read the state of bit 6 in register a whose content is 200, AND a with 26 = 64 = (1 << 6). Because the sixth bit in a is ‘1’, the result of this operation will be non-zero (64) as shown in the figure below.

Reading the state of a bit in a register

In C code, this translates to:

uint8_t a , y;
/*Testing state of bit 5 in a*/
y = a & 32;
if(y == 0 )
  printf("Bit 5 in register a is zero");
else
  printf("Bit 5 in register a is one");

/*Now testing state of bit 6 in a*/
y = a & 64;
if(y == 0 )
  printf("Bit 6 in register a is zero");
else
  printf("Bit 6 in register a is one");

In C a non-zero value evaluates to true so the statement

if(y)

will evaluate to true if ‘y’ is non-zero. Using this knowledge and shorthand notation the previous code snippet becomes:

/*Testing state of bit 5 in a*/
if(a & (1 << 5))
  printf("Bit 5 in register a is one");
else
  printf("Bit 5 in register a is zero");

/*Now testing state of bit 6 in a*/
if(a & (1 << 6))
  printf("Bit 6 in register a is one");
else
  printf("Bit 6 in register a is zero");

In some cases the programmer will want to return the exact state of the bit in a register and not a non-zero value when the state of the bit is one, and zero when the state of the bit is zero. This can be accomplished with the ternary operator. If bit x in register a is 1, res will be 1, else res will be zero.

res = (a & ( 1<< x )) ? 1 : 0 ;

An even better way to read the state of a bit x in register a is to use this notation:

res = (a >> x) & 1;

This shifts the content of register a to the right by x bits, putting the xth bit to be tested in the zeroth position. It is then ANDed with one. If this bit is zero, the result of the operation is zero, else it is one.

In summary to read the state of bit x in register a with an if statement:

if(a & (1 << x))
  printf("Bit x in register a is one");
else
  printf("Bit x in register a is zero");

If the state of the bit is to be returned without an if statement, use either

res = (a & ( 1<< x ) ) ? 1 : 0 ;

or

res = (a >> x) & 1;

I personally prefer the second option.

Building a bit manipulation library

Now that we know how to manipulate bits in C we can write ourselves a little library!

The macro version should look like this:

#define m_setBit(reg,x) reg |= (1 << x)
#define m_clearBit(reg,x) reg &= ~(1 << x)
#define m_toggleBit(reg,x) reg ^= (1 << x)
#define m_readBit(reg,x)  (reg >> x) & 1
#define m_readBit2(reg,x) (reg & ( 1<< x ) ) ? 1 : 0

If you are not a big fan of macros, the ‘function’ version of the library would look something like this:

void setBit(int* reg, int x){      *reg |= (1 << x);}
void clearBit(int* reg, int x){    *reg &= ~(1 << x);}
void toggleBit(int* reg, int x){   *reg ^= (1 << x);}
int  readBit( int* reg, int x){  return (*reg >> x) & 1;}
int  readBit2( int* reg, int x){ return (*reg & ( 1<< x ) ) ? 1 : 0 ;}

STM32G070 Microcontroller Development Board for Tinkerers

Welcome to my first blog entry! It will be about a simple STM32G070 microcontroller board design that this salty tinkerer designed in KiCAD. The requirements for this development board are:

  • 2 layer printed circuit board (PCB)
  • Minimalist in nature with as few components as possible all placed on the top copper layer.
  • Make the board as small as possible (possibly compromising this a bit by using slightly bigger easier to solder components)
  • Cheap / low cost
  • Utilize large surface mount components that are easy to solder (1206 for passives)
  • Use a relatively simple, low cost and easy to program microcontroller.
  • Designed for experimentation and tinkering!

The newly introduced STM32G070 from ST Microelectronics is a great candidate for the board for many reasons. For starters, it sports a 64MHz Cortex M0+ core with a healthy dose of Flash (128KB) and RAM (36KB), it has a decent array of easy to use peripherals, relatively simple clock tree, comes in an easy to solder 0.8mm pitch 32-LQFP package, has only single power and ground pins for easy routing and finally it can be had for $2.40CAD ($1.60USD) in unit quantities !

The STM32G071 is also a good candidate and has all peripherals that the STM32G070 has in addition to USB, USB-C power delivery, analog comparators, digital to analog converters, analog comparators and a low power UART. It is also a bit more expensive (but still an excellent deal) at about $4.41CAD ($2.94USD) in unit quantities. Since both parts have the same pinout, the board should be able to accomodate both parts. But for now the STM32G070 is the part that I’ll go with.

A pre-eliminary Bill of materials for the board (save for the headers) costs $5.13 CAD which is very affordable. The KiCAD design files and gerbers are available for download

The circuit schematic is shown in the figure below:

STM32G070 Development board Schematic

The circuit design consists of the following:

  • 3.3V linear voltage regulator is used to provide a stable 3.3V supply to power the microcontroller.
  • Schottky diode at the input is used for input polarity protection.
  • Crystal circuit providing the microcontroller with a stable 8MHz clock source.
  • Two pushbuttons and pull-up / pull down resistors attached to the NRST and BOOT0 pins that facilitate reseting and/or programming the STM32G070 via serial bootloader.
  • The NRST, PA14/BOOT0/SWCLK and PA13/SWDIO pins facilitate the programming the microcontroller via the serial wire debug (SWD) interface.
  • Finally, an LED and a current limiting series resistor are connected to pin PC6 for testing.

Pins associated with SWD programming are placed on the same 4-pin header. All other microcontroller pins are broken out to the other large 0.1” pitch 28-pin header.

Adding a USB connector with a USB to serial IC (such as the CH340E or the CP2102N) was deliberately avoided as it would increase the BOM cost, complicate the layout and increase the difficulty of soldering the components to the board. Besides, cheap USB to serial and SWD ST-Link programming/debugging hardware are widely available.

The board is 2.82” x 0.7” in dimension and can be fabricated (3 boards) at OSH Park for about $9.90 USD.

The Layout and 3D view are shown below:

Layout

3D View

I’ve already submitted the PCB for fabrication and ordered the parts. Nothing left to do but wait! I will make a new entry once the PCB / parts arrive.