Jump to content

    

Библиотеки для STM32L4 ( "STM32L4xx_StdPeriph_Driver" не бывает, к сожалению)

Recommended Posts

Ruslan1

Здравствуйте!

Заложил в одной поделке STM32L431, думал что легко на него спортирую с STM32F0, и имея опыт с STM32F4. Но проблема в том, что для STM32L4 не существует SPL библиотек, на которые мой код ссылается.

Кто как выкручивается в похожей ситуации? На что проще перейти, если привык к SPL ?

Выбираю между:

1) начать использовать libopencm3

2) пользоваться вручную проверенными на корректность этому STM32L4 микроконтроллеру функциями из "STM32А4xx_StdPeriph_Driver"

3) использовать библиотеки STM32Cube low-layer (LL).  Насколько я вижу, STM также предлагает "SPL2LL-Converter" для перевода старых исходников на новые рельсы, только что-то я не вижу кучи восторженных отзывов. Может, он просто работает и там нечего обсуждать.

 

Пока что склоняюсь к (3) и очень не нравится (2). Но может (1) лучше ?

Upd: есть интересный документ, AN5044: STM32 standard peripheral library to STM32Cube low-layer migration , там вроде красиво разжевано (не дочитал еще :)

Share this post


Link to post
Share on other sites

Arlleex

ИМХО, достаточно определиться с набором задействованной периферии в STM32L431, найти ближайший похожий контроллер в серии F4 (например).

Затем найти в RM отличия между устройством и регистрами нужной Вам периферии в двух этих МК. Она часто бывает тупо одинаковой.

Если она одинаковая - считай повезло, используешь SPL от F4. Если не сильно отличается - допиливаешь по мелочи.

Если отличается кардинально, то проще самому все железо поднять постепенно. Это будет всяко надежнее (если руки из нужного места), чем все эти кубы.

Share this post


Link to post
Share on other sites

Ruslan1
15 hours ago, Arlleex said:

ИМХО, достаточно определиться с набором задействованной периферии в STM32L431, найти ближайший похожий контроллер в серии F4 (например).

Затем найти в RM отличия между устройством и регистрами нужной Вам периферии в двух этих МК. Она часто бывает тупо одинаковой.

Если она одинаковая - считай повезло, используешь SPL от F4. Если не сильно отличается - допиливаешь по мелочи.

Если отличается кардинально, то проще самому все железо поднять постепенно. Это будет всяко надежнее (если руки из нужного места), чем все эти кубы.

 

Я тоже так думал, останавливает стратегическая слабость этого подхода. Например, завтра перейду на еще более другой МК от STM, опять все сначала? С этой точки зрения "Cube low-layer" очень заманчиво выглядит. Да и экономия времени сомнительная, и вероятность сделать новые ошибки не меньше, чем если LL от STM использовать.

Подчеркиваю, я не про HAL, я про low-layer - сами STM именно его рекомендуют как прямую замену SPL.  

Кстати, посмотрел код этих Low Level библиотек - вполне похож на SPL, такое ощущение что с него и ваяли. Например, ниже привожу кусок из "stm32l4xx_ll_spi.c": все внятно и прозрачно, ассерты отключаемые.

 

/**
  * @brief  Initialize the SPI registers according to the specified parameters in SPI_InitStruct.
  * @note   As some bits in SPI configuration registers can only be written when the SPI is disabled (SPI_CR1_SPE bit =0),
  *         SPI peripheral should be in disabled state prior calling this function. Otherwise, ERROR result will be returned.
  * @param  SPIx SPI Instance
  * @param  SPI_InitStruct pointer to a @ref LL_SPI_InitTypeDef structure
  * @retval An ErrorStatus enumeration value. (Return always SUCCESS)
  */
ErrorStatus LL_SPI_Init(SPI_TypeDef *SPIx, LL_SPI_InitTypeDef *SPI_InitStruct)
{
  ErrorStatus status = ERROR;

  /* Check the SPI Instance SPIx*/
  assert_param(IS_SPI_ALL_INSTANCE(SPIx));

  /* Check the SPI parameters from SPI_InitStruct*/
  assert_param(IS_LL_SPI_TRANSFER_DIRECTION(SPI_InitStruct->TransferDirection));
  assert_param(IS_LL_SPI_MODE(SPI_InitStruct->Mode));
  assert_param(IS_LL_SPI_DATAWIDTH(SPI_InitStruct->DataWidth));
  assert_param(IS_LL_SPI_POLARITY(SPI_InitStruct->ClockPolarity));
  assert_param(IS_LL_SPI_PHASE(SPI_InitStruct->ClockPhase));
  assert_param(IS_LL_SPI_NSS(SPI_InitStruct->NSS));
  assert_param(IS_LL_SPI_BAUDRATE(SPI_InitStruct->BaudRate));
  assert_param(IS_LL_SPI_BITORDER(SPI_InitStruct->BitOrder));
  assert_param(IS_LL_SPI_CRCCALCULATION(SPI_InitStruct->CRCCalculation));

  if (LL_SPI_IsEnabled(SPIx) == 0x00000000U)
  {
    /*---------------------------- SPIx CR1 Configuration ------------------------
     * Configure SPIx CR1 with parameters:
     * - TransferDirection:  SPI_CR1_BIDIMODE, SPI_CR1_BIDIOE and SPI_CR1_RXONLY bits
     * - Master/Slave Mode:  SPI_CR1_MSTR bit
     * - ClockPolarity:      SPI_CR1_CPOL bit
     * - ClockPhase:         SPI_CR1_CPHA bit
     * - NSS management:     SPI_CR1_SSM bit
     * - BaudRate prescaler: SPI_CR1_BR[2:0] bits
     * - BitOrder:           SPI_CR1_LSBFIRST bit
     * - CRCCalculation:     SPI_CR1_CRCEN bit
     */
    MODIFY_REG(SPIx->CR1,
               SPI_CR1_CLEAR_MASK,
               SPI_InitStruct->TransferDirection | SPI_InitStruct->Mode |
               SPI_InitStruct->ClockPolarity | SPI_InitStruct->ClockPhase |
               SPI_InitStruct->NSS | SPI_InitStruct->BaudRate |
               SPI_InitStruct->BitOrder | SPI_InitStruct->CRCCalculation);

    /*---------------------------- SPIx CR2 Configuration ------------------------
     * Configure SPIx CR2 with parameters:
     * - DataWidth:          DS[3:0] bits
     * - NSS management:     SSOE bit
     */
    MODIFY_REG(SPIx->CR2,
               SPI_CR2_DS | SPI_CR2_SSOE,
               SPI_InitStruct->DataWidth | (SPI_InitStruct->NSS >> 16U));

    /*---------------------------- SPIx CRCPR Configuration ----------------------
     * Configure SPIx CRCPR with parameters:
     * - CRCPoly:            CRCPOLY[15:0] bits
     */
    if (SPI_InitStruct->CRCCalculation == LL_SPI_CRCCALCULATION_ENABLE)
    {
      assert_param(IS_LL_SPI_CRC_POLYNOMIAL(SPI_InitStruct->CRCPoly));
      LL_SPI_SetCRCPolynomial(SPIx, SPI_InitStruct->CRCPoly);
    }
    status = SUCCESS;
  }

  return status;
}

Upd:

Выглядит хорошо, думаю вполне уже пора переходить на LL из Куба.

Минус: нужно качать и ставить полный Куб для семейства, и из него уже вытаскивать нужные директории. У меня это "STM32L4xx_HAL_Driver" - там вместе лежат исходники для LL и для HAL. Вместе занимают 10 Мегабайт, плюс 135 мегабайт хелпа.

Второй минус из замеченных:  вложенные в актуальный HAL библиотеки CMSIS более старые, чем я скачивал из других источников, так что нужно брать для пользования именно директорию "**_HAL_Driver", а не все что там есть.

Share this post


Link to post
Share on other sites

Eddy_Em
35 minutes ago, Ruslan1 said:

Выглядит хорошо

Уродливо выглядит: уйма ненужного оверхеда!

Пишите по-человечески: либо делайте на С++ обертки на шаблонах, чтобы избавиться от оверхеда, либо на С напрямую на регистрах.

Как вариант, можно перейти на opencm3, если не смущает, что авторы по воле левой пятки могут апи поломать. В отличие от индусокода от ST, opencm3 писали более-менее вменяемые люди!

Share this post


Link to post
Share on other sites

Ruslan1
45 minutes ago, Eddy_Em said:

Уродливо выглядит: уйма ненужного оверхеда!

Что именно в приведенном примере является "ненужным оверхедом"? 

 

 

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

Принципиальный вопрос, что меньше проблем создаст: libopencm3 или LL от STM.

Смотрел вчера опять на libopencm3, смущает непрозрачность кода. То есть там не "все файлы этого МК в этой директории", а перекрестные ссылки: что-то в папке "common", причем несколько версий (выбирается в зависимости от семейства), что-то в папке, посвященной семейству. Занимает время продраться сквозь, если нужно понять какой из кусков кода реально будет скомпилирован. Можно, конечно, однажды удалить все неиспользуемые, но это не так просто как было с SPL и как сейчас с Cube-LL. Понимаю, что можно просто скомпилировать и не знать из чего оно собралось, но этот путь джедая мне не нужен.

 

Upd: вот кусок кода из libopencm3 посвященный этому же МК, тот же SPI инит:

/*---------------------------------------------------------------------------*/
/** @brief Configure the SPI as Master.

The SPI peripheral is configured as a master with communication parameters
baudrate, frame format lsb/msb first, clock polarity and phase. The SPI 
enable, CRC enable, CRC next CRC length controls are not affected.
These must be controlled separately.

@param[in] spi Unsigned int32. SPI peripheral identifier @ref spi_reg_base.
@param[in] br Unsigned int32. Baudrate @ref spi_baudrate.
@param[in] cpol Unsigned int32. Clock polarity @ref spi_cpol.
@param[in] cpha Unsigned int32. Clock Phase @ref spi_cpha.
@param[in] lsbfirst Unsigned int32. Frame format lsb/msb first @ref
spi_lsbfirst.
@returns int. Error code.
*/

int spi_init_master(uint32_t spi, uint32_t br, uint32_t cpol, uint32_t cpha,
		    uint32_t lsbfirst)
{
	uint32_t reg32 = SPI_CR1(spi);

	/* Reset all bits omitting SPE, CRCEN, CRCNEXT and CRCL bits. */
	reg32 &= SPI_CR1_SPE | SPI_CR1_CRCEN | SPI_CR1_CRCNEXT | SPI_CR1_CRCL;

	reg32 |= SPI_CR1_MSTR;	/* Configure SPI as master. */

	reg32 |= br;		/* Set baud rate bits. */
	reg32 |= cpol;		/* Set CPOL value. */
	reg32 |= cpha;		/* Set CPHA value. */
	reg32 |= lsbfirst;	/* Set frame format (LSB- or MSB-first). */

	SPI_CR2(spi) |= SPI_CR2_SSOE; /* common case */
	SPI_CR1(spi) = reg32;

	return 0; /* TODO */
}

Я понимаю, что про CRC они просто забыли, а return code просто стыдливо обозначили "TODO", но как-то этот вариант мне меньше нравится, чем написанное "индусами в STM".

Edited by Ruslan1
добавил код из libopencm3

Share this post


Link to post
Share on other sites

Integro
15 hours ago, Ruslan1 said:

Выбираю между:

1) начать использовать libopencm3

2) пользоваться вручную проверенными на корректность этому STM32L4 микроконтроллеру функциями из "STM32А4xx_StdPeriph_Driver"

3) использовать библиотеки STM32Cube low-layer (LL).  Насколько я вижу, STM также предлагает "SPL2LL-Converter" для перевода старых исходников на новые рельсы, только что-то я не вижу кучи восторженных отзывов. Может, он просто работает и там нечего обсуждать.

Пользуюсь HAL и LL(относительно свежий) c момента их выхода, по начало нужно было все перепроверять и сверять с datasheet'ом, можно было найти ошибки, сейчас таких проблем нет.

 

 

24 minutes ago, Eddy_Em said:

Уродливо выглядит: уйма ненужного оверхеда!

Бред! оверхер на что? 
- FLASH? чтение - модификация - запись пары регистров - тоже самое написали бы и Вы в своем коде со своими "флагами" и своими ошибками.
- RAM? В LL не используется контекст только в HAL да и эти 20 байт контекста на фоне даже 64KB выглядят не серьезно.
- Perfomance? если речь о LL тоже сомнительно, для HAL возможно, но сегодня проще\дешевле взять более производительный контроллер чем вылизывать каждую строчку\инструкцию кода

Share this post


Link to post
Share on other sites

Ruslan1

вот тут  еще в 2016 написано то, до чего и я дошел сейчас. Перевод гугла может и корявый иногда, но смысл ясен:

Quote

В какой-то момент ST планировал полностью исключить  Стандартную периферийную библиотеку и идти с HAL до конца. К счастью, они вернулись из этого и создали « Библиотеку низкого уровня». Эта вещь намного лучше (хотя она может определенно использовать улучшения). Это довольно близко к тому, как  работала Стандартная периферийная библиотека, но, на мой взгляд, она чище и определенно приводит к меньшему, более оптимизированному коду. Одна из причин заключается в том, что вы можете отключить «полную» версию этой библиотеки, которая покончит с более сложными функциями (подумайте о том, как GPIO настраивается со структурой), и можете просто использовать то, что в основном является простыми оболочками CMSIS в Форма встроенных функций для выполнения этих задач. Это значительно облегчает проверку правильности всего этого.

Короче говоря, библиотека низкого уровня небольшая, чистая и, кроме того, ее легко построить с помощью Makefile, так как вы можете просто собрать библиотеку со всеми именованными файлами * _ll _ *. C и связать ее в своем проекте так же, как Standard Peripherals Library раньше работал. Это также означает, что любые пользовательские файлы компоновщика хорошо переносятся. Я перенес проект разумного размера, который использовал Выходную библиотеку стандартной периферии, в новую библиотеку низкого уровня за выходные. Будем надеяться, что улучшения будут продолжаться, и что это (или прямой путь CMSIS) станет стандартным способом программирования микроконтроллеров STM в более серьезных средах.

 

Share this post


Link to post
Share on other sites

Ruslan1

Вести с полей: в пятницу принял решение использовать LL. Решил попробовать сконвертировать исходники от STM32F0 (использовал SPL) в STM32L4 (буду использовать LL).

Коротко: утилитка- огонь, маст хэв. Не волшебная палочка, но уж точно лучше чем ковыряние исходников ручками для такого перехода  между либами и семействами.

 

Детали:

 

Утилита "spl2ll_converter.pl" сама по себе заняла много времени при запуске из-за необходимости иметь perl на машине. Пришлось регистрироваться и делать в онлайне сборку под себя  чтоб "Win32-Console-ANSI" работало: доступный для скачивания у них дистрибутив эту опцию не содержит (ну и  до кучи он под  Win10, а я раз уж все равно пересобираю, указал что под Win7 хочу).

После установки Перла утилитка заработала, все просто: указываю откуда и куда и запускаю. Так как вывод обильный и процесс долгий, то перенаправил в логфайл для разборок  и пошел пиво пить:

>perl spl2ll_converter.pl --fsrc=STM32F0 --fdst=STM32L4 --psrc=c:\Firmware --pdst=c:\Firmware2  >log_20200313.txt

 

Оно пыхтело долго, накропало 75 килобайт текста в лог файл, результат:

Statistics : All Files = 136 | Updated Files = 79 | Errors = 400 | Warnings = 106 | Migrated in 3354 sec

 

Прочесало все исходники (*.c и *.h файлы), во многих просто поменяла вызовы, сгенерировала туеву хучу предупреждений и ошибок где не справилась, с именем файла, строкой и описанием что именно "не шмогла"или не уверена что все норм. Например:

Quote

     Parsing "BSP/ADCint.c"
       [WARNING] -- Line 53 -- "GPIO_Init" Default constant LL_GPIO_AF_0 is set for Alternate field,
                                           To be manualy updated by user depending on the pin's configuration
       [WARNING] -- Line 66 -- "DMA_DIR_PeripheralSRC" When DMA_M2M field in the DMA_InitTypeDef structure is set to DMA_M2M_Enable LL_DMA_DIRECTION_MEMORY_TO_MEMORY
       [WARNING] -- Line 74 -- "DMA_M2M" DMA_M2M field is removed from available LL_DMA_InitTypeDef structure
       [ERROR] -- Line 77 -- "ADC_DMARequestModeConfig" function is not available for the target STM32 serie
       [ERROR] -- Line 88 -- "ADC_ClockModeConfig" function is not available for the target STM32 serie
       [ERROR] -- Line 105 -- "ADC_GetFlagStatus" function is not available for the target STM32 serie
       [WARNING] -- Line 114 -- "NVIC_Init" No LL API, StdPeriph legacy API is used instead (legacy library)

Результат в коде понятный, пример ниже (это нотация Гита: строки "-" удалены, строки "+" вставлены).

Осталось проверить все упомянутые в логе конвертера места и можно компилировать-тестировать.

*h файл:
 //LCD_E: PA12
-#define LCD_E_RCC_PeriphClockCmd()		{RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);}
+#define LCD_E_RCC_PeriphClockCmd()		{LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);}
 #define	LCD_E_PORT						GPIOA
-#define	LCD_E_PIN						GPIO_Pin_12
+#define	LCD_E_PIN						LL_GPIO_PIN_12
 #define LCD_E_SET()		{LCD_E_PORT->BSRR = LCD_E_PIN;}
 #define LCD_E_CLEAR()	{LCD_E_PORT->BRR = LCD_E_PIN;}
 //LCD_RS: PB2
-#define LCD_RS_RCC_PeriphClockCmd()		{RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);}
+#define LCD_RS_RCC_PeriphClockCmd()		{LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOB);}
 #define	LCD_RS_PORT						GPIOB

*.c файл:
 	// init GPIO
 	{// PWR_MAIN_ON
-		GPIO_InitTypeDef  GPIO_InitStructure;
+		LL_GPIO_InitTypeDef  GPIO_InitStructure;
 		PWR_MAIN_ON_RCC_PeriphClockCmd();
-		GPIO_InitStructure.GPIO_Pin = PWR_MAIN_ON_PIN;
-		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
-		GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
-		GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
-		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
-		GPIO_Init(PWR_MAIN_ON_PORT, &GPIO_InitStructure);
+		GPIO_InitStructure.Pin = PWR_MAIN_ON_PIN;
+		GPIO_InitStructure.Mode = LL_GPIO_MODE_OUTPUT;
+		GPIO_InitStructure.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
+		GPIO_InitStructure.Pull = LL_GPIO_PULL_NO;
+		GPIO_InitStructure.Speed = LL_GPIO_SPEED_FREQ_LOW;
+		GPIO_InitStructure.Alternate = LL_GPIO_AF_0;
+		LL_GPIO_Init(PWR_MAIN_ON_PORT, &GPIO_InitStructure);
 	}
.............
 	{// TX DMA init
-		DMA_InitTypeDef dma;
-	    USART_DMACmd(RPICOMM_UART_PORT, USART_DMAReq_Tx, DISABLE);
+		LL_DMA_InitTypeDef dma;
+	    LL_USART_DisableDMAReq_TX(RPICOMM_UART_PORT);
     	DMA_Cmd(RPICOMM_TX_DMAChannel, DISABLE);
     	DMA_DeInit(RPICOMM_TX_DMAChannel);
-		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
-		RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
-		DMA_StructInit(&dma);
-		dma.DMA_PeripheralBaseAddr = (uint32_t)&(RPICOMM_UART_PORT->TDR);
-		dma.DMA_MemoryBaseAddr = NULL;	//it's updated in the task
-		dma.DMA_DIR = DMA_DIR_PeripheralDST;
-		dma.DMA_BufferSize = 1;	//it's updated in the task
-		dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
-		dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
-		dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
-		dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
-		dma.DMA_Mode = DMA_Mode_Normal;
-		dma.DMA_Priority = DMA_Priority_Low;
+		LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
+		LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
+		LL_DMA_StructInit(&dma);
+		dma.PeriphOrM2MSrcAddress = (uint32_t)&(RPICOMM_UART_PORT->TDR);
+		dma.MemoryOrM2MDstAddress = NULL;	//it's updated in the task
+		dma.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
+		dma.NbData = 1;	//it's updated in the task
+		dma.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
+		dma.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
+		dma.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
+		dma.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
+		dma.Mode = LL_DMA_MODE_NORMAL;
+		dma.Priority = LL_DMA_PRIORITY_LOW;
 		DMA_Init(RPICOMM_TX_DMAChannel, &dma);
 		RPICOMM_TX_DMA_REMAP;
 	}

 

Share this post


Link to post
Share on other sites

Ruslan1

Ну, в-общем, LL вполне годные библиотеки, можно юзать.

Еще раз убедился, что HAL не для меня- там действительно  наворочено много, без поллитра не разберешься- в исходнике МНОГО зависимостей, через которые глазами собственно исполняемый код сложно вычленить чтоб понять что же они наворотили.

Автоматический конвертер этот: да, работает, но иногда ошибается. Например, мне в итоговый текст повставлял "LL_AHB1_GRP1_PERIPH_GPIOC", которого в принципе не существует в дефайнах семейства STM32L4: реально бывает только "LL_AHB2_GRP1_PERIPH_GPIOC"  (то есть клоки GPIO управляются через регистр AHB2). Но такие ошибки легко находятся, оно просто не компилируется.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.