wtorek, 23 czerwca 2020

[0] STM32F1 - Biblioteka LL - Sterowanie pinami

Tym razem chciałem krótko opisać sposób obsługi portów wejścia/wyjścia w mikrokontrolerze STM32F1 zamontowanym na płycie Nucleo.


[Źródło: mouser]


Program:


Projekt został szybko przygotowany w środowisku STM32CubeMX a następnie uruchomiony w STM32CubeIDE.

Dioda została podłączona do GPIOA Pin 5, przycisk natomiast jest podłączony do GPIOC Pin 13.

Po zdefiniowaniu pinów ich inicjalizacja zostaje bezpośrednio umieszczona w kodzie. 

Uruchomienie diody:

  1. static void MX_GPIO_Init(void)
  2. {
  3.     LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
  4.     //...
  5.     LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
  6.     //...
  7.     LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_5);
  8.     //...
  9.     GPIO_InitStruct.Pin = LD2_Pin;
  10.     GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  11.     GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  12.     GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  13.     LL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);
  14. }

W celu zapalenia oraz zgaszenia diody korzystam z dwóch instrukcji. Pierwsza ustawia pin wysoko, druga wprowadza pin w stan niski:

  1. while (1)
  2. {
  3.       LL_GPIO_SetOutputPin(LD2_GPIO_Port, LD2_Pin);
  4.       LL_mDelay(1000);
  5.       LL_GPIO_ResetOutputPin(LD2_GPIO_Port, LD2_Pin);
  6.       LL_mDelay(1000);
  7. }

To samo działanie można osiągnąć przez odniesienie się bezpośrednio do rejestrów. Co oczywiście finalnie jest wykonywane przez biblioteki LL oraz biblioteki HAL. Natomiast przy bibliotekach LL ilość kroków potrzebnych na dojście do instrukcji wykonujących polecenia na rejestrach jest zdecydowanie krótsza niż w przypadku biblioteki HAL. Co przekłada się na mniejszy rozmiar programu oraz szybsze działanie.

  1. //Insturukcje wykonane przez biblioteki LL
  2. #define WRITE_REG(REG, VAL)   ((REG) = (VAL))
  3.  
  4. __STATIC_INLINE void LL_GPIO_SetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
  5. {
  6.   WRITE_REG(GPIOx->BSRR, (PinMask >> GPIO_PIN_MASK_POS) & 0x0000FFFFU);
  7. }
  8. __STATIC_INLINE void LL_GPIO_ResetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
  9. {
  10.   WRITE_REG(GPIOx->BRR, (PinMask >> GPIO_PIN_MASK_POS) & 0x0000FFFFU);
  11. }
  12.  
  13. //Instrukcje na rejestrach
  14. (GPIOA->BSRR) = ((LL_GPIO_PIN_5 >> 8U) & 0x0000FFFFU);
  15. LL_mDelay(1000);
  16. (GPIOA->BRR) = ((LL_GPIO_PIN_5 >> 8U) & 0x0000FFFFU);
  17. LL_mDelay(1000);

Funkcja LL_mDelay() zapewnia dosyć dokładne odmierzanie czasu. Zliczanie odbywa się na liczniki Systick. Podobnie jak w przypadku biblioteki HAL (HAL_Delay()) ta funkcja jest funkcją blokującą.

  1. void LL_mDelay(uint32_t Delay)
  2. {
  3.   __IO uint32_t  tmp = SysTick->CTRL;  /* Clear the COUNTFLAG first */
  4.   /* Add this code to indicate that local variable is not used */
  5.   ((void)tmp);
  6.  
  7.   /* Add a period to guaranty minimum wait */
  8.   if (Delay < LL_MAX_DELAY)
  9.   {
  10.     Delay++;
  11.   }
  12.  
  13.   while (Delay)
  14.   {
  15.     if ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) != 0U)
  16.     {
  17.       Delay--;
  18.     }
  19.   }
  20. }

Teraz przejdę do obsługi przycisku na przerwaniach. Na sam początek inicjalizacja:

  1. static void MX_GPIO_Init(void)
  2. {
  3.   LL_EXTI_InitTypeDef EXTI_InitStruct = {0};
  4.   LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
  5.  
  6.   LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOC);
  7.   LL_GPIO_AF_SetEXTISource(LL_GPIO_AF_EXTI_PORTC, LL_GPIO_AF_EXTI_LINE13);
  8.  
  9.   EXTI_InitStruct.Line_0_31 = LL_EXTI_LINE_13;
  10.   EXTI_InitStruct.LineCommand = ENABLE;
  11.   EXTI_InitStruct.Mode = LL_EXTI_MODE_IT;
  12.   EXTI_InitStruct.Trigger = LL_EXTI_TRIGGER_FALLING;
  13.   LL_EXTI_Init(&EXTI_InitStruct);
  14.  
  15.   LL_GPIO_SetPinMode(GPIOC, LL_GPIO_PIN_13, LL_GPIO_MODE_FLOATING);
  16.  
  17.   NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
  18.   NVIC_EnableIRQ(EXTI15_10_IRQn);
  19. }

Przed wywołaniem funkcji uruchamiającej przerwanie należy jeszcze ustawić tzw. PriorityGrouping()

  1. NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

Obsługa przerwania następuje w funkcji obsługi przerwania. Gdzie po sprawdzeniu który pin z podanego portu wygenerował przerwanie. (Jest to konieczny zabieg, który pozwala na upewnienie się, że przerwanie zostało wywołane z podanego źródła. Gdyby instrukcja warunkowa została pominięta to może nastąpić niespodziewane wywołanie przerwania spowodowane np. przez ESD).

  1. void EXTI15_10_IRQHandler(void)
  2. {
  3.   /* USER CODE BEGIN EXTI15_10_IRQn 0 */
  4.  
  5.   /* USER CODE END EXTI15_10_IRQn 0 */
  6.   if (LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_13) != RESET)
  7.   {
  8.     LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_13);
  9.     /* USER CODE BEGIN LL_EXTI_LINE_13 */
  10.     LL_GPIO_TogglePin(GPIOA, LL_GPIO_PIN_5);
  11.  
  12.     /* USER CODE END LL_EXTI_LINE_13 */
  13.   }
  14.   /* USER CODE BEGIN EXTI15_10_IRQn 1 */
  15.  
  16.   /* USER CODE END EXTI15_10_IRQn 1 */
  17. }

W przerwaniu wykonuje zmianę stanu na pinie od diody.

Programy można pobrać z dysku Google pod tym linkiem