PWM with STM32

PWM stands for Pulse-Width Modulation. Once the switching frequency (fsw) has been chosen, the ratio between the switch-on time (TON) and the switch-off time (TOFF) is varied. This is commonly called duty-cycle (D). The duty cycle can be between 0 and 1 and is generally expressed as a percentage (%).

D = TON / (TON + TOFF) = TON x fsw

The variation of the pulse width, made at a high frequency (kHz), is perceived as continuous and can be translated into a variation of the rotation speed of a motor, dimming a LED, driving an encoder, driving power conversion, and etc. The use of PWM is also widely used in the automotive sector in electronic control units (ECU - Electronic Control Unit) to manage the energy to be supplied to some actuators, both fixed and variable frequency. Among the various actuators used in a vehicle and controlled by PWM i.e. diesel pressure regulator, EGR valve actuator, voltage regulator in modern alternators, turbocharger variable geometry regulation actuator, electric fans, etc.

Where To Buy?
No.ComponentsDistributorLink To Buy
1STM32 NucleoAmazonBuy Now

PWM signal generation through Timer in STM32

Let's see now how it is possible to generate a PWM with STM32. In this case, it can be generated using a timer made available by the microcontroller.

STM32 configuration with STCube

In this article, we will see how to configure and write an application to dim an LED connected to an output pin (GPIO) of the STM32F446RE Nucleo board. First of all, let's see how to configure the initialization code with the STCube tool.

Basic Configurations

GPIO selection and configuration

It is necessary to identify a GPIO to associate a timer to generate the PWM. For example, we choose PB3 associated with Channel 2 of Timer 2 (TIM2_CH2). Note that the pin turns orange to remind us of that timer 2 still needs to be configured.

System Reset and Clock Control (RCC) Initialization

  • To initialize the RCC use the following path: “Pinout & Configuration”-> System Core -> RCC. “High-Speed Clock” (HSE) and “Low-Speed Clock” (LSE) select for both “Crystal/Ceramic Resonators”.

Timer 2 Configuration

Now you need to configure timer two which is hooked to pin PB3.

Clock Configuration

We configure the clock tree so that the clock frequency (HCLK) of the microcontroller is 84 MHz, handling frequency divisor and multiplier of PLLCLK. The bus to which the timers are connected (APB1 timer and APB2 timer clocks) is also at 84 MHz.

Timer Configuration

We now know that timer 2 has a clock source of 84 MHz, so every time the clock switches the timer does a count. If we configure it to count to 84 million (it is a 32-bit timer, the Counter must be between 0 and 4294967295) it will take a second. We can set this number to exactly half so that the LED lights up for half a second and turns it off for another half-second. In practice, we will see at the output a square wave that will have a duty cycle of 50% and a switching frequency of 1Hz (too slow not to notice the switching on and off the LED !!). To do what has been said, the timer must be configured as follows:

  1. Enable PWM Generation on Channel 2;
  1. Set Prescaler to 0
  2. Set Counter Mode to “Up”;
  3. Set Counter Period to 83999999;
  4. Set Internal Clock Division to “No Division”;
  5. Set auto-reload preload to “Disable”;
  6. Set Master-Slave Mode to “Disable”;
  7. Set Trigger Event Selection to “Reset (UG bit from TIMx_EGR”;
  8. Set Mode to “PWM mode 1”;
  9. Set Pulse to 0;
  10. Set Output compare preload to “Enable”;
  11. Set Fast Mode to “Disable”
  12. Set CH Polarity to “High”.

Now we are ready to lunch the initialization code and write our application.

The initialization code

In “Private variables” we find TIM_HandleTypeDef htim2, it is an instance to C struct that needs to manipulate the DAC peripheral. In “Private function prototypes” the function prototype used to initialize and configure the peripherals:
/* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM2_Init(void);/* USER CODE BEGIN PFP */
At the end of main() we find the initialization code of System Clock, Timer 2 and GPIO ( as previously selected in STCube):
void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};   /** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3); /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLM = 16; RCC_OscInitStruct.PLL.PLLN = 336; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4; RCC_OscInitStruct.PLL.PLLQ = 2; RCC_OscInitStruct.PLL.PLLR = 2; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;   if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } }   /** * @brief TIM2 Initialization Function * @param None * @retval None */ static void MX_TIM2_Init(void) {   /* USER CODE BEGIN TIM2_Init 0 */   /* USER CODE END TIM2_Init 0 */   TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0};   /* USER CODE BEGIN TIM2_Init 1 */   /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 0; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 83999999; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_PWM_Init(&htim2) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */   /* USER CODE END TIM2_Init 2 */ HAL_TIM_MspPostInit(&htim2);   }   /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) {   /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOB_CLK_ENABLE();   }   /* USER CODE BEGIN 4 */   /* USER CODE END 4 */   /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ }

On/Off LED with STM32

Now we are ready to write our code in main() to on/off LED.
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_TIM2_Init(); /* USER CODE BEGIN 2 */ HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 41999999); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }

As mentioned earlier we have set the timer two to count up to 84 million, so we know that counting 84 million a second has elapsed. Now let's use the PWM function to make sure that for half a second the LED stays on and the other half a second off. In practice, to generate a square wave with a duty cycle of 50% and a frequency of one second.

We use the function “HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2)” to enable timer 2 to start in PWM mode and the macro “__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 41999999)” tells the timer which is the value with which to compare the internal count (in this case 41999999) to decide whether the LED should be off or on.

Now we just must connect a led between pin PB3 and ground through a 220OHm resistor to limit the current (if we want to work with a duty cycle of 100%) and compile our application.

Once this is done, we see that the LED will remain on for 500ms second and off for another 500ms as can be seen from the waveform acquired by the PB3.

Since we have chosen a switching frequency of the PWM that is too low (1Hz), we will only see the LED turn on and off and we do not check its luminosity: we will see in the next paragraph how to increase the switching frequency, adjust the duty cycle in order to increase and decrease the brightness.

Dimming LED using PWM in STM32

First, we declare the following define:

#define D_COUNTER 839

Now, we associate it with the field “htim2.Init.Period” of the structure *htim2:

htim2.Init.Period = D_COUNTER;

In this way, we can quickly the number of counts that the timer must do and therefore manage the frequency and duty cycle of our PWM.

This way our timer will count up to 839 in 10us. Consequently, the switching frequency will be 100kHz (clearly exceeding 1Hz !!). Note that as in the previous example we have subtracted 1 from the count value because the timer starts at zero.

Then, we define an unsigned int variable to set the duty cycle:

unsigned int D; //duty cycle In the main() we write; /* USER CODE BEGIN 2 */ D= 10; //it menas duty cycle of 10% HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, (D_COUNTER/100)*D); /* USER CODE END 2 */

Where (D_COUNTER/100)*D needs to re-proportion the value of the duty cycle to the count that the timer must perform.

Compiling we will see that now the LED will always be on but with low brightness, in fact, the duty cycle is only 10% as can be seen from the generated waveform and the rms value of the voltage given to the LED is about 1.1 Volt (as you can see in the bottom corner of the RMS figure for the green trace). Furthermore, the figure confirms that the duty cycle is 10% both intuitively by looking at the green trace and by reading the measurement at the bottom center (Dty+ = 10,48%).

If we set D=70, the LED will be brighter, in fact the RMS value is about 2.82 Volt (as you can see in the bottom corner of the RMS figure for the green trace). Furthermore, the figure confirms that the duty cycle is 70% both intuitively by looking at the green trace and by reading the measurement at the bottom center (Dty+ = 69,54%).

If we set D=100, the led will be illuminated with the maximum brightness imposed by the limitation of the 220 Ohm resistor. The rms value at the ends of the LED with the resistance in series will be 3.3 Volts (the maximum generated by the GPIO)

Now if you write the following code on while(), we will see that the LED will change brightness every 100ms (in practice it increases, every 100ms the duty by 1% starting from 1% until it reaches 100% and then starts all over again)

/* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ for(D=1; D<=100; D++){ HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, (D_COUNTER/100)*D); HAL_Delay(100); } /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */

To do this we include the PWM configuration function ( HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, (D_COUNTER/100)*Duty) in the following loop for(D=1; D<=100; D++) which increases the value of variable D by 1 every 100 ms through the function HAL_Delay(100).

Now we are ready to effectively manage the PWM to control the brightness of a led, but in a similar way, we can control the speed of a DC motor or various actuators.

Using DAC with STM32

A Digital to Analog Converter(DAC) performs the task of converting digital words of n bits into voltages whose amplitude will be proportional to the value of the code expressed by the words themselves. Since the input binary words represent a succession of finite codes, the voltage coming out of a DAC cannot be continuous over time but is made up of as many levels as the converted codes are. This means that the devices to which the analog signal produced by a DAC is sent must filter it with a low-pass characteristic (integrating action). The operating criterion of a DAC is simple: in fact, it is sufficient to have a succession of as many voltages as there are convertible codes, obtained for example by means of a weighted resistance network (i.e. the value proportional to the code implied in the binary word). The conversion consists in sending the voltage corresponding to the code applied to the input to the output of the converter.

One of the simplest and most classic DACs is the R-2R ladder, but today there are more complex objects with optimization in the signal reconstruction. Below is shown a 3bit R-2R ladder DAC.

In practice, the circuit is an inverting adder where the bits (B0, B1, ... Bn) command a switch. The output voltage in the case of the 3-bit DAC is:

Vout= -1/2*B1*Vref - (1/4)*B1*Vref- (1/8)*B1*Vref

If the 3bit string is D and Vref is equal to the logical voltage of 3.3V

Vout= (3.3*D)/2^3

The typical output characteristic is shown in the following figure.

Compared to the weighted resistor DAC, the R-2R scale DAC has the advantage of using only two resistive values. Therefore, it is more easily achievable with integrated circuit technology.

Where To Buy?
No.ComponentsDistributorLink To Buy
1STM32 NucleoAmazonBuy Now

DAC on STM32 platform

Many of the STM32 microcontrollers have on board at least one DAC (generally 12 bit) with the possibility of buffering the output signal (with internal operational amplifier OP-AMP). The use of the DAC finds various applications, for example, it can be used to reconstruct a sampled signal or to generate any waveform (sine wave, square wave, sawtooth, etc.), to generate a reference voltage (for example for a digital comparator).

The DAC peripheral can be controlled in two ways:

  • Manually
  • Using a Data Memory Access (DMA) with a trigger source (can be an internal timer or external source).

DAC in manual mode

In this modality we can drive DAC to on/off a LED, to generate a reference voltage, etc. We will use a NUCLEO STM32L053R8 board to show as configure DAC with STCube. This NUCLEO has available a DAC with only one channel (in general every DAC has one or more channels) with resolution up to 12bit with a maximum bus speed of 32 MHz and a maximum sampling rate of 4 Msps. First, let's see how to initialize the peripherals using STCube Tool:

Configuration of DAC in manual mode

DAC Initialization

  • Select DAC with following path: “Pinout & Configuration”-> Analog -> DAC. Select the Output 1 (OUT1 Configuration):
  • In Configuration->Parameter Setting select Output Buffer= Enable and Trigger = None
  • The GPIO PA4 is associated to DAC Output1. PA4 has been configurated in Analog Mode, No Pull-Up and No Pull-Down.

System Reset and Clock Control (RCC) Initialization

  • Select RCC with following path: “Pinout & Configuration”-> System Core -> RCC. “High Speed Clock” (HSE) and “Low Speed Clock” (LSE) select for both “Crystal/Ceramic Resonator”.

Now we are ready to generate the initialization code.

Diving into the initialization code

At this point, let's look at the generated code:

  • In “Private variables” we find DAC_HandleTypeDef hdac, it is an instance to C struct that need to manipulate the DAC peripheral:
typedef struct { DAC_TypeDef *Instance; /*!< Register base address */   __IO HAL_DAC_StateTypeDef State; /*!< DAC communication state */   HAL_LockTypeDefLock; /*!< DAC locking object */   DMA_HandleTypeDef *DMA_Handle1; /*!< Pointer DMA handler for channel 1 */   #if defined (DAC_CHANNEL2_SUPPORT) DMA_HandleTypeDef *DMA_Handle2; /*!< Pointer DMA handler for channel 2 */ #endif __IO uint32_t ErrorCode; /*!< DAC Error code }DAC_HandleTypeDef;
  • In “Private function prototypes” the function prototype used to initialize and configure the peripherals:
/* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DAC_Init(void); /* USER CODE BEGIN PFP */
  • Where we find the initialization select in STCube Tool.

Driving DAC to generate a reference voltage

Now, before writing our application, let's see what functions the HAL library makes available to handle the DAC.

  • HAL_DAC_Start(DAC_HandleTypeDef* hdac, uint32_t Channel): need to enable DAC and start conversion of channel.
  • “hdac” is a pointer to DAC structure
  • “Channel” is the selected DAC channel
  • HAL_DAC_Stop(DAC_HandleTypeDef* hdac, uint32_t Channel): need to disable DAC and start the conversion of the channel.
  • “hdac” is a pointer to DAC structure
  • “Channel” is the selected DAC channel
  • HAL_DAC_SetValue(DAC_HandleTypeDef* hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data): is used to set in DAC channel register the value passed.
  • “hdac” is pointer to DAC structure
  • “Channel” is the selected DAC channel
  • “Alignment” needs to select the data alignment; we can select three configurations, because the DAC wants the data in three integer formats:
  • “DAC_ALIGN_8B_R” to configure 8bit right data alignment;
  • “DAC_ALIGN_12B_L” to configure 12bit left data alignment;
  • “DAC_ALIGN_12B_R” to configure 12bit left data alignment.
  • “Data” is the data loaded in the DAC register.

The voltage output will be:

Vout,DAC = (Vref*data)/2^nb

where nb is a resolution (in our case 12bit), Vref is voltage reference (in our case 2 Volt) and the passed data.

So, to set DAC output to 1 Volt data must be 2047 (in Hexadecimal 0x7FF) so we call the function this way:

HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 0x7FF);

To set the output voltage to 3.3 Volt we call function in this way:

HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 0xFFF);

 

To verify that change the value in our main we write the following code and then we check the output connecting it to the oscilloscope probe.

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DAC_Init(); while (1) { /* USER CODE END WHILE */ HAL_DAC_Start(&hdac, DAC_CHANNEL_1); HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 0x7FF); HAL_Delay(1000); HAL_DAC_Stop(&hdac, DAC_CHANNEL_1); HAL_Delay(1000); HAL_DAC_Start(&hdac, DAC_CHANNEL_1); HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 0x7FF); HAL_Delay(1000); HAL_DAC_Stop(&hdac, DAC_CHANNEL_1); HAL_Delay(1000); /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }

We expect the output voltage to change every second by repeating the following sequence: 1V - 0V - 2V – floating (as shown in the figure below)

The signal displayed on the oscilloscope checks the sequence we expected. In the first second the output voltage from the DAC is 1V then in the next second 0V then 2V and in the last second of the sequence, the output is floating.

Using DAC in Data Memory Access (DMA) mode with a Timer

In this modality we can drive DAC to on/off a LED, to generate a reference voltage, etc. We will use a NUCLEO STM32L053R8 board to show as configure DAC with STCube. This NUCLEO has available a DAC with only one channel (in general every DAC has one or more channels) with resolution up to 12bit with a maximum bus speed of 32 MHz and a maximum sampling rate of 4 Msps. First, let's see how to initialize the peripherals using STCube Tool:

Configuration DAC in DMA mode

  • DAC Configuration
  • Select DAC with following path: “Pinout & Configuration”-> Analog -> DAC. Select the Output 1 (OUT1 Configuration):
  • In “Parameter Settings” the Output Buffer is enabled, Timer 6 is selected as Trigger Out Event and Wave generation mode is disabled.
  • Activate DMA to handle DAC using channel 2. The direction is Memory to Peripheral. The buffer mode is “circular”, and the data length is a word.
  • Set interrupt related channel 2 of DMA

TIM 6 Configuration

  • We use TIM6 because is one of two timers used by uP to trigger the DAC output. At the moment we do not change the initial configuration, later we will see what we need.
  • TIM6 -> NVIC Setting: flag “TIM6 interrupt and DAC1/DAC2 underrun error interrupts” to activate interrupts.

Now we are ready to generate the initialization code. Before we need to learn as the waveform can be generated using DAC.

Sinewave generation

Let's see mathematically how to reconstruct a sinewave starting from a given number of samples. The greater the number of samples, the more "faithful" the reconstructed signal will be. So, the sampling step is 2pi / ns where ns is the number of samples in this way, we have to save our samples in a vector of length ns. The values ??of every single element of the vector will be given by the following equation:

S[i] = (sin(i*(2p/ns))+1)

We know that the sinusoidal signal varies between 1 and -1 so it is necessary to shift it upwards to have a positive sinewave (therefore not with a null average value) therefore it must be moved to the middle of the reference voltage. To do this, it is necessary to retouch the previous equation with the following:

S[i] = (sin(i*(2p/ns))+1)*((0xFFF+1)/2)

Where 0xFFF is the maximum digital value of DAC (12bit) when the data format is 12 bits right aligned.

To set the frequency of the signal to be generated, it is necessary to handle the frequency of the timer trigger output (freq.TimerTRGO, in our case we use the TIM6) and the number of samples.

Fsinewave = freq.TimerTRGO/ns

In our case, we define Max_Sample = 1000 ( is uint32_t variable) and let's redefine some values of the timer 6 initialization.

static void MX_TIM6_Init(void) { /* USER CODE BEGIN TIM6_Init 0 */ /* USER CODE END TIM6_Init 0 */ TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM6_Init 1 */ /* USER CODE END TIM6_Init 1 */ htim6.Instance = TIM6; htim6.Init.Prescaler = 1; htim6.Init.CounterMode = TIM_COUNTERMODE_UP; htim6.Init.Period = 100; htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim6) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM6_Init 2 */ /* USER CODE END TIM6_Init 2 */ }

We have changed the following parameters:

htim6.Init.Prescaler = 1; htim6.Init.Period = 100;

So with 1000 samples, the output sinewave will be a frequency of 10 Hz. We can change the number of samples (being careful not to use too few samples) or the “Init.Prescaler” and “Init.Period” values of timer 6.

Driving DAC in DMA mode with Timer

Using the DAC in DMA mode the HAL library makes available to handle the DAC another function to set the DAC output.

HAL_DAC_Start_DMA(DAC_HandleTypeDef* hdac, uint32_t Channel, uint32_t* pData, uint32_t Length, uint32_t Alignment)

Compared to the manual function we find two more parameters:

  • uint32_t* pData is the peripheral buffer address;
  • uint32_t Length is the length of data to be transferred from the memory to DAC peripheral.

As you can see from the following code we first need to include the "math.h" library, define the value of pigreco (pi 3.14155926), and write a function to save the sampled sinewave values in a array (we wrote a function declared as get_sineval () ).

#include "math.h" #define pi 3.14155926 DAC_HandleTypeDef hdac; DMA_HandleTypeDef hdma_dac_ch1; TIM_HandleTypeDef htim6; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_DAC_Init(void); static void MX_TIM6_Init(void); uint32_t MAX_SAMPLES =1000; uint32_t sine_val[1000]; void get_sineval() { for (int i =0; i< MAX_SAMPLES; i++) { sine_val[i] = ((sin(i*2*pi/MAX_SAMPLES)+1))*4096/2; } } /* USER CODE END 0 */
  • Once this is done, we can start the DAC by saving the sampled values of the sinewave in the buffer (* pdata) as shown below:
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_DMA_Init(); MX_DAC_Init(); MX_TIM6_Init(); /* USER CODE BEGIN 2 */ get_sineval(); HAL_TIM_Base_Start(&htim6); HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, sine_val, MAX_SAMPLES, DAC_ALIGN_12B_R); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }

Now we just have to show the acquisition that the oscilloscope:

If we change the number of MAX_SAMPLE to 100 the sinewave will be a frequency of 100 Hz, but as can be seen from the acquisition, the number of samples, in this case, is a bit low.

We can optimize the waveform by acting on timer 6 in order to increase the number of samples. For example by modifying htim6.Init.Period = 400

So, that was all for today. I hope you have enjoyed today's lecture. Let me know if you have any questions. Thanks for reading. Take care !!! :)

How to use ADC with STM32?

An Analog to Digital Converter (ADC) converts a continuous signal (usually a voltage) into a series of discrete values ??(sequences of bits). The main features are:

  • Resolution (in analog terms): It is the minimum variation of the analog input voltage that can determine the variation of the LSB, that is of the least significant bit of the output code. Since the quantization step Q corresponds to the LSB, it can be said that the resolution coincides with the quantization step Q (and therefore is measured in Volts). We can say that the quantization step Q corresponds to the LSB because two contiguous quantization bands, each of amplitude Q, are identified by codes that differ only for the least significant bit.
  • Resolution (in digital terms): It is the number n of bits present at the converter output, that is the number of bits with which the converter encodes a sample of the analog input signal. As the number of bits of the converter increases, the number of quantization bands increases and (with the same full-scale value VFS) their amplitude decreases, an amplitude which is nothing more than the step Quantization Q. If the quantization step narrows, the smaller the voltage variation necessary to determine the variation of the LSB, i.e., of the least significant bit of the code, becomes the exit. So, saying that a converter has many bits is equivalent to saying that the voltage variation necessary to make the LSB vary is small. The image below shows the 3 bits ADC input-output characteristics.
  • Full-scale voltage: It is the range, that is the maximum excursion, of the input voltage. Typical dynamic values are between 10 Vpp ( pp peak to peak) and 20 Vpp, unipolar or bipolar.
  • Types of response: in general, ADCs have a response of a linear theoretical type of response, but there are also types with a logarithmic response.
  • Accuracy: indicates the goodness of the conversion depends on it. The error made by the ADC is usually measured. This error consists of two components: a quantization error and a non-linearity error.
  • Sampling frequency: A sampling is an operation with which the input signal is discretized over time, transforming it into a succession of values, samples in fact, which will subsequently be digitized. The simplest way to extract values is to use a switch, in series with the signal, which closes and opens at defined and equidistant intervals. The smaller this interval, called the sampling step (Ts), the more faithful the reconstruction of the signal will be starting from its samples. Likewise, too small a sampling step leads to a waste of resources (measurement time, memory for data storage). A sampling of the signal generally indicates not only its discretization over time but also its maintenance until the next closing of the circuit-breaker. These two phases are realized by special circuits called Sample & Hold (S / H).

There are different types of ADCs, the most common are listed below (illustrating their operation is not the purpose of this article):

  • A direct conversion ADC (Flash ADC)
  • A Successive Approximation Register (SAR) ADC
  • One dual ramp ADC (Dual Slope or integration)
  • A pipeline ADC
  • A tracking ADC (delta-coded)

Generally, STM32 microcontrollers have at least one ADC (a SAR ADC) with the following characteristics:

  • Resolution: ADCs have a resolution of up to 12 bits with a maximum conversion frequency of 2.5 MHz, with 18 multiplexed channels among which 16 can be available for measurements of external signals, the other two are for internal measurements (temperature and voltage).
  • Conversion Time and Conversion Groups: The conversion time can be individually programmed for each channel. There are 8 discrete times conversions for each ADCCLK clock frequency (Fc), these times range from 1.5 to 239.5 cycles.

Fc = ADCCLK / (12.5 + Number of cycles)

Each ADC has two conversion modes: “regular” and “injected”.

  • The "regular" mode allows you to specify a channel or a group of channels to be converted in turn one after the other. The conversion core can consist of more than 16 channels, and the order in which the channels must be converted can also be programmed. The conversion can be initiated by software or by a hardware event consisting of a series of timer signals or by line 1 of the EXTI. Once the conversion has started, you can carry out continuous conversions, or you can operate discontinuously by converting a selected number of channels and then stopping the conversion pending the triggering of the next core. At the end of a conversion the result is stored in a single register (result register) and an interrupt can be generated. The ADC1 has a dedicated DMA channel that can be used for transferring the converted value from the result register to a memory buffer. Through this method, an entire conversion cycle can be copied into memory, eventually obtaining a single interrupt generated by the DMA. To further speed up the conversion, a double-sized buffer can be used to generate two interrupts: one when the first half has been filled (first conversion cycle) and the other when the second half is filled (second conversion cycle). This mode can be combined with the "DMA circular buffer mode" to handle multiple conversions with hardware.
  • The second conversion mode is called the “injected group”. It is able to carry out the conversion sequence up to a maximum of four channels, which can be triggered by a software or hardware event. Once triggered, it will stop the conversion of the regular group, carry out its sequence of conversion and then will allow the regular group to continue the conversion. A conversion sequence can be configured in this mode. Unlike the regular group, in this mode, each result has its own register (result register) and its own offset register. This last register can be programmed with a 16-bit value automatically deducted from the ADC result.

Furthermore, the "Dual Conversion Modes" can be active:

In the STM32 with almost two ADCs and it is, therefore, possible to perform different conversion modes: in these types of conversion the ADC2 acts as a slave while the ADC1 acts as a master allowing 8 different types of conversion.

  • Injected Simultaneous Mode and Regular Simultaneous Modes: These two modes synchronize the regular and injected group conversion operations on two converters. This is very useful when two quantities (current and voltage) have to be converted simultaneously.
  • Combined Regular / Injected Simultaneous Mode: This mode is a combination of both the regular and injected modes and allows us to have a synchronized conversion sequence.

We are now ready to write a first simple example using the ADC peripheral. The goal is to measure the voltage in a voltage divider composed of a fixed value resistor and a potentiometer (so that by moving the potentiometer cursor, the voltage to be read varies) we begin by configuring our peripheral with STCube Tool. For this project, we will use the NUCLEO STM32L053R8. This board has only one ADC with 16 channels and a resolution of up to 12bit.

Now we’ll see the configuration step by step:

Where To Buy?
No.ComponentsDistributorLink To Buy
1STM32 NucleoAmazonBuy Now

ADC channel selection

We have to flag IN0 to activate Channel 0, then we can configure the peripheral. Channel 0 is on GPIO PA0 as we can see in the picture below:

ADC setting

We select the ADC_prescaler equal to 4, resolution to 12bit (maximum of a resolution, we can choice between 6, 8, 10 and 12 bits), “right data alignment” (we can choose between right and left alignment), and “forward” as scan direction (we can choose between forward and backward).

For this first example we’ll hold disabled Continuous, Discontinuous conversion and DMA mode. Furthermore, the ADC sets, at the end of single conversion, the EoC (End of Conversion) flag.

 

ADC Regular conversion mode

We select 12.5 Cycles as sampling time (in this way the sampling frequency is 320 kHz obtained from the formula described above), the start of conversion is triggered by software. Furthermore, for this application the watchdog is disabled.

After the generation of the initialization code with STCube, we can find in our project the ADC configuration. As for every peripheral, the HAL library defines the dedicated C structure, for the ADC defines “ADC_HandleTypeDef”.

 

In our case the “ADC1” is the instance that points to our ADC. The structure “ADC_InitTypeDef” is used to handle the configuration parameters. In our example is generated as follow:

static void MX_ADC_Init(void) { /* USER CODE BEGIN ADC_Init 0 */ /* USER CODE END ADC_Init 0 */ ADC_ChannelConfTypeDef sConfig = {0}; /* USER CODE BEGIN ADC_Init 1 */ /* USER CODE END ADC_Init 1 */ /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) */ hadc.Instance = ADC1; hadc.Init.OversamplingMode = DISABLE; hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc.Init.Resolution = ADC_RESOLUTION_12B; hadc.Init.SamplingTime = ADC_SAMPLETIME_12CYCLES_5; hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD; hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc.Init.ContinuousConvMode = DISABLE; hadc.Init.DiscontinuousConvMode = DISABLE; hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc.Init.DMAContinuousRequests = DISABLE; hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV; hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED; hadc.Init.LowPowerAutoWait = DISABLE; hadc.Init.LowPowerFrequencyMode = ENABLE; hadc.Init.LowPowerAutoPowerOff = DISABLE; if (HAL_ADC_Init(&hadc) != HAL_OK) { Error_Handler(); } /** Configure for the selected ADC regular channel to be converted. */ sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_RANK_CHANNEL_NUMBER; if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN ADC_Init 2 */ /* USER CODE END ADC_Init 2 */ }
The function HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) needs to initialize the peripheral and define the clock and the GPIO ( in our case PA0).
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(hadc->Instance==ADC1) { /* USER CODE BEGIN ADC1_MspInit 0 */ /* USER CODE END ADC1_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**ADC GPIO Configuration PA0 ------> ADC_IN0 */ GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USER CODE BEGIN ADC1_MspInit 1 */ /* USER CODE END ADC1_MspInit 1 */ } } /** * @brief ADC MSP De-Initialization * This function freeze the hardware resources used in this example * @param hadc: ADC handle pointer * @retval None */
The function HAL_ADC_MspDeInit(ADC_HandleTypeDef* hadc) needs to de-initialize the peripheral.
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* hadc) { if(hadc->Instance==ADC1) { /* USER CODE BEGIN ADC1_MspDeInit 0 */ /* USER CODE END ADC1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_ADC1_CLK_DISABLE(); /**ADC GPIO Configuration PA0 ------> ADC_IN0 */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0); /* USER CODE BEGIN ADC1_MspDeInit 1 */ /* USER CODE END ADC1_MspDeInit 1 */ } }

Before describing the code let's see how to make the connections on the development board.

We need a 10kOhm potentiometer and a 2kOhm resistor. The potentiometer is connected between 3.3V and 2kOhm resistor, the common point is connected to PA0, and finally, the other end of the 2k Ohm resistor is connected to the ground pin.

Acting on the potentiometer we will see the read voltage vary from 3.3 Volt to about 0 Volt.

Now let's dive into the code: In the Includes section we add the header file of main.
/* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */

In “Private variables” section we find “ADC_HandleTypeDef hadc” as previous said is an instance to C structure to handle the ADC peripheral. Then, we add three variables:

  • Resolution defines the number of steps used by ADC (12bit = 2^12 -1= 4095) is a constant integer;
  • vs defines the maximum voltage to read, is a constant float;
  • volt is the variable where the voltage value read by the ADC is store ( is a float variable)
/* Private variables -----------------------*/ ADC_HandleTypeDef hadc; /* USER CODE BEGIN PV */ const int Resolution = 4095; const float Vs =3.300; float volt; /* USER CODE END PV */

Then, we can find the protype of function to handle the peripherals and resources initialized (system timer, GPIO, and ADC).

/* Private function prototypes -----------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_ADC_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */

Finally, the main starts.

In the first part we call functions to initialize the peripherals and resources used:

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_ADC_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */

In the second part, that is, inside an infinite loop (while (1)) there is the function to start the conversion of the ADC, read the data and save it in the variable volt and finally stop the conversion wait for a second and start with the conversion and so on.

while (1) { /* USER CODE END WHILE */ HAL_ADC_Start(&hadc); if(HAL_ADC_PollForConversion(&hadc,10)==HAL_OK) { volt=HAL_ADC_GetValue(&hadc)*Vs/Resolution; } HAL_Delay(1000); HAL_ADC_Stop(&hadc); /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }

Now, once our code has been compiled, we can debug it in real-time, just press the "spider" icon (see figure below) and see how the volt variable varies by acting on the potentiometer.

Once we have clicked on the debug button, at the top right, we can select the "live expression" window and add (by writing the name in the table) the variable to be monitored.

Now we can start the debug by clicking on the “Resume” button (on the top right) or by pressing the F8 key (on our keyboard).

We are now ready to read our voltage value. We will see that by acting on the potentiometer we will read the voltages in the whole range considered.

Some measures are shown below:

  1. First reading volt=1.3188 Volt
 
  1. First reading volt=3.29919 Volt
  1. First reading volt=1.02158 Volt
So, that was all about How to handle ADC in STM32 Microcontrollers. In the next tutorial, we are going to work on STM32 DAC operations. Till then take care !!!

STM32 SPI Communication

The SPI (Serial Peripheral Interface) protocol, or rather the SPI interface, was originally devised by Motorola (now Freescale) to support their microprocessors and microcontrollers. Unlike the I2C standard designed by Philips, the SPI interface has never been standardized; nevertheless, it has become a de-facto standard. National Semiconductor has developed a variant of the SPI under the name Microwire bus. The lack of official rules has led to the addition of many features and options that must be appropriately selected and set in order to allow proper communication between the various interconnected devices. The SPI interface describes a single Master single Slave communication and is of the synchronous and full-duplex type. The clock is transmitted with a dedicated line (not necessarily synchronous transmission that has a dedicated line for the clock) and it is possible to both transmit and receive data simultaneously. The figure below shows a basic connection diagram between two peripherals that make use of the SPI interface.

From the figure, it is immediately possible to notice what has just been said, namely that the communication generally takes place between a Master and a Slave. The interface presents 4 connection lines (excluding the ground however necessary), for which the standard SPI is also known as 4 Wire Interface. The Master starts the communication and provides the clock to the Slave. The nomenclature of the various lines in the SPI interface is normally as follows:

  • MOSI: Master Output Slave In. Through this line the master sends the data to the selected slave;
  • MISO: Master Input Slave Output. Through this line the slave sends the data to the master;
  • SCLK: Serial Clock is generated by the master device, so it is the master starts the communication and the clock synchronizes the data transfer over the bus. The SPI clock speed is usually several MHz (today up to 100 MHz);
  • SS: Slave Select or CS (Chip Select) generated by the master to choose which slave device it wants to communicate with (it must be set to a low logic level). SS (or CS) is not indispensable in all applications.

In addition to this standard nomenclature, there are other acronyms.

For example:
  • The MOSI line is also called: SDO (Serial Data Out), DO (Data Out), DOUT and SO (Serial Out)
  • The MISO line is also called: SDI (Serial Data In), DI (Data In), DIN and SI (Serial In)
  • The Clock line is also called: CLK, SCK (Serial Clock).
  • The Enable line is also called: CS (Chip Select), CE (Chip Enable)

The first advantage in SPI communication is faster communication, instead, the first disadvantage is the presence of the SS pin necessary to select the slave. It limits the number of slave devices to be connected and considerably increases the number of lines of the master dedicated to SPI communication as the connected slaves increase.

To overcome these problems, the devices in the daisy chain can be connected (output of a device connected to the input of the next device in the chain) as shown in the figure below where a single slave selection line is used.

The disadvantages, however, are the lower updating speed of the individual slaves and signal interruption due to the failure of an element.

We can use this communication to put in communication our micro-controller with different peripherals as Analog-Digital Converters (ADCs), Digital-Analog Converters (DACs), EEPROM memories, sensors, LCD screen, RF module, Real Time Clock, etc.

The STM32 micro-controllers provide up to 6 SPI interfaces based on the type of package that can be quickly configured with STCube Tool.

STCube Tool initializes the peripherals with HAL (Hardware Abstraction Layer) library. The HAL library creates for SPI (as all peripherals) an C structure:

  • struct SPI_HandleTypeDef
It is so defined: Where the main parameters are:
  • Instance: is the pointer variable it describes the SPI that we want to use. If we use SPI1, the name of the instance is SPI1.
  • Init: is an instance that points to the structure ( SPI_InitTypeDef) used to initialize the device. We will discuss the structure SPI_InitTypeDef shortly.
  • pTxBuffPtr, pRxBuffPtr: are pointer variables that point to an internal buffer. They are used to store the data during the communication when the programmer handles the SPI in interrupt mode (we will see forward)
  • hdmatx, hdmarx: are the pointer variable to instances of the DMA_HandleTypeDef struct. They are used when the programmer handles the SPI in DMA mode (will see forward).

As just said to initialize the SPI peripheral to be used, it is necessary to use the struct SPI_InitTypeDef. It is defined as follow:

When we use the STcubeMX to initialize the SPI peripheral we are modifying this structure

In details:
  • Mode specifies the SPI operating mode, and Direction specifies the SPI bidirectional mode state. It is very easy to configure in STCubeMx. If we want to configure the SPI1. We can find SPI windows in Pinout&Configuration -> Connectivity. Here we can select between the SPI available. Now is possible to select the communication mode (Master, Slave, half-duplex, full-duplex, etc.) as follow:

If the slave supports, the full-duplex communication can be enabled.

  • DataSize indicates the SPI data size. The user can select 8bit or 16bit.
  • CLKPolarity defines if the serial clock steady state is LOW or HIGH.
  • CLKPhase defines if the bit capture (trigger) takes place when the clock is on the falling edge or rising edge.
  • NSS: if selected "Output Hardware" the slave select signal is managed by hardware otherwise is managed by software using the SSI bit.
  • BaudRatePrescaler can be select the Baud Rate prescaler value.
  • FirstBit indicates if data transfers start from Most Significant Bit (MSB) or Last Significant Bit (LSB).
  • TIMode specifies if the TI mode is enabled or not.
  • CRCCalculation: to enable to activate the CRC calculation.
  • CRCLength: to define the length of CRC data.
  • CRCPolynomial specifies the polynomial (X0+X1+X2) used for the CRC calculation. This parameter is an odd number 1 and 65535.

By enabling the SPI and the chip select pin, the pins available on the microcontroller are automatically chosen to manage this interface (but they can be changed by looking for the alternative functions of the different pins of the microcontroller). For example, in our case the following pins are selected:

  • PA4 SP1_NSS
  • PA5 SP1_SCK
  • PA6 SP1_MISO
  • PA7 SP1_MOSI

Now you can generate the initialization code. Before being able to write the first code to manage this communication interface, it is necessary to understand the functions that the libraries provide and the different communication modes.

As for other communication interfaces, the HAL library provides three modes to communicate: polling mode, interrupt mode, and DMA mode.

Where To Buy?
No.ComponentsDistributorLink To Buy
1STM32 NucleoAmazonBuy Now

STM32 SPI Communication in Polling Mode

Using the SPI in Polling Mode is the easiest way, but it is the least efficient way as the CPU will remain in a waiting state for a long time. HAL library provides the following functions to transmit and receive in polling mode:

  • HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)

Master receives data packets in blocking mode (polling mode).

The parameters are:

  • hspi is a pointer to a “SPI_HandleTypeDef” structure. “SPI_HandleTypeDef” structure includes the configuration information for SPI module.
  • pData is a pointer to data buffer
  • Size is the amount of data to be sent
  • Timeout is the timeout duration
  • HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)

Master transmits data packets in blocking mode (polling mode).

If the slave device supports, the full-duplex mode:

  • HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout)

Master transmits and receives data packets in blocking mode (polling mode).

The parameters are:

  • hspi is a pointer to a “SPI_HandleTypeDef” structure. “SPI_HandleTypeDef” structure includes the configuration information for SPI module
  • pTxData is a pointer to transmission data buffer
  • PRxData is a pointer to reception data buffer
  • Size is the amount of data to be sent
  • Timeout is the timeout duration

STM32 SPI Protocol in Interrupt Mode

Using the SPI in Interrupt Mode, also called non-blocking mode. In this way, the communication can be made more effective by enabling the interrupts of the SPI in order to receive, for example, signals when the data has been sent or received. This improves CPU time management. In applications where all the management must be deterministic and it is not known when an interrupt can arrive, these can potentially manage the time management of the CPU, especially when working with very fast buses such as SPI. We can enable the SPI interrupts directly during the initialization with STCube Mx.

HAL library provides the following functions to transmit and receive in interrupt mode:

  • HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)
Master receives data packets in non-blocking mode (interrupt mode). The parameters are:
  • hspi is a pointer to a “SPI_HandleTypeDef” structure. “SPI_HandleTypeDef” structure includes the configuration information for SPI module
  • pData is a pointer to data buffer
  • Size is the amount of data to be sent
To handle the interrupt needs to write our code in the callback:
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef * hspi) { // Message received .. Do Something ... }
  • HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)

Master transmits data packets in blocking mode (interrupt mode).

To handle the interrupt needs to write our code in the callback:

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef * hspi) { // Message transmitted.... Do Something ... }

If the slave device supports, the full-duplex mode:

  • HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size)

Master transmits and receives data packets in non-blocking mode (interrupt mode).

To handle the interrupt needs to write our code in the callback:

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef * hspi) { // Message transmitted or received.. .. Do Something ... }

STM32 SPI Communication in DMA Mode

Using the SPI in DMA Mode the SPI bus can be used at its maximum speed, in fact, since the SPI must store the received and transmitted data in the buffer to avoid overloading it is necessary to implement the DMA. In addition, use by DMA mode frees the CPU from performing "device-to-memory" data transfers. We can easily configure the DMA during the initialization using STCubeMx :

In this case, the DMA is enabled in normal (we can use it in circular mode) mode both in transmission and reception

HAL library provides the following functions to transmit and receive in DMA mode:

  • HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)

Master receives data packets in non-blocking mode (DMA mode).

The SPI device receives all bytes of data in the buffer one by one until the end in DMA mode. At this point, the callback function will be called and executed where something can be done.

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef * hspi) { // Message received .. Do Something ... }
  • HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)

Master transmits data packets in non-blocking mode (DMA mode).

The SPI device sends all bytes of data in the buffer one by one until the end in DMA mode. At this point, the callback function will be called and executed where something can be done.

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef * hspi) { // Message transmitted….. Do Something ... }

If the slave device supports, the full-duplex mode:

  • HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,)

Master transmits and receives data packets in non-blocking mode (DMA mode).

The SPI device sends or receives all bytes of data in the buffer one by one until the end in DMA mode. At this point, the callback function will be called and executed where something can be done.

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef * hspi) { // Message transmitted or received.... Do Something ... }

We are now ready to handle an SPI communication with STM32.

Write and Read an I2C EEPROM with STM32

EEPROMs (Electrically Erasable Programmable Read-Only Memories) allow the non-volatile storage of application data or the storage of small amounts of data in the event of a power failure. Using external memories that allow you to add storage capacity for all those applications that require data recording. We can choose many types of memories depending on the type of interface and their capacity.

EEPROMs are generally classified and identified based on the type of serial bus they use. The first two digits of the code identify the serial bus used:

  • Parallel: 28 (for example 28C512) much used in the past but now too large due to having many dedicated pins for parallel transmission
  • Serial I2C: 24 (for example 24LC256)
  • Serial SPI: 25 (for example 25AA080A)
  • Serial - Microwire: 93 (for example 93C56C-E/SN)
  • Serial – UN I/O: 11 (for example 11LC040-I/SN)

Now we will see how to write or read data on an I2C EEPROM like 24C256C. This serial EEPROM is organized as 32,768 words of 8 bits each. The device’s cascading feature allows up to eight devices to share a common 2-wire bus. It is available in various 8-pin packages

The device can be used in applications consuming low power. The device is available in all standard 8-pin packages. The operating voltage is comprised of between 1.7V and 5.5V.

  • Serial Clock (SCL) is an input pin used to control data flow. On the positive-edge clock, the data is inserted into the EEPROM device, while on the negative edge clock, the data is processed out of the EEPROM module.
  • Serial Data (SDA) is a bidirectional input-output for serial data transfer. It is an open-drain pin.
  • Device Addresses (A2, A1, A0) are input pins to set the device address. These pins allow you to customize the address of the device within the I2C bus. They must connect directly to GND or to VCC (hard wired). If these pins are left floating, the A2, A1, and A0 pins will be internally pulled down to GND. When using a pull-up resistor, it recommends using 10kOhm or less.
  • Write Protect (WP) is an input pin. We can perform normal writing operations, by connecting it to GND; When connected directly to VCC, all write operations to the memory are restricted. If this pin is left open/floating, it will be pulled down to the GND(internally). When using a pull-up resistor, it recommends using 10kOhm or less.
  • Device Power Supply (VCC)
  • Ground (GND)

In our example, we connect A0, A1, A2 directly to VCC in this way the device address is 1010111 (in general A0, A1, A2 identify the last three significant bits of the device address 1 0 1 0 A2 A1 A0) is 0x57 in Hexadecimal. The 4 most significant bits are preset (Control Code), the A0, A1, A2 are Chip Select Bits.

Now we start with our project using STNucleoL053R8 and STCube to generate the initialization code. Below is shown the connection

  • A0, A1, A2 are connected directly to VCC in this way the device address is 1010111 (in general A0, A1, A2 identify the last three significant bits of the device address 1 0 1 0 A2 A1 A0) is 0x57 in Hexadecimal.
  • WP is connected to the ground to allow the normal write operation
  • SCL and SDA are connected to PA8 and PA9 respectively of STM32L053R8

So, we configure the I2C1 using STCube and leave all configuration as it is and we will operate in polling mode.

In GPIO setting select PA9 (SDA) and PA8 (SCL).

Now we create a new STM32CubeMX project with the following steps:
  • Select File > New project from the main menu bar. This opens the New Project window.
  • Go to the Board selector tab and filter on STM32L0 Series.
  • Select NUCLEO-L053R8 and click OK to load the board within the STM32CubeMX user interface
      Then the tool will open the pinout view.
  • Select Debug Serial Wire under SYS, for do it click on System Core (on the topo right) and then select SYS and finally flag on “Debug Serial Wire”.
 
  • Select Internal Clock as clock source under TIM2 peripheral. To do this click on Timers and then select TIM2. Now go to clock source and select through the drop-down menu “internal clock”.
  • Select and enable in “Connectivity” the I2C1 and left all configuration as it is and we will operate in polling mode.
  • Configure in GPIO setting PA9 (SDA) and PA8 (SCL) to manage the I2C communication.
  • Check that the signals are properly assigned on pins:
    • SYS_SWDIO on PA13
    • TCK on PA14
    • SDA I2C1on PA9
    • SCL I2C1on PA8
  • Go to the Clock Configuration tab and no change the configuration in order to use the MSI as input clock and an HCLK of 2.097 MHz.
  • Select Timers -> TIM2 and change the Prescaler to 16000 and the Counter Period to 1000.
  • In the Project Manager tab, configure the code to be generated and click OK to generate the code.

Our project has been initialized by STCubeMX. In the /Core/Src/main.c we will find our main where we will write the main body of our program.

Now let’s see what the code generator did:

First of all, we find the “Include” section we can add the library needed.

 
/* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h"
In our case we can add also “stm32l0xx_hal.h” library to be able to use HAL library (I2C HAL library included)
#include "stm32l0xx_hal.h " #include "Var.h " #include "Funct.h "
In “Private variables” has been defined two privates variable htim2 and hi2c1;
  • - htim2 as first parameter an instance of the C struct TIM_HandleTypeDef;
  • - hi2c1 as first parameter an instance of the C struct UART_HandleTypeDef.
/* Private variables ---------------------------------------------------------*/ TIM_HandleTypeDef htim2; UART_HandleTypeDef hi2c1; unsigned short int address; // eeprom address unsigned char EEP_pag = 0x00 // EEPROM page unsigned char EEP_pos = 0x00 // EEPROM position unsigned char rdata = 0x00 // to store the data read from EEPROM
In “Private function prototypes” we find the protype of function to initialize the System Clock, GPIO, timer and peripheral:
/* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM2_Init(void); static void MX_I2C1_Init(void);

This function has been generated automatically by STCubeMx with the parameter selected.

The main contains the initialization of the peripherals and variables, before the while loop the code call the function Write_EEPROM() and Read_EEPROM() to write and read a data in a specific address of the EEPROM. these functions were written in EEPROM.c, a C file added to our project in the src folder.

  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_TIM2_Init(); MX_I2C1_Init();   /* USER CODE BEGIN 2 */ address = 0x00 << 8 | 0x00 // eeprom page 0 , position 0 // Now we want to store 10 in page 0x00 and position 0x00 of EEPROM Write_EEPROM(address, 10, 0) // Now we want store in rdata variable the content of cell memory 0x0000 rdata = Read_EEPROM(address, 0)   /* USER CODE END 2 */   /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ } /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
Furthermore, we have added two header files and one c file:
  • Var.h contains the declaration of global variables:
/** Var.h * Created on: 27 ott 2021 * Author: utente */ #ifndef INC_VAR_H_ #define INC_VAR_H_ extern unsigned char buf[20]; extern int i; #endif /* INC_VAR_H_ */
  • Funct.h contains the prototype of the user function
/** Funct.h * Created on: 28 ott 2021 * Author: utente */ #ifndef INC_FUNCT_H_ #define INC_FUNCT_H_ extern unsigned char Read_EEPROM(unsigned int, unsigned char); extern void Write_EEPROM(unsigned int, unsigned char, unsigned char); #endif /* INC_FUNCT_H_ */
  • EEPROM.c contains the function written by the user to handle the writing and reading operation with EEPROM:
  • unsigned char Read_EEPROM(addr, device) reads from cell memory address (addr)and store the content in dato.
  • void Write_EEPROM(addr, dato, device) writes data (dato) to memory address (addr).
/** Serial.c * Created on: Oct 29, 2021 * Author: utente */ #include "stm32l0xx.h" // Device header #include "stm32l0xx_hal_conf.h" #include "stm32l0xx_hal.h " #include "Var.h " #include "Funct.h " extern I2C_HandleTypeDef hi2c1; unsigned char Read_EEPROM(unsigned int addr, unsigned char device) { unsigned char page; uint8_t dato; page=0xAF; // due to chip select bits setting HAL_I2C_Mem_Read(&hi2c1,page, addr, I2C_MEMADD_SIZE_16BIT, &dato,1,5); return dato; } void Write_EEPROM(unsigned int addr, unsigned char dato, unsigned char device) { unsigned char page; page=0xAF; //due to chip select bits setting HAL_I2C_Mem_Write(&hi2c1,page, addr, I2C_MEMADD_SIZE_16BIT, &dato,1,5 ); while(HAL_I2C_IsDeviceReady(&hi2c1, 0xA0, 1, HAL_MAX_DELAY) != HAL_OK); HAL_Delay(10); }
Now we are ready to compile and run the project:
  1. Compile the project within IDE.
  1. Download it to the board.
  2. Run the program.
So, that was all for today. I hope you have enjoyed today's lecture. In the next tutorial, we will have a look at How to perform SPI Communication with STM32. Till then take care and have fun !!! :)

I2C communication with STM32

Hello friends, I hope you all are doing great. In today's lecture, we will have a look at the I2C Communication with STM32 Microcontroller board. I am going to use the Nucleo board for today's lecture. In the previous lecture, we have discussed STM32 Serial communication both in Interrupt Mode and polling Mode. Today, we will study another way of communication(i.e. I2C) with STM32. So, let's first have a look at what is I2C Communication:

Where To Buy?
No.ComponentsDistributorLink To Buy
1STM32 NucleoAmazonBuy Now

What is I2C Communication?

I²C (Inter-Integrated Circuit) is a two-wire serial communication system used between integrated circuits. Like any serial protocol, one of its advantages is that of using only two lines that transmit or receive a sequence of bits, the limit is the communication speed which has been improved over the years.

The bus was conceived and developed by Philips (now NXP) It was designed to overcome the difficulties inherent in the use of parallel buses for communication between a control unit and various peripherals.

Serial transmission is a mode of communication between digital devices in which bits are sent one at a time and sequentially to the receiver in the same order in which they were transmitted by the sender. Although the communication modules are more complex than the parallel transmission, the serial mode is one of the most widespread especially in communications between chips that must communicate with each other over great distances, because:

  • it requires fewer wires and pins available on the integrated circuit with a consequent reduction in costs and space on the board;
  • is more tolerant of interference and transmission errors;
  • up to 128 devices can be connected to each other

I2C Pinout

  • SDA (Serial Data) is the line where master and slave send or receive the information (sequence of bit);
  • SCL (Serial Clock)  is the line dedicated to the clock to synchronize the data flow.

SDA and SCL lines need to be pulled up with resistors. The value of these resistors depends on the bus length ( ie the bus capacitance) and the transmission speed. The common value is 4.7kO. In any case, there are many guides to size them and we refer their reading to the more attentive reader.

The transmission mode is Half-duplex ie the transmission between devices is alternated.

As shown by the previous image, we can use this communication to put in communication different peripherals as Analog-Digital Converters (ADCs), Digital-Analog Converters (DACs), EEPROM memories, sensors, LCD screen, RF module, Real-Time Clock, etc.

I2C Communication in STM32

The Nucleo boards provide one or more I2C interfaces that can be quickly configured with STCube Tool.

There are four modes of operation:

  1. Slave Transmitter
  2. Slave Receiver
  3. Master Transmitter
  4. Master Receiver

The first two are used to operate in slave mode, while the last two are in master mode. By default, the interface is configurated in slave mode.

By default, the I2C interface operates in Slave mode, but it is possible to switch to Master mode to send a Start condition message. Furthermore, it needs to write in I2C_CR2 register the correct clock configuration to generate the expected timings.  The Master sends a Stop condition when the last data byte is transferred, and the interface generates an interrupt.

I2C Packet Message

In general, the packet message is as follow:

  • Start condition: the master pulls SDA low and SCL is High to inform slave devices that a transmission is ready to start.
  • Address frame: the master sends the address of the slave, each device has an address of 7-10bit, then sends the Read (R) or Write (W) bit, which are respectively 1 and 0. Finally, the master waits that the slave sends the Acknowledge bit (ACK).
  • Data frame(s): Send (master) / Receive (slave) Data Byte (DATA) and then Waiting (master) / sending (slave) the Acknowledge bit (ACK)
  • Stop condition: the master sends the stop conditions pull SDA to High defined by a low while SCL remains high.

I2C Modes in STM32

Furthermore, there are three ways to exchange data, named:

  1. Polling Mode
  2. Interrupt Mode

STM32 I2C Polling Mode

  • In polling mode, also called blocking mode, the application waits for the data transmission and reception.
  • This is a simple way to communicate between devices when the bit rate is not very low, for example when we can debug the board and we want to display the result on screen console.

HAL library provides the following functions to transmit and receive in polling mode:

I2C Master Reciever

  • The function to receive data in master mode is as follows:
HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
The parameters are:
  • hi2c is a pointer to an I2C_HandleTypeDef structure that contains the configuration information for the specified I2C.
  • DevAddress is device address: The device 7 bits address value in the datasheet must be shifted to the left before calling the interface.
  • pData is a pointer to data buffer.
  • Size is the amount of data to be sent.
  • Timeout is the timeout duration.

I2C Master Transmitter

  • Master transmits in master mode an amount of data in blocking mode.
HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)

I2C Slave Reciever

  • The function to receive data in slave mode is as follows:
HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)

I2C Slave Transmitter

  • The function to transmit data in slave mode is as follows:
HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)

I2C Memory Read

  • Master reads an amount of data in blocking mode from a specific memory address.
HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
The additional parameters are:
  • MemAddress is the internal device address.
  • MemAddSize is the size of the internal device address.

I2C Memory Write

  • Master reads an amount of data in blocking mode from a specific memory address.
HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)

STM32 I2C Interrupt Mode

  • In interrupt mode, also called non-blocking mode, in this way the application waits for the end of transmission or reception.
  • It is used when the transmission is not used continuously with respect to the activity of the microcontroller.
HAL library provides the following functions to transmit and receive in interrupt mode:
HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
Master receives in master mode an amount of data in non-blocking mode with interrupt.
HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
Master transmits in master mode an amount of data in non-blocking mode with interrupt.
HAL_I2C_Slave_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
Slave receives in master mode an amount of data in non-blocking mode with interrupt.
HAL_I2C_Slave_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
Slave transmits in master mode an amount of data in non-blocking mode with interrupt.

STM32 I2C DMA Mode

  • DMA mode is the best way the exchange data, especially when we want to exchange data quickly and continuously that often require access to memory.
HAL library provides the following functions to transmit and receive in DMA mode:
HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
Master receives in master mode an amount of data in non-blocking mode with DMA.
HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
Master transmits in master mode an amount of data in non-blocking mode with DMA.
HAL_I2C_Slave_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
Slave receives in master mode an amount of data in non-blocking mode with DMA.
HAL_I2C_Slave_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
Slave transmits in master mode an amount of data in non-blocking mode with DMA. In the STCube tool, the I2C can be configurated fastly and easily as follow.

In Pinout & Configuration, widow selects Connectivity and selects one of the available I2C (I2C1, I2C2, etc). In parameter settings, the master and slave features can be set. Master features are I2C speed mode (standard mode by default and fast mode) and the I2C clock speed (Hz). In standard mode, the device can send up to 400kbit/s while in fast mode up to 1Mbit/s. In general, like clock speed, the STM32 supports 100kHz, 400kHz and sometimes 1MHz.

The main feature of slaves is the primary address length that in general, as previously said, is 7-Bit. Furthermore, the slave can have a secondary address.

Then need to configure the GPIO, as follow:

Now the I2C configuration is terminated and can be possible to generate the code initialization and finally be ready to write our application.

STM32 Serial Communication in Polling Mode

USART is the acronym for Universal Synchronous-Asynchronous Receiver-Transmitter, and is the advancement of the old UART that was unable to handle synchronous communications; in computers, it deals with the management of communication via the RS-232 interface.

Generally, in the communication between devices, there is a transmitter and receiver that can exchange data bidirectionally, thus it happens for example in the communication between the microcontroller and third-party peripherals.

Where To Buy?
No.ComponentsDistributorLink To Buy
1STM32 NucleoAmazonBuy Now

What is Serial Communication?

In serial communications, the transmitting device sends information (bitstream) through a single channel one bit at a time. Serial communications are distinguished from parallel communications where the bitstream is sent over several dedicated lines altogether. Over the years, serial communication has become the most used as it is more robust and economical. On the other hand, serial communication requires, compared to the parallel case, a more complex management and control architecture, think for example how a trivial loss of a bit during a communication could irreparably corrupt the content of the final message. To this end, various communication protocols have been introduced over the years in order to improve speed, reliability and synchronization. In detail, three different communication modes can be found: synchronous, asynchronous, and isochronous.

Synchronous Mode

  • In synchronous communication, the transmitter sends the data one bit at a time at a certain frequency (constant frequency), so you have the channel on which the data travels and another on which a clock signal travels. Thanks to the clock signal, the receiving device (receiver) knows when one bit ends and another start, or rather a bit starts and ends on a rising or falling edge of the clock signal.

Asynchronous Mode

  • In asynchronous communication, there is no channel dedicated to the clock signal so there is no type of synchronization between receiver and transmitter.
  • When the transmitter is ready it starts to send the bits serially which are read directly by the receiver.
  • The sent packet always contains a start bit, which signals the start of transmission to the receiver, the start of transmission.
  • Generally, the sent packet is made up of 8 bits plus any parity bit which has the purpose of verifying the correctness of the data transmitted and of the stop bits that signal the end of the packet to the receiver.
  • In the case of sending a large flow of data, this type of communication is less efficient than synchronous as bits that do not contain information such as start and stop bits are sent several times.

Isochronous Mode

  • Isochronous mode is a hybrid mode, that is obtained by making a synchronous device communicate with an asynchronous one, this can only happen under certain conditions.

UART communication on STM32 Microcontrollers using HAL

The microcontrollers from the ST family are equipped with at least one USART, for example, the NUCLEO-L053R8 Board has two (USART1 and USART2). It is possible to configure the dedicated pins easily directly from the STCUBE tool, as we will see in the example, and the dedicated HAL libraries allow you to easily write functions and algorithms to transmit or receive data. There are three ways to exchange data via serial port in STM32, which are:
  1. Polling Mode
  2. Interrupt Mode
  3. DMA Mode
Let's discuss these serial communication modes of STM32 in detail:

Polling Mode

  • In polling mode, also called blocking mode, the application waits for the data transmission and reception. This is a simple way to communicate between devices when the bit rate is not very low, for example when we can debug the board and we want to display the result on screen console.
HAL library provides the following functions to transmit and receive in polling mode:
  • HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size, uint32_t Timeout).

Interrupt Mode

  • In interrupt mode, also called non-blocking mode, in this way the application waits the end of transmission or reception. it is used when the transmission is not used continuously with respect to the activity of the microcontroller.
HAL library provides the following functions to transmit and receive in polling mode:
  • HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size, uint32_t Timeout).

DMA Mode

  • DMA mode is the best way the exchange data, especially when we want to exchange data fastly and continuously that often require access to memory.
HAL library provides the following functions to transmit and receive in polling mode:
  • HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size, uint32_t Timeout).
In the next example, we will see the polling mode communication using NUCLEO-L053R8.

Using USART in Polling Mode for STM32

In this example, we will write a project using USART in a polling mode to transmit text on the laptop monitor. To do that we need:
  • NUCLEO-L053R8 board.
  • Windows Laptop
  •  The ST-Link USB connector needs both for serial data communications, and firmware downloading and debugging on the MCU.
  •  A Type-A to mini-B USB cable must be connected between the board and the computer.
The USART2 peripheral is configurated to use PA2 and PA3 pins, which are wired to the ST-Link connector. In addition, USART2 is selected to communicate with the PC via the ST-Link Virtual COM Port. A serial communication client, such as Tera Term, needs to be installed on the PC to display the messages received from the board over the virtual communication Port.

Creating New Project in STM32CubeMX

Now we create a new STM32CubeMX project with the following steps:
  • Select File > New project from the main menu bar. This opens the New Project window.
  • Go to the Board selector tab and filter on STM32L0 Series.
  • Select NUCLEO-L053R8 and click OK to load the board within the STM32CubeMX user interface.
  • Then the tool will open the pinout view.
  • Select Debug Serial Wire under SYS, for do it click on System Core (on the top right) and then select SYS and finally flag on “Debug Serial Wire”.
  • Select Internal Clock as clock source under TIM2 peripheral. To do this click on Timers and then select TIM2. Now go to clock source and select through the drop-down menu “internal clock”.
  • Select the Asynchronous mode for the USART2 peripheral. Click on connectivity and select USART2. Now go to Mode and select through the drop-down menu “asynchronous”.
  • Check that the signals are properly assigned on pins :
    • SYS_SWDIO on PA13
    • TCK on PA14
    • USART_TX on PA2
    • USART_RX on PA3
  • Go to the Clock Configuration tab and no change the configuration in order to use the MSI as input clock and an HCLK of 2.097 MHz.
  • Come back to Pinout&Configuration and select Connectivity -> USART2 to open the peripheral Parameter Settings window and set the baud rate to 9600. Make sure the Data direction is set to “Receive and Transmit”.
  • Select Timers -> TIM2 and change the prescaler to 16000 and the Counter Period to 1000.
  • Go to NVIC Settings tab and flag TIM2 global interrupt to enable the interrupt related to TIM2.
  • In the Project Manager tab, configure the code to be generated and click OK to generate the code.
  • Our project has been initialized by STCubeMX. In  /UsartEx/Core/Src/main.c we will find our main where we will write the main body of our program.

Understanding STM32 Code for Serial Communication

Now let’s see what the code generator did:
  • First of all, we find the “Include” section we can add the library needed.
  • In our case, we can add also a string.h library to handle and send text data.
  • In “Private variables” has been defined two private variables htim2 and huart2; - htim2 as the first parameter an instance of the C struct TIM_HandleTypeDef; -huart2 as first parameter an instance of the C struct UART_HandleTypeDef.
/* Private variables ---------------------------*/ TIM_HandleTypeDef htim2; UART_HandleTypeDef huart2;
 
  • In “Private function prototypes” we find the prototype of function to initialize the System Clock, GPIO, timer and peripheral:
/* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM2_Init(void); static void MX_USART2_UART_Init(void);
  • This function has been generated automatically by STCubeMx with the parameter selected as shown below:
  • It is important to highlight that the WordLength parameter of huart2 is UART_WORDLENGTH_8B, since we are sending 8-Bit ASCII chars.
  • We can easily adjust the baud rate with the BaudRate parameter.
  • Now we are ready to write our code in main():
  • As shown in the code below, two strings, str1 and str2, are declared. Then the string concatenation function ( strncat() ) is used and finally, the HAL_UART_Transmit function is used to display them on the monitor through UART2.
Now we are ready to compile and run the project:
  1. Compile the project within IDE.
  2. Download it to the board.
  3. Run the program.
In order to show the text on display needs to Configure serial communication clients on the PC such as Tera Term software, CoolTerm, Putty etc. In our example, we will use CoolTerm, but not change the aim if you will use another one.
  • On the computer, check the virtual communication port used by ST Microelectronics from the Device Manager window.
  • To configure Tera Term to listen to the relevant virtual communication port, adjust the parameters to match the USART2 parameter configuration on the MCU.
You must be careful to configure the correct com port and use the same setting which has set the USART2. After that we click on connect and run the program.
  • The CoolTerm window displays a message coming from the board at a period of a few seconds.
So, that was all for today. I hope you have enjoyed today's lecture. In the next tutorial, we will discuss Serial Communication using Interrupt Method. Till then take care and have fun !!!

First Project using STM32 in STM32CubeIDE

We will use for our examples STM32CubeIDE released by ST and completely free. STM32CubeIDE is a development tool and supports multi operative system (SO), which is part of the STM32Cube software ecosystem. STM32CubeIDE allows using a single platform to configure peripherals, to generate/compile/debug the code for STM32 microcontrollers and microprocessors. The framework used is Eclipse®/CDT, as tool-chain for the development is used GCC toolchain and GDB for the debugging.

To start the project, you must first select the MCU and then initialize and configure the peripherals the user wants to use. At this point, the initialization code is generated. At any time, the user can go back and change initializations and configurations, without affecting the user code. We will dive into these steps with a simple example in the next paragraph.

Where To Buy?
No.ComponentsDistributorLink To Buy
1STM32 NucleoAmazonBuy Now

First Project in STM using STM32CubeIDE

  • First of all, you have to install on your PC the STM32CubeIDE. In order to do it, you have to go on the ST site and after registered in it you can navigate to "https://www.st.com/en/development-tools/stm32cubeide.html" to download it.
  • At this point, you can install it, if you'll find problems you can check the "UM2563 - STM32CubeIDE installation guide".
  • STM32 Nucleo board (in our example we will use NUCLEO-L053R8, but you can use the one you prefer, as already said all Nucleo boards are pinout compatible);
  • USB 2.0 Cable - A-Male to Mini-B Cord to connect PC to ST-LINK;
  • Breadboard to hold and connect the simple electrical components;
  • Jumper Cables Kit to connect the Nucleo board to breadboard or other components;
  • Various basic electrical components such as Resistors (THT), Capacitors (THT), Buzzers, LEDs, etc.
Now let’s start!

Blinking LED using STM32 Nucleo Board

  • Turning a LED on and off is a basic and classical experiment when dealing with NUCLEO for the first time.
  • First, let's turn on the led, to do this we have to connect the LED to the power supply (on Nucleo we can find 3.3V and 5V).
Assuming:
  • the power supply 5V;
  • LED forward voltage of LED 7 Volt;
  • LED maximum forward current 20mA;

We must connect in series to LED a resistor. What resistance value must be considered to limit the current to 20 mA (see the formula below)?

R > 5 - 1.7/0.02 , where R > 165 ?

A good value of R is 220 ?.

Now we must be able to turn off the led, to do it we need a switch to connect and disconnect the power supply as shown below.

In this case when the switch is closed the Led is ON, when the switch is open the Led is OFF. This can be easily done with a Nucleo board by configuring a GPIO (General Purpose Input Output) pin.

The steps required to configure a core board are explained below. Obviously, this is a very simple practical example, but peripherals, communication protocols and anything else can be configured in a similar way.

Steps to generate the config. files from STM32CubeMX

As already said, we will use for our examples a NUCLEO-L053R8 board. For our examples, so also a Windows PC is required. The ST-Link USB connector is used both for serial data communications, and firmware downloading and debugging on the MCU. A Type-A to mini-B USB cable must be connected between the board and the computer. The USART2 peripheral uses PA2 and PA3 pins, which are wired to the ST-Link connector. In addition, USART2 is selected to communicate with the PC via the ST-Link Virtual COM Port. A tool that emulates the serial communication terminal is necessary to view received messages and send them. You can download many open-source tools as Tera Term. In this way, you can display on PC the messages received from the board over the virtual communication Port and the other way around.

In this paragraph, we will initialize the peripherals using STM32CubeMX. The STM32CubeMX tool to create the necessary config. files to enable drivers of peripheral used (for more detail read “UM1718 - User manual STM32CubeMX for STM32 configuration and initialization C code generation.”).

Steps to Follow

  • Select the NUCLEO-L053R8 board looking in a selection bar within the New Project menu.
  • Select the required features (debug, GPIOs, peripherals, timer) from the Pinout view: peripheral operating modes as well as assignment of relevant signals on pins.
  • Configure the MCU clock tree from the Clock Configuration view.
  • Configure the peripheral parameters from the Configuration view.
  • Configure the project settings in the Project Manager menu and generation of the project (initialization code only).
  • Update the project with the user application code corresponding to the Led blinking example.
  • Compile, and execute the project on the board.

Creating a new STM32CubeMX project

  • Select File -> New project from the main menu bar to open the New Project window.
  • Go to the Board selector tab and filter on STM32L0 Series.
  • Select NUCLEO-L053R8 board and then click on OK button to confirm. In this way the board is loaded within the STM32CubeMX user interface.
  • Insert the project name, in this case, "BlinkLed" and click finish.
  • Then the tool will open the pinout view.

Selecting the features from the Pinout view

  • Select Debug Serial Wire under SYS, for do it click on System Core (on the topo right) and then select SYS and finally flag on “Debug Serial Wire”.
 
  • Select Internal Clock as clock source under TIM2 peripheral. To do this click on Timers and then select TIM2. Now go to clock source and select through the drop-down menu “internal clock”.
  • Select one of the available pins (basically anyone!) as GPIO output, for example PA9 (the last pin of CN5)
Check that the signals are properly assigned on pins:
  • SYS_SWDIO on PA13
  • SYS_SWCLK on PA14
  • GPIO OUTPUT on PA5

Configuring the MCU clock tree

  • Go to the Clock Configuration tab and in this project, there is no need to change the configuration.

Configuring the peripheral parameters

  • Come back to Pinout&Configuration and select System Core -> GPIO to open the peripheral Parameter Settings window and in this case no change the configuration.
  • Select Timers -> TIM2 and change the Prescaler to 16000 and the Counter Period to 1000.
  • Go to the NVIC Settings tab and flag TIM2 global interrupt to enable the interrupt related to TIM2.

Configuring the project settings and generating the project

  • In the Project Manager tab, configure the code to be generated and click OK to generate the code.

Updating the project with the user application code

  • Our project has been initialized by STCubeMX.
  • In /BlinkLed/Core/Src/main.c we will find our main where we will write the main body of our program.

STM32 Programming Code for Led Blinking

The user code must be inserted between the "USER CODE BEGIN" and "USER CODE END" so that if you go to regenerate the initializations that part of the code is not deleted or overwritten. As shown in the code below, the simplest way is to use the function:

  • HAL_GPIO_TogglePin(GPIOx, GPIO_Pin) in the infinite loop ( while (1) ) with a chosen delay, for example of 1 second. To do this we use the function HAL_Delay(Delay). The declarations of these two functions with their parameters are described below
  • HAL_GPIO_TogglePin(GPIOx, GPIO_Pin): Toggles the specified GPIO pins.
  • GPIOx: Where x can be (A..E and H) to select the GPIO peripheral for STM32L0xx family devices. Note that GPIOE is not available on all devices. All port bits are not necessarily available on all GPIOs.
  • GPIO_Pin: Specifies the pins to be toggled.
  • HAL_Delay(Delay): This function provides minimum delay (in milliseconds) based on variable incremented. In the default implementation, SysTick timer is the source of time base. It is used to generate interrupts at regular time intervals where uwTick is incremented.

Note This function is declared as __weak to be overwritten in case of other implementations in a user file.

- Delay: specifies the delay time length, in milliseconds.

So, in our case GPIOx is GPIOA and the GPIO_PIN is GPIO_PIN_9 (see below).

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_TIM2_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_9); HAL_Delay(1000); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }

Now we are ready to compile and run the project:

  • Compile the project within IDE.
  • Download it to the board.
  • Run the program.

Another simple way is to use the function

  • HAL_GPIO_WritePin(GPIOx, GPIO_Pin, PinState).
  • HAL_GPIO_WritePin(GPIOx, GPIO_Pin, PinState): Sets or clears the selected data port bit.

Note This function uses GPIOx_BSRR register to allow atomic read/modify accesses. In this way, there is no risk of an IRQ occurring between the read and the modified access.

  • GPIOx: where x can be (A..E and H) to select the GPIO peripheral for STM32L0xx family devices.

Note that GPIOE is not available on all devices.

  • GPIO_Pin: specifies the port bit to be written.

This parameter can be one of GPIO_PIN_x where x can be (0..15).

All port bits are not necessarily available on all GPIOs.

  • PinState: specifies the value to be written to the selected bit.

This parameter can be one of the GPIO_PinState enum values:

  • GPIO_PIN_RESET: to clear the port pin
  • GPIO_PIN_SET: to set the port pin

So, in our case GPIOx is GPIOA and the GPIO_PIN is GPIO_PIN_9 and the pin state changes between 0 (LOW or CLEAR) and 1 (HIGH or SET).

In this case, the code is the following:

while (1) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9,0); HAL_Delay(1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9,1); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }

There are many other ways to blink an Led such as PWM, Interrupts etc. and will discuss it in the upcoming lectures. Thanks for reading.

Introduction to Nucleo Development Board

To become familiar with the world of microcontrollers it is necessary to have a development board (also known as a kit), which generally allows you to start working on it easily. Fortunately, the ST provides a wide portfolio of development boards. In this guide, we will describe and use the Nucleo board.

The Nucleo has been introduced a few years ago and its line is divided into three main groups:

  • Nucleo-32;
  • Nucleo-64;
  • Nucleo-144.
Where To Buy?
No.ComponentsDistributorLink To Buy
1STM32 NucleoAmazonBuy Now

Nucleo-32 Development Board

The number of pins available, so the package, gives the name to the board: Nucleo-32 uses an LQFP-32 package; Nucleo-64 and LQFP-64; Nucleo-144 an LQFP-144. The Nucleo-64 was the first line introduced and counts 16 different boards.

The Nucleo boards have interesting advantages compared to the Discovery. First, is the cheaper cost, you can buy it for around 15-25 dollars. Now in 2021 due to the lack of processed semiconductors, it is very difficult to find them on the normal distribution channels and costs are rising. A return to normal is expected from 2023. Furthermore, Nucleo boards are designed to be pin-to-pin compatible with each other. It is a very important advantage, in fact, if I start to develop my firmware on generic Nucleo later then I can adapt my code to another one.

In the next paragraphs, we will see the main structure of STM32-64

STM32 Nucleo-64 parts

The Nucleo-64 is composed of two parts:

  • ST-LINK part
  • MCU part

The part with the mini-USB connector is an ST-LINK 2.1 integrated debugger. It needs to upload the firmware on the target MCU and run the debugging. Furthermore, the ST-LINK interface provides a Virtual COM Port (VCP), which can be used to exchange data and messages with the host PC. The ST-LINK interface can be used as a stand-alone ST-LINK programmer, in fac,t can be easily cuttable to reduce board size.

To program the STM32 on board, simply plug in the two jumpers on CN4, as shown in the figure below in pink, but do not use the CN11 connector as that may disturb communication with the STM32 microcontroller of the Nucleo.

However, the ST-LINK provides an optional SWD interface which can be used to program another board without detaching the ST-LINK interface from the Nucleo by removing the two jumpers labeled ST-LINK (CN4).

The rest of the board, MCU part, contains the target MCU (the microcontroller we will use to develop our applications), a RESET button, a user-programmable push button (switch), and an LED. It is possible to mount an external high-speed crystal (HSE) through X3 pads (see figure below). Generally, the Nucleo boards, especially the most recent ones, provide a low-speed crystal (LSE).

Finally, the board has several pin headers we will look at in the next paragraph.

STM32 Nucleo-64 connectors

The STM32 Nucleo-64 board has 8 connectors:

  • CN2: ST-LINK USB connector on ST-LINK (already described in the previous paragraph);
  • CN11: SWD connector in ST-LINK part (already described in the previous paragraph);
  • CN7 and CN10: ST morpho connectors;
  • CN5, CN6, CN8, and CN9: ARDUINO® Uno V3 connectors;

The CN7 and CN10 ST morpho connectors are male pin headers (2x19, 2.54mm male pin headers) accessible on both sides of the STM32 Nucleo-64 board (see the figure below). All signals and power pins can be probed by an oscilloscope, logical analyzer, or voltmeter through the ST morpho connectors. They are two. They are called Morpho connectors and are a convenient way to access most of the MCU pins.

  • The figure below is shown the CN7 pinout (the STM32 peripherals and GPIOs associated with the Morpho connector) for NUCLEO-L053R8.
  • In the next figure, is shown the CN10 pinout for NUCLEO-L053R8.
  • The previous figure showed the pinout for NUCLEO STM32L053R8, but I remember you the Nucleo is pinout compatible with each other.
  • Another important feature of the Nucleo board is the compatibility with ARDUINO® Uno V3 through the CN5, CN6, CN8, and CN9 connectors (see figure below).
  • CN6 and CN8 are directly connected to CN7 and CN5 and CN9 to CN10.

Setting-Up the Tool-Chain

The first step in developing an application on the STM32 platform is to fully set up the tool-chain. A tool-chain is a set of programs, compilers, and tools that allows us:

  • to write our code and to browse the source files of our application;
  • to browse inside the application code, in order to examine variables, definitions, function declarations, and etc;
  • to compile our code using a cross-platform compiler;
  • to upload and debug our application on the development board (or a custom board).

To carry out these activities we basically need:

  • an IDE with integrated source editor and navigator;
  • a cross-platform compiler able to compile source code for our platform;
  • a debugger that executes the debugging of firmware on our board;
  • a tool that interacts with the ST-LINK interface.

There are several complete tool-chain for the STM32 Cortex-M family, both free and commercial. The most used tools are: IAR for Cortex-M, ARM Keil, and AC6 SW4STM32.

They can integrate everything necessary for the development of applications on STM32 to simplify and accelerate their development. The first two are commercial solutions and therefore have a price to the public that may be too high for those approaching the first time.

So, that was all for today. I hope you have enjoyed today's lecture and have understood this Nucleo Development Board. In the next lecture, we will design our first project in STM32CubeIDE. Thanks for reading.

Introduction to STM32 Family

In this guide, we will explain step by step to start programming on the STMicroelectronics (STM) platform, especially the STM32 family.

The term, "STM32" refers to a family of 32-bit microcontroller integrated circuits based on the ARM® Cortex®M processor. The architecture of these CPUs (Central Processing Unit) is ARM (Advanced Risk Machine) which is a particular family of Reduced Instruction Set Computing (RISC). RISC architecture differs from Complex Instruction Set Computing (CISC) for its simplicity that allows you to create processors capable of executing instruction sets with shorter times. Why use STM32? The advantages are many, and now we will list a part of them:

  1. ST offers a wide portfolio of solutions depending on the developer's needs. We can find products that combine different advanced features while maintaining a high level of integration. In fact, we can choose products with high performance, real-time processing, digital signal processing, and low consumption.
  2. Thanks to the availability of different development tools and available support material, the development of a simple or complex project is quite simple and fast, reducing the time to market if you want to develop a product.
  3. Each microcontroller has an integrated processor core, static RAM, flash memory, debug interface, and various peripherals such as GPIO, ADC, DAC, Timer, SPI, UART-USART, I2C, etc.
  4. For every MCU, ST provides the STM32 Nucleo Board that helps anyone who wants to fastly build and test prototypes for new projects with any STM32 MCU. STM32 Nucleo boards share the same connectors and can be easily expanded with many specialized application hardware add-ons (Nucleo-64 includes ST Morpho and Arduino Uno Rev3 connectors, while Nucleo-32 includes Arduino Nano connectors). Another and not insignificant advantage is the cheap cost of these development boards.

In the next paragraph, it will be illustrated how the STM32 is divided to easily identify the one used for your purposes.

Where To Buy?
No.ComponentsDistributorLink To Buy
1STM32 NucleoAmazonBuy Now

STM32 Family

  • To date, the STM32 family has 16 series of microcontrollers divided into four groups in order to cover all the needs of developers.
  • The four groups are Mainstream, Ultra-Low-Power, High-Performance Wireless.

STM32 Mainstream

The STM32 Mainstream has been designed to offer solutions for a wide range of applications where costs, time to market, reliability, and availability are fundamental requirements. They are widely used in real-time control signal processing applications.

There are 5 series in this group:

  1. STM32F1 are microcontrollers based on the ARM Cortex-M3 core. It was launched in 2007 and evolved over time in terms of maximum clock rate, memory depth, and peripherals. In fact, the maximum clock rate has gone from 24 MHz to 72 MHz, static RAM up to 96 kB, and Flash up to 1024 kB. It also supports Thumb-1 and Thumb-2 instruction sets.
  2. STM32F0 are microcontrollers based on the ARM Cortex-M0 core. It was launched in 2012. The maximum clock rate is 48 MHz and includes the SysTick timer. Static RAM up to 32 kB, and Flash up to 256 kB. It also supports Thumb-1 and Thumb-2 instruction sets.
  3. STM32F3 are microcontrollers based on the ARM Cortex-M0 core. It was launched in 2012. The maximum clock rate is 72 MHz and includes the SysTick timer. Static RAM up to 40 kB, and Flash up to 256 kB. It also supports Thumb-1, Thumb-2, Saturated, DSP, FPU instruction sets.
  4. STM32G0 are microcontrollers based on the Cortex-M0/M0+ core. It was launched in 2018. The maximum clock rate is 64 MHz. Static RAM up to 128 kB, and Flash up to 512 kB. It also supports Thumb-1 and Thumb-2 instruction sets. Compared to the older F0 series, it presents improvements in terms of efficiency and performance.
  5. STM32G4 are microcontrollers based on the Cortex-M4F core. It was launched in 2019. The maximum clock rate is 170 MHz. Static RAM up to 128 kB, and Flash up to 512 kB. Thumb-1, Thumb-2, Saturated, DSP, FPU instruction sets. Compared to the older F3/F4 series, it presents improvements in terms of efficiency and performance and higher performance compared to the L4 series.

STM32 Ultra-Low-Power

The STM32 Ultra-Low-Power has been designed to meet the need to develop devices with low energy consumption (such as portable and wearable devices) but maintaining a good compromise with performance.  There are 6 series in this group:

  1. STM32L0 are microcontrollers based on the ARM Cortex-M0+ core. It was launched in 2014. The maximum clock is 32 MHz, static RAM is of 8 kB, and Flash is up to 64 kB. It also supports Thumb-1 and Thumb-2 instruction sets.
  2. STM32L1 are microcontrollers based on the ARM Cortex-M3 core. It was launched in 2010. The maximum clock rate is 32 MHz, static RAM up to 80 kB, and Flash up to 512 kB. It also supports Thumb-1 and Thumb-2 instruction sets.
  3. STM32L4 are microcontrollers based on the ARM Cortex-M4 core. It was launched in 2015. The maximum clock rate is 80 MHz, static RAM is of 64 kB, and Flash is of 1024 kB. It also supports Thumb-1 and Thumb-2, Saturated, DSP, FPU instruction sets.
  4. STM32L4+ are microcontrollers based on the ARM Cortex-M4 core. It was launched in 2016. The maximum clock rate is 120 MHz, static SRAM up to 640 kB, and Flash up to 2048 kB. It also supports Thumb-1 and Thumb-2, Saturated, DSP, FPU instruction sets. It has been enriched with advanced peripherals such as a TFT-LCD controller, Camera interface, etc.
  5. STM32L5 are microcontrollers based on the ARM Cortex-M33F core. It was launched in 2018. The maximum clock rate is 110 MHz, static SRAM up to 640 kB, and Flash up to 2048 kB. It also supports Thumb-1 and Thumb-2, Saturated, DSP, FPU instruction sets.
  6. STM32U5 is the last ultra-low-power series launched (in 2021). It is an evolution of the L series and is based on the ARM Cortex-M33F core. the new 40 nm silicon technology allows to further reduce energy consumption, it also includes advanced cyber security features and graphics accelerators. The maximum clock rate is 160 MHz, static SRAM up to 640 kB, and Flash up to 2048 kB. It also supports Thumb-1 and Thumb-2, Saturated, DSP, FPU instruction sets.

STM32 High-Performance

The STM32 High-Performance has been designed for data processing and data transfer. It also has a high level of memory integration. There are 5 series in this group:

  1. STM32H7 are microcontrollers based on the ARM Cortex-M7F core. It was launched in 2017. The maximum clock is 480 MHz, static RAM is up to 1.4 MB, and Flash is up to 128 kB. It includes Ethernet and some advanced features such as dual Octo-SPI, JPEG codec, etc. It supports Thumb-1 and Thumb-2, Saturated, DSP, FPU instruction sets.
  2. STM32F7 are microcontrollers based on the ARM Cortex-M7F core. It was launched in 2014. The maximum clock is 216 MHz, static RAM is up to 1.4 MB, and Flash is up to 128 kB. It is fully pin-compatible with F4-series. It supports Thumb-1 and Thumb-2, Saturated, DSP, FPU instruction sets.
  3. STM32F4 was the first series of microcontrollers based on the ARM Cortex-M4F core. It was launched in 2011. The maximum clock is up to 180 MHz. It is the first series to have DSP and FPU. It also has faster ADCs, full-duplex I2S and an improved real-time clock. It supports Thumb-1 and Thumb-2, Saturated, DSP, FPU instruction sets.
  4. STM32F2 are microcontrollers based on the ARM Cortex-M3 core. It was launched in 2010. The maximum clock is 120 MHz, static RAM is up to 128 kB, and Flash is up to 1024 kB. It is fully pin-compatible with the F2 series. It supports Thumb-1, Thumb-2 and Saturated instruction sets.

STM32 Wireless

With STM32 Wireless ST adds in the portfolio a platform for wireless connectivity to the portfolio. It has a broad spectrum of frequencies and is used in various industrial and consumer applications.

It has features compatible with multiple protocols which allows it to communicate with different devices in real-time. Now, only two series belong to this group:

  • STM32WB provides Bluetooth®LE 5.2 and IEEE 802.15.4 communication protocols, Zigbee® and Thread, which can work simultaneously or individually.
 
  • STM32WL is the first series that support LoRa® communication.

So, that was all for today. I hope you have enjoyed today's lecture. In the next lecture, I am going to focus on the Nucleo Development board, as we are going to use that in our upcoming tutorials. Thanks for reading. Take care !!! :)

Syed Zain Nasir

I am Syed Zain Nasir, the founder of <a href=https://www.TheEngineeringProjects.com/>The Engineering Projects</a> (TEP). I am a programmer since 2009 before that I just search things, make small projects and now I am sharing my knowledge through this platform.I also work as a freelancer and did many projects related to programming and electrical circuitry. <a href=https://plus.google.com/+SyedZainNasir/>My Google Profile+</a>

Share
Published by
Syed Zain Nasir