W tym poście chciałbym opisać podstawową implementację bibliotek LL (ang. Low Layer Library) za pomocą CubeMx. Szybki przykład będzie obsługiwał przycisk oraz wykonywał sterowanie diodami.
Biblioteki LL są to nisko poziomowe biblioteki wygenerowane obok bibliotek HAL. Pozwalają one w bardzo łatwy sposób, za pomocą makr i prostych funkcji, odwoływać się bezpośrednio do rejestrów. Przy pominięciu mocno rozbudowanych instrukcji znanych z bibliotek HAL.
Cube MX:
Główna konfiguracja wygląda bardzo standardowo co do podobnego projektu z bibliotekami HAL.
Czyli tak samo ustawiamy cztery diody jako wyjścia (sterowanie diodami PD12 do PD15) oraz PA0 jako wejście.
Konfiguracja zegara:
Teraz najważniejszy element czyli wybór bibliotek (Project Manager->Advanced Settings->Driver Selector):
I tym sposobem za pomocą jednego kliknięcia uzyskujemy obsługę układu na bibliotekach LL zamiast HAL. Wygenerowanie projektu wygląda standardowo.
Po wygenerowanie kodu i załadowaniu go do edytora np. System Workbench wszystko powinno się skompilować bez żadnych problemów. Konfigurajcę wygenerowanego projektu można podglądać w seksci C/C++ General -> Path And Symbols -> Symbols. Tutaj ponieważ korzystamy z bibliotek LL zamiast definicji USE_HAL_DRIVER wykorzystała została USE_FULL_LL_DRIVER.
Projekt:
W opisanym przykładzie uruchomię diody oraz obsługę przycisku:
Konfiguracja pinów dla diod czyli jako wyjścia wykonuje się w następujący sposób:
Całość prezentuje się bardzo podobnie co w bibliotekach HAL. Na początku następuje inicjalizacja struktury po czym przechodzimy do włączenia zegarów, resetu ustawianych pinów oraz głównej inicjalizacji.
Funkcja uruchamiająca diody oraz przycisk:
Uruchomienie wszystkich diod na płycie STM32F4 Discovery wygląda następująco:
Funkcja ustawiająca stan wygląda następująco:
Funkcja pozwalająca na zmianę stanu na porcie na niski:
Przykładowa funkcja odpowiedzialna za mruganie diodami:
Prosta obsługa diod oraz przycisku:
Cały kod jest do pobrania z dysku Google pod tym linkiem.
Konfiguracja pinów dla diod czyli jako wyjścia wykonuje się w następujący sposób:
- static void MX_GPIO_Init(void)
- {
- LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
- LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOH);
- LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOD);
- LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);
- LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_12|LL_GPIO_PIN_13|LL_GPIO_PIN_14|LL_GPIO_PIN_15);
- GPIO_InitStruct.Pin = LL_GPIO_PIN_12|LL_GPIO_PIN_13|LL_GPIO_PIN_14|LL_GPIO_PIN_15;
- GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
- GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
- GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
- GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
- LL_GPIO_Init(GPIOD, &GPIO_InitStruct);
- }
Całość prezentuje się bardzo podobnie co w bibliotekach HAL. Na początku następuje inicjalizacja struktury po czym przechodzimy do włączenia zegarów, resetu ustawianych pinów oraz głównej inicjalizacji.
Funkcja uruchamiająca diody oraz przycisk:
- static void MX_GPIO_Init(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct = {0};
- /* GPIO Ports Clock Enable */
- __HAL_RCC_GPIOH_CLK_ENABLE();
- __HAL_RCC_GPIOA_CLK_ENABLE();
- __HAL_RCC_GPIOD_CLK_ENABLE();
- /*Configure GPIO pin Output Level */
- HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
- /*Configure GPIO pin : PA0 */
- GPIO_InitStruct.Pin = GPIO_PIN_0;
- GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
- /*Configure GPIO pins : PD12 PD13 PD14 PD15 */
- GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
- }
Uruchomienie wszystkich diod na płycie STM32F4 Discovery wygląda następująco:
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_12);
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_13);
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_14);
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_15);
Funkcja ustawiająca stan wygląda następująco:
- __STATIC_INLINE void LL_GPIO_SetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
- {
- WRITE_REG(GPIOx->BSRR, PinMask);
- }
Funkcja pozwalająca na zmianę stanu na porcie na niski:
- __STATIC_INLINE void LL_GPIO_ResetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
- {
- WRITE_REG(GPIOx->BSRR, (PinMask << 16));
- }
Przykładowa funkcja odpowiedzialna za mruganie diodami:
- LL_mDelay(100);
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_12);
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_13);
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_14);
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_15);
- LL_mDelay(100);
- LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_12);
- LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_13);
- LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_14);
- LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_15);
Prosta obsługa diod oraz przycisku:
- if (LL_GPIO_ReadInputPort(GPIOA)&LL_GPIO_PIN_0) //button
- {
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_12);
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_13);
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_14);
- LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_15);
- }
- else
- {
- LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_12);
- LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_13);
- LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_14);
- LL_GPIO_ResetOutputPin(GPIOD, LL_GPIO_PIN_15);
- }
Cały kod jest do pobrania z dysku Google pod tym linkiem.