Ruslan1 0 Posted March 12, 2020 · Report post Здравствуйте! Заложил в одной поделке 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 , там вроде красиво разжевано (не дочитал еще :) Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...
Arlleex 1 Posted March 12, 2020 · Report post ИМХО, достаточно определиться с набором задействованной периферии в STM32L431, найти ближайший похожий контроллер в серии F4 (например). Затем найти в RM отличия между устройством и регистрами нужной Вам периферии в двух этих МК. Она часто бывает тупо одинаковой. Если она одинаковая - считай повезло, используешь SPL от F4. Если не сильно отличается - допиливаешь по мелочи. Если отличается кардинально, то проще самому все железо поднять постепенно. Это будет всяко надежнее (если руки из нужного места), чем все эти кубы. Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...
Ruslan1 0 Posted March 13, 2020 · Report post 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", а не все что там есть. Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...
Eddy_Em 0 Posted March 13, 2020 · Report post 35 minutes ago, Ruslan1 said: Выглядит хорошо Уродливо выглядит: уйма ненужного оверхеда! Пишите по-человечески: либо делайте на С++ обертки на шаблонах, чтобы избавиться от оверхеда, либо на С напрямую на регистрах. Как вариант, можно перейти на opencm3, если не смущает, что авторы по воле левой пятки могут апи поломать. В отличие от индусокода от ST, opencm3 писали более-менее вменяемые люди! Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...
Ruslan1 0 Posted March 13, 2020 (edited) · Report post 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 March 13, 2020 by Ruslan1 добавил код из libopencm3 Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...
Integro 0 Posted March 13, 2020 · Report post 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 возможно, но сегодня проще\дешевле взять более производительный контроллер чем вылизывать каждую строчку\инструкцию кода Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...
Ruslan1 0 Posted March 13, 2020 · Report post вот тут еще в 2016 написано то, до чего и я дошел сейчас. Перевод гугла может и корявый иногда, но смысл ясен: Quote В какой-то момент ST планировал полностью исключить Стандартную периферийную библиотеку и идти с HAL до конца. К счастью, они вернулись из этого и создали « Библиотеку низкого уровня». Эта вещь намного лучше (хотя она может определенно использовать улучшения). Это довольно близко к тому, как работала Стандартная периферийная библиотека, но, на мой взгляд, она чище и определенно приводит к меньшему, более оптимизированному коду. Одна из причин заключается в том, что вы можете отключить «полную» версию этой библиотеки, которая покончит с более сложными функциями (подумайте о том, как GPIO настраивается со структурой), и можете просто использовать то, что в основном является простыми оболочками CMSIS в Форма встроенных функций для выполнения этих задач. Это значительно облегчает проверку правильности всего этого. Короче говоря, библиотека низкого уровня небольшая, чистая и, кроме того, ее легко построить с помощью Makefile, так как вы можете просто собрать библиотеку со всеми именованными файлами * _ll _ *. C и связать ее в своем проекте так же, как Standard Peripherals Library раньше работал. Это также означает, что любые пользовательские файлы компоновщика хорошо переносятся. Я перенес проект разумного размера, который использовал Выходную библиотеку стандартной периферии, в новую библиотеку низкого уровня за выходные. Будем надеяться, что улучшения будут продолжаться, и что это (или прямой путь CMSIS) станет стандартным способом программирования микроконтроллеров STM в более серьезных средах. Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...
jenya7 0 Posted March 16, 2020 · Report post на своем STM32L475 пользуюсь LL библиотекой. вполне доволен. не хуже чем SPD. Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...
Ruslan1 0 Posted March 16, 2020 · Report post Вести с полей: в пятницу принял решение использовать 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; } Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...
Ruslan1 0 Posted March 18, 2020 · Report post Ну, в-общем, LL вполне годные библиотеки, можно юзать. Еще раз убедился, что HAL не для меня- там действительно наворочено много, без поллитра не разберешься- в исходнике МНОГО зависимостей, через которые глазами собственно исполняемый код сложно вычленить чтоб понять что же они наворотили. Автоматический конвертер этот: да, работает, но иногда ошибается. Например, мне в итоговый текст повставлял "LL_AHB1_GRP1_PERIPH_GPIOC", которого в принципе не существует в дефайнах семейства STM32L4: реально бывает только "LL_AHB2_GRP1_PERIPH_GPIOC" (то есть клоки GPIO управляются через регистр AHB2). Но такие ошибки легко находятся, оно просто не компилируется. Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...
jenya7 0 Posted March 25, 2020 · Report post Единственная печалька - на CAN нет LL драйвера. Quote Ответить с цитированием Share this post Link to post Share on other sites More sharing options...