Использование библиотеки stm32cube для создания платформо независимых драйверов

в 9:04, , рубрики: C, embox, open source, stm32, stm32cube, Блог компании Embox, микроконтроллеры, операционные системы, осрв, программирование микроконтроллеров, системное программирование

Использование библиотеки stm32cube для создания платформо независимых драйверов - 1
Всем привет!
В данной статье я бы хотел поделиться опытом создания драйверов для платформ серии stm32. Идея заключается в том, чтобы в ОС Embox, не приходилось создавать драйвера для каждой серии платформ STM32F3, STM32F4 и так далее. Ведь кроме того, что это занимает время, новый код неизбежно будет содержать новые ошибки.

Изначально мы брали примеры для разных устройств и переделывали их под свои нужды. При этом всё-таки хотелось использовать некоторую общую базу, и в качестве такой базы производитель данных микроконтроллеров (STMicroelectronics) предлагает библиотеку stm32cube (точнее, серию библиотек). К сожалению, данные библиотеки содержат несколько различное, хотя и очень схожее API для платформ разных серий. Ведь, как многие, наверное, знают, в состав stm32cube входит stm32cubemx, который является генератором кода. То есть, с его помощью, можно сгенерировать каркас проекта под любую платформу из данной серии, и уже в нём разработчиками дописывается нужный функционал. Благодаря этому, можно не сильно заботиться о едином интерфейсе.

Но нам, как я уже говорил, хотелось использовать stm32cube для создания драйверов с унифицированным API, ведь это позволило бы использовать независимое от уровня устройств ПО более высокого уровня. К тому же, как я уже писал в одной из статей, характеристики современных мелких ARM (cortex-m) устройств приближаются, условно, к пентиумам. Можно, конечно, продолжать использовать их как крутые ардуино, но это вряд ли полностью раскроет их потенциал.

Стартовый код и линкер-скрипт

Первое, с чего обычно начинается работа с контроллером, да и процессором тоже, — это загрузочный код, вектор прерываний и прочие архитектурные особенности. Я кратко затрону, как это организовано в stm32cube.

В stm32cube есть специфичный стартовый код для каждой модели контроллера, да ещё и для каждого компилятора (ARM, AIR и gcc). Всё это находится в папке Drivers/CMSIS/Device/ST/STM32, где XXX — это серия, например, F3, F4 или F7.
Сам стартовый код содержится в отдельном ассемблерном файле для каждой серии контроллера и подключается во время конфигурации проекта. Нам ближе gcc-шные версии файлов. Они лежат во вложенной папке Source/Templates/gcc.
При сравнении пары ассемблерных файлов оказалось, что в них лежит один и тот же загрузочный код, отличающийся только таблицей (вектором) прерываний. Это различие вполне ожидаемо, ведь у разных моделей контроллеров разный набор аппаратных устройств. Но на мой взгляд, если вынести общий код для всех контроллеров в отдельный файл, а различные таблицы прерываний генерить на основе поддерживаемого контроллером оборудования, станет гораздо лучше. Приблизительно так у нас в Embox и сделано.

Кроме стартового кода в этой же папке лежат линкер-скрипты для каждой серии контроллеров. Опять же, если мы заглянем в них и сравним, увидим насколько они похожи. Различия я нашел только в размерах секций и в наличии секции CCMRAM. У себя в проекте мы избавились от дублирования линкер-скриптов, и сделали небольшой генератор, который берёт конфиг-файл и с помощью препроцессора превращает его в линкер-скрипт.

Вот как, к примеру, у нас выглядит конфигурационный файл для 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)

Отладочный интерфейс

В принципе, загрузочного кода и линкер-скрипта достаточно для запуска программы, после этого можно подключиться отладчиком и походить по шагам. Но этого, определённо, мало для выполнения какой-либо полезной работы. Программа должна реагировать на внешние воздействия или хотя бы как-то проявлять себя. Самое простое проявление — это либо мигание светодиодом, либо вывод чего-нибудь в UART (COM порт). В нашем проекте принято начинать с интерфейса diag на основе UARTа, посколько это сильно упрощает последующую отладку.

Как и сам интерфейс UART, реализация последовательного порта у нас очень простая

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,
};

По сути, достаточно реализовать четыре функции, три из которых тривиальны — они представляют из себя запись или чтение определённых управляющих регистров. Функция uart_setup() не сильно сложнее, в ней устанавливаются параметры порта.

В итоге, с помощью API из STM32CUBE код получился следующим:

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);
}

Для того, чтобы UART заработал в STM32Cube, необходимо реализовать HAL_UART_MspInit() для того или иного семейства контроллеров. Эта функция объявлена как слабая (weak) в самом cube и переопределяется для конкретного примера. Вызывается она из HAL_UART_Init() и, по сути, настраивает контакты ввода-вывода нужным образом.

Для stm32f4 код выглядит так

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);
}

В ОС Embox возможно использовать несколько UART-ов через UNIX-интерфейсом, то есть, с возможностью обратиться к устройству по имени файла (/dev/ttyS0, /dev/ttyS1). Для того, чтобы поддержать эту фичу для stm32, мы пока не придумали ничего лучше, чем сделать для каждой серии заголовочный файл, и в нём определить нужные параметры, причем это касается даже регистров порта, поскольку, вероятно, для большей оптимальности, аппаратные реализации для разных серий различаются.

Для STM32F4 это выглядит так

#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

Для 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

Для 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

Если заглянете в приведённый код, то увидите, что поддержано у нас только по паре номеров UART-ов для тех плат которые мы проверяли. С другой стороны, задача была выполнена, при запуске Embox возникали устройства /dev/ttyS0 и /dev/ttyS1, которые были привязаны к аппаратным портам UART-ов с номерам заданными в конфигурации.

Пример конфига

@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)

Таймеры и контроллер прерываний

Следующим шагом при портировании обычно идёт поддержка таймера и контроллера прерываний. Для этих устройств драйвера основаны на другой библиотеке — CMSIS (Cortex Microcontroller Software Interface Standard). У нас они уже были и переделывать их не пришлось. Забегая вперёд, уточню, что пришлось реализовать функцию uint32_t HAL_GetTick(void) для того, чтобы STM32FCube работал корректно, ну и в инициализации ещё пришлось похимичить.

Минимальная инициализация для всех платформ очень похожа. По сути, необходимо вызвать SystemInit() и HAL_Init() из stm32cube и настроить клоки под конкретную платформу.

Для 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();
  }
}

Для 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 failedn");
  }

  /* activate the OverDrive to reach the 216 Mhz Frequency */
  if(HAL_PWREx_EnableOverDrive() != HAL_OK)
  {
    printf(">>> SystemClock_Config failedn");
  }

  /* 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 failedn");

  }
}

Для 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();
  }
}

Собственно, после этого в системе работают прерывания и таймеры, а этого вполне достаточно для организации вытесняющей многозадачности. Для того, чтобы почувствовать мощь многозадачности на данных микроконтроллерах, достаточно запустить на них серверы http и telnet, но для этого необходимо реализовать драйвер сетевой карты. Тут ситуация похожа на UART в том смысле, что есть общий драйвер и есть отдельная конфигурация для конкретной платформы, которая настраивает ноги и тому подобное.

Ethernet

Начнем с конфигурации. У нас есть версии только для stm32f4 и stm32f7, так как у stm32f3 нет ethernet-контроллера.

Для 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);
}

Для 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();
}

На самом деле, напрашивается какой-нибудь генератор кода инициализации ethernet-контроллера, но пока руки до него не доходят.

Для реализации сетевого устройства в Embox достаточно заполнить несколько полей структуры struct net_driver:

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

Наша модель драйверов похожа на линуксовую, иногда вообще полностью совпадает с ней по интерфейсу. Поэтому не удивляйтесь, если увидите код, очень похожий на линукс, это было сделано сознательно, чтобы упростить разработку драйверов.

Инициализация

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);
}

Кроме этого необходима инициализация самой библиотеки

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 %dn", err);
    }
    if (stm32_eth_handler.State == HAL_ETH_STATE_READY) {
        log_error("STATE_READY sp %d duplex %dn", 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 %dn", err);
    }

    /* (#)Enable MAC and DMA transmission and reception: */
    HAL_ETH_Start(&stm32_eth_handler);
}

передача

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;
}

Для передачи мы выделяем несколько буферов для пакетов, и когда нам приходит пакет на отправку, мы копируем данные в уже выделенную область и уже потом вызываем функцию отправки из STM32CUBE

Приём пакетов происходит по прерыванию:

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;
}

Код функции приема

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;
}

Собственно, это и есть весь драйвер. После его реализации работают все сетевые утилиты, которые есть у нас в Embox. О применении можно почитать в наших предыдущих статьях (pjsip и httpd)

Кроме перечисленных драйверов на базе stm32cube реализованы также spi, i2c, lcd, accelerator, gyroscope и прочие.

P.S. Наш доклад приняли на OSDAY (23-24 мая в Москве), если интересно послушать вживую о проекте, милости просим.

Автор: abondarev

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js