Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Beginning with the ST32F3DISCOVERY Microcontroller Board from STMicroelectronics

5.00/5 (6 votes)
14 Oct 2021CPOL25 min read 12.9K   119  
An introduction and a first walkthrough of using STM32CubeIDE with the ST32F3DISCOVERY board.
The STM32F3DISCOVERY board is an interesting way to work with a new device other than an Arduino. The STM32CubeIDE is a more complex IDE than the Arduino IDE. However, it is not easy to find helpful information about the STM32F3DISCOVERY board unlike some other DISCOVERY boards also offered by ST.

Introduction

The STM32F3DISCOVERY board is a board from STMicroelectronics that contains a STM32F303VCT6 microcontroller (featuring 256‑Kbyte Flash memory and 48‑Kbyte RAM along with two mini-B USB connectors,) a couple of ST MEMS devices, a user pushbutton, and some ten LEDs of various colors. It is one of several DISCOVERY prototypes and experimental boards offered by ST to allow someone to work with a microcontroller from the STMicroelectonrics product line.

I've done experiments with the Arduino UNO, ESP32, and the ATMega 2560 microcontrollers and thought it interesting to try an entirely different kind of microcontroller.

I hoped to use the Arduino IDE but ran into problems so switched to the STM32CubeIDE from the manufacturer, STMicroelectronics. I now believe that working with the Arduino IDE would have been much more difficult than with the ST IDE despite having to learn new software.

I'm using Windows 10 on a Dell i7-7700, however there is a version of the ST IDE for Linux as well. I've had a few bumps and lessons learned but overall, using the ST IDE has been surprisingly easy despite its complexity.

What I like about the DISCOVERY products, there are several variations with different STMicroelectronics products whose features vary, is that unlike the other microcontroller boards I've worked with, there are a number of devices built into the product allowing experiments with nothing more than the board and a mini-B to Type A USB cable.

The downside is that the STMicroelectronics product line targets commercial microcontroller applications and there isn't the kind of maker community around these products as exists for the Arduino and ESP products.

The STM32F3DISCOVERY board also lacks any external communication other than USB. There is no WiFi capability nor Bluetooth.

A major difference between the STM32F3DISCOVERY board and an Arduino Uno board is that pins are 3v and not 5v which reduces power consumption. You must be careful about the components used to ensure they are within the 3 volt range of the STM32 chip or risk damaging the chip. The board has pins for both 3v and 5v sources as well as ground however always remember that the GPIO pins sink or source 3v.

The board is powered by the USB connector, either the ST-LINK USB connector or the USER USB connector. This does mean that you need to be careful about the amount of current any devices using the 3v or 5v source pins on the board are drawing.

Communication with the board through the ST-LINK USB connector is a bit different than an Arduino UNO or ESP32. Rather than a Serial COM port, it's more like a mass storage device. However, the USER USB is supposed to appear as a Serial COM port. Linux may just work however with Windows you may run into a problem using the USER USB. See embedded - stm32f3 'USER USB' not detected - Stack Overflow for details.

Background

I purchased my STM32F3DISCOVERY board from Mouser Electronics which had a lower price than Amazon. What I discovered was the board has two mini-B type USB connectors (one for the ST-LINK USB connector and one for the USER USB connector) so a standard cell phone USB Type A to USB mini-A cable doesn't work. Fortunately, I had a card reader device that had the mini-B USB connector and was able to use that cable. See What is the difference between USB Mini A and Mini B? as well as Wikipedia topic: USB hardware.

Click the image below for a full size image to see details. Of the two mini-B USB connectors at the top edge of the board, the one on the left is an ST-LINK for debugging and the one on the right is for a standard Type B USB connection. There are two 50 pin connectors, P1 on the left edge and P2 on the right edge. In the center of the lower edge are a set of eight LEDs of different colors arranged in a circle. The blue push button on the left next to the middle of the P1 connector is connected to a GPIO pin allowing it to be used for input. The black button on the right next to the middle of the P2 connector is a RESET button to reset/restart the board.

See also UM1570 User manual, Discovery kit with STM32F303VC MCU - User manual which provides more details about the board.

Image 1

I had hoped to use the Arduino IDE, as I'm familiar with it and I saw articles indicating that I could use it with the STM32F3DISCOVERY board. However after a brief time, I gave up on that approach and went with the STM32CubeIDE from STMicroelectronics instead. See the article STM32CubeIDE: The First Free ST IDE with STM32CubeMX Built-in on the ST website. I just did a Google search for STM32CubeIDE and the download link was at URL STM32CubeIDE - Integrated Development Environment for STM32 - STMicroelectronics was the first item listed.

STMicroelectronics has a number of videos available on YouTube and I suggest you spend some time browsing through the list and watch the introductions to the STM32CubeIDE. The IDE seems to be built on the Eclipse platform and if you are not careful, you will find yourself fighting the IDE trying to get something done rather than actually getting it done.

STMicroelectronics also has an educational area on their website, STM32 MOOCs (Massive Open Online Courses) - STMicroelectronics.

STM32CubeIDE Quick Overview

The STM32CubeIDE is much more complex than the Arduino IDE and is built on the Eclipse platform. I have a large 32" LCD monitor with a resolution of 3840 x 2160 and I find that size and resolution very helpful as the IDE seems to take a lot of screen real estate.

If you have experience with Eclipse, then you will feel more comfortable with the IDE. The IDE uses the Eclipse workspace concept so multiple, independent projects may be in the same workspace. You may also have multiple, independent workspaces for your work. You open a project by locating the .loc file within the file tree of a project in the left hand Project Explorer panel of the IDE. Most objects such as files and folders open or close with a double click.

The IDE also uses the Perspective concept. When you open the .loc file, you are in one Perspective or presentation mode and when you open a source code file, you can change to a different Perspective. Some perspectives are visually more complex than others with the Perspective having multiple panes dividing up the window. However, individual panes can be collapsed or expanded as well as resized.

You can add additional source files to a project. See How to include library files in STM32CubeIde | VIDEO25 - YouTube for the procedure.

Basic Workflow of STM32CubeIDE

The basic workflow using the STM32CubeIDE is:

  • Create a new project setting the target selection
  • Specify the settings of the various pins of the microcontroller
  • Generate the code creating a template or skeleton containing initialization code
  • Modify the generated code in sections marked by comments
  • Build and run your application downloading it to the STM32F3DISCOVERY board

Each time you modify the settings of the pins, you will need to redo the generate code action creating a new version of the main.c file. There are special areas within the main.c source file where you can put your own source code, marked by comments, which the generator will keep. Source code outside of those commented areas will be removed as part of generating a new version of main.c.

The generator puts its files into the folders Core/Inc and Core/Src within the project file tree where it puts the main.c along with any ST source code files needed for the project.

You can also put your own source files in those folders, however it may be best to create a separate folder to put things in order to be tidy and organized.

Using the STM32CubeIDE

Starting a New Project

When starting a new project with the IDE and the STM32F3DISCOVERY board, your first task is to set the target device. This is when you are going to find out the bewildering number of devices that ST provides. The STM32 product line is composed of complex and varied devices and the IDE is designed to accommodate them all.

Once you've selected the target device, the IDE starts with an image of the microcontroller showing the pinout and package within a frame with several tabs. The only tab I've used is the Pinout & Configuration tab which is where you provision the pins you are using for your project.

You can change from Pinout view to System view within the Pinout & Configuration tab. Pinout view is more graphical while System view is tables of text. You will most probably find yourself switching between one view and the other. You can also combine the views. All in all, the IDE seems flexible in how it presents information to the user but it does take a bit of experimentation.

In Pinout view, you can zoom in and move the microcontroller chip pinout image around in order to read or select or change pins. The scroll wheel of a mouse performs the zoom function and you can click and hold the left mouse button, then drag the image about and then release the mouse button.

In either Pinout view or System view, you indicate what pins you will be using with their settings and then the IDE will generate the initialization code with a template for you to insert your source code into.

The pin user labels are helpful to organize code. The format for these appear to be similar to "B1 [Blue push button]" (without the quotes) which indicates the pin name prefix is B1 and the text "[Blue push button]" is a comment about the purpose of the pin. The pin name prefix is used in the generated source code to create a mnemonic for the pin port number and the pin. The following defines are added to the include file main.h:

C++
#define B1_Pin GPIO_PIN_0
#define B1_GPIO_Port GPIOA

Without the user label, the pin's mnemonic would be GPIO_PIN_0 and the port would be GPIOA which is not really a helpful reminder of what the pin is for.

In the generated function static void MX_GPIO_Init(void), there will also be initialization source code generated such as the following:

C++
/*Configure GPIO pin : B1_Pin */
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

Using C++

One thing that puzzled me was how to change the code generation and target from main.c to main.cpp so that I could use C++. I created a new project and selected C++ as the Targeted language however the generated file was still main.c and not main.cpp. Then I found this article, How to Use C++ with STM32CubeIDE - Shawn Hymel, which indicated that a second step, renaming the main.c file to main.cpp, was needed.

However what I ran into was that the STM32CubeIDE will always generate source code into a file main.c so should you use this approach, the main.cpp you created will not be updated.

What I have done instead is to create a new user maintained C++ source file, mymain.cpp, which the main() in main.c calls after initializing the environment and the real work begins with that function. So the main() in main.c looks like the following:

C++
int main(void)
{
  /* USER CODE BEGIN 1 */

    // This is the entry point to my C++ source code files.
    // The STM32CubeIDE does not seem to be able to generate and update
    // C++ main.cpp files for some reason.
    // The workaround is to have another C++ source file that contains the
    // real entry point which this generated main.c source file will call after
    // completing initialization.
    extern int mymain(void);

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
//  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

  // The main entry point in the C++ source after initializing
  // the runtime environment.
  return mymain();
  /* USER CODE END 3 */
}

And in the file mymain.cpp, I make sure to include the header file main.h which contains all the generated constants along with a declaration to ensure that the mymain() entry point is compatible with C using extern "C" int mymain(void); to prevent name mangling.

Target Selection

The target selection dialog has several tabs. I suggest that you click on the Board Selector tab and then in the Commercial Part Number combo box, look for STM32F3DISCOVERY. The combo box will update the list depending on what is entered in the text control so if you enter "stm32f3d" (without the quotes), you should find it easily. This seems to be the simplest procedure though I haven't spent a lot of time experimenting with alternatives.

Image 2

Generated Code and Modifications

Below is an example of the generated source code in main.c of a simple onboard User LED blink program. This code sample gives some idea as to what source is generated. This is not all of the generated source most of which is initialization code of various kinds which is generated depending on the pin settings you've picked out.

Notice there are comments that show where the programmer can insert non-generated source code such as /* USER CODE BEGIN 3 */ and /* USER CODE END 3 */. These comments bracket a section of the source code file that the generator will save and restore so this is where you put any source code.

If you do not insert your source code in the proper place, the next time the code generator is run, any source that is not in the proper place will be deleted and will not be in the new generated source.

C++
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_SPI1_Init();
  MX_USB_PCD_Init();
  /* USER CODE BEGIN 2 */
    static unsigned short pin_state = 0;
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
      pin_state = !pin_state;
      // write pin state
      // NOTE: You can in turn use HAL_GPIO_TogglePin
      HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, pin_state);
      // synchronous delay for 500 ms
      HAL_Delay(500);
  }
  /* USER CODE END 3 */
}

In the above example, I have two sections where I inserted source code: (1) at /* USER CODE BEGIN 2 */ where I defined the variable pin_state which is used to remember the current pin state so that I can toggle it and (2) the source code at /* USER CODE BEGIN 3 */ where I set the GPIO pin state in order to light and unlight an LED.

C++
/* USER CODE BEGIN 2 */
static unsigned short pin_state = 0;
/* USER CODE END 2 */

and the blinking source in the while loop:

C++
 /* USER CODE BEGIN 3 */
pin_state = !pin_state;
// write pin state
// NOTE: You can in turn use HAL_GPIO_TogglePin
HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, pin_state);
// synchronous delay for 500 ms
HAL_Delay(500); 

STM32CubeIDE Debugger Connection Failure

See this material from STMicroelectronics, UM2576 which describes how the STM32CubeIDE ST-LINK GDB server works. STM32CubeIDE ST-LINK GDB server - User manual provides an overview of the architecture of the debug setup. See as well UM1734 which describes the STM32Cube USB device library, UM1734_STM32Cube.book.

A problem I ran into with the STM32CubeIDE was after a couple of times being able to download and run my test application successfully, I suddenly ran into the following error.

Waiting for debugger connection...

Debugger connected

Debugger connection lost.

Shutting down...

The problem along with a solution is described here, stm32 - STM32CubeIDE can only flash once, no SWD debugging - Stack Overflow. However the posted answer applies to an ST device other than the STM32F3DISCOVERY.

This problem seems to be associated with whether you are using pins that somehow interfere or conflict with the ST-LINK device on the STM32F3DISCOVERY board used for the Debugger connection. Most of the pins on the STM32 chips are multi-functional and it seems to be fairly easy for someone new to the board to run into pin conflicts.

There are two pins, PC4 and PC5 which seem to be involved in USB connectivity that seem to help some Debugger connectivity problems. According to Discovery kit with STM32F303VC MCU - User manual in section 6.2.3 VCP configuration on page 15/36 there is this brief bit of prose:

Quote:

The ST-LINK/V2-B on STM32F3DISCOVERY supports virtual Com port (VCP) on U2 pin 12 (ST-LINK_TX) and U2 pin 13 (ST-LINK_RX), which are connected to the STM32F303 MCU target STM32 USART1 (PC4, PC5), thanks to SB13 and SB15 solder bridges.

Looking at the chip pinout diagram in the STM32CubeIDE, those two pins, located at the lower edge of the chip, were disabled. I changed the setting of pin PC4 to USART1_TX and of pin PC5 to USART1_RX at which point downloads seemed to begin working again.

However, when working on a new project starting from scratch, the Debugger connection seemed to work fine whether the pins were disabled or not.

It also seems that using the pin PA0 in your project to allow the use of the blue User button can possibly conflict with the ST-LINK UART functionality of the ST32F3DISCOVERY board. What I noticed is that when I change pin PA0 to GPIO_Input, then I see a warning with SYS in the System Core list. However, Debugger and download may still work.

I've also found that using I2C can affect the use of the STM32CubeIDE Debugger. Stepping through a simple application using I2C, the Debugger lost connectivity at the point where the I2C system was initialized. When I removed the use of I2C, the Debugger worked fine and I was able to step through the program, look at variables, set breakpoints, etc.

So at this point, I'm not sure what the actual solution is and suspect that there are several different scenarios that can cause a conflict resulting in loss of Debugger connectivity. Each scenario has its own solution or workaround. And a solution may involve not only pin assignment and function, but the clock settings as well.

USB Driver Update followed by ST-LINK Failure

After doing a Windows 10 update that included two USB driver updates, I am now having problems using the STM32CubeIDE to download and run a program on my STM32F3DISCOVERY board. What has happened is that the USB driver that is now being loaded by Windows 10 when I connect the board with a mini-B USB connector to the ST-LINK debug connector, a Serial port driver to COM4, is different from the driver that was being loaded before the updated, a mass storage device driver.

When I use the Run menu item to download and run a program, a dialog box displays with the text "No ST-LINK detected! Please connect ST-LINK and restart the debug session."

See stm32 - STM32F3DISCOVERY board using STM32CubeIDE download fails "No ST-LINK detected!" but CubeProgrammer works - Electrical Engineering Stack Exchange as well as microcontroller - stm32 - is there a factory reset? - Electrical Engineering Stack Exchange.

A workaround to be able to download a program to the board is to use the STM32CubeProgrammer application from STMicroelectronics. However, while this allows me to continue to download new versions of my program to the board, I am currently unable to use the Debugger to step through code, look at variables, and the other tasks that a remote debugger allows when developing and testing a microcontroller program.

I will update the Electrical Engineering Stack Exchange post I created as I learn more about this problem. I may need to either uninstall and reinstall STM32CubeIDE or to install the ST-LINK driver available here, STSW-LINK009 - ST-LINK, ST-LINK/V2, ST-LINK/V2-1, STLINK-V3 USB driver signed for Windows7, Windows8, Windows10 - STMicroelectronics.

Using the Code

Introduction

The source code of the project attached to this article performs a couple of simple tasks:

  • Turn on and off an external LED by pressing the blue USER button on the STM32F3DISCOVERY
  • Monitor the setting of a 10K ohm potentiometer or variable resister using an ADC and representing the value using the onboard LEDs

The materials for the first task are a current limiting resister and an LED. The reason for the resister is described here, ohms law - Why do we need resistors in led - Electrical Engineering Stack Exchange.

The materials for the second task are a 10K ohm potentiometer and a capacitor. The reason for the capacitor is described here, voltage - 10K Potentiometer with Arduino Uno and 5v works but same pot with ESP32 and 3.3v floating values - Electrical Engineering Stack Exchange.

You will also need a few Dupont connectors and a small breadboard to hold the various components. See this article about Dupont connectors: Crimping Dupont Connectors : 10 Steps (with Pictures) - Instructables.

Hardware

The hardware is used to demonstrate three common tasks for embedded microcontrollers using the STM32F3DISCOVERY board:

  • Reading a digital signal, high or low, from a pushbutton
  • Writing a digital signal, high or low, to light or unlight an LED
  • Reading an analog signal using an ADC

The eight onboard LEDs are arranged in a circle like a clockface.

Image 3

The onboard LED numbering and the pins are not in numerical order. Holding the board so that the USB connectors are at the top and looking at the circular arrangement, you can see on the board that there is a compass diagram with N at the top, E to the right, S at the bottom, and W to the left. Clockwise around the circle of LEDs beginning at the top or N:

  • LED 3 at the top, commonly labeled LD_3, red, attached to pin PE9
  • LED 5 at top right, commonly labeled LD_5, orange, attached to pin PE10
  • LED 7 at right, commonly labeled LD_7, green, attached to pin PE11
  • LED 9 at bottom right, commonly labeled LD_9, blue, attached to pin PE12
  • LED 10 at bottom, commonly labeled LD_10, red, attached to pin PE13
  • LED 8 at bottom left, commonly labeled LD_8, orange, attached to pin PE14
  • LED 6 at left, commonly labeled LD_6, green, attached to pin PE15
  • LED 4 at top left, commonly labeled LD_4, blue, attached to pin PE8

The source uses the following pins on the board, all of which are located on the P1 50-pin connector on the left edge of the board:

  • voltage source, 3v pin
  • ground, gnd pin
  • external LED, PC1 pin as GPIO_Output
  • potentiometer input through the ADC, PA1 pin as GPIO_Analog with ADC1_IN2

In addition, there are several pins that are internally connected to onboard devices:

  • blue push button, PA0 as GPIO_Input
  • LED 4, PE8 as GPIO_Output
  • LED 3, PE9 as GPIO_Output
  • LED 5, PE10 as GPIO_Output
  • LED 7, PE11 as GPIO_Output
  • LED 9, PE12 as GPIO_Output
  • LED 10, PE13 as GPIO_Output
  • LED 8, PE14 as GPIO_Output
  • LED 6, PE15 as GPIO_Output

Finally, there is the settings for the Analog-to-digital converter device ADC1 to be provisioned. The potentiometer provides an analog signal in the form of a voltage that varies depending on how the knob is turned. We need to use an ADC to convert the analog signal to a digital value.

As the potentiometer output is wired to pin PA1 and that pin is provisioned as GPIO_Analog with ADC1_IN2, then we need to make sure that the Mode and Configuration settings for ADC1 channel IN2 allow us to read the analog signal and convert it to a digital value.

Image 4

Looking at the Mode dialog of the Mode and Configuration section for ADC1 in the STM32CubeIDE, we see there are nine different channels, IN1 through IN9. These are ordinarily set to Disable. Since our PA1 pin is using ADC1_IN2 we want to set the Mode of channel IN2 of the ADC device ADC1 to a Mode setting that provides a digital value representing the analog voltage signal being detected.

Set the mode of ADC1 channel IN2 to IN2 Single-ended in the Mode and Configuration dialog.

For further reading on the Analog-to-digital conversion, take a look at Application note AN3116, STM32™'s ADC modes and their applications.

Wiring the Experiment

The wiring is straightforward. You will need the following connections:

  • Circuit from pin PC1 to the LED and to ground through the resistor
  • Circuit from 3v pin to the potentiometer and to pin PA1 with a connection to ground through the capacitor

Warning: The GPIO pins are 3v and not 5v. To prevent damage to the ST32F3DISCOVERY board, double check the power sources being connected to any pins on the board.

Connect one outside terminal of the potentiometer to a lead on the capacitor. Connect the other lead of the capacitor to the GND pin. Connect the other outside terminal to the 3v pin. Then connect the center terminal to pin PA1 on the board. This will cause pin PA1 to see voltage levels from 0v to 3v depending on the setting of the potentiometer knob.

Connect one terminal of the LED to a lead on the resistor. Connect the other lead of the resistor to the GND pin. Connect the other terminal of the LED to pin PC1. This will light the LED when the pin PC1 is HIGH and unlight the LED when the pin is LOW.

The brightness of the LED depends on the amount of current. The current is limited by the resistor so different resistor values will provide different levels of brightness. Since we are powering the LED using the 3v of the PC1 pin being HIGH, the LED will not be as bright as if we were using 5v as less current will flow See Does LED brightness change with voltage? - Electrical Engineering Stack Exchange. Also different colored LEDs have different levels of brightness since they have slightly different electrical characteristics.

Note: An LED is a kind of diode which means that current flows through the diode in only one way. If an LED is placed in the circuit so that the current direction is not correct then the LED will not light because the current that powers it is not able to flow.

Hint: The easiest way to test the LED circuit is to take the wire going to pin PC1 and to put it on the 3v source pin. If the LED lights, then the placement of the circuit the LED in the circuit is correct and you can change the wire back to the pin PC1. If the LED doesn't light, then most probably you have the polarity incorrect so turn it around and test the circuit again. You also need to have a good connection between the 3v source and the LED, between the LED and one leg of the resistor, and between the other leg of the resistor and ground.

Source Code

The source code for the project is composed of the main.c file which is mostly generated code from the STM32CubeIDE after making choices about the various microcontroller resources being used. However, in order to use C++ in this project, I did make added a few lines of source in the user code sections to invoke the C++ code which starts with the function mymain() in the source file mymain.cpp.

C++
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */

    // this is the entry point to my C++ source code files.
    // the STM32CubeIDE does not seem to be able to generate and update
    // C++ main.cpp files for some reason.
    // the work around is to have another C++ source file that contains the
    // real entry point which this generated main.c source file will call after
    // completing initialization.
    extern int mymain(void);

//  while (1)  comment out the while() because the main loop is in mymain() in mymain.cpp
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

  // the main entry point in the C++ source after initializing
  // the runtime environment.
  return mymain();
  /* USER CODE END 3 */

Here is the source code from the source file mymain.cpp which I put into the folder Core/Src along side of the source file main.c. A couple of things to note about this source file.

The header file main.h is included into this source file because that is where the generator puts all of its generated code. The header file contains defined constants as well as header files from the STM32 development environment so it's necessary to have.

Two other things to note are (1) the extern "C" int mymain(void); statement which prevents C++ name mangling of the entry point function int mymain(void) and (2) the list of extern statements to declare global objects created by the generator and defined in the source file main.c. With a more complex example, I would probably choose to place these extern declarations in the file main.h and am a bit dismayed that the generator doesn't automatically do that for me. In main.h, there is this handy section:

C++
/* Exported functions prototypes ---------------------------------------------*/
void Error_Handler(void);

/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

I've tried to provide comments for the important areas of code. This is a simplistic approach to obtaining the potentiometer setting from the ADC by polling each time through the loop. There are other ways of using the ADC for real world applications but in this case, we don't have any real performance or power constraints.

As a part of testing the code which lights varying numbers of the onboard LEDs based on the value read from the ADC, I used a simple counter to simulate varying ADC results so that I did not need an actual circuit and hardware.

C++
#include "main.h"

extern "C" int mymain(void);           // make this main entry point callable from C program

// external objects defined in main.c by the code generator.
// any such objects created in main.c used here need to be
// declared here.

extern "C" ADC_HandleTypeDef hadc1;

int mymain (void){
      GPIO_PinState pinState = GPIO_PIN_SET;

      HAL_GPIO_WritePin(LD_EXT_GPIO_Port, LD_EXT_Pin, pinState);

      uint16_t iCount = 0;  // this is used to generate values to simulate ADC readings.

      // the main infinite loop where we do our work. ordinarily
      // this would be in main() in the file main.c however we have
      // moved it to here n order to allow C++ for this project.
      while (1)
      {
          // For example, for a 8-bit ADC, the digital output value will
          // be between 0-255, for a 10-bit ADC, the digital output value
          // will be between 0-1023 and for a 12-bit ADC, the digital
          // output value will be between 0-4095.
          uint32_t Adc_value = 0;
          uint32_t Adc_range = 4095;  // using 12 bit ADC, ADC_RESOLUTION_12B

            HAL_ADC_Start(&hadc1);    // start A/D conversion
            if(HAL_ADC_PollForConversion(&hadc1, 5) == HAL_OK) //check if conversion 
                                                               //is completed
            {
                Adc_value  = HAL_ADC_GetValue(&hadc1); // read digital value and 
                                                       // save it inside uint32_t variable
            }
            HAL_ADC_Stop(&hadc1);     // stop conversion
            HAL_Delay(200);

            // determine the LEDs to light up. this order is in the clockwise order of the
            // LEDs on the STM32F3DISCOVERY board rather than the numeric order.
            // LED 3 is the red LED on the top with the board oriented 
            // so that the USB connectors are on the top edge.
            uint32_t pinNo [] = {LD3_Pin, LD5_Pin, LD7_Pin, LD9_Pin, LD10_Pin, 
                                 LD8_Pin, LD6_Pin, LD4_Pin};
            GPIO_TypeDef *pinPort [] = {LD3_GPIO_Port, LD5_GPIO_Port, LD7_GPIO_Port, 
            LD9_GPIO_Port, LD10_GPIO_Port, LD8_GPIO_Port, LD6_GPIO_Port, LD4_GPIO_Port};

            // Uncomment the following if you want to test the logic
            // for lighting the LED array based on the ADC reading without
            // needing an analogue source connected to the ADC pin.
            // Adc_value = iCount * 200;

            // scale the ADC value to the number of LEDs in the array of
            // LEDs and then go through the list of LEDs to turn on and
            // turn off a subset of the LEDs in order to represent the
            // position of the potentiometer.
            Adc_value /= Adc_range / (sizeof(pinNo)/sizeof(pinNo[0])) + 1;

            for (uint32_t id = 0; id < sizeof(pinNo)/sizeof(pinNo[0]); id++) {
                if (id <= Adc_value)
                    HAL_GPIO_WritePin(pinPort[id], pinNo[id], GPIO_PIN_SET);
                else
                    HAL_GPIO_WritePin(pinPort[id], pinNo[id], GPIO_PIN_RESET);
            }

            // give the LEDs the opportunity to turn on before we
            // continue.
            HAL_Delay(250);

            // check if the blue USER contact pushbutton is depressed
            if (HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin)) {
              // blue button pressed. change the pin state and the light status of the LED
              // then delay a moment to debounce the button.
              pinState = (pinState == GPIO_PIN_SET) ? GPIO_PIN_RESET : GPIO_PIN_SET;
//              HAL_GPIO_WritePin(LD4_GPIO_Port, LD4_Pin, pinState);

              // toggle the external LED by either turning on the pin to
              // source 3v to light the LED or turn it off to unlight the LED.
              HAL_GPIO_WritePin(LD_EXT_GPIO_Port, LD_EXT_Pin, pinState);
              HAL_Delay(250);   // give the LED a moment to change state.

              // ADC value simulator
              iCount++;
              if (iCount * 200 > 4090) iCount = 0;
            }
      }

      return 0;
}

Points of Interest

Getting to this point has taken a bit of time. There has been a fairly steep learning curve due to having to learn how to use both the STM32CubeIDE software and to learn about the STM32F3DISCOVERY board.

The STM32F3DISCOVERY board does not seem to be a popular choice as finding material from others about how to do things has been a bit of a challenge. It looks like the board has been out for about a decade now however the F0 and F4 Discovery kits seem to have more material available on the internet.

I first ran across this board when I found a tutorial on using the Rust programming language for embedded projects. The tutorial was based on using this board and I'm interested in learning Rust. That tutorial is at Hardware - The Embedded Rust Book (rust-embedded.org).

I'm looking forward to using the board in further exploring embedded topics. I think that the complexity of the microcontroller will push me to learn more about electronics as I work on topics such as I2C and SPI and the ADC.

The board also allows for the use of FreeRTOS and it looks like the STM32CubeIDE supports that so there is another large area of discovery to pursue.

STM32CubeIDE Points

The way that the STM32CubeIDE presents some of the board setting options doesn't always use the same language as articles about the board and microcontroller.

On the other hand, I haven't spent much time on the STMicroelectronics web site and their learning materials yet so perhaps it's time to do so.

The STM32CubeIDE helps immensely in making the learning curve more gentle and less steep. Most of the toolchain sorcery is hidden so I've not had to deal with that so I can concentrate on learning the STM32F3DISCOVERY board itself.

My impression is that using open source toolchains such as the one for Rust are easier in a Linux environment and I'm using Windows 10.

The problems with Debugger connectivity are a bit annoying and I'm not sure what to do about those for any project which results in pin use conflicts. I suspect that there are workarounds which would depend on the cause of the conflict however that requires a more indepth knowledge of the board and the microcontroller than I have.

When I was able to use the Debugger, I found it to work well. I was able to set breakpoints, step through code, examine variables, and to the other things that I would normally do with a Debugger.

History

  • 14th October, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)