Всем привет! В данной статье я бы хотел поделиться опытом создания драйверов для платформ серии 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 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");

  }
}


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


передача
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 мая в Москве), если интересно послушать вживую о проекте, милости просим.
Поделиться с друзьями
-->

Комментарии (33)


  1. Indemsys
    17.05.2017 12:34

    Индивидуальным разработчикам наличие мультиплатформенности в исходниках поддержки железа только мешает разобраться в коде и вынуждает дополнительно изучать некий чужой HAL.

    Индивидуал не меняет платформы каждую неделю и не его проблемы, что разработчикам набора платформ нужен HAL чтобы быстрее переключаться с платформы на платформу.
    К примеру я всегда избавляюсь от HAL-ов сторонних разработчиков. Они всегда хуже документированы и не настолько гибкие, как непосредственно работа с регистрами периферии.

    Еще несколько странно выглядят отсылки к линуксу.
    Почему подражание программному интерфейсу настольной системы может быль каким-то плюсом для малой RTOS?


    1. abondarev
      17.05.2017 13:13
      +5

      Спасибо за вопрос!

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

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

      По поводу отсылки к Линуксу. Я буду выступать на OSDAY и как раз коснусь этой темы. Если кратко, то у Линукса, на сегодняшний день, самая большая и прилично отлаженная (в популярных приложениях и библиотеках) кодовая база. Если Вам требуется помигать светодиодом, то достаточно и работы с регистрами напрямую, но, если Вам нужны какие нибудь общепринятые штуки, типа http сервера, то лучше взять готовую стороннюю библиотеку причем желательно изменения по адаптации внести в основную ветку разработки. Конечно есть вариант взять какую нибудь популярную ОСРВ, тот же FreeRTOS и ориентироваться на его API, но мне кажется это может оказаться ошибочным если проект умрет, и точно более накладным, поскольку количество приложений сильно меньше.


      1. Indemsys
        17.05.2017 13:49
        -5

        Важно не то где разработчик находится, а в какую организационную схему он встроен.
        И тут разница и проявляется.

        У вас может быть архитектор сверху, кодер снизу и хардварщик сбоку. А может это все помещаться в вас одном.
        И мне кажется ардуинщики, т.е. самая массовая аудитория — это люди где все в одном.

        И только кажется, что платформы меняются быстро. Два три года всегда есть, прежде чем придумывают действительно что-то новое и стоящее.

        Это все к тому что HAL придуман производителями чтобы быстрее плодить в своих недрах демки к своим чипам.
        А конечный разработчик ориентирован на манул к чипу, а не на представление неких сторонних программистов как надо работать с аппаратурой.
        Тут я вижу принципиальный конфликт интересов.


        1. abondarev
          17.05.2017 14:43
          +2

          Тут я вижу принципиальный конфликт интересов.

          Как Вы жестко и даже принципиально:)

          Мне кажется, что Вы противоречите себе.
          Во первых, если разрабочик делает все, то ему тем более стоит пользоваться готовыми частями. Те же упомянутые ардуинщики, пользуются ардуино студио, в которой есть огромное количество готовых библиотек.
          Во вторых, 2-3 года, которые есть на освоение чипа, на мой взгляд недостаточно, чтобы досконально изучить его потроха ориентируясь исключительно на мануал к чипу. Точнее достаточно, но на функционал уже времени не останется.

          Мне кажется, наше с Вами непонимание в другом!
          А именно, как я уже сказал, что если нужна мигалка светодиодов, то конечно всего перечисленного в статье не требуется. В этом случае следует взять имеющийся в stm32cube, или в другой поддерживаемой производителем или сообществом систему, пример и быстро сваять устройство. Я же говорю о довольно функциональных системах, которые написать с нуля, наверное можно, но непонятно зачем и стоить это будет ну очень много, ведь в этом случае разработчику прийдется изачать не только чип, но и сетевые технологии, системное программирование, файловые системы и еще ворох специализированных задач.


          1. Indemsys
            17.05.2017 19:48
            +1

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

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

            Я скажем доволен, что в своем фреймворке мне удалось RTOS MQX практически очистить от всяких абстракций и перевести на прямую работу с регистрами. Остались только нативные драйвера SD, USB и Ethernet.
            Без HAL я могу быстро реализовывать и легко отлаживать до десятка задач с жестким реальным временем. Исходники получаются прозрачными и чистыми. Нет никаких длиннющих макросов. Нет сбивающего с толку переименовывания обращений к регистрам. Взяв любое имя регистра в текстах с легкостью можно найти его в мануале, не гуляя по дебрям HAL в попытках понять что есть что.
            Также без HAL легче централизовать всю информацию о периферии в одном файле и т.д. и т.п.


            1. abondarev
              17.05.2017 21:29
              +2

              Так, стоп!
              О чем мы дискутируем?

              Я конечно смешиваю все в кучу, но:
              Во первых: Почему же Вы оставили драйвера SD, USB и Ethernet? И зачем Вы используете MQX? Ведь сами же сказали, что Вы относитесь к «индивидуалам» которые делают все и прикладное ПО пишут, и драйвера по даташитам, и многое другое как я понял!
              Во вторых: Я так понял Вы не читали статью, поскольку почему то все время критикуете STMicroelectronics, за навязывание своего HAL. А в статье как раз написано, как мы ушли от этого HAL-а. И сейчас на этой серии микроконтроллеров имеем независимый от оборудования интерфейс. То есть, программист может использовать обычные вызовы socket(), listen(), sendto() которые будут работать одинаково на всех платформах.

              Получается, данную дискуссию Вы затеяли, чтобы доказать, что все платформы кроме Вашей (я не сомневаюсь, что она прекрасна), неправильны!

              Давайте сойдемся на том, что я принял Ваше мнение, к сведению. И при случае посмотрю на микроконтроллеры LPX и прилагаемую к нему ОСРВ MQX!


              1. Indemsys
                17.05.2017 21:50
                -1

                Я открываю ваши тексты и вижу кругом функции и макросы от HAL предоставляемого ST.
                И этого следует что вы делаете еще одну надстройку поверх HAL.

                Функции socket(), listen(), sendto() это прикладной уровень. Если в вашем стеке TCP их раньше не было, то это проблема вашего стека.
                А скажем в MQX, uCOS они всегда были.
                К HAL т.е. к уровню аппаратной абстракции или платформо-независимости эти функции отношения не имеют. Это все равно что назвать платформо-независимой функцию printf

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

                Просто я не согласен в одной мелкой детали во все этой истории, а именно с вашей презентацией полезности некоей кроссплатформенности построенной на концепции HAL от ST.
                Это на мой взгляд пустая трата времени.
                В вашей организации она нужна для реюзинга, а индивидуалам она вредна.

                Мой подход, кстати, не уникален, я его подхватил у таких известных проектов как uCOS и Keil RTX. Минимализм и пропуск абстракции аппаратуры сильно экономит время.


                1. abondarev
                  17.05.2017 22:21

                  В вашей организации она нужна для реюзинга, а индивидуалам она вредна.

                  В этом случае, я Вас попрошу не использовать наш проект. Конечно нужно использовать, только те средства которые полезны!


  1. GarryC
    17.05.2017 13:29

    /* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
    /* copy received frame to pbuf chain */
    Лично для меня этих двух комментариев достаточно, что не смотреть дальше. Может, в Linux так принято, но в embedded так делать не то что нельзя, но крайне нежелательно.


    1. abondarev
      17.05.2017 13:42
      +2

      Ну поскольку у нас используется не lwip, то скорее всего комментарии остались от какого-то примера из куба, сам код конечно адаптирован.
      Но если честно не очень понимаю, что тут не нравится?
      То есть, все равно должны выделить память под пакет, если Вы имеете в виду, что можно было бы использовать ту память где уже лежит принятый пакет, то это сильно ограничит функциональность, то есть необходимо как можно быстрее освободить место куда указывает дескриптор, ведь обработка пакета занимает приличное время, а нужно иметь возможность принимать следующие пакеты. Или я не прав?


      1. GarryC
        17.05.2017 17:36

        Да, я именно это имел в виду, в хорошо организованном стеке данные идут по нему, физически не перемещаясь, что избавляет от дикого оверхеда, особенно в случае маленьких пакетов. А принимать очередные пакеты можно в свободные слоты, и для встроенных систем важно уметь определить необходимое количество слотов для работы без потерь и зарезервировать их заранее. Единственное место, откуда информацию действительно необходимо как можно скорее вытащить — это буферная память периферийного узла, но и то только в том случае, если у Вас нет развитой DMA для работы с этим узлом.


        1. grossws
          17.05.2017 17:56

          А skb_malloc в lwip не использует какой-нибудь предаллоцированный slab? Хотя здесь ещё всякие memcpy лишние при нормальном zero-copy.


          1. abondarev
            17.05.2017 18:05

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


        1. abondarev
          17.05.2017 18:03

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


          1. GarryC
            17.05.2017 18:19
            +1

            Ну истина всегда конкретна. Когда я делал один девайс, сначала использовал именно 1wip, потом был вынужден с него уйти (в силу принципиальной длины окна в один пакет) и нашел эту реализацию с продвижением данных по стеку, получилось почти на 20% быстрее, может быть, потому, что делал на Luminary, а у них memcpy была не DMA (у него DMA вообще не было), может, у Вас прирост и будет не столь заметен.


            1. abondarev
              17.05.2017 18:34

              Тут сложно не согласиться, при таком ограничении в один пакет, копирование будет лишним.
              По поводу производительности, мы делали замеры и у нас получалось на 10% хуже линуксового стека, на той же железке. Хотя нужно отметить, что это был довольно большой ARM, конкретная железка и, конечно, полный стек.

              Я рад, что понял, а то первый Ваш комментарий меня немного напугал:)


  1. denis_obrezkov
    17.05.2017 21:46

    А вы смотрели в сторону TMS570 и HalCoGen от TI?


    1. abondarev
      17.05.2017 21:48

      Пока нет.
      TMS570 менее популярный чем STM32, но если есть сильный интерес, то можем посмотреть:)


  1. ukt
    17.05.2017 23:24

    Подскажите, чем отличается EmBox от той же FreeRTOS, кроме архитектуры?
    Вы же пилите своё не ради пиления, а с какой то конкретной целью?

    Ещё вопрос, чем именно драйвер Cortex-M3 отличается от Cortex-M4, например, USART?

    Может совсем наглость с моей стороны, а можно какие нибудь примеры для ДискоФ4?


  1. abondarev
    17.05.2017 23:58

    Подскажите, чем отличается EmBox от той же FreeRTOS, кроме архитектуры?

    В двух словах трудно ответить, но например, что мы позволяем представить окружение на STM32 как будто это маленький линукс, то есть там можно запускать утилиты из линукс (конечно если собрать их в Embox), похожая командная строка, POSIX интерфейс, при этом сохраняя легковесность как в FreeRTOS. Пример, VoIP телефон на базеSTM32 и библиотеки pjsip. Это можно наверное сделать и на FreeRTOS, но потребует существенно больших усилий.

    Ещё вопрос, чем именно драйвер Cortex-M3 отличается от Cortex-M4, например, USART?
    В нашем случае ничем, нужно в конфигурации задать какая плата.

    Может совсем наглость с моей стороны, а можно какие нибудь примеры для ДискоФ4?

    Более точно можно плату? В принципе у нас есть базовый пример для stm32f4-discovery. А наши конфиги очень просто переделываются под другие платы. Если возникнут трудности, будем рады помочь.


    1. ukt
      18.05.2017 00:21

      то есть там можно запускать утилиты из линукс (конечно если собрать их в Embox)

      Где они храниться будут? Можно чуть больше примеров?
      У стм есть CircleOs, приложения можно переключать, а у вас как?
      В принципе у нас есть базовый пример для stm32f4-discovery.

      Это она и есть, т.е. для нее нужны примеры.


      1. abondarev
        18.05.2017 08:43

        Где они храниться будут?

        Не очень понял вопроса, но предположим в образе который получиться. Вы собираете Embox в требуемой конфигурации. Для Вас наверное подойдет arm/stm32f4cube (то есть для конфигурации наберите make confload-arm/stm32f4cube). В конфигурационном файле ./conf/mods.config можно задавать список и параметры всех модулей, в том числе список утилит. Например, include embox.cmd.fs.ls добавит утилиту ls (почти стандартную) в полученный в результате образ. Утилиты пишутся стандартным способом, то есть присуствует main() и можно использовать стандартную библиотеку, в том числе pthread, если нужна конечно.
        В этом же конфиге можно настроить и работу планировщика и других частей. Например, в приведенном конфиге есть строчка
        include embox.kernel.thread.core(thread_stack_size=5120,thread_pool_size=4)

        Она задает количество потоков со стеком (вытесняющая многозадачность) и размер стека для каждого потока

        Можно чуть больше примеров?

        В конфигурации описанной чуть выше есть httpd, пример как мы его использовали вот

        У стм есть CircleOs, приложения можно переключать, а у вас как?

        Если я правильно понял, что это такое, то у нас гораздо серьезнее. В принципе можно сделать похожим образом, правильно настроив конфигурацию, но по большому счету у нас есть полноценное управление процессами и потоками. То есть приложение разработанное для Линукса, может работать у нас, без изменений, естественно с огладкой на ограничения аппаратуры.


  1. denis_obrezkov
    18.05.2017 01:45

    Раз уж на то пошло, то в чем цель Embox? Есть ведь RTEMS, eCos, ChibiOS/RT и десятки других. В чем её основное отличие от всех остальных кроме поддержки отечественного железа?


    1. abondarev
      18.05.2017 08:54

      кроме поддержки отечественного железа?

      Такого отличия не припомню:)
      Нет, у нас есть поддержка некоторых специализированных отечественных комплексов и устройств, но насколько я знаю, ее нет в открытом доступе.
      Мы любим играться с отечественными процессорами и микроконтроллерами, но это тоже не может служить нашей отличительной чертой.

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


      1. denis_obrezkov
        18.05.2017 12:34

        Можете еще ответить, а что значит заметка на википедии, что у Embox экзоядро? Действительно это так?


        1. abondarev
          18.05.2017 12:56

          Это сложный вопрос.
          Экзоядро подразумевает, что само ядро контроллирует только обращение к ресурсам, то есть при загрузке какого то ресурса куда то, идет проверка, после этого процесс который запросил ресурс использует его как хочет. Конечно это требует существенно меньше времени чем микроядро, в котором нужно постоянно переключаться.
          На, «нулевом» этапе Embox предназначался для отладки оборудования и был наверное очень близок к экзоядру. Но с развитием проекта, он стал скорее конфигурируемым. То есть, все еще можно создать конфигурацию, при которой он будет экзоядром, мы пользуемся этим на ранних этапах портирования и в некоторых особенно маленьких платформах. Но Embox может быть сконфигурирован и как микроядро, и как монолитное и еще много различные гибридных вариантов, заточенных под задачу!


    1. abondarev
      18.05.2017 13:16

      Немного подробнее о целях Embox.

      Изначально это был маленький загрузчик для отладки аппаратуры. Помогали разрабочикам ПЛИС. В проекте учавствовали несколько студентов, и чтобы им было нескучно, решили расширить функциональность и разработать ОС. Довольно быстро поняли, что писать вытесняющие планировщики распределители памяти и тд не очень эффективно, нужно реальное применение этих частей. Первым стали роботы лего ну и вообще всякие любительские и не очень роботы.
      Дальше пришло осознание, что без сетевого стека и полезных библиотек ОС никому не нужна, в том числе нам самим. Стали наращивать функциональность.
      Потом поняли, что не успеваем все поддерживать и придумали как внедрять Линукс приложения, прямо целиком проекты с исходниками. То же самое с драйверами это как раз и описано в данной статье.

      Подводя итог:
      На сегодняшний день Embox имеет существенно более простые средства разработки и отладки систем по сравнению с приведенными Вами ОСРВ. Поскольку можно использовать мощный потенциал Линукса и не нужно лазить в регистры (вообще забота об аппаратуре сильно упрощается). Перед большими системами (Линукс и ко) у Embox есть другие приимущества.


  1. AVI-crak
    18.05.2017 03:54
    +1

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

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

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


    1. abondarev
      18.05.2017 13:28

      Если честно не понял суть вопроса, но постараюсь прокоментировать понятные части.

      Проблема перехода на новый чип отвалится сама собой при чётком разделении железного уровня и программного.
      В ситуации с HAL — буквально каждый второй пишет программный код в тесной интеграции с железным уровнем.
      Вместо стандартного стандартного обращения через инлайн функцию — включает полный код в тело программного уровня. Где этот кусок теряется под множеством строк проекта, без контекстных ссылок и описания в шапке.
      Так проблема исчезает или все таки каждый второй пишет программный код в тесной интеграции с железным уровнем?
      На самом деле, статья о нашем подходе к пути решения проблемы написания самописного кода. То есть, у нас используется код от производителя, который поддерживается и там будут фикситься баги, Мы же будем брать новые версии, что существенно проще.

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

      Насчёт универсальных конфигов — это пройденный этап, и он тупиковый. Подходит для быстрого старта — не более.
      Во первых — написать новый конфиг дело десяти минут.
      Во вторых — свой конфиг может быть каким угодно: хоть с отладкой в памяти, хоть с размещением части кода на внешней флешке, а конкретно для М7 — несколько вариантов использования TCM памяти.
      Можете предложить лучший вариант?
      На самом деле мы себе льстим, что частично решили проблему и наш конфиг позволяет сделать не только быстрый старт, но и довольно хорошее развитие.
      По поводу M7 можете поподробнее на счет настройки. Интерес меркантильный, она у нас работает, но есть вопросы как раз с памятью:)


  1. AVI-crak
    18.05.2017 16:31

    Для отладки в памяти необходим особо урезанный код на ассемблере. Для того чтобы сделать его универсальным — нужно загружать _estack не из кода флеша, а по ссылке на первый адрес рам памяти. Естественно для флеша стоит указать минимальный размер рама, а потом в принудительном порядке переписать. Работает на всех чипах st, от M0, L0, до M7.

    _estack
    Reset_Handler
    Читать содержимое первой ячейки рам памяти, писать в Vector table offset register.
    Переход на рам память (нулевой адрес +4).

    Для самой ID все настройки линкера и оболочки забиваются на рам память, примеры есть даже у самой st. Разве что имеет смысл удалить двойное копирование, и инициализацию от самого GCC.

    Для использования TCM в М7 есть два варианта, нативное и противное.
    Примеры от st запутаны и непонятны даже для меня. Суть в том что ITCM (от 0x0) память программ может быть самостоятельной областью с нулевым доступом, и областью с автоматическим перекрытием флеша (аналог раздутого L1 кеша).
    В случае самостоятельной области памяти — возникают определённые трудности с размещением в ней таблицы векторов, но зато есть возможность разместить куски собственного критичного ко времени выполнения кода.
    Трудность заключается в написании бута предварительной загрузки, для него и основной программы придётся писать разные конфиги линкера.

    Второй вариант — не слишком красив, но зато работает с пол пинка. Смысл в том что ITCM область предварительно заполняется копией флеша (адреса не важны, главное объём) — а после запускается репликация. При этом в ITCM область будет автоматически копироваться наиболее используемые куски кода, причём не только из собственного флеша, но из внешнего то-же. Управлять процессом не получится, и от этого становится грустно. Разница в скорости выполнения кода будет зависить от фазы луны и магнитных бурь на Марсе.

    И наверное самое главное, внешняя sdram и spi флеш память. Тут можно составить куклу для самостоятельной настройки, потому как универсальных вариантов просто не существует. Пользователь может возжелать использовать раздельную компиляцию самостоятельных программ, с размещением их на sd или spi флешке. Это естественный шаг, когда всё остальные варианты уже не умещаются в самом чипе. Чаще всего это готовые модули из мира больших машин. Переписывать их с нуля — нет смысла, а вот пользоваться — одно удовольствие.
    Почти всегда это либо игрушка на Си, либо программа управления чем-то большим и сложным.
    В любом случае предлагать собственное решение для подобных случаев — не слишком хорошая идея. Пользователь вместо поиска оптимального решения, начнёт менять условия задачи.

    Уровень железа отделяется от программного использованием универсальных инлайн функций. Для программного уровня остаются постоянными названия инлайн функций, перечисление аргументов и свойства аргументов. Для железного уровня каждый раз меняется содержимое самой инлайн функции. Не важно что там будет внутри, даже если это ногодрыг — он должен выполнять изначально задуманную функцию.
    Инстал периферии отдельная песня, для каждого проекта своя — уникальная.


    1. abondarev
      18.05.2017 17:25

      Спасибо.
      Постараюсь разобраться!


  1. Dima_Sharihin
    19.05.2017 10:54
    +1

    Может у меня мало опыта, но не видел ничего хуже HAL от ST. Дичайший оверхед при урезании функционала периферии.
    Скомпилировал Hello World на STM32F0 с двумя USART'ами, каждый дескриптор USART_HandleTypeDef занимает по 122 байта драгоценнейшей оперативки. Зачем делать весь этот оверхед, когда ту же информацию можно прочесть из конфигурационных регистров периферии?


    1. abondarev
      19.05.2017 12:48

      Вы совершенно правы!
      Это наверное самая фиговая библиотека для BSP которую я видел, там действительно гиганский оверхед и куча маскросов в которых вообще нереально разобраться!
      Но, важно, что он поддерживается производителем и вряд-ли умрет, а самому выгребать баги во всех драйверах, убиться можно. Я верю, что обеспечить поддержку реально для UARTа и еще нескольких устройств, но для более сложных, сильно сомневаюсь.
      В общем мы пришли к такому подходу, только из-за того, что поддерживать все драйвера которые есть у нас, мы просто не в состоянии.