📜 ⬆️ ⬇️

Using the stm32cube library to create platform independent drivers

Hello! In this article I would like to share the experience of creating drivers for the stm32 series platforms. The idea is that in Embox OS , it was not necessary to create drivers for each series of STM32F3, STM32F4 platforms and so on. Indeed, besides the fact that it takes time, the new code will inevitably contain new errors.

Initially, we took examples for different devices and reworked them to fit our needs. At the same time, I still wanted to use some common base, and as such a base, the manufacturer of microcontroller data (STMicroelectronics) offers the stm32cube library (more precisely, a series of libraries). Unfortunately, these libraries contain a slightly different, although very similar API for platforms of different series. After all, as many probably know, stm32cubemx, which is the code generator, is part of the stm32cube. That is, with its help, you can generate a project framework for any platform in this series, and the developers need to add the necessary functionality. Because of this, you can not much care about a single interface.

But, as I said, we wanted to use stm32cube to create drivers with a unified API, because it would allow using higher-level software independent of the device level. In addition, as I wrote in one of the articles , the characteristics of modern small ARM (cortex-m) devices approach, conditionally, the Pentiums. You can, of course, continue to use them as cool Arduino, but this is unlikely to fully reveal their potential.

Start code and linker script


The first thing that usually starts working with the controller, and the processor too, is the boot code, interrupt vector and other architectural features. I will briefly touch on how it is organized in stm32cube.
')
In stm32cube there is a specific start code for each controller model, and even for each compiler (ARM, AIR and gcc). All this is in the Drivers / CMSIS / Device / ST / STM32 directory, where XXX is a series, for example, F3, F4 or F7.

The start code itself is contained in a separate assembler file for each controller series and is connected during project configuration. We are closer gcc-shnye versions of files. They are in the Source / Templates / gcc subfolder.

When comparing a pair of assembler files, it turned out that they contain the same boot code, which differs only in the table (vector) of interrupts. This difference is quite expected, because different models of controllers have a different set of hardware devices. But in my opinion, if you put the common code for all controllers in a separate file, and generate different interrupt tables based on the hardware supported by the controller, it will be much better. Something like this is done in Embox.

In addition to the start code in the same folder are the linker scripts for each series of controllers. Again, if we look at them and compare, we will see how similar they are. I found the differences only in the section sizes and in the presence of the CCMRAM section. In our project, we got rid of duplicating linker scripts, and made a small generator that takes a config file and turns it into a linker script with the help of the preprocessor.

Here is how, for example, we have a configuration file for stm32f3-discovery
/* region (origin, length) */
ROM (0x08000000, 256K)
RAM (0x20000000, 40K)
region(SRAM_CCM, 0x10000000, 8K)

/* section (region[, lma_region]) */
text (ROM)
rodata (ROM)
data (RAM, ROM)
bss (RAM)

Debugging interface


In principle, the boot code and linker script is enough to start the program, then you can connect with a debugger and walk along the steps. But this is definitely not enough for doing any useful work. The program must respond to external influences or at least somehow manifest itself. The simplest manifestation is either a flashing LED, or the output of something to the UART (COM port). In our project, it is customary to begin with the UART-based diag interface, since this greatly simplifies subsequent debugging.

Like the UART interface itself, the implementation of the serial port is very simple.
 const struct uart_ops stm32_uart_ops = { .uart_getc = stm32_uart_getc, .uart_putc = stm32_uart_putc, .uart_hasrx = stm32_uart_hasrx, .uart_setup = stm32_uart_setup, }; static struct uart stm32_diag = { .uart_ops = &stm32_uart_ops, .irq_num = USARTx_IRQn, .base_addr = (unsigned long) USARTx, }; 


In fact, it is enough to implement four functions, three of which are trivial - they are writing or reading certain control registers. The uart_setup() function is not much more complicated, it sets the port parameters.

As a result, using the API from STM32CUBE, the code is as follows:
 static int stm32_uart_setup(struct uart *dev, const struct uart_params *params) { UART_HandleTypeDef UartHandle; memset(&UartHandle, 0, sizeof(UartHandle)); UartHandle.Instance = (void*) dev->base_addr; UartHandle.Init.BaudRate = params->baud_rate; UartHandle.Init.WordLength = UART_WORDLENGTH_8B; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; if (HAL_UART_Init(&UartHandle) != HAL_OK) { return -1; } if (dev->params.irq) { /* Enable the UART Data Register not empty Interrupt */ __HAL_UART_ENABLE_IT(&UartHandle, UART_IT_RXNE); } return 0; } static int stm32_uart_putc(struct uart *dev, int ch) { USART_TypeDef *uart = (void *) dev->base_addr; while ((STM32_USART_FLAGS(uart) & USART_FLAG_TXE) == 0); STM32_USART_TXDATA(uart) = (uint8_t) ch; return 0; } static int stm32_uart_hasrx(struct uart *dev) { USART_TypeDef *uart = (void *) dev->base_addr; return STM32_USART_FLAGS(uart) & USART_FLAG_RXNE; } static int stm32_uart_getc(struct uart *dev) { USART_TypeDef *uart = (void *) dev->base_addr; return (uint8_t)(STM32_USART_RXDATA(uart) & 0xFF); } 


In order for the UART to work in the STM32Cube, it is necessary to implement HAL_UART_MspInit() for one or another family of controllers. This function is declared as weak in the cube itself and is redefined for a specific example. It is called from HAL_UART_Init() and, in fact, sets up the I / O pins as needed.

For stm32f4, the code looks like
 void HAL_UART_MspInit(UART_HandleTypeDef *huart) { GPIO_InitTypeDef GPIO_InitStruct; void *uart_base = huart->Instance; /*##-1- Enable peripherals and GPIO Clocks #################################*/ /* Enable GPIO TX/RX clock */ USART_TX_GPIO_CLK_ENABLE(uart_base); USART_RX_GPIO_CLK_ENABLE(uart_base); /* Enable USART2 clock */ USART_CLK_ENABLE(uart_base); /*##-2- Configure peripheral GPIO ##########################################*/ /* UART TX GPIO pin configuration */ GPIO_InitStruct.Pin = USART_TX_PIN(uart_base); GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = USART_TX_AF(uart_base); HAL_GPIO_Init(USART_TX_GPIO_PORT(uart_base), &GPIO_InitStruct); /* UART RX GPIO pin configuration */ GPIO_InitStruct.Pin = USART_RX_PIN(uart_base); GPIO_InitStruct.Alternate = USART_RX_AF(uart_base); HAL_GPIO_Init(USART_RX_GPIO_PORT(uart_base), &GPIO_InitStruct); } 


In Embox, it is possible to use several UARTs through a UNIX interface, that is, with the ability to access the device by the file name (/ dev / ttyS0, / dev / ttyS1). In order to support this feature for stm32, we have not yet come up with anything better than to do a header file for each series, and in it to determine the necessary parameters, and this applies even to the port registers, since, probably, for greater optimality, hardware implementations for different series differ.

For STM32F4 it looks like this
 #define MODOPS_USARTX OPTION_GET(NUMBER, usartx) #if MODOPS_USARTX == 6 #define USARTx USART6 #define USARTx_CLK_ENABLE() __HAL_RCC_USART6_CLK_ENABLE() #define USARTx_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE() #define USARTx_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE() #define USARTx_FORCE_RESET() __HAL_RCC_USART6_FORCE_RESET() #define USARTx_RELEASE_RESET() __HAL_RCC_USART6_RELEASE_RESET() /* Definition for USARTx Pins */ #define USARTx_TX_PIN GPIO_PIN_6 #define USARTx_TX_GPIO_PORT GPIOC #define USARTx_TX_AF GPIO_AF8_USART6 #define USARTx_RX_PIN GPIO_PIN_7 #define USARTx_RX_GPIO_PORT GPIOC #define USARTx_RX_AF GPIO_AF8_USART6 /* Definition for USARTx's NVIC */ #define USARTx_IRQn USART6_IRQn + 16 #define USARTx_IRQHandler USART6_IRQHandler #elif MODOPS_USARTX == 2 #define USARTx USART2 #define USARTx_CLK_ENABLE() __HAL_RCC_USART2_CLK_ENABLE() #define USARTx_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define USARTx_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define USARTx_FORCE_RESET() __HAL_RCC_USART2_FORCE_RESET() #define USARTx_RELEASE_RESET() __HAL_RCC_USART2_RELEASE_RESET() /* Definition for USARTx Pins */ #define USARTx_TX_PIN GPIO_PIN_2 #define USARTx_TX_GPIO_PORT GPIOA #define USARTx_TX_AF GPIO_AF7_USART2 #define USARTx_RX_PIN GPIO_PIN_3 #define USARTx_RX_GPIO_PORT GPIOA #define USARTx_RX_AF GPIO_AF7_USART2 /* Definition for USARTx's NVIC */ #define USARTx_IRQn USART2_IRQn + 16 #define USARTx_IRQHandler USART2_IRQHandler #else #error Unsupported USARTx #endif #define STM32_USART_FLAGS(uart) uart->SR #define STM32_USART_RXDATA(uart) uart->DR #define STM32_USART_TXDATA(uart) uart->DR 


For STM32F7
 #define MODOPS_USARTX OPTION_GET(NUMBER, usartx) #if MODOPS_USARTX == 6 #define USARTx USART6 #define USARTx_CLK_ENABLE() __HAL_RCC_USART6_CLK_ENABLE(); #define USARTx_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE() #define USARTx_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE() #define USARTx_FORCE_RESET() __HAL_RCC_USART6_FORCE_RESET() #define USARTx_RELEASE_RESET() __HAL_RCC_USART6_RELEASE_RESET() /* Definition for USARTx Pins */ #define USARTx_TX_PIN GPIO_PIN_6 #define USARTx_TX_GPIO_PORT GPIOC #define USARTx_TX_AF GPIO_AF8_USART6 #define USARTx_RX_PIN GPIO_PIN_7 #define USARTx_RX_GPIO_PORT GPIOC #define USARTx_RX_AF GPIO_AF8_USART6 /* Definition for USARTx's NVIC */ #define USARTx_IRQn USART6_IRQn + 16 #define USARTx_IRQHandler USART6_IRQHandler #elif MODOPS_USARTX == 2 #define USARTx USART2 #define USARTx_CLK_ENABLE() __HAL_RCC_USART2_CLK_ENABLE(); #define USARTx_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define USARTx_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define USARTx_FORCE_RESET() __HAL_RCC_USART2_FORCE_RESET() #define USARTx_RELEASE_RESET() __HAL_RCC_USART2_RELEASE_RESET() /* Definition for USARTx Pins */ #define USARTx_TX_PIN GPIO_PIN_2 #define USARTx_TX_GPIO_PORT GPIOA #define USARTx_TX_AF GPIO_AF7_USART2 #define USARTx_RX_PIN GPIO_PIN_3 #define USARTx_RX_GPIO_PORT GPIOA #define USARTx_RX_AF GPIO_AF7_USART2 /* Definition for USARTx's NVIC */ #define USARTx_IRQn USART2_IRQn + 16 #define USARTx_IRQHandler USART2_IRQHandler #elif MODOPS_USARTX == 1 #define USARTx USART1 #define USARTx_CLK_ENABLE() __HAL_RCC_USART1_CLK_ENABLE(); #define USARTx_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define USARTx_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define USARTx_FORCE_RESET() __HAL_RCC_USART1_FORCE_RESET() #define USARTx_RELEASE_RESET() __HAL_RCC_USART1_RELEASE_RESET() /* Definition for USARTx Pins */ #define USARTx_TX_PIN GPIO_PIN_9 #define USARTx_TX_GPIO_PORT GPIOA #define USARTx_TX_AF GPIO_AF7_USART1 #define USARTx_RX_PIN GPIO_PIN_7 #define USARTx_RX_GPIO_PORT GPIOB #define USARTx_RX_AF GPIO_AF7_USART1 /* Definition for USARTx's NVIC */ #define USARTx_IRQn USART1_IRQn + 16 #define USARTx_IRQHandler USART1_IRQHandler #else #error Unsupported USARTx #endif #define STM32_USART_FLAGS(uart) uart->ISR #define STM32_USART_RXDATA(uart) uart->RDR #define STM32_USART_TXDATA(uart) uart->TDR 


For STM32F3
 #define MODOPS_USARTX OPTION_GET(NUMBER, usartx) #if MODOPS_USARTX == 1 #define USARTx USART1 #define USARTx_CLK_ENABLE() __USART1_CLK_ENABLE(); #define USARTx_RX_GPIO_CLK_ENABLE() __GPIOC_CLK_ENABLE() #define USARTx_TX_GPIO_CLK_ENABLE() __GPIOC_CLK_ENABLE() #define USARTx_FORCE_RESET() __USART1_FORCE_RESET() #define USARTx_RELEASE_RESET() __USART1_RELEASE_RESET() /* Definition for USARTx Pins */ #define USARTx_TX_PIN GPIO_PIN_4 #define USARTx_TX_GPIO_PORT GPIOC #define USARTx_TX_AF GPIO_AF7_USART1 #define USARTx_RX_PIN GPIO_PIN_5 #define USARTx_RX_GPIO_PORT GPIOC #define USARTx_RX_AF GPIO_AF7_USART1 /* Definition for USARTx's NVIC */ /* In Embox we assume that the lower external irq number is 0, * but in the cortexm3 it is -15 */ #define USARTx_IRQn USART1_IRQn + 16 #define USARTx_IRQHandler USART1_IRQHandler #elif MODOPS_USARTX == 2 #define USARTx USART2 #define USARTx_CLK_ENABLE() __USART2_CLK_ENABLE(); #define USARTx_RX_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE() #define USARTx_TX_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE() #define USARTx_FORCE_RESET() __USART2_FORCE_RESET() #define USARTx_RELEASE_RESET() __USART2_RELEASE_RESET() /* Definition for USARTx Pins */ #define USARTx_TX_PIN GPIO_PIN_2 #define USARTx_TX_GPIO_PORT GPIOA #define USARTx_TX_AF GPIO_AF7_USART2 #define USARTx_RX_PIN GPIO_PIN_3 #define USARTx_RX_GPIO_PORT GPIOA #define USARTx_RX_AF GPIO_AF7_USART2 /* Definition for USARTx's NVIC */ /* In Embox we assume that the lower external irq number is 0, * but in the cortexm3 it is -15 */ #define USARTx_IRQn USART2_IRQn + 16 #define USARTx_IRQHandler USART2_IRQHandler #elif MODOPS_USARTX == 3 #define USARTx USART3 #define USARTx_CLK_ENABLE() __USART3_CLK_ENABLE(); #define USARTx_RX_GPIO_CLK_ENABLE() __GPIOB_CLK_ENABLE() #define USARTx_TX_GPIO_CLK_ENABLE() __GPIOB_CLK_ENABLE() #define USARTx_FORCE_RESET() __USART3_FORCE_RESET() #define USARTx_RELEASE_RESET() __USART3_RELEASE_RESET() /* Definition for USARTx Pins */ #define USARTx_TX_PIN GPIO_PIN_10 #define USARTx_TX_GPIO_PORT GPIOB #define USARTx_TX_AF GPIO_AF7_USART3 #define USARTx_RX_PIN GPIO_PIN_11 #define USARTx_RX_GPIO_PORT GPIOB #define USARTx_RX_AF GPIO_AF7_USART3 /* Definition for USARTx's NVIC */ /* In Embox we assume that the lower external irq number is 0, * but in the cortexm3 it is -15 */ #define USARTx_IRQn USART3_IRQn + 16 #define USARTx_IRQHandler USART3_IRQHandler #endif #define STM32_USART_FLAGS(uart) uart->ISR #define STM32_USART_RXDATA(uart) uart->RDR #define STM32_USART_TXDATA(uart) uart->TDR 


If you look at the code above, you will see that we have been supported only by a pair of UART numbers for those boards that we checked. On the other hand, the task was completed, when you started Embox, the devices / dev / ttyS0 and / dev / ttyS1 appeared, which were tied to the hardware ports of the UART with the numbers specified in the configuration.

Config example
@Runlevel(1) include embox.driver.serial.stm_ttyS1(baud_rate=57600, usartx=2)
@Runlevel(1) include embox.driver.serial.stm_ttyS0(baud_rate=115200, usartx=6)


Timers and Interrupt Controller


The next step in porting is usually support for the timer and interrupt controller. For these devices, the drivers are based on a different library - CMSIS (Cortex Microcontroller Software Interface Standard). We already had them and did not have to redo them. Looking ahead, I’ll clarify that I had to implement the uint32_t HAL_GetTick(void) function in order for the STM32FCube to work correctly, well, and I still had to poke it in the initialization.

The minimum initialization for all platforms is very similar. In fact, it is necessary to call SystemInit() and HAL_Init() from stm32cube and configure clocks for a specific platform.

For stm32f4
 static void SystemClock_Config(void) { RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitTypeDef RCC_OscInitStruct; /* Enable Power Control clock */ __HAL_RCC_PWR_CLK_ENABLE(); /* The voltage scaling allows optimizing the power consumption when the device is clocked below the maximum system frequency, to update the voltage scaling value regarding system frequency refer to product datasheet. */ __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); /* Enable HSE Oscillator and activate PLL with HSE as source */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 8; RCC_OscInitStruct.PLL.PLLN = 336; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 7; HAL_RCC_OscConfig(&RCC_OscInitStruct); /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */ RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5); /* STM32F405x/407x/415x/417x Revision Z devices: prefetch is supported */ if (HAL_GetREVID() == 0x1001) { /* Enable the Flash prefetch */ __HAL_FLASH_PREFETCH_BUFFER_ENABLE(); } } 


For stm32f7
 static void SystemClock_Config(void) { RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitTypeDef RCC_OscInitStruct; /* Enable HSE Oscillator and activate PLL with HSE as source */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSIState = RCC_HSI_OFF; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 25; RCC_OscInitStruct.PLL.PLLN = 432; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 9; if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { printf(">>> SystemClock_Config failed\n"); } /* activate the OverDrive to reach the 216 Mhz Frequency */ if(HAL_PWREx_EnableOverDrive() != HAL_OK) { printf(">>> SystemClock_Config failed\n"); } /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */ RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7) != HAL_OK) { printf(">>> SystemClock_Config failed\n"); } } 


For stm32f3
 static void SystemClock_Config(void) { RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitTypeDef RCC_OscInitStruct; /* Enable HSE Oscillator and activate PLL with HSE as source */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK) { // Error_Handler(); } /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */ RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | 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(); } } 


Actually, after that interruptions and timers work in the system, and this is quite enough for organizing preemptive multitasking. To feel the power of multitasking on these microcontrollers, it is enough to run http and telnet servers on them, but for this you need to implement a network card driver. Here the situation is similar to the UART in the sense that there is a common driver and there is a separate configuration for a particular platform that adjusts the legs and the like.

Ethernet


Let's start with the configuration. We only have versions for stm32f4 and stm32f7, since stm32f3 does not have an ethernet controller.

For stm32f4
 void HAL_ETH_MspInit(ETH_HandleTypeDef *heth) { /*(##) Enable the Ethernet interface clock using (+++) __HAL_RCC_ETHMAC_CLK_ENABLE(); (+++) __HAL_RCC_ETHMACTX_CLK_ENABLE(); (+++) __HAL_RCC_ETHMACRX_CLK_ENABLE(); (##) Initialize the related GPIO clocks (##) Configure Ethernet pin-out (##) Configure Ethernet NVIC interrupt (IT mode) */ GPIO_InitTypeDef GPIO_InitStructure; /* Enable ETHERNET clock __HAL_RCC_ETHMAC_CLK_ENABLE(); __HAL_RCC_ETHMACTX_CLK_ENABLE(); __HAL_RCC_ETHMACRX_CLK_ENABLE(); */ __HAL_RCC_ETH_CLK_ENABLE(); /* Enable GPIOs clocks */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); /* Ethernet pins configuration ************************************************/ /* ETH_MDIO --------------> PA2 ETH_MDC ---------------> PC1 ETH_RMII_REF_CLK-------> PA1 ETH_RMII_CRS_DV -------> PA7 ETH_MII_RX_ER -------> PB10 ETH_RMII_RXD0 -------> PC4 ETH_RMII_RXD1 -------> PC5 ETH_RMII_TX_EN -------> PB11 ETH_RMII_TXD0 -------> PB12 ETH_RMII_TXD1 -------> PB13 ETH_RST_PIN -------> PE2 */ /* Configure PA1,PA2 and PA7 */ GPIO_InitStructure.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7; GPIO_InitStructure.Speed = GPIO_SPEED_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Alternate = GPIO_AF11_ETH; HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure PB10,PB11,PB12 and PB13 */ GPIO_InitStructure.Pin = GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13; /* GPIO_InitStructure.Alternate = GPIO_AF11_ETH; */ HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); /* Configure PC1, PC4 and PC5 */ GPIO_InitStructure.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5; /* GPIO_InitStructure.Alternate = GPIO_AF11_ETH; */ HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); if (heth->Init.MediaInterface == ETH_MEDIA_INTERFACE_MII) { /* Output HSE clock (25MHz) on MCO pin (PA8) to clock the PHY */ HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_1); } /* Configure the PHY RST pin */ GPIO_InitStructure.Pin = GPIO_PIN_2; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStructure.Pull = GPIO_PULLUP; GPIO_InitStructure.Speed = GPIO_SPEED_FAST; HAL_GPIO_Init(GPIOE, &GPIO_InitStructure); HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_SET); HAL_Delay(1); } 


For stm32f7
 void HAL_ETH_MspInit(ETH_HandleTypeDef *heth) { GPIO_InitTypeDef GPIO_InitStructure; /* Enable GPIOs clocks */ __HAL_RCC_GPIOA_CLK_ENABLE() ; __HAL_RCC_GPIOC_CLK_ENABLE() ; __HAL_RCC_GPIOG_CLK_ENABLE(); /* Ethernet pins configuration *****************************************/ /* RMII_REF_CLK ----------------------> PA1 RMII_MDIO -------------------------> PA2 RMII_MDC --------------------------> PC1 RMII_MII_CRS_DV -------------------> PA7 RMII_MII_RXD0 ---------------------> PC4 RMII_MII_RXD1 ---------------------> PC5 RMII_MII_RXER ---------------------> PG2 RMII_MII_TX_EN --------------------> PG11 RMII_MII_TXD0 ---------------------> PG13 RMII_MII_TXD1 ---------------------> PG14 */ /* Configure PA1, PA2 and PA7 */ GPIO_InitStructure.Speed = GPIO_SPEED_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Alternate = GPIO_AF11_ETH; GPIO_InitStructure.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7; HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure PC1, PC4 and PC5 */ GPIO_InitStructure.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5; HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); /* Configure PG2, PG11, PG13 and PG14 */ GPIO_InitStructure.Pin = GPIO_PIN_2 | GPIO_PIN_11 | GPIO_PIN_13 | GPIO_PIN_14; HAL_GPIO_Init(GPIOG, &GPIO_InitStructure); /* Enable the Ethernet global Interrupt */ HAL_NVIC_SetPriority(ETH_IRQn, 0x7, 0); HAL_NVIC_EnableIRQ(ETH_IRQn); /* Enable ETHERNET clock */ __HAL_RCC_ETH_CLK_ENABLE(); } 


In fact, some kind of ethernet controller initialization code generator suggests itself, but so far no hands reach it.

To implement a network device in Embox, it is enough to fill in several fields of the struct net_driver structure:

 static const struct net_driver stm32eth_ops = { .xmit = stm32eth_xmit, .start = stm32eth_open, .set_macaddr = stm32eth_set_mac, }; 

Our driver model is similar to the Linux one, sometimes completely coinciding with it in interface. So do not be surprised if you see code that is very similar to Linux, it was deliberately done to simplify the development of drivers.

Initialization
 static int stm32eth_init(void) { int res; struct net_device *nic; nic = (struct net_device *) etherdev_alloc(0); if (nic == NULL) { return -ENOMEM; } nic->drv_ops = &stm32eth_ops; nic->irq = STM32ETH_IRQ; nic->base_addr = ETH_BASE; nic_priv = netdev_priv(nic, struct stm32eth_priv); stm32eth_netdev = nic; res = irq_attach(nic->irq, stm32eth_interrupt, 0, stm32eth_netdev, ""); if (res < 0) { return res; } return inetdev_register_dev(nic); } 


In addition, the library itself must be initialized.
 static void low_level_init(unsigned char mac[6]) { //uint32_t regvalue; int err; memset(&stm32_eth_handler, 0, sizeof(stm32_eth_handler)); stm32_eth_handler.Instance = (ETH_TypeDef *) ETH_BASE; /* Fill ETH_InitStructure parametrs */ stm32_eth_handler.Init.MACAddr = mac; stm32_eth_handler.Init.AutoNegotiation = ETH_AUTONEGOTIATION_DISABLE; //stm32_eth_handler.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE; stm32_eth_handler.Init.Speed = ETH_SPEED_100M; stm32_eth_handler.Init.DuplexMode = ETH_MODE_FULLDUPLEX; stm32_eth_handler.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; stm32_eth_handler.Init.ChecksumMode = ETH_CHECKSUM_BY_SOFTWARE;//ETH_CHECKSUM_BY_HARDWARE; stm32_eth_handler.Init.PhyAddress = PHY_ADDRESS; stm32_eth_handler.Init.RxMode = ETH_RXINTERRUPT_MODE; if (HAL_OK != (err = HAL_ETH_Init(&stm32_eth_handler))) { log_error("HAL_ETH_Init err %d\n", err); } if (stm32_eth_handler.State == HAL_ETH_STATE_READY) { log_error("STATE_READY sp %d duplex %d\n", stm32_eth_handler.Init.Speed, stm32_eth_handler.Init.DuplexMode); } /*(#)Initialize Ethernet DMA Descriptors in chain mode and point to allocated buffers:*/ HAL_ETH_DMATxDescListInit(&stm32_eth_handler, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB); /*for Transmission process*/ if (HAL_OK != (err = HAL_ETH_DMARxDescListInit(&stm32_eth_handler, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB))) { /*for Reception process*/ log_error("HAL_ETH_DMARxDescListInit %d\n", err); } /* (#)Enable MAC and DMA transmission and reception: */ HAL_ETH_Start(&stm32_eth_handler); } 


broadcast
 static int stm32eth_xmit(struct net_device *dev, struct sk_buff *skb) { __IO ETH_DMADescTypeDef *dma_tx_desc; dma_tx_desc = stm32_eth_handler.TxDesc; memcpy((void *)dma_tx_desc->Buffer1Addr, skb->mac.raw, skb->len); /* Prepare transmit descriptors to give to DMA */ HAL_ETH_TransmitFrame(&stm32_eth_handler, skb->len); skb_free(skb); return 0; } 


For transmission, we allocate several buffers for the packets, and when the packet arrives for sending, we copy the data into the already selected area and then call the send function from STM32CUBE

Receipt of packets occurs by interruption:
 static irq_return_t stm32eth_interrupt(unsigned int irq_num, void *dev_id) { struct net_device *nic_p = dev_id; struct sk_buff *skb; ETH_HandleTypeDef *heth = &stm32_eth_handler; if (!nic_p) { return IRQ_NONE; } /* Frame received */ if (__HAL_ETH_DMA_GET_FLAG(heth, ETH_DMA_FLAG_R)) { /* Receive complete callback */ while (NULL != (skb = low_level_input())) { skb->dev = nic_p; show_packet(skb->mac.raw, skb->len, "rx"); netif_rx(skb); } /* Clear the Eth DMA Rx IT pending bits */ __HAL_ETH_DMA_CLEAR_IT(heth, ETH_DMA_IT_R); } __HAL_ETH_DMA_CLEAR_IT(heth, ETH_DMA_IT_NIS); return IRQ_HANDLED; } 


Receive function code
 static struct sk_buff *low_level_input(void) { struct sk_buff *skb; int len; uint8_t *buffer; uint32_t i=0; __IO ETH_DMADescTypeDef *dmarxdesc; skb = NULL; /* get received frame */ if (HAL_ETH_GetReceivedFrame_IT(&stm32_eth_handler) != HAL_OK) return NULL; /* Obtain the size of the packet and put it into the "len" variable. */ len = stm32_eth_handler.RxFrameInfos.length; buffer = (uint8_t *) stm32_eth_handler.RxFrameInfos.buffer; /* We allocate a pbuf chain of pbufs from the Lwip buffer pool */ skb = skb_alloc(len); /* copy received frame to pbuf chain */ if (skb != NULL) { memcpy(skb->mac.raw, buffer, len); } /* Release descriptors to DMA */ dmarxdesc = stm32_eth_handler.RxFrameInfos.FSRxDesc; /* Set Own bit in Rx descriptors: gives the buffers back to DMA */ for (i=0; i< stm32_eth_handler.RxFrameInfos.SegCount; i++) { dmarxdesc->Status |= ETH_DMARXDESC_OWN; dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr); } /* Clear Segment_Count */ stm32_eth_handler.RxFrameInfos.SegCount =0; /* When Rx Buffer unavailable flag is set: clear it and resume reception */ if ((stm32_eth_handler.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET) { /* Clear RBUS ETHERNET DMA flag */ stm32_eth_handler.Instance->DMASR = ETH_DMASR_RBUS; /* Resume DMA reception */ stm32_eth_handler.Instance->DMARPDR = 0; } return skb; } 


Actually, this is the whole driver. After its implementation, all network utilities that we have in Embox work . You can read about the application in our previous articles ( pjsip and httpd )

In addition to the listed drivers based on stm32cube, spi, i2c, lcd, accelerator, gyroscope and others are also implemented.

PS Our report was accepted on OSDAY (May 23-24 in Moscow), if it is interesting to hear live about the project, you are welcome.

Source: https://habr.com/ru/post/328866/


All Articles