Niniejszym wpisem chciałbym zapoczątkować cykl postów opisujących bibliotekę do zarządzania pracą silnika krokowego. W tej części zajmę się krótkim opisem projektu, podłączeniem silnik-L6258 (płytka P46d)-STM32F429_DISCO-PC oraz konfiguracją peryferiów mikrokontrolera.
Projekt do pobrania z: https://github.com/PiotrPoterala/stm32f429i-disco_stepper_motor
Wykorzystane technologie:
- C++11,
- RTOS: RTX5.
Cechy:
- Obsługa driverów silników krokowych dysponujących interfejsem równoległym opartym na:
- parze sygnałów dir/clock,
- parze sygnałów zegarowych przesuniętych w fazie + informacja o poziomie prądu płynącego przez dane uzwojenie,
- Obsługa interfejsu UART do komunikacji z PC.
Zależności:
- CMSIS,
- CORE,
- RTOS2,
- PP library: https://github.com/PiotrPoterala/PP_Library
Podłączenie systemu:
Pliki konfiguracyjne (config.h, config.cpp):
Konfiguracja zegara
config.cpp
void RCC_Config(void){
SystemInit();
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY))
;
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS;
RCC->CR&=~RCC_CR_PLLON;
while(RCC->CR & RCC_CR_PLLRDY);
RCC->PLLCFGR|=RCC_PLLCFGR_PLLSRC;
RCC->PLLCFGR=(RCC->PLLCFGR & ~RCC_PLLCFGR_PLLM) | RCC_PLLCFGR_PLLM_DIV4;
RCC->PLLCFGR=(RCC->PLLCFGR & ~RCC_PLLCFGR_PLLN) | RCC_PLLCFGR_PLLN_MUL180;
RCC->PLLCFGR=(RCC->PLLCFGR & ~RCC_PLLCFGR_PLLP) | RCC_PLLCFGR_PLLP_DIV2;
RCC->CR|=RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY))
;
PWR->CR |= PWR_CR_ODEN;
while(!(PWR->CSR & PWR_CSR_ODRDY));
PWR->CR |= PWR_CR_ODSWEN;
while(!(PWR->CSR & PWR_CSR_ODSWRDY));
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_LATENCY_5WS;
RCC->CFGR=(RCC->CFGR & ~RCC_CFGR_SW) | RCC_CFGR_SW_PLL;
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
/* Wait till the main PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
SystemCoreClockUpdate();
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOGEN;
RCC->APB2ENR|=RCC_APB2ENR_USART1EN;
}
W powyższym przykładzie brak Standard Peripheral Library od ST. Uważam, że wspomniana biblioteka wprowadza zbyt dużo niepotrzebnych „zawiłości” w postaci konieczności tworzenia nikomu niepotrzebnych struktur do zainicjowania konkretnych peryferiów. Dużo czytelniejszym wydaje mi się operowanie „bezpośrednio” na rejestrach (z wykorzystaniem biblioteki CMSIS-CORE), tym bardziej, że w „manualach” są one naprawdę dobrze opisane. Konfiguracja zegara jest jednak bodaj jedynym miejscem, gdzie wspieram się CubeMX’em – jest fajnym kalkulatorem wartości poszczególnych mnożników i dzielników sygnału zegarowego. Zaznaczamy z jakiego źródła będziemy pobierać sygnał zegarowy (tutaj z kwarcu o częstotliwości rezonansu 8MHz). W PLL Source Mux wybieramy HSE, w System Clock Mux PLLCLK, następnie w okienku HCLK wpisujemy maksymalną częstotliwość taktowania rdzenia (tutaj 180MHz), po czym wciskamy Enter i następuje przeliczenie wszystkich wartości mnożników/dzielników.
Konfiguracja GPIO
Wszystkie wyjścia skonfigurowane jako wyjścia typu push-pull. Jedynym wyjątkiem jest pin PD7 podłączony do wejścia *EN płytki P46d skonfigurowany jako „otwarty kolektor”. PA9 oraz PA10 ustawione zostały jako wejścia/wyjścia alternatywne (w tym przypadku obsługują linie TX, RX komunikacji USART).
config.h
/**
* Keil project for stepper motor driver
* @author Piotr Poterała
* @email poterala.piotr@gmail.com
* @website http://zappp.pl
* @version v1.0
* @ide Keil uVision 5
* @license GNU GPL v3
*
@verbatim
----------------------------------------------------------------------
Copyright (C) Piotr Poterała, 2021c
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
----------------------------------------------------------------------
@endverbatim
*/
#ifndef _CONFIG_H
#define _CONFIG_H
#include "stm32xx.h"
#define NVIC_PriGroup_0 ((uint32_t)0x7) /*!< 0 bits for pre-emption priority bits for subpriority */
#define NVIC_PriGroup_1 ((uint32_t)0x6) /*!< 1 bits for pre-emption priority 3 bits for subpriority */
#define NVIC_PriGroup_2 ((uint32_t)0x5) /*!< 2 bits for pre-emption priority 2 bits for subpriority */
#define NVIC_PriGroup_3 ((uint32_t)0x4) /*!< 3 bits for pre-emption priority 1 bits for subpriority */
#define NVIC_PriGroup_4 ((uint32_t)0x3) /*!< 4 bits for pre-emption priority 0 bits for subpriority */
0 bits for subpriority */
#define RCC_PLLCFGR_PLLN_V40 0x2800
#define RCC_PLLCFGR_PLLR_V2 0x0
#define RCC_PLLCFGR_PLLM_V2 0x10
#define RCC_PLLCFGR_PLLM_DIV4 4
#define RCC_PLLCFGR_PLLN_MUL180 (180<<6)
#define RCC_PLLCFGR_PLLP_DIV2 0x0
#define USART_BSTOP_CLR 0xCFFF
#define USART_BSTOP_1 0x0000
#define USART_BSTOP_2 0x2000
#define GPIO_AFR_AF0 0x0
#define GPIO_AFR_AF1 0x1
#define GPIO_AFR_AF2 0x2
#define GPIO_AFR_AF3 0x3
#define GPIO_AFR_AF4 0x4
#define GPIO_AFR_AF5 0x5
#define GPIO_AFR_AF6 0x6
#define GPIO_AFR_AF7 0x7
#define GPIO_AFR_AF8 0x8
#define GPIO_AFR_AF9 0x9
#define GPIO_AFR_AF10 0xA
#define GPIO_AFR_AF11 0xB
#define GPIO_AFR_AF12 0xC
#define GPIO_AFR_AF13 0xD
#define GPIO_AFR_AF14 0xE
#define GPIO_AFR_AF15 0xF
#define IO_SET_REG1(REG, PIN, SETS) ((REG & ~(0x1<<(PIN))) | ((SETS)<<(PIN)))
#define IO_SET_REG2(REG, PIN, SETS) ((REG & ~(0x3<<(PIN*2))) | ((SETS)<<(PIN*2)))
#define IN_SET_REG(REG, PIN, SETS) (REG->MODER=IO_SET_REG2(REG->MODER, PIN, IO_IN), REG->PUPDR=IO_SET_REG2(REG->PUPDR, PIN, SETS))
#define OUT_SET_REG(REG, PIN, TYPE, SPEED) (REG->MODER=IO_SET_REG2(REG->MODER, PIN, IO_OUT), REG->OTYPER=IO_SET_REG1(REG->OTYPER, PIN, TYPE), REG->OSPEEDR=IO_SET_REG2(REG->OSPEEDR, PIN, SPEED))
#define ALT_SET_REG(REG, PIN, SPEED) (REG->MODER=IO_SET_REG2(REG->MODER, PIN, IO_ALT), REG->OTYPER&=~(1<<PIN), REG->OSPEEDR=IO_SET_REG2(REG->OSPEEDR, PIN, SPEED))
void RCC_Config(void);
void NVIC_Config(void);
void GPIO_Config(void);
void USART_Config(void);
#endif
config.cpp
void GPIO_Config(void)
{
//port A
ALT_SET_REG(GPIOA, Pin9, IO_OUT_HS);
GPIOA->AFR[1]=(GPIOA->AFR[1] & ~(0xF<<((Pin9-8)*4))) | (GPIO_AFR_AF7<<((Pin9-8)*4)); //USART1 TX
ALT_SET_REG(GPIOA, Pin10, IO_OUT_HS);
GPIOA->AFR[1]=(GPIOA->AFR[1] & ~(0xF<<((Pin10-8)*4))) | (GPIO_AFR_AF7<<((Pin10-8)*4)); //USART1 RX
//port C
OUT_SET_REG(GPIOC, Pin11, IO_PP, IO_OUT_HS);
OUT_SET_REG(GPIOC, Pin12, IO_PP, IO_OUT_HS);
OUT_SET_REG(GPIOC, Pin13, IO_PP, IO_OUT_HS);
//port D
OUT_SET_REG(GPIOD, Pin2, IO_PP, IO_OUT_HS);
OUT_SET_REG(GPIOD, Pin4, IO_PP, IO_OUT_HS);
OUT_SET_REG(GPIOD, Pin5, IO_PP, IO_OUT_HS);
OUT_SET_REG(GPIOD, Pin7, IO_OD, IO_OUT_HS);
//port E
OUT_SET_REG(GPIOE, Pin2, IO_PP, IO_OUT_HS);
OUT_SET_REG(GPIOE, Pin3, IO_PP, IO_OUT_HS);
OUT_SET_REG(GPIOE, Pin4, IO_PP, IO_OUT_HS);
OUT_SET_REG(GPIOE, Pin5, IO_PP, IO_OUT_HS);
OUT_SET_REG(GPIOE, Pin6, IO_PP, IO_OUT_HS);
//port G
OUT_SET_REG(GPIOG, Pin13, IO_PP, IO_OUT_HS); //LED1
OUT_SET_REG(GPIOG, Pin14, IO_PP, IO_OUT_HS); //LED2
PIN_CLR(PORT_LED, LED1);
PIN_CLR(PORT_LED, LED2);
}
Konfiguracja USART
USART skonfigurowany został tak aby:
- Ramka składała się z: 8 bitów danych, 1 bitu stopu, bez parzystości,
- Prędkość transmisji wynosiła 9600 bodów na sekundę
config.cpp
void USART_Config(void){
double divider = 0x00;
USART1->CR1&=~USART_CR1_PCE; //parity: no
USART1->CR1&=~USART_CR1_M; //8 data bits
USART1->CR2=(USART1->CR2 & USART_BSTOP_CLR) | USART_BSTOP_1; //1 stop bit
USART1->CR1&=~USART_CR1_OVER8; //oversampling=16
divider = ((double)90000000 / (16*9600)); //PLCK1/(16*BAUD) for oversampling=16
USART1->BRR |= (uint16_t)divider << 4;
USART1->BRR |= (uint16_t)((divider-(uint16_t)divider)*16);
USART1->CR1|=USART_CR1_TE;
USART1->CR1&=~USART_CR1_TXEIE;
USART1->CR1|=USART_CR1_RE;
USART1->CR1|=USART_CR1_RXNEIE;
USART1->CR1|=USART_CR1_UE;
}