Author Topic: Starting with STM32 (NUCLEO-L412KB)  (Read 15596 times)

0 Members and 1 Guest are viewing this topic.

Offline pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4386
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #25 on: May 14, 2024, 03:56:45 pm »
Get yourself the reference manual from the ST website and start reading about the clock configuration.

Bare metal programming these MCU's, for me at least, makes more sense then the whole HAL crap. Some will say there is the LL crap, but it all requires learning and reading, so why not go directly to the bottom.

It is all based on setting registers with values, much like you did in your post above.

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #26 on: May 14, 2024, 04:08:15 pm »
This configuration works (with internal oscillator):



Output: 4717MHz
Improved program (more clear):

Code: [Select]
while (1) {
   GPIOA->BSRR |= (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
   GPIOA->BRR |= (1<<3);  // Reset PA3
}
« Last Edit: May 14, 2024, 04:10:21 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #27 on: May 14, 2024, 04:16:16 pm »
The MX configurator works fine with internal oscillators, the problem I think is in the input frequency of the external oscillator. It is not well configured for some reason.

For now I don't need it, but later I will need a more stable oscillator than the internal oscillator of the microcontroller.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #28 on: May 14, 2024, 04:22:33 pm »
A jumper (SB17) must be soldered on the board to connect the oscillator to the input pin of the microcontroller.
« Last Edit: May 14, 2024, 04:30:32 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #29 on: May 14, 2024, 04:40:54 pm »
Jumper soldered with a small wire. External clock working properly.

4703 kHz output in PA3. Apparently it takes 17 cycles to turn on one pin, turn off one pin and jump. An Atmega would only take 4 cycles.

I'm going to investigate code execution in RAM which is apparently even faster.

EDIT:
https://community.st.com/t5/stm32-mcus/how-to-place-and-execute-stm32-code-in-sram-memory-with/ta-p/49528
 

Offline pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4386
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #30 on: May 14, 2024, 04:51:59 pm »
Jumper soldered with a small wire. External clock working properly.

4703 kHz output in PA3. Apparently it takes 17 cycles to turn on one pin, turn off one pin and jump. An Atmega would only take 4 cycles.

I'm going to investigate code execution in RAM which is apparently even faster.

EDIT:
https://community.st.com/t5/stm32-mcus/how-to-place-and-execute-stm32-code-in-sram-memory-with/ta-p/49528

Has to do with the FLASH configuration. There is a setting for the number of wait states it uses when fetching from FLASH. In the STM32F103 it is set with the "FLASH->ACR" register.

Then there is the speed of the different busses in the ARM system to consider. The peripheral bus the GPIO port is connected to is of influence of how fast it can switch.

It is a completely different beast then the AVR.

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #31 on: May 14, 2024, 04:53:09 pm »
The program now generates an A3 output at a lower frequency (3634 kHz).
It does not appear that running in ram speeds up this small piece of code.

Code: [Select]
/* USER CODE BEGIN PD */

static void __attribute__((section(".RamFunc"))) toggle_a3(void)  {
   while (1) {
      GPIOA->BSRR |= (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
      GPIOA->BRR |= (1<<3);  // Reset PA3
   }
}


/* USER CODE END PD */
 

Offline tellurium

  • Frequent Contributor
  • **
  • Posts: 274
  • Country: ua
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #32 on: May 14, 2024, 05:29:27 pm »
Make sure your linker script (.ld file) puts RamFunc into the .data
Open source embedded network library https://github.com/cesanta/mongoose
TCP/IP stack + TLS1.3 + HTTP/WebSocket/MQTT in a single file
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1338
  • Country: de
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #33 on: May 14, 2024, 05:56:07 pm »
Only write to BSRR/BRR, don't read-modify-write.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #34 on: May 14, 2024, 06:01:43 pm »
Only write to BSRR/BRR, don't read-modify-write.

This code runs faster (8883 kHz) in flash and have the same effect:
Code: [Select]
void toggle_a3(void)  {
   while (1) {
      GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
      GPIOA->BRR = (1<<3);  // Reset PA3
   }
}
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #35 on: May 14, 2024, 06:04:23 pm »
Make sure your linker script (.ld file) puts RamFunc into the .data

.ld file:
Code: [Select]
  /* Initialized data sections into "RAM" Ram type memory */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */

  } >RAM AT> FLASH


I think so, but the speed is slower.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #36 on: May 14, 2024, 06:10:34 pm »
Ok I got it. It's slower the jump!

If I write the changes in a row, the output goes to 9630kHz:

Code: [Select]
static void __attribute__((section(".RamFunc"))) toggle_a3(void)  {
   while (1) {
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3

     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
     GPIOA->BSRR = (1<<3); // Set PA3 (Pin A2 in the Nucleo board)
     GPIOA->BRR = (1<<3);  // Reset PA3
  }
}

It appears that the minimum GPIO write time is about 50ns, even though the internal cycle time is 12.5ns.
« Last Edit: May 14, 2024, 06:13:04 pm by Picuino »
 

Offline pcprogrammer

  • Super Contributor
  • ***
  • Posts: 4386
  • Country: nl
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #37 on: May 14, 2024, 06:36:04 pm »
The core clock differs from the peripheral clocks. Look at the clock system in the reference manual. Some timers may run at the same speed, others at half or less. The ADC runs on another clock, etc.

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15398
  • Country: fr
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #38 on: May 14, 2024, 09:04:48 pm »
And even if the GPIO is clocked from the same clock as the core, there will be synchronization with the peripheral bus, which will lead to a 1-clock delay (at least), which is consistent with what you get here.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2400
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #39 on: May 14, 2024, 09:59:01 pm »
When looking at the disassembly i see that
Code: [Select]
PortConst->BSRR = PinConst;translates into three instructions. Plus the sync cycle gives the four cycles. Same as AVR except at 80 MHz.
If i turn on optimization, the disassembly shows it can be done in one instruction instead of three (producing constants from registers).

Regards, Dieter
« Last Edit: May 14, 2024, 10:05:12 pm by dietert1 »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #40 on: May 15, 2024, 01:34:33 pm »
Optimizations were disabled.
I have now enabled them for speed.

EDIT:
Now the output pin reaches 40MHz!!!
(a bit lower due to the jump)
« Last Edit: May 15, 2024, 01:40:05 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #41 on: May 15, 2024, 01:43:22 pm »
I am now going to test serial communications and add a read and write buffer to UART2.
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #42 on: May 15, 2024, 04:25:05 pm »
It works.

usart.h:
Code: [Select]
/*
 * Function declaration
 */
#include <ctype.h>

void usart_init(void);
void usart_fputs(int8_t *str);
void usart_fputc(int8_t ch);
void usart_send(void);
void usart_receive(void);
int8_t usart_kbhit(void);
int8_t usart_fgetc(void);
int16_t usart_free(int16_t init, int16_t end);


usart.c:
Code: [Select]
#include "main.h"
#include "usart.h"


/*
 * Types and variables declaration
 */
#define USART_BUFF_SIZE (255)
#define USART_EOF (-1)

struct usart_buff_t {
    char buff[USART_BUFF_SIZE + 1];
    unsigned char init;
    unsigned char end;
};

struct usart_buff_t usart_buff_in, usart_buff_out;

/*
 * Inits USART2
 */
void usart_init(void) {
    USART2->CR1 |= (USART_CR1_RE | USART_CR1_TE);  // RX, TX enable.
    USART2->CR1 |= USART_CR1_UE;                   // USART2 enable.
    USART2->CR1 |= USART_CR1_RXNEIE;   // USART2 Receive Interrupt Enable.
    USART2->CR1 |= USART_CR1_TXEIE;    // USART2 Transmit Interrupt Enable.

    // Enable interrupt from USART2 (NVIC level)
    NVIC_EnableIRQ(USART2_IRQn);
}

/*
 * Save string to USART buffer output
 */
void usart_fputs(int8_t *str) {
    while (*str) {
        usart_fputc(*str++);
    }
}

/*
 * Save char to USART buffer output
 */
void usart_fputc(int8_t ch) {
    if (usart_free(usart_buff_out.init, usart_buff_out.end) == 0) {
        return;
    }

    usart_buff_out.buff[usart_buff_out.init] = ch;
    if (++usart_buff_out.init == USART_BUFF_SIZE)
        usart_buff_out.init = 0;
    USART2->CR1 |= USART_CR1_TXEIE;    // USART2 Transmit Interrupt Enable.
}

/*
 * Test if input buffer is empty
 * return 0 if buffer empty
 */
int8_t usart_kbhit(void) {
    if (usart_buff_in.init == usart_buff_in.end)
        return 0;
    return 1;
}

/*
 * Get char from USART buffer input
 */
int8_t usart_fgetc(void) {
    int8_t ch;
    if (usart_buff_in.init == usart_buff_in.end)
        return USART_EOF;
    ch = usart_buff_in.buff[usart_buff_in.init];
    if (++usart_buff_in.init == USART_BUFF_SIZE)
        usart_buff_in.init = 0;
    return ch;
}

/*
 * Return bytes remaining in rs232 buffer
 *   0 = buffer full
 *   1 = buffer with one space remaining
 */
int16_t usart_free(int16_t init, int16_t end) {
    end -= init;
    if (end >= 0)
        return (USART_BUFF_SIZE - 1) - end;
    return 1 - end;
}

/*
 * Send char from buffer to USART
 * Callable only by Interrupt Service Routine
 */
void usart_send(void) {
    if (usart_buff_out.init == usart_buff_out.end) {  // No more char to send
        USART2->CR1 &= ~USART_CR1_TXEIE;   // USART2 Transmit Interrupt Disable.
        return;
    }

    USART2->TDR = usart_buff_out.buff[usart_buff_out.end];
    usart_buff_out.end++;
    if (usart_buff_out.end == USART_BUFF_SIZE)
        usart_buff_out.end = 0;
}

/*
 * Receive char from USART to buffer
 * Callable only by Interrupt Service Routine
 */
void usart_receive(void) {
    // If the input buffer is full, the input character is discarded.
    if ((usart_buff_in.end + 1) == usart_buff_in.init
            || (usart_buff_in.end == (USART_BUFF_SIZE - 1)
                    && usart_buff_in.init == 0)) {
        uint8_t ch = USART2->RDR;
        return;
    }

    usart_buff_in.buff[usart_buff_in.end] = USART2->RDR;
    usart_buff_in.end++;
    if (usart_buff_in.end == USART_BUFF_SIZE)
        usart_buff_in.end = 0;

    // Error handling

}

/*
 * USART2 IRQ Handler
 */
void USART2_IRQHandler(void) {

    //check if we are here because of RXNE interrupt
    if (USART1->ISR & USART_ISR_RXNE) {
        usart_receive();
    }

    //check if we are here because of TXEIE interrupt
    if (USART1->ISR & USART_ISR_TXE) {
        usart_send();
    }
}


main.c:
Code: [Select]
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include <stdio.h>
#include <ctype.h>
#include "usart.h"


/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);

/* USER CODE BEGIN PFP */

void blink_led(void);
static void __attribute__((section(".RamFunc"))) blink_PA3(void);
void hello_world(void);

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
 * @brief  The application entry point.
 * @retval int
 */
int main(void) {

    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

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

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

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

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

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

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

    /* USER CODE END 2 */

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

        /* USER CODE BEGIN 3 */
        usart_fputs("Hello World!\n");    // <<<------
        HAL_Delay(1000);

    }
    /* USER CODE END 3 */
}

/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
    RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };

    /** Configure the main internal regulator output voltage
     */
    if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1)
            != HAL_OK) {
        Error_Handler();
    }

    /** Initializes the RCC Oscillators according to the specified parameters
     * in the RCC_OscInitTypeDef structure.
     */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 2;
    RCC_OscInitStruct.PLL.PLLN = 40;
    RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
    RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    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_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) {
        Error_Handler();
    }

    /** Enables the Clock Security System
     */
    HAL_RCC_EnableCSS();
}

/**
 * @brief USART2 Initialization Function
 * @param None
 * @retval None
 */
static void MX_USART2_UART_Init(void) {

    /* USER CODE BEGIN USART2_Init 0 */

    /* USER CODE END USART2_Init 0 */

    /* USER CODE BEGIN USART2_Init 1 */

    /* USER CODE END USART2_Init 1 */
    huart2.Instance = USART2;
    huart2.Init.BaudRate = 115200;
    huart2.Init.WordLength = UART_WORDLENGTH_9B;
    huart2.Init.StopBits = UART_STOPBITS_1;
    huart2.Init.Parity = UART_PARITY_EVEN;
    huart2.Init.Mode = UART_MODE_TX_RX;
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart2.Init.OverSampling = UART_OVERSAMPLING_16;
    huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
    huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
    if (HAL_UART_Init(&huart2) != HAL_OK) {
        Error_Handler();
    }
    /* USER CODE BEGIN USART2_Init 2 */
    usart_init();

    /* USER CODE END USART2_Init 2 */

}

/**
 * @brief GPIO Initialization Function
 * @param None
 * @retval None
 */
static void MX_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = { 0 };
    /* USER CODE BEGIN MX_GPIO_Init_1 */
    /* USER CODE END MX_GPIO_Init_1 */

    /* GPIO Ports Clock Enable */
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);

    /*Configure GPIO pin : PA3 */
    GPIO_InitStruct.Pin = GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /*Configure GPIO pin : LD3_Pin */
    GPIO_InitStruct.Pin = LD3_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(LD3_GPIO_Port, &GPIO_InitStruct);

    /* USER CODE BEGIN MX_GPIO_Init_2 */
    /* USER CODE END MX_GPIO_Init_2 */
}

/* 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 */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */



Outputs "Hello World!" every second with interrupts (cpu not blocked).

Routines discard received data or sended data when the input or the output buffer (255 bytes) is full.

 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #43 on: May 15, 2024, 05:52:05 pm »
The receive doesn't work.

It gets blocked in the interrupt routine, going in over and over again, even though I have completely reset USART1->ISR = 0;


EDIT:
I have written USART1 instead of USART2. It works now.
« Last Edit: May 15, 2024, 05:59:43 pm by Picuino »
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #44 on: May 15, 2024, 07:02:52 pm »
This program send the characters received, with interrupts.

main.c:
Code: [Select]
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <ctype.h>
#include "usart.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

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

void blink_led(void);
static void __attribute__((section(".RamFunc"))) blink_PA3(void);
void hello_world(void);

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

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

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

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

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

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

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

  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
        while (usart_kbhit()) {
            char ch = usart_fgetc();
            usart_fputc(ch);
        }
    }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 2;
  RCC_OscInitStruct.PLL.PLLN = 40;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  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_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }

  /** Enables the Clock Security System
  */
  HAL_RCC_EnableCSS();
}

/**
  * @brief USART2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */
    usart_init();

  /* USER CODE END USART2_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : PA3 */
  GPIO_InitStruct.Pin = GPIO_PIN_3;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pin : LD3_Pin */
  GPIO_InitStruct.Pin = LD3_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LD3_GPIO_Port, &GPIO_InitStruct);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

static void __attribute__((section(".RamFunc"))) blink_PA3(void) {
    while (1) {
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
        GPIOA->BSRR = (1 << 3); // Set PA3 (Pin A2 in the Nucleo board)
        GPIOA->BRR = (1 << 3);  // Reset PA3
    }
}

void blink_led(void) {
    HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_SET);
    HAL_Delay(1000);
    HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
    HAL_Delay(100);
}

void hello_world(void) {
    uint8_t message[] = "Hello World!\n"; //Data to send
    HAL_UART_Transmit(&huart2, message, sizeof(message), 10); // Sending in normal mode
    HAL_Delay(1000);
}

/*
 * Put char in USART transmit register
 */
int __io_putchar(int ch) {
    while (!(USART2->ISR & 0x0080))
        ; // Wait for transfer buffer to be empty
    USART2->TDR = (ch & 0xFF);
    return ch;
}

/* 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 */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


usart.h:
Code: [Select]
/*
 * Function declaration
 */
#include <ctype.h>

void usart_init(void);
void usart_fputs(char *str);
void usart_fputc(char ch);
void usart_send(void);
void usart_receive(void);
char usart_kbhit(void);
char usart_fgetc(void);
int16_t usart_free(int16_t init, int16_t end);


usart.c:
Code: [Select]
#include "main.h"
#include "usart.h"

/*
 * Types and variables declaration
 */
#define USART USART2
#define USART_BUFF_SIZE (250)
#define USART_EOF (-1)

struct usart_buff_t {
    char buff[USART_BUFF_SIZE + 1];
    uint16_t init;
    uint16_t end;
};

struct usart_buff_t usart_buff_in, usart_buff_out;

/*
 * Inits USART
 */
void usart_init(void) {
    USART->CR1 |= USART_CR1_RE;  // RX enable.
    USART->CR1 |= USART_CR1_TE;  // TX enable.
    USART->CR1 |= USART_CR1_UE;  // USART enable.
    USART->CR1 |= USART_CR1_RXNEIE;   // USART Receive Interrupt Enable.

    /* Disable the UART Parity Error Interrupt */
    USART->CR1 &= ~(USART_CR1_PEIE | USART_CR1_CMIE);
    /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
    USART->CR3 &= ~(USART_CR3_EIE);

    // Enable interrupt from USART (NVIC level)
    NVIC_EnableIRQ(USART2_IRQn);
}

/*
 * Save string to USART buffer output
 */
void usart_fputs(char *str) {
    while (*str) {
        usart_fputc(*str++);
    }
}

/*
 * Save char to USART buffer output
 */
void usart_fputc(char ch) {
    if (usart_free(usart_buff_out.init, usart_buff_out.end) == 0) {
        return;
    }

    usart_buff_out.buff[usart_buff_out.init] = ch;
    if (++usart_buff_out.init == USART_BUFF_SIZE)
        usart_buff_out.init = 0;
    USART->CR1 |= USART_CR1_TXEIE;    // USART Transmit Interrupt Enable.
}

/*
 * Test if input buffer is empty
 * return 0 if buffer empty
 */
char usart_kbhit(void) {
    if (usart_buff_in.init == usart_buff_in.end)
        return 0;
    return 1;
}

/*
 * Get char from USART buffer input
 */
char usart_fgetc(void) {
    int8_t ch;
    if (usart_buff_in.init == usart_buff_in.end)
        return USART_EOF;
    ch = usart_buff_in.buff[usart_buff_in.init];
    if (++usart_buff_in.init == USART_BUFF_SIZE)
        usart_buff_in.init = 0;
    return ch;
}

/*
 * Return bytes remaining in rs232 buffer
 *   0 = buffer full
 *   1 = buffer with one space remaining
 */
int16_t usart_free(int16_t init, int16_t end) {
    end -= init;
    if (end >= 0)
        return (USART_BUFF_SIZE - 1) - end;
    else
        return 1 - end;
}

/*
 * Send char from buffer to USART
 * Callable only by Interrupt Service Routine
 */
void usart_send(void) {
    if (usart_buff_out.init == usart_buff_out.end) {  // No more char to send
        USART->CR1 &= ~USART_CR1_TXEIE;   // USART Transmit Interrupt Disable.
        return;
    }

    USART->TDR = usart_buff_out.buff[usart_buff_out.end];
    usart_buff_out.end++;
    if (usart_buff_out.end == USART_BUFF_SIZE)
        usart_buff_out.end = 0;
}

/*
 * Receive char from USART to buffer
 * Callable only by Interrupt Service Routine
 */
void usart_receive(void) {
    // If the input buffer is full, the input character is discarded.
    if (usart_free(usart_buff_in.init, usart_buff_in.end) == 0) {
        char ch = USART->RDR;
        return;
    }

    usart_buff_in.buff[usart_buff_in.end] = USART->RDR;
    usart_buff_in.end++;
    if (usart_buff_in.end == USART_BUFF_SIZE)
        usart_buff_in.end = 0;
}

/*
 * USART IRQ Handler
 */
void USART2_IRQHandler(void) {
    //check if we are here because of RXNE interrupt
    if (USART->ISR & USART_ISR_RXNE) {
        usart_receive();
    }

    //check if we are here because of TXEIE interrupt
    if (USART->ISR & USART_ISR_TXE) {
        usart_send();
    }

    // Error handling
}
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2400
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #45 on: May 16, 2024, 06:01:09 am »
Apparently you are having fun.

Anyway let's mention the HAL libraries. E.g. interrupt-based UART transmission is just few lines:
status=HAL_UART_Transmit_IT(handle,data,size);
and e.g. implement
void HAL_UART_TxCplt_Callback(handle);

For the interested, they have a manual about STM32L4 HAL libraries: UM1884, file: en.DM00173145.pdf
Should be easy to find. A huge file so one can only try and understand their naming conventions and then use what is needed. Looking at example projects also helps using existing driver libraries.

Regards, Dieter
 
The following users thanked this post: Picuino

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #46 on: May 16, 2024, 08:08:58 am »
Apparently you are having fun.
Yes  ;D

Thank you very much, it will be useful to get started in many cases.
On the other hand for now I prefer to use my own libraries and adjust the behavior by hand. For example, what should happen when a data arrives and the receive buffer is full, is the data discarded or do I store it in the buffer discarding the first one stored?

Later I will need to control the peripherals very precisely to synchronize them. I come from programming 8-bit and 16-bit micros where it is normal to directly control the registers and I like to have that kind of direct control of the microcontroller.
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2400
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #47 on: May 16, 2024, 08:42:55 am »
The nice thing about the STM supplied driver libraries is they are open source. One can debug into the code and see what it does.
The host interface in my applications uses the HAL_UART_Receive_IT() function to start one initial receive request, with a buffer large enough for some messages, like 1 KByte. Then the main application loop polls the receive status and handles the input data. About 10 C statements (with interrupt disabled) extract the data from the receive buffer and let the receive operation continue.
As far as i remember the STM32 uart can detect a given character in the input stream, so one could get a callback once a complete line or packet was received. That may be more efficient.

Regards, Dieter
 

Offline PicuinoTopic starter

  • Super Contributor
  • ***
  • Posts: 1033
  • Country: es
    • Picuino web
« Last Edit: May 16, 2024, 09:38:59 am by Picuino »
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2400
  • Country: br
    • CADT Homepage
Re: Starting with STM32 (NUCLEO-L412KB)
« Reply #49 on: May 16, 2024, 10:42:47 am »
When i configure a project with CubeMX and generate the code, it copies all used driver sources and their inc files into my project folder. This copying may be optional. I guess the files come from the STM32L4 repository i mentioned above, in my case version 17.2 in C:\users\<userid>\STM32Cube\Repository.
During debugging there is no difference between those vendor supplied drivers and my own sources.

Regards, Dieter
 
The following users thanked this post: Picuino


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf