Przeszukaj forum
Pokazywanie wyników dla tagów 'stm32'.
Znaleziono 182 wyników
-
Cześć, ostatnio skończyłem robić PCB do mojej listwy czujników koloru. Po polutowaniu i pomierzeniu czujnik nie działa. Na załączonych zdjęciach przesyłam schemat, schemat PCB, screenshot całego PCB i zdjęcie na którym widać że diody się świecą. Próbowałem świecić latarką, świecić dioda z pilota, zmieniać dynamicznie z czarnej powierzchni na białą i ani na odczycie z stm'a ani na mierniku nie widać zmiany albo widać jakąś leciutką zmianę typu z 3,3V na 3,2V, 3,15V, a na analogowym odczycie na live expressions odczyt sie nie zmienia na tyle znacząco żebym umiał stwierdzić czy działa czy nie. Czy może to być problem tego ze rezystor pull up ma za duża wartosc?
-
Co, jeśli wizytówka oprócz danych kontaktowych od razu mogła pokazywać umiejętności? Ciekawym projektem który w przyszłości można było by pokazać dla potencjalnego pracodawcy jest właśnie wizytówka PCB. Inspiracje do stworzenia własnej wersji dostałem po przejedzeniu różnych projektów innych osób ( https://hackaday.com/tag/pcb-business-card/) właśnie takiej płytki i stwierdziłem ze musze zrobić własną wersje. Głównymi założeniami było dobranie takich elementów które są małe żeby całość była mała. Elementy na płytce to elementy SMD w większości w obudowie 0605 więc naprawdę małe. Na płytce znajduje się macierz diod Led 8x8 sterowanych przez dwa rejestry przesuwne 74HC595 których wartości ustawia mikrokontroler STM32L010F4P. Płytka jest zasilana z baterii CR2032. Przez to w jaki sposób została skonstruowana elektronika czyli jeden rejestr odpowiada za kolumny a drugi wiersze, żeby wyświetlić grafikę na tym sztucznym ekraniku potrzebne jest napisanie multipleksowania. Jesteśmy tak naprawdę w stanie sterować diodami tylko na jednym wierszu w danym momencie, ale przez przełączenie się bardzo szybkie pomiędzy wieszamy ludzkie oko nie jest w stanie zobaczyć przełączanie i widzi samą grafikę. // struktura na podstawie której są wyświetlane grafiki const uint8_t displayColumns[] = { 0b01111110, 0b10000001, 0b10100101, 0b10000001, 0b10100101, 0b10011001, 0b10000001, 0b01111110, }; // Funkcja do kumunikacji z 2 rejestrami przesuwnymi void shiftOut(uint8_t dataRow, uint8_t dataCol) { for (int i = 7; i >= 0; i--) { HAL_GPIO_WritePin(DATA_PIN_GPIO_Port, DATA_PIN_Pin, (dataCol & (1 << i)) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(CLOCK_PIN_GPIO_Port, CLOCK_PIN_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(CLOCK_PIN_GPIO_Port, CLOCK_PIN_Pin, GPIO_PIN_RESET); } for (int i = 7; i >= 0; i--) { HAL_GPIO_WritePin(DATA_PIN_GPIO_Port, DATA_PIN_Pin, (dataRow & (1 << i)) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(CLOCK_PIN_GPIO_Port, CLOCK_PIN_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(CLOCK_PIN_GPIO_Port, CLOCK_PIN_Pin, GPIO_PIN_RESET); } HAL_GPIO_WritePin(LATCH_PIN_GPIO_Port, LATCH_PIN_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LATCH_PIN_GPIO_Port, LATCH_PIN_Pin, GPIO_PIN_RESET); } // Pętla main programu/Mulitplexing while (1) { displayShift = 0b00000001; for(i=0;i<8;i++) { shiftOut(displayShift,~displayColumns[i]); displayShift = displayShift << 1; HAL_Delay(1); } } Płytka jest na tyle mała ze mieści się do portfela. Można też na niej uruchamiać różne animacje a nawet proste gry, ograniczeniami jest pamięć STM32L0. Poniżej jedna z animacji które można zaimplementować.
-
Cześć, Realizuję właśnie 6 lekcję kursu STM32L4 z forbot (https://forbot.pl/blog/kurs-stm32l4-oszczedzanie-energii-5-lat-na-baterii-id46581) i chciałbym zachować zasilanie SRAM2 w trybie Standby Mógłby mi ktoś przybliżyć ręczne ustawianie bitów w rejestrze bez używania biblioteki HAL? - Chodzi mi szczególnie o RRS w rejestrze PWR_CR3 wspomniany w kursie. Interesowałoby mnie też jak wybrać konkretne adresy do zapisywania deklarowanych zmiennych (żeby faktycznie były w SRAM2). Dziękuję za każdą odpowiedź i docenię również odesłanie do innych artykułów, materiałów. /PS: Dla ludzi niewtajemniczonych w kurs, posługujemy się mikrokontrolerem STM32L4RGT6
-
Dzień dobry, Od kilku dni staram się utworzyć minimalną aplikacje dla ST67W61M (nowo wypuszczonego modułu WiFI/BLE od ST). Zestaw nad którym pracuje to NUCLEO-H753ZI i X-NUCLEO-67W61M (płytki połączone są przez standardowe złącze arduino). Tutaj link do kodu: https://github.com/RolandSobczak/H753ZI_WIFI Problem polega na tym, że nie jestem w stanie opuścić semafora w kodzie sterownika dostarczanego przez ST. Mianowicie w pliku: `Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_at/w61_at_common.c:372` xReturned = xSemaphoreTake(mdm->sem_if_ready, pdMS_TO_TICKS(4000)); if (xReturned != pdPASS) { SYS_LOG_ERROR("sem_if_ready not received\n"); ret = -1; goto __err; } Do modułu staram się podłączyć przez SPI1. Tutaj jest moja inicjalizacja SPI1: void MX_SPI1_Init(void) { /* USER CODE BEGIN SPI1_Init 0 */ /* USER CODE END SPI1_Init 0 */ /* USER CODE BEGIN SPI1_Init 1 */ /* USER CODE END SPI1_Init 1 */ hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 0x0; hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE; hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA; hspi1.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN; hspi1.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN; hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE; hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE; hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE; hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE; hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE; if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN SPI1_Init 2 */ /* USER CODE END SPI1_Init 2 */ } Tutaj moja konfiguracja GPIO: #define CHIP_EN_Pin GPIO_PIN_11 #define CHIP_EN_GPIO_Port GPIOE #define SPI_RDY_Pin GPIO_PIN_13 #define SPI_RDY_GPIO_Port GPIOE #define STLINK_RX_Pin GPIO_PIN_8 #define STLINK_RX_GPIO_Port GPIOD #define STLINK_TX_Pin GPIO_PIN_9 #define STLINK_TX_GPIO_Port GPIOD #define SPI_CS_Pin GPIO_PIN_14 #define SPI_CS_GPIO_Port GPIOD Patrząc w schematy ze strony ST: https://www.st.com/resource/en/schematic_pack/mb2230-antenna-a04-schematic.pdf https://www.st.com/resource/en/schematic_pack/mb1364-h753zi-c01_schematic.pdf Po lewej schemat pinout X-NUCLEO po prawej płytki z MCU Na powyższych grafikach widać, że połączenia powinny wyglądać następująco: Czy ktoś jest mi wstanie podpowiedzieć dlaczego nie mogę przejść tego kroku? Widzę, że ten semafor jest opuszczany w tym miejscu: `Middlewares/ST/ST67W6X_Network_Driver/Driver/W61_at/w61_at_common.c:723` MODEM_CMD_DEFINE(on_cmd_ready) { struct modem *mdm = (struct modem *)data->user_data; (void)xSemaphoreGive(mdm->sem_if_ready); return 0; } Ale nie rozumiem co wywołuje tą część kodu... Poniżej jeszcze zrzuty konfiguracji w CubeMX:
-
Rozwiązanie zasilania płytki NUCLEO-L476RG nie przez komputer
igorw6 opublikował temat w Mikrokontrolery
Witam, Zacząłem dopiero co kurs STM32L4 na tej stronie oczywiście i zastanawia mnie, jak mogę zasilić moją płytkę z zestawu NUCLEO-L476RG wraz z gotowym układem jeśli nie chce specjalnie uruchamiać komputera być zasilić go przez miniUSB. Czytałem że na wejściu powinno być max 5V, lecz nie jestem pewien tej informacji, dlatego narazie mam pomysł by dokupić samą kostkę 5 V (ładowarkę) z USB A i tylko podłączyć przewód USB A - miniUSB i do płytki. Prosiłbym o pomoc w dobraniu tej ładowarki by nie uszkodzić płytki (ile amperów?) i wszystko było w porządku, jeśli mój pomysł jest sensowny, oraz może zaproponować prosty i w miarę tani sposób na zasilanie nucleo z bezprzewodowego źródła. Poprzednio programowałem w Arduino i była opcja zasilania układu gotowego z baterii 9V przez vin (dzięki gotowemu elementowi z kursu również forbot) i myślę o czymś podobnym. z góry dziękuję. -
Potrzebuję wygenerować sygnały PWM na czterech wyjściach mikrokontrolera, specyfika jest taka: przed podaniem pierwszego pulsu - nazwijmy go roboczo preimpuls muszę mieć nieco czasu na uruchomienie innych peryferii ADC i/lub komparatora, ten preimpuls powinien trwać około 20μS po czym należy odczekać 10...20μS na odpowiedź układów peryferyjnych. Po tej odpowiedzi należy podjąć decyzję czy generować impuls główny na kolejnym kanale PWM. Impuls główny powinien być regulowany w czasie trwania z zewnętrznego urządzenia. Następnie cykl powtarza się dla drugiej pary. Do tej pory próbowałem wykorzystać timer1 z DMA jednak ze względu na ograniczoną szybkość działania nie umiem uzyskać przerwy krótszej niż te 200μS co spowalnia ogólną częstotliwość powtórzeń, no i jak dla tak prostego rozwiązania zużywa niepotrzebnie cztery kanały DMA. Potrzebuję raczej koncepcji, naprowadzenia, niż gotowego rozwiązania, nie wiem może użyć licznika zdarzeń i trybu triggera? Chętnie wykluczyłbym też DMA w tym miejscu bo sygnały nie są aż tak skomplikowane. roboczo mam funkcję która generuje te powtórzenia, wygląda tak (narazie dla dwóch kanałów) /* USER CODE END Header_StartTask02 */ void StartTask02(void *argument) { /* USER CODE BEGIN StartTask02 */ /* Infinite loop */ # define tab_size 3 uint32_t pre_puls[tab_size] = {800,0,0}; uint32_t main_puls[tab_size] = {0,4000,0}; // HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,pre_puls,10 ); // HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_2,main_puls,10 ); // __HAL_TIM_MOE_ENABLE(&htim1); //HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3); // HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4); uint16_t pwm1 = 0; int8_t dir = 1; int counter = 0; for(;;) { counter++; vTaskDelay(1/portTICK_RATE_MS); HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); if (counter < 1000) { HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,pre_puls,tab_size *2 ); HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_2,main_puls,tab_size*2); } } /* USER CODE END StartTask02 */ } init /* TIM1 init function */ void MX_TIM1_Init(void) { /* USER CODE BEGIN TIM1_Init 0 */ /* USER CODE END TIM1_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}; /* USER CODE BEGIN TIM1_Init 1 */ /* USER CODE END TIM1_Init 1 */ htim1.Instance = TIM1; htim1.Init.Prescaler = 0; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 9999; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim1) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } if (HAL_TIM_PWM_Init(&htim1) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_ENABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(); } if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) { Error_Handler(); } sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; sBreakDeadTimeConfig.DeadTime = 0; sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.BreakFilter = 0; sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE; sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH; sBreakDeadTimeConfig.Break2Filter = 0; sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM1_Init 2 */ /* USER CODE END TIM1_Init 2 */ HAL_TIM_MspPostInit(&htim1); } Powyższy program generuje takie sygnały, dość blisko tego czego poszukuję ale to wciąż za długo trwa
-
Witam wszystkim! Jakiś czas temu zakupiłem płytkę prototypową bluepill z mikrokontrolerem stm32 F103C8T6 na pokładzie. Przy jej programowaniu natrafiłem na pewien błąd, który udało mi się rozwiązać i chciałbym tymże rozwiązaniem się z wami podzielić. Przy próbie wgrania doń programu z wykorzystaniem STM32CubeProgramme i st-link v2 natrafiłem na błąd o następującej treści: Gdy wyłączyłem wymazywanie pamięci błąd zmienił się na następujący Okazało się, że mikrokontroler miał włączoną blokadę odczytu i zapisu. Udało mi się ją zdjąć poprzez zainstalowanie oprogramowania st-link utility. Tam podłączyłem się do programowanego urządzenia z użyciem przycisku Connect to the target i zresetowałem płytkę przyciskiem reset na niej umieszczonym. Następnie otworzyłem zakładkę target > option bytes. Kolejno zmieniłem opcję Read Out Protection na Disabled i w części tego okna opisanej jako Flash sectors protection zaznaczyłem opcję unselect all i nacisnąłem Apply. Po wykonaniu tych czynności nacisnąłem przycisk Full chip erase. Po tych wszystkich czynnościach możliwe stało się wgrywanie programów z wykorzystaniem STM32CubeProgrammer!
- 1 odpowiedź
-
- 3
-
-
Ten wpis wynika z tego, że chciałem zrozumieć skąd się biorą ustawienia FSMC dla wyświetlaczy LCD. Doświadczenia przeprowadzałem na zestawie STM32F407VET6 nazywanym także "Black board" Schemat zestawu: Oryginalny kod konfigurujący FSMC dla LCD z dostarczonych przykładów wyglądał tak: readWriteTiming.FSMC_AddressSetupTime = 0XF; //地址建立时间(ADDSET)为16个HCLK 1/168M=6ns*16=96ns readWriteTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间(ADDHLD)模式A未用到 readWriteTiming.FSMC_DataSetupTime = 60; //数据保存时间为60个HCLK =6*60=360ns readWriteTiming.FSMC_BusTurnAroundDuration = 0x00; readWriteTiming.FSMC_CLKDivision = 0x00; readWriteTiming.FSMC_DataLatency = 0x00; readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A; //模式A writeTiming.FSMC_AddressSetupTime =9; //地址建立时间(ADDSET)为9个HCLK =54ns writeTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间(A writeTiming.FSMC_DataSetupTime = 8; //数据保存时间为6ns*9个HCLK=54ns writeTiming.FSMC_BusTurnAroundDuration = 0x00; writeTiming.FSMC_CLKDivision = 0x00; writeTiming.FSMC_DataLatency = 0x00; writeTiming.FSMC_AccessMode = FSMC_AccessMode_A; //模式A Przy okazji szacun za skuteczne boje wykorzystując do konfiguracji bibliotekę stdPeriph, podejrzewam że dzisiaj 90% projektów bez wizualnego projektowania w CubeMX nigdy by się nie uruchomiła... Uprzedzając tok poznania od razu wyjawię że powyższy kod jest konfiguracją osobną dla ODCZYTU: readWriteTiming.FSMC_AddressSetupTime = 0XF; //地址建立时间(ADDSET)为16个HCLK 1/168M=6ns*16=96ns readWriteTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间(ADDHLD)模式A未用到 readWriteTiming.FSMC_DataSetupTime = 60; //数据保存时间为60个HCLK =6*60=360ns readWriteTiming.FSMC_BusTurnAroundDuration = 0x00; readWriteTiming.FSMC_CLKDivision = 0x00; readWriteTiming.FSMC_DataLatency = 0x00; readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A; //模式A oraz osobną dla ZAPISU do LCD: writeTiming.FSMC_AddressSetupTime =9; //地址建立时间(ADDSET)为9个HCLK =54ns writeTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间(A writeTiming.FSMC_DataSetupTime = 8; //数据保存时间为6ns*9个HCLK=54ns writeTiming.FSMC_BusTurnAroundDuration = 0x00; writeTiming.FSMC_CLKDivision = 0x00; writeTiming.FSMC_DataLatency = 0x00; writeTiming.FSMC_AccessMode = FSMC_AccessMode_A; //模式A Wynika to z tego iż sterowniki LCD mają różne czasy dostępu dla odczytu i zapisu. Teraz przechodząc bardziej do szczegółów należy wiedzieć że: 1. Omawiany zestaw ma LCD 320x240 px ze sterownikiem ILI9341 2. Magistrala FSMC jest równoległa, emulując sprzętowo interfejs 16bit i8080 3. Procesor zapuszczamy na 168 [MHz] 4. Magistrala FSMC jest na szynie AHB3, co w tym przypadku napędza ją bezpośrednio zegarem porcesora bez podziału. 5. Należałoby policzyć że w przypadku taktowania 168 [MHz] 1 cykl magistrali zajmuje 5,9523 nanosekund [ns] co w przybliżeniu daje 6 [ns] Wiedząc już że należy posłużyć się datasheetem od ILI9341 oszczędzę jego wertowania i od razu pokażę dwie kluczowe strony, które będą podstawą do oszacowania timingu FSMC. Pierwszy to graf dla timingu interfejsu i8080, drugi to tabela z timingami dla tego interfejsu. Dobrze, teraz jak działa ten interfejs FSMC dla LCD w trybie i8080: Otóż interfejs ten steruje około 20 liniami sygnałowymi w zależności od wyboru operacji i adresu. Także sama konfiguracja FSMC poprzez wybór banku pamięci determinuje pod jakim adresem będzie dostępny interfejs FSMC i8080. Linie sterujące to: CS/ nazywana na schemacie FSMC_NE1 - aktywna w stanie niskim, oznacza wybór układu. Innymi słowy sterownik LCD po wybraniu CS/ dostosowuje się do ustawień pozostałych linii sterujących przygotowując się do przyjęcia danej z szyny danych, lub wystawienia danej na szynę. RS w nomenklaturze FSMC określana w tym wypadku jako FSMC_A18. Stan tej linii jest zależny od wybranego adresu i odpowiada za wybór zapisu/odczytu do/z rejestru sterownika, lub odczytu/zapisu danej z/na szyny/szynie danych. 0 oznacza wybór rejestru, 1 jest wyborem danych. RD FSMC_NOE output enable stan niski oznacza żądanie odczytu, wysoki oznacza stan nieaktywny. WR FSMC_NWE write enable stan niski oznacza żądanie zapisu, wysoki oznacza stan nieaktywny. Powyższe dwie linie wykluczają się pod względem jednoczesnego żądania zapisu i odczytu. Najprawdopodobniej ich użycie wynika z zaszłości historycznych, kiedy to linię WR przepuszczano przez inwerter tworząc parę RD/WR DB - magistrala danych FSMC_D1 do D15 W datasheet ILI9341 linie są określone jako: D/CX = RS (FSMC_Ax) CSX = CS/ (FSMC_NEx) WRX = WR (FSMC_NWE) RDX = RD (FSMC_NOE) Dla zapisu stan linii CS/ oraz WR musi być wystawiony zgodnie z danymi w tabelce. Interesują nas tutaj 3 podane timingi dla zapisu nazwane : Twc - Write cycle time | 66 [ns] minimum Twrh - Write control pulse H duration 15 [ns] minimum Twrl - Write control pulse L duration 15 [ns] minimum Te 3 parametry widać przy zaznaczeniu. W tabelce jest także uproszczenie dla tast "Address setup time" = 0 i taht "Address hold time" = 0 i w sumie dobrze, bo wszystko to da się osiągnąć odpowiednio konfigurując FSMC. tast, taht jak i wszystkie pozostałe parametry można odnaleźć na diagramie. Jest także parametr tcsf 10 ns minimum, który został "wkomponowany" w timing CS/ - o tym za chwilę. Jeszcze kolejne 3 parametry dla odczytu a są to: trcfm - Read Cycle (FM) 450 [ns] minimum trdlfm - Read Control pulse H duration (FM) 355 [ns] minimum trdlfm - Read Control pulse L duration (FM) 90 [ns] minimum Teraz można przyjrzeć się parametrom FSMC dla odczytu: readWriteTiming.FSMC_AddressSetupTime = 15; //地址建立时间(ADDSET)为16个HCLK 1/168M=6ns*16=96ns readWriteTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间(ADDHLD)模式A未用到 readWriteTiming.FSMC_DataSetupTime = 60; //数据保存时间为60个HCLK Wartość parametru jest liczona od zera i określa ilość cykli magistrali. W przypadku FSMC_DataSetupTime = 60; daje to 61 cykli magistrali, czyli około 366 [ns]. Dla parametru FSMC_AddressSetupTime = 15; daje to 96 [ns] Są watości bardzo zbliżone do parametrów: trdlfm - Read Control pulse H duration (FM) 355 [ns] minimum trdlfm - Read Control pulse L duration (FM) 90 [ns] minimum Jeżeli założyć że to o nie chodzi, to czasy są prawidłowe, jedna nieścisłość jest tu, która wynika z diagramu timingu odczytu dla. Skoro to działa, to linia NOE powinna wyglądać jak NADV, przyjmuję że ten diagram jest niedokładnie oddający stan linii NOE. W każdym razie mamy takie ustawienia magistrali i to działa. Idąc dalej przechodzimy do zapisu, który wygląda tak: writeTiming.FSMC_AddressSetupTime =9; //地址建立时间(ADDSET)为9个HCLK =54ns writeTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间(A writeTiming.FSMC_DataSetupTime = 8; //数据保存时间为6ns*9个HCLK=54ns Czasy te dają w sumie 120 [ns] 60 [ns] na Twrl i 60 [ns] na Twrh co daje dla Twc 120 [ns]. Warunek jest spełniony (Twc min 66 [ns]), niemniej czasy można podkręcić dla lepszej wydajności interfejsu. Za chwilę do tego powrócę. Na grafie dla zapisu linia NWE wygląda jak bliska rzeczywistości. Teraz jeszcze należy omówić parametry zapisu z datasheeta ILI9341. Otóż: Twrl i Twrh mają czas MINIMALNY 15 [ns], ale całkowity minimalny czas cyklu zapisu wynosi 66 [ns]. To oznacza, że owszem, można ustawić Twrl na tylko 15 [ns], ale wówczas Twrh MUSI trwać co najmniej 51 [ns], aby spełnić warunek: Twc = 66 [ns] MINIMUM Ustawiając nowe czasy dla zapisu: Twrl = 30 [ns] Twrh = 36 [ns] sprawdziłem, że w ciągu 1 sekundy wypełniałem ekran pojedynczym kolorem 192 razy, co dało przepustowość 29.5 MB/s i prędkość działania magistrali na poziomie 15.099 MHz, co jest realne i odbiega od wyliczonego 15.1515 MHz tylko z powodu używania magistrali programowo, a nie sprzętowo. Nowe ustawienia dla FSMC wykonane w CubeMX Przyznam że czytałem notę AN2970 nt. podłączania LCD do FSMC, natomiast kompletnie dla mnie niezrozumiały był zastosowany wzór do sprawdzenia poprawności timingów, w szczególności wydumane parametry dla tego wzoru jak Tsu, Tv. Program korzystający z nowych ustawień zapisu dla FSMC na poziomie Twc = 66 [ns] działa stabilnie, bez żadnych glitchy.
-
Hejka wszystkim! Chciałbym prosić o pomoc przy sprawdzeniu mojego pierwszego samodzielnie zaprojektowanego schematu elektronicznego. Do tej pory korzystałem głównie z gotowych płytek deweloperskich, ale w końcu postanowiłem zmierzyć się z zaprojektowaniem czegoś od podstaw. Zależy mi na tym, żeby wszystko było w porządku, zanim przejdę do etapu projektowania PCB i składania, aby uniknąć niepotrzebnych błędów. Projekt jest stosunkowo prosty, oparty na mikrokontrolerze STM32F030F4P6. Główne założenia to: Odczyt potencjometrów: Płytka posiada 4 złącza analogowe (oznaczone jako PA0, PA1, PA2, PA3 na schemacie), do których będą podłączone 4 potencjometry. Będą one służyć do odczytu wartości analogowych za pomocą przetwornika ADC mikrokontrolera. Transmisja danych po MAX485: Po naciśnięciu przycisku (oznaczonego jako "Button2", podłączonego do PA4), wartości odczytane z potencjometrów mają zostać wysłane poprzez układ MAX485 (do komunikacji RS-485) do innego modułu. Złącze MAX485 jest podłączone do pinów USART1_RX i USART1_TX mikrokontrolera. Sterowanie przekaźnikiem i sygnalizacja LED: Równocześnie z naciśnięciem przycisku, ma zostać włączony moduł przekaźnika (złącze Relay, podłączone do PA5), a dioda LED (oznaczona jako "STATUS") ma zacząć migać, sygnalizując aktywność. Zasilanie: Płytka będzie zasilana napięciem 5V, które następnie będzie stabilizowane do 3.3V za pomocą regulatora LM1117MP-3.3 dla mikrokontrolera i pozostałych komponentów. Tutaj zastanawiam się czy nie lepiej jest użyć AMS1117-3.3 ze względu na jego powszechność Programowanie: Do programowania mikrokontrolera przewidziane jest złącze SWD. Bardzo proszę o sprawdzenie schematu pod kątem poprawności połączeń, ewentualnych brakujących elementów (np. rezystorów podciągających/ściągających, kondensatorów filtrujących), błędów w podłączeniu peryferiów (ADC, USART, GPIO) oraz ogólnej logiki. Czy są jakieś elementy, które powinienem dodać lub zmienić, aby zapewnić stabilną pracę, ułatwić debugowanie lub poprawić niezawodność? Każda wskazówka będzie na wagę złota! Dziękuję z góry za wszelką pomoc i uwagi! Pozdrawiam,
-
Tytułowy zestaw: Jest dość ciekawym zestawem, wyposażonym w multum dodatkowych peryferii... Jeden drobny błąd jak dla mnie to że zapomniano pociągnąć 2 ścieżek do ST-Linka żeby był dostępny CDC przez SPI... w końcu sam je sobie dolutowałem ale to było trudne jak na moje umiejętności i możliwości. Udało się tylko dlatego, że w ST-Linku były to dwa narożne piny, co dało sporo miejsca... Pomimo wielu dodatkowych rzeczy na płycie zestaw ciut rozczarowuje, ponieważ nie można bezpośrednio działać z wyświetlaczem, a zastosowany procek posiada interfejs FSMC mogący emulować magistralę równoległą 8/16 bit i8080. Tę przypadłość ulepszano stosując dodatkowe daughterboard'y jak np. STM32F4DIS-BB(/LCD) nie mniej obecnie zestaw ten jest już obsolete i niedostępny. Istnieje także zestaw Open407 team'u Waveshare dedykowany specjalnie dla STM32F407DISC-1: do czego można dokupić wyświetlacz przynajmniej 3.2 '' HY32 obecnie ze sterownikiem ILI9341 dawniej ILI9325: Istnieje jeszcze jeden sposób, który jest tematem tego artykułu, mianowicie samoróbka adaptera na FSMC dla F407-DISC1. Otóż istnieją znakomite zestawy dostępne na przykład na Aliexpress, które są bardzo kompaktowe i posiadają zintegrowane złącza FSMC i wyświetlacze 3.2'' na ILI9341 320x240 16 bit color. Jedne (F407VE/F407ZG) z tych zestawów był onegdaj sprzedawane przez sklep OMDAZZ, natomiast wszechobecne i popularne stały się zestawy DEVEBOX produkowane przez wiele firm, tak naprawdę nie wiem kto tu kogo rebranduje czy ACELEX TZT, czy TZT DEVEBOX'a itd. Nie jest zbyt istotne dociekanie kto był pierwszy, istotne jest, że te zestawy mają prawie identycznego procka jak F407VG-DISC1, bo F407VET6 różni się tym, że ma mniej wewnętrznej pamięci flash - zamiast 1MB posiada 512KB. Za to elektrycznie i programowo praktycznie jest to samo. Oto zestaw zwany F407-BLACK: z dołączonym LCD: oraz LCD widok od spodu: Jak widać wyświetlacz jest dołączony 16 bitową magistralą (i8080) równoległą. Nie zagłębiając się niepotrzebnie w cały schemat zestawu przedstawię tylko kluczowy fragment podłączenia magistrali FSMC do wyświetlacza LCD: Przy okazji jeszcze pokażę że ten zestaw nadal żyje i jest ulepszany. Widać kolejne złącza i przyciski w bardziej poręcznym miejscu. Dodatkowy 232 konwerter... Sam wyświetlacz posiada dodatkowe złącze i możliwość przełączenia do trybu SPI. Także jest dodatkowe 8MB flasha z nagranymi zestawami czcionek! Mój sposób myślenia przebiegał następująco: Jeżeli procesory (F407VG-DISC1 i F407-BLACK) są elektrycznie identyczne, to aby skutecznie podpiąć się do wyświetlacza od F407-BLACK wystarczy zmałpować (odtworzyć) połączenia dla pokazanej magistrali FSMC (gwoli ścisłości przy okazji także SPI dla dotyku) na własnym daughterboardzie i praktycznie będzie to 1/1 co powinno pozwolić odpalać i prawidłowo działać z wyświetlaczem programom, które działają na F407-BLACK. Jeszcze z rozpędu czy przy okazji dorobiłem drugie złącze dla wyświetlaczy od zestawów OMDAZZ - nieważne. Na bazie tej odtwórczości powstał taki oto schemat czyli posiłkując się wyprowadzeniem pinów na DISCOVERY: stworzyłem takie dzieło: Kiedy to komponowałem, byłem tak cwany że zamiast zroutować ścieżki samemu użyłem autoroutera. Nie myślałem nawet o czymś takim jak "signal integrity", ścieżki nie są równej długości no ale... skoro działa to znaczy się jest dobrze Nie wygląda to zbyt imponująco a i pewnie mój przyjaciel @H1M4W4R1 po obejrzeniu tego PCB mógłby złapać się za głowę, niemniej powstało coś takiego: Mimo wszystko zestaw działa i to zgodnie z założeniami - programy z F407-BLACK działają na F407VG-DISC1 oto dowód: Więc gdyby ktoś chciał podłączyć LCD do swojego DISCOVERY F407VG to może w ten sposób.
-
problem z programowaniem zewnętrznego STM32 z programatora Nucleo
bestsiwy opublikował temat w Mikrokontrolery
Cześć. Do tej pory w zastosowaniach hobbystycznych używałem modułów Nucleo. Jednak teraz postanowiłem zaprojektować własną płytkę PCB ze zintegrowanym STM32L476RG, czyli takim samym jak w kursie. Zrobiłem wyprowadzenia do programowania na goldpinach i połączyłem je do odpowiednich złącz na procesorze zgodnie z dokumentacją programatora. Jest też zworka do BOOT. No i niestety nie jestem w stanie zaprogramować procesora. Próba wgrania przez STM32CubeIDE kończy się błędem komunikacji, załączam poniżej. Początkowo źle podłączyłem zasilanie, ale doczytałem w oficjalnej dokumentacji, że zasilanie powinno iść z mojego urządzenia do programatora, a nie odwrotnie. Poprawiłem to połączenie pobierając 5V bezpośrednio z płytki. Sprawdziłem woltomierzem i jestem pewny, że napięcie dochodzi zarówno do procesora jak i do programatora. Styki procesora również nie mają zwarcia, sprawdzone pod mikroskopem i przedzwonione multimetrem podczas montażu. Zworki CN2 zdjęte. Proszę o pomoc w rozwiązaniu problemu. Pierwszy raz próbuję zrobić niezależny system ze zintegrowanym procesorem i robiłem port programowania na oko, czytając doku i podobne projekty. https://www.st.com/en/evaluation-tools/nucleo-l476rg.html#documentation -
Cześć, na początek chciałbym się przywitać. Nie pamiętam czy pisałem coś na forum, a jeżeli tak to musiało być bardzo dawno. Zaprojektowałem urządzenie serwera HTTP na bazie stm32f207 i DP83848 (PHY). Oprogramowanie wygenerowałem z użyciem najnowszego CubeMX oraz wersji biblioteki dla procesora stm32f207 v1.9. Wykorzystałem LWIP jako stos TCP/IP oraz FreeRtos-a jako system, komunikacja TCP za pomocą netconn. Wygenerowany szablon zmodyfikowałem na wzór dostarczonego przez ST. Utworzyłem wątek http_server_netconn_thread(), w którym oczekuję na informacje ze strony. Nic odkrywczego. Pierwsza linia to utworzenie nowego wątku a poniżej jest sam wątek. httpTaskHandle = sys_thread_new("HTTP", http_server_netconn_thread, NULL, 4095, osPriorityHigh); void http_server_netconn_thread(void *arg) { static struct netconn *TCPListener, *newconn; static err_t err, conn_check, deleteErr; TCPListener = netconn_new(NETCONN_TCP); if (TCPListener != NULL) { err = netconn_bind(TCPListener, IP_ADDR_ANY, PORT_60662); if (err == ERR_OK) { netconn_listen(TCPListener); while(1) { netconn_set_recvtimeout(TCPListener, TIME_DELAY_500_MS); conn_check = netconn_accept(TCPListener, &newconn); if(osOK == osTimerStart (timeOutHttpServerHandle, TIME_DELAY_60_SECONDS)) { timerHttpStatus = TIMER_RUN; } if(ERR_OK == conn_check) { http_server_serve(newconn); netconn_close(newconn); netconn_delete(newconn); } osTimerStop(timeOutHttpServerHandle); } } else { netconn_close(newconn); netconn_delete(newconn); } } } Funkcja http_server_serve(newconn)() sprawdza czy przyszło zapytanie http i na nie reaguje. Również nic odkrywczego. void http_server_serve(struct netconn *conn) { connCheck = netconn_recv(conn, &inbuf); if(connCheck == ERR_OK) { if (netconn_err(conn) == ERR_OK) { // pobranie danych do bufora netbuf_data(inbuf, (void**)&buf, &buflen); analiza_metody_GET(buf); analiza_metody_POST(buf); } } netconn_close(conn); netbuf_delete(inbuf); } Jeżeli przyszło zapytanie ze strony, program wysyła odpowiedź na to zapytanie. I mógłbym powiedzieć, że wszystko działa. Program działa dobrze do czasu gdy pracuje w sieci lokalnej. Problemy zaczęły się gdy udostępniłem stronę poza sieć lokalną. Szczególnie zauważalne to jest gdy chcę wyświetlić stronę na smartfonie poprzez sieć komórkową. Dane do przeglądarki przesyłam za pomocą funkcji netconn_write_partly(). netconnResult = netconn_write_partly(conn, buffer_send, len, NETCONN_NOCOPY, &written); Niestety po pewnym czasie program blokuje się na tej funkcji i pozostaje już tylko reset. Sytuacje taką zauważyłem przy przesyłaniu do przeglądarki pakietów danych o objętości 6,5 kB. Przy przesyłaniu pakietów o objętości poniżej 1kB takich problemów nie zauważyłem. Przeglądając internet znalazłem parę wątków o problemach z oprogramowaniem od ST. Niestety nie pomogły mi rozwiązać mojego problemu. Pytanie do osób przerabiających temat serwera www na stm32 czy przerabiały podobny problem lub czy istnieje możliwość włączenia dla tej funkcji timeout-u umożliwiającego jej opuszczenie i dalsze działanie urządzenia?
-
Rzecz jest owiana wieloma legendami. Jeżeli sprawdzić dokładniej, to HAL w zależności od wersji tworzył inny kod wynikowy, aniżeli wyselekcjonowany. Przykładowo na filmie ST: wybrano użycie interfejsu SDIO 4Bit mode, generowany kod wybierał 1Bit mode. Dla starszych wersji HAL/CubeMX wygląda to tak: A wygenerowany kod wyglądał tak: Koniec końców użytkownik najczęściej nie wie, że pracuje na interfejsie 1 bit zamiast 4. Ale program działa. W nowszych wersjach kod generowany przez HAL wygląda tak: Pomimo że jest faktycznie wybrany interfejs 4 bit, to program odwołujący się do interfejsu SDIO zwyczajnie zawiesza się i cały program nie działa. Wokół tematu narosło i utworzono wiele mitów: Według rozsiewanych mitów wszędzie doszukuje się malfunkcji po stronie elektrycznej... Od sugerowanego właściwego routingu na PCB, poprzez zakłócenia, dzwonienie, przesłuchy... Zbyt "silne" , czy zbyt "słabe" pull up'y na liniach SDIO, mające zwiększać podatność na zakłócenia ... Brzmi to tak, jakby projektanci z ST nie wiedzieli jak elektrycznie połączyć interfejs SDIO na PCB (czy to ma sens?). Taka sytuacja jest jak widać od 2018 roku - mity, legendy, pozamykane wątki i ani jednej solucji poza dobrymi radami w stylu "uważaj jak projektujesz PCB". Być może temat nie jest wielką wojną, ponieważ w ogóle z STM32 daje się dobić do karty SD. I także prawdopodobnie nie ma projektu, który by uzależnił jego egzystencję w zależności czy SDIO 4BIT mode zostało poprawnie uruchomione. Natomiast dziwnym wydawał się fakt, że ST opracowało ten interfejs w logice procesora, a teraz nie może czy nie potrafi go uruchomić? Długo dociekałem tego tematu i w zasadzie straciłem wiarę, że jest możliwe uruchomienie na STM32 SDIO 4BIT. W końcu spotkałem streamera na YT o nicku STM32World, który po naszej dyspucie udowodnił mi i pokazał, że da się uruchomić SDIO 4BIT mode na STM32. Po przeprowadzonych testach rzeczywiście zauważyłem że karta SD "przyspiesza" i odczyt jest szybszy od trybu 1bit. W zależności od konkretnie użytej karty oraz prędkości jej taktowania. Nie ma tak dobrze, że jest 4 razy szybciej niż w trybie 1bit, ale w granicach 2 razy szybciej faktycznie jest. Czasem trochę mniej, czasem trochę więcej ale jest to zauważalna różnica. Wystarczyły 3 magiczne linie tekstu/kodu żeby faktycznie SDIO4BIT mode dał się uruchomić. W trybie 1bit odczytywał dane raw bitmapy 510KB w 128 mS, w SDIO4BIT odczytał to w 68mS - w sumie dobra sprawa, bez dopłaty mieć działający taki interfejs. Przeprowadziłem próby na innych boardach F407VET6/ZGT6 i F103ZET6, wszędzie z podobnym skutkiem.
-
Sprawdzenie ukladu - projekt kierownicy do simracing
Klimwojt opublikował temat w Sprawdzanie schematów
Czesc, to moj pierwszy wiekszy plan ukladu elektroniki do projektu(dokladnie projekt kierownicy). Nazwy komponentow sa podpisane na ukladzie. Najbardzie mi zalezy na sprawdzeniu podlaczenia przyciskow jako matrix, podlaczalem to na podstawie dokumentacji. Podam linki pod dokumentacje aplikacji freejoy: https://github.com/FreeJoy-Team/FreeJoyWiki/tree/master Z gory dziekuje Pozdrawiam -
W skrócie: chcesz zrobić projekt na STM32 za który dostaniesz wynagrodzenie, ale nie oczekujesz pracy na etacie. Hardware gotowy, wystarczy napisać firmware. Firma Enbio poszukuje programisty do współpracy przy realizacji firmwaru do nowego sterylizatora parowego. Wymagania: umiejętność napisania produkcyjnego kodu, STM32U5, TouchGFX, Git C/C++ FMC, OCTOSPI, NOR, PSRAM, SPI, UART, Modbus RTU, FileX, LevelX, Azurte RTOS, podstawy sterowania: PID, histereza, debugowanie softwaru + hardwaru Praca raczej na zasadzie zlecenia realizacji firmwaru, ewentualnie kontynuacji założonego projektu. Jest opcja dalszej współpracy przy kolejnych projektach, możliwe że nie tylko firmware ale i hardware. Do dogadania. Siedziba firmy Gdynia-Rumia.
-
Przedstawiam mojego najnowszego robota klasy LineFollower Standard "Fuzzy". Z opisywaną konstrukcją pojawiam się na większości zawodów jakie organizowane są w ostatnim czasie. W porównaniu do moich poprzednich robotów Fuzzy nie został wyposażony w napęd tunelowy. Powodem tej decyzji była chęć testowania nowych algorytmów. Efekty mojej pracy łatwiej zauważyć na robocie bez docisku, ponieważ jest on trudniejszy to wysterowania przy większych prędkościach. Konstrukcja mechaniczna Robot standardowo wyposażony został w dwa silniki Pololu 10:1 HP z obustronnym wałem, na którym zamocowane zostały magnesy od enkoderów. Podwozie stanowi płytka PCB wykonana w Satlandzie. Czujniki wysunięte zostały do przodu na węglowej listewce. Koła wytoczone zostały na zamówienie. Całość, zależnie od dobranego akumulatora waży 70-100g. Elektronika Prezentowana konstrukcja to czwarty prototyp robota. Głównymi elementami części elektronicznej są: mikrokontroler STMF103RBT6, enkodery AS5040 oraz mostek TB6612. Konstrukcja może obsługiwać do 16 czujników KTIR. Po przeprowadzonych testach pozostałem jednak przy 8 transoptorach. Pozostałe połączenia z czujnikami pozwalają na wizualizację ich stanów poprzez diody LED. Schemat prototypu Prototyp W poprzedniej konstrukcji do komunikacji z otoczeniem wykorzystywałem złącza typu Goldpin o rastrze 0.5mm. Częste podpinanie i odpinanie dodatkowego osprzętu sprawiło, że złącza te szybko uległy uszkodzeniu wprowadzałem spore problemów. Dlatego w nowej wersji zastosowałem złącza firmy HARTING Flexicon, które rozwiązały mój problem. Szczerze mogę je polecić, nie miałem żadnych problemów od kiedy je stosuję. Ostateczna wersja elektroniki ze złączami HARTING Flexicon Oprogramowanie Program napisany został w języku C z wykorzystaniem biblioteki dostarczanej przed producenta STMów. Kod w głównej mierze składa się z dwóch części. Pierwsza odpowiedzialna jest za komunikację z otoczeniem, druga za podążanie za linią. Kontroler robota Robot może komunikować się z komputerem lub specjalnym kontrolerem za pomocą interfejsu USART. Możliwa jest również komunikacja przez moduły Bleutooth. Całość obsługiwana jest przez własny, prosty protokół do komunikacji. Algorytm pakuje wszystkie dane w ramki oraz oblicza sumę kontrolną. Rozwiązanie takie pozwoliło na bezbłędne przesyłanie wymaganych danych. Prototyp z modułem BlueTooth Podążanie za linią wykonane zostało w oparciu o 3 regulatory: PD - podążanie za linią, sprzężenie zwrotne od czujników 2x PID - sterowanie faktyczną prędkością silników, sprzężenie zwrotne od enkoderów magnetycznych Efekty Szczególną wagę przykładałem do precyzji przejazdu, która moim zdaniem jest zadowalająca - szczególnie na kątach prostych. Robot wygrał wszystkie zawody w kategorii LineFollower Standard w jakich brał udział. Zdarzyło mu się przegonić również niejednego robota z turbiną. W optymalnych, domowych warunkach średnia prędkość robota, to ponad 2m/s. Poniżej filmik z zawodów RoboMotion z prędkością około 1,4 m/s - nie był to najszybszy przejazd. Ostateczny wygląd robota W chwili obecnej konstrukcja różni się od powyższej jedynie kolorem opon i długością listewki od czujników. Później postaram dodać więcej zdjęć - szczególnie jeśli będzie z Waszej strony zainteresowanie czymś konkretnym. Podziękowania W tym momencie chciałbym podziękować firmom HARTING oraz TME, które wspierały pracy przy tym projekcie. Zachęcam do zadawania pytań, odpowiem praktycznie na każde
-
STM32H735-DK Grzebanie po rejestrach - zajęcia praktyczne
virtualny opublikował temat w Mikrokontrolery
Dzisiaj na warsztacie nie byle jaki zestaw tuningowany na oscyloskop: STM32H735-DK Skrócone dane techniczne: Arm® Cortex®-M7 32-bit 550 MHz MCU, 1 MB flash, 564 KB RAM STM32H735IGK6 microcontroller featuring 1 Mbyte of flash memory and 564 Kbytes of SRAM in UFBGA176+25 package 4.3" TFT 480 × 272 pixels colored LCD module with capacitive touch panel and RGB interface 4.3 inch TFT 480x272 FPS(???) - sprawdzimy 128-Mbit HyperRAMTM po interfejsie OCTOSPI // framebuffer 512-Mbit Octo-SPI Flash Do badań wziąłem firmowy example "BSP", który jest wygodny z uwagi, że ma już dołączone czcionki. Wiemy już do jakiej prędkości można rozbujać procka, w pliku main komentarze podają że jest ustawiony na 520MHz, a magistrale APB1,2,3,4 biegają na 130MHz. Teraz chciałbym to sprawdzić, zmierzyć aby dokonać dalszych pomiarów... W jaki sposób? Uruchomić nieużywany przez program timer najlepiej 32bitowy i zmierzyć przynajmniej z grubsza ile cykli timer zliczy po sekundzie. Z dejtaszita od proca można się dowiedzieć że jest w nim aż 24 przeróżne timery z czego 4 są 32bitowe. Przypuszczam atak na Timer2. Żeby go okiełznać należy zerknąć do schematu blokowego w dejtaszicie procka i ustalić do jakiej szyny (magistrali) podwieszony jest TIM2: Ze schematu wynika że: 1. TIM2 jest "podwieszony" na APB1 2. TIM2 jest 32 bitowy Kolejny ważny niuans, to że TIM2 jest na APB1 nie oznacza że jest taktowany 130 MHZ, ponieważ z obrazka Clock Configuration dowiedziałem się, że akurat TIMERY są taktowane 2 razy szybciej niż magistrala: W każdym razie warto to wiedzieć, chociaż zaraz zbadamy to empirycznie. OK, teraz uruchomienie Timera2 wygląda w kolejności, że najpierw trzeba go włączyć w rejestrze bloku kontrolnego (tym razem używamy reference manuala - RM): Z obrazka widać że najmłodszy bit uruchamia taktowanie TIM2, a więc prosta instrukcja: RCC->APB1LENR |= 1; I TIM2 zostaje włączony Kolejna rzecz to ustawienie prescalera dla TIM2 oraz rejestru przeładowania wartości na 0xFFFFFFFF. Tak się składa, że rejestry te są po resecie ustawione na pożądane właśnie wartości, ale dla porządku należy zrobić tak: TIM2->PSC = 0; TIM2->ARR = 0xffffffff; Teraz dochodzi ustawienie TIM2 na wyłączenie (póki co) i jego wartość początkowa - rejestr CR1 i CNT TIM2->CR1 = 0; TIM2->CNT = 0; Teraz należ koniecznie przyjrzeć się rejestrowi CR1, bo w nim jest sporo kluczowych ustawień, które musimy znać i uwzględnić podczas uruchamiania timera: Wartość początkowa 0 jest pożądaną przez nas wartością do zatrzymania timera, a do jego włączenia dla naszych celów konieczna jest wartość 1 To ustawia między innym wartość podziału taktowania TIM2 na 1, czyli timer będzie zliczał impulsy taktowania bez podziału. Co przypominam w domniemaniu dla tej konkretnej konfiguracji powinno dać wartość 260 000 000 "tyknięć" na sekundę. Także np. bit DIR = 0 ustawia licznik jako "zliczający w górę". Ostatnią rzeczą jaką potrzebujemy, to odmierzenie 1 sekundy (przynajmniej z grubsza) i sprawdzenie ile cykli zliczył w tym czasie TIM2. Do określenia 1 sekundy posłuży funkcja HAL_Delay(1000); Czyli: 1. Uruchamiamy timer i Hal_Delay(1000) 2. Po powrocie z funkcji HAL_Delay(1000) zatrzymujemy TIM2 i sprawdzamy w rejestrze CNT ile cykli zliczył w czasie działania funkcji HAL_Delay(1000) - przypominam, że oczekujemy wartości jakichś 260 milionów Oto część programu wykonująca to dzieło: uint32_t dana = 0; // TIM2->CNT value char desc[64]; // null terminated string RCC->APB1LENR |= 1; // enable TIM2 clk __asm("dsb sy"); // data synchrinization barrier TIM2->PSC = 0; // prescaler = 0 TIM2->ARR = 0xffffffff; // reload value TIM2->CR1 = 0; // stop TIM2 TIM2->CNT = 0; // reset TIM2 CNT value to 0 TIM2->CR1 = 1; // RUN TIMER2 HAL_Delay(1000); // wait 1 sec. TIM2->CR1 = 0; // stop TIMER2 dana = TIM2->CNT; // read CNT value UTIL_LCD_Clear(UTIL_LCD_COLOR_BLUE); // clear screen sprintf(desc, "0x%08X", (unsigned int) dana); // prepare string UTIL_LCD_DisplayStringAt(0, 60, (uint8_t *) desc, CENTER_MODE); // display string at screen I oto wynik działania tej części programu: wartość 0x0F8322E0, to dziesiętnie 260 252 384 !!! Wynik jest ciut większy od 260M, ale odchyłka jest mniejsza od 1 tysięcznej, ponieważ funkcję Hal(Delay(1000) wywołujemy "pomiędzy tykami SysTicka", który działa 1000 razy na sekundę. W każdym razie udało się ustalić, że Timer2 jest taktowany 260Mhz. Super sprawa! Jest timer z którego pomocą można odmierzać czas i do tego z wielką rozdzielczością!!! Więc teraz za pomocą tego timera chciałbym zmierzyć ile cykli timera trwa 1 ramka ekranu... Do tego jest nam potrzebny jakiś rejestr, który potrafi nas poinformować, w jakim miejscy jest tworzenie (odświeżanie) obrazu (plamka rastra). Tak się szczęśliwie składa, że jest taki rejestr i nazywa się LTDC->CPSR: Rejestr jest niezwykle dokładny, ponieważ podaje pozycję X i Y aktualnie tworzonego obrazu, do naszych celów wystarczy jego najmłodszych 16 bitów, co poda w której aktualnie linii jest plamka, a właściwe sprawdzenie pozwoli stwierdzić moment rozpoczęcia danej linii. Co należy zrobić? 1. Przygotować TIM2 2. Poczekać na wybraną linię ekranu i uruchomić TIM2 3. Poczekać aż skończy się wyświetlanie danej linii 4. Poczekać ponownie na wybraną linię i zatrzymać TIM2 5. Odczytać i wyświetlić wartość zliczoną przez Timer2 To zadanie wykonuje poniższy program: RCC->APB1LENR |= 1; __asm("dsb sy"); TIM2->PSC = 0; TIM2->ARR = 0xffffffff; __disable_irq(); while((LTDC->CPSR & 0xffff) != 50){} // wait until after line 10 TIM2->CR1 = 0; // prepare timer to start TIM2->CNT = 0; while((LTDC->CPSR & 0xffff) != 10){} // wait fir line 10 TIM2->CR1 = 1; // run timer2 count while((LTDC->CPSR & 0xffff) == 10){} // wait for end of line 10 while((LTDC->CPSR & 0xffff) != 10){} // wait one more time for line 10 TIM2->CR1 = 0; // stop TIM2 dana = TIM2->CNT; // read CNT value __enable_irq(); UTIL_LCD_Clear(UTIL_LCD_COLOR_BLUE); sprintf(desc, "0x%08X", (unsigned int) dana); UTIL_LCD_DisplayStringAt(0, 60, (uint8_t *) desc, CENTER_MODE); // print CNT value on the screen I oto wynik działania programu: 0x004155FA to dziesiętnie 4 281 850 I co niby z tego miałoby wynikać??? Otóż jeżeli wielkość 260 000 000 podzielimy przez 4 281 850, to będziemy wiedzieć ile razy liczba 4 281 850 mieści się w 260 000 000 LOL Może i śmiesznie, ale jeżeli przypomnimy sobie że owe 260 000 000 to jest sekunda, a 4 281 850 to czas tworzenia się 1 ramki obrazu, wówczas otrzymany wynik to będzie REFRESH RATE !!! Czyli częstotliwość odświeżania obrazu, Ponieważ nigdzie w dokumentacji STM32H735-DK nie znalazłem tej informacji (być może źle szukałem), to uzyskałem ją empirycznie. Nasz REFRESH RATE wynosi około 60,7 [Hz] Jest to bardzo ważna informacja chociażby gdy uwzględnimy prędkość transferu danych z frame bufora do LCD, co przy 30 [Hz] dla trybu ARGB8888 dało by: 30*480*272*4 = 15 667 200 Bajtów na sekundę, natomiast przy 60 [HZ] wielkość ta będzie dwukrotnie wyższa i będzie wynosić ponad 31 [MB/s], a nawet ciut więcej, jeżeli pamiętamy, że REFRESH RATE wyniósł 60,7 [Hz] a nie 60 [Hz]. Być może również źle szukałem, ale również nie znalazłem gdzie mapowany jest nasz OCTO-SPI HyperRAM. Z podglądu w STM32Cubeporgrammer widać że external falsh jest mapowany pod 0x90000000 i jest go 64MB Z UM2679 strona 7 (UM do STM32H735-SK) //Figure 3. Hardware block diagram// widać że HyperRAMTM jest na interfejsie OCTOSPI2 w wielkości 128Mbit, czyli 16 [MB] - tak wiem, że 128 brzmi lepiej od 16, ale wolałbym żeby działy marketingu podawały wielkości w MEGABAJTACH a nie w megabitach. Z kolei nie znalazłem informacji GDZIE mapowany jest HyperRAMTM Jak wspomniałem, może źle szukałem, ale zamiast dalej szukać postanowiłem znów sprawdzić to empirycznie. Istnieje rejestr LTDC_LxCFBAR, który oprócz tego że przechowuje adres framebufora, także pozwala się odczytać: Nic prostszego jak program: dana = LTDC_Layer1->CFBAR; UTIL_LCD_Clear(UTIL_LCD_COLOR_BLUE); sprintf(desc, "0x%08X", (unsigned int) dana); UTIL_LCD_DisplayStringAt(0, 60, (uint8_t *) desc, CENTER_MODE); Powiedział gdzie mapowana jest pierwsza ramka framebufora: Może nie z całą pewnością, ale z dużym prawdopodobieństwem można domniemywać, że HyperRamTM jest mapowany od 0x70000000 !!! Po zadaniu sobie tylu trudów, aby zbadać powyższe parametry tego DEVBOARDA już nic nie mogło mnie powstrzymać od uruchomienia na nim vectordotów 60FPS Program został osadzony w internal flashu (0x08000000), razem z fontami i narzutem przykładu BSP nie przekroczył 50 [KB] Link do github z HEX'em Github W załączeniu Hex z vectordotami: STM32H735-DK FANTASTIC VECTORDOTS.zip -
Dzisiaj zestaw totalna petarda: STM32H7S78-DK z procesorem STM32H7S7L8H6H To jest chyba najbardziej wypasione Discovery od STM. Długo by mówić nad listą ulepszeń i dostępnych peryferiali, tylko pokrótce, aby nie zanudzać. Dane z pierwszej strony MCU DS: High-Performance Arm Cortex-M7 MCU, 600MHz, 64KB Bootflash, 620KB SRAM, with DSP, cache, USB HS PHY, NeoChrom GPU, TFT-LCD Ustaliłem że LCD to 5 INCH (!!!) cappacitive TFT 800x480 60FPS Uwaga: To jest ten z tych proców jak w zestawie STM32H750-DK, który ma mało internal flash'a, w zamian jest multum externa flasha w zestawie. W zasadzie (choć nie zawsze jak na przykład dzisiejszy program z Vectordotami) jest on przeznaczony do wstępnego skonfigurowania proca i peryferiów, aby odpalić program umieszczony w zewnętrznym flash'u. W zestawie osadzono pamięci: 1. 256Mbit Hexadeca-SPI PSRAM (pseudo static RAM) mapowane od 0x90000000 do 92000000 2. 1Gbit OCTO-SPI external flash mapowany od 0x70000000 do 0x78000000 Iterfejs LTDC jest cały czas niemal taki sam i za każdym razem działają sztuki przyswojone w walce z zestawem STM32H750-DK jak na przykład zapisywanie framebuffora, wait for vsysnc, czy ustanowienie adresu framebuffora: define FRAME_ADDRESS 0x90000000 // change layer address from new frame LTDC_Layer1->CFBAR = (FRAME_ADDRESS); LTDC->SRCR = LTDC_SRCR_VBR; // or LTDC_SRCR_IMR for immediatelly change while((LTDC->CPSR & 0x0000ffff) < screen_Y); // wait for #screen_Y raster line while((LTDC->CDSR & LTDC_CDSR_VSYNCS) == 0); // wait for VSYNC uint32_t* pFBaddr = (uint32_t *) FRAME_ADDRESS; //--- // UTIL_LCD_Clear(UTIL_LCD_COLOR_BLACK); for(uint32_t i=0; i< (256*1024); i++){ pFBaddr[i] = UTIL_LCD_COLOR_BLACK; // fill SDRAM layer buffors as black color ; UTIL_LCD_COLOR_BLACK = 0xFF000000 } Także sztuczka z uruchomieniem działającego programu operującego na framebuforze jest cały czas taka sama: Zaadoptowanie przykładu używającego LCD z firmowych dem. Dzisiaj moim przyjacielem było demo o nazwie "Bsp". Ten procesor ma raptem 64KB internal flash'a co mimo wszystko nie jest aż tak mało, bo program z Vectordotami zajmuje tylko 23KB, gdzie lwia część z tego to procedury BSP i HAL konfigurujące procka. O ile pamiętam procek grasuje na 400MHZ co i tak dla vectordotów jest aż nadto, więc nie było sensu kręcić go na maxa. Żeby nie zamazywać fabrycznego dema usytuowanego od 0x70000000 program jest skompilowany pod 0x08000000 (internal flash), zamazuje firmowy Boot do wystartowania dema, dlatego najpierw Boota odczytałem i zapisałem do HEX'a. Ponieważ ten zestaw to dość świeża sprawa, środowisko i STM32Cubeprogrammer zaczęło poprawnie z tym gadać po aktualizacji do najnowszej wersji. Tak wygląda firmowe demo: A tak uruchomione Vectordoty: #shorts W załączeniu pliki HEX 1 - firmowego Boota 2 - Vectordoty FW.zip 3. link do githuba z hexami: STM32H7S7-DK FANTASTIC VECTORDOTS Podsumowując - ten zestaw z LCD 5 cali, 800x480, 60FPS i procem ze wspomagaczami, dzięki którym utrzymuje to 60FPS, robi piorunujące wrażenie.
-
Jak w temacie - oczywiście pod warunkiem, że program się w RAM'ie zmieści. Zdarza się, że uruchomienie programu w RAM jest bardzo wygodne. W moim przypadku mam external loadery, które zapisują flash SPI przy użyciu ST-LINK UTILITY, albo STM32CubeProgrammer. Problem z tymi aplikacjami jest taki, że o ile zapis do pamięci flash jest bezbłędny, o tyle odczyt czy weryfikacja nie przebiega poprawnie, chociaż dane w pamięci SPI flash zostały właściwie zapisane. Oprócz weryfikacji chciałbym mieć możliwość odczytywania do pliku danych zawartych w tych pamięciach flash i w tym momencie bardzo wygodnie jest wrzucić program do RAM, odczytać żądane dane swoim programem na VCP, nie naruszając programu głównego osadzonego we flaszu mikrokontrolera. Oczywiście wielka szkoda że firmowe programy tego nie uciągną i trzeba to rozwiązać we własnym zakresie. Odnalazłem 2 metody do linkowania programu w RAM. Pierwszą zastosowałem dla BLUEPILL'a PLUS (F103). Wystarczy podać linkerowi że początkowa część RAM jest pamięcią flash. MEMORY { RAM (xrw) : ORIGIN = 0x20002800, LENGTH = 10K FLASH (rx) : ORIGIN = 0x20000000, LENGTH = 10K } Druga rzecz, to dodanie 2 linii w startupie do zainicjalizowania stosu (SP) Reset_Handler: /* set stack pointer */ mov r0, 0x20000000 ldr sp, [r0] Trzecia rzecz, aby wszystko sprawnie działało z przerwaniami, to relokacja tablicy wektorów za pomocą rejestru SCB->VTOR SCB->VTOR = (uint32_t) 0x20000000; I... już - program działa, przerwania dla HAL_Delay(500) przychodzą od Systicka i wszystko pięknie śmiga. Pierwszy sposób, choć prosty, jest troszkę mniej poręczny od drugiego, w którym nie trzeba sztucznie dzielić pamięci RAM na FLASH i RAM. Drugi przykład wykonałem dla zestawu "Black board F407VE". Druga metoda jest dzięki uprzejmości MCD Application Team, który od pewnego czasu dla niektórych procesorów zaczął zamieszczać dwie wersje skryptu linkera, pierwszy z postfixem "FLASH", drugi z postfixem "RAM". Otóż wystarczy tylko w ustawieniach podmienić nazwę pliku linkera i na początku funkcji "main" zmienić adres tablicy wektorów jak w przypadku F103. Teraz jeszcze jedna rzecz - można by faktycznie binarki tych programów dołączać do programu we flaszu, który po resecie przepisywałby go do RAM, ale z drugiej strony... jaki sens ma umieszczenie programu z RAM we flash? Trochę dziwnie prawda? Chodzi mi o to, że takie programy z RAM zgodnie ze specyfikacją ARM'ów nie mają prawa uruchomić się, ponieważ proc startuje się z flasza!!! Co więcej, nawet gdyby zmieniono bootowanie na pamięć RAM, to nadal niewiele daje, ponieważ po odłączeniu zasilania w RAM przy starcie procka wartości są po prostu RANDOMOWE! I tutaj z pomocą przychodzi kolejny raz support STM32, który za pomocą programatora ST-link robi taką sztuczkę, że odczytuje adres startu programu (Entry Point) z tablicy wektorów i po zaprogramowaniu RAMu naszym programem na końcu rejestr PC zostaje zainicjowany adresem do Entry Pointa ! Prawda że genialne? W przypadku botowania ustawionego na flash program działa do pierwszego hardware'owego resetu. Nie sprawdzałem opcji, gdy bootowanie ustawione jest na RAM, ale według specyfikacji program powinien działać od nowa po hardware'owym resecie do momentu wyłączenia zasilania. W załączeniu programy: BLUEPILL_RAMRUN.zip , _F407_RAMRUN.zip Linki do repozytoriów na github: BLUEPILL PLUS RAM RUNNING STM32F407VET6 BLACK RUNNING IN RAM
-
- 1
-
-
- ARM
- PROGRAMOWANIE
-
(i 1 więcej)
Tagi:
-
Zestaw STM32F769i-DISCO przedstawia się następująco: Z danych technicznych posiada on: 128 Mbit SDRAM (mapowane od 0xC0000000) 512 Mbit QSPI flash 4 calowy LCD 800x480, pojemnościowy dotyk 2 MB internal FLASH i 532 KB wewnętrznego RAM, procka można kręcić do bodaj 216 MHZ Wyświetlacz jest identyczny, jak w zestawie 469i, czyli na jedną ramkę bufora obrazu w trybie ARGB8888 potrzeba 1500KB RAMU. Idąc tropem zestawu 469i, niewiele się napracowałem, żeby dobić się do frame bufora. Z przykładów do zestawu użyłem LCD_PicturesFromSDCard następnie w funkcji main() użyłem niemal identycznego kodu, jak w zestawie 469i, co wystarczyło do wyrysowania kolorowej flagi na ekranie: BSP_LCD_SetTransparency(LTDC_ACTIVE_LAYER_BACKGROUND, 255); BSP_LCD_SetTransparency(LTDC_ACTIVE_LAYER_FOREGROUND, 0); #define FRAME_ADDRESS 0xC0000000 LTDC_Layer1->CFBAR = FRAME_ADDRESS ; // set frame buffor address to 0xC0000000 LTDC->SRCR = LTDC_SRCR_VBR; // or LTDC_SRCR_IMR for immediatelly change uint32_t* pFBaddr ; pFBaddr = (uint32_t *) FRAME_ADDRESS; // 0xC00000000 for(uint32_t i=0; i< (1500*1024/8); i++){ pFBaddr[i] = LCD_COLOR_RED; // fill SDRAM layer buffor at 0xC0000000 as red color pFBaddr[i+192000] = LCD_COLOR_BLUE; // and second half fill as blue color } Raptem 10 linii kodu i sprawa załatwiona. Znów BSP odwaliło 99,9% roboty. Funkcja main() zajmuje niewiele linii więcej od powyższego kodu: int main(void) { MPU_Config(); CPU_CACHE_Enable(); HAL_Init(); SystemClock_Config(); BSP_LED_Init(LED1); /*##-1- Configure LCD ######################################################*/ LCD_Config(); BSP_LCD_SetTransparency(LTDC_ACTIVE_LAYER_BACKGROUND, 255); BSP_LCD_SetTransparency(LTDC_ACTIVE_LAYER_FOREGROUND, 0); #define FRAME_ADDRESS 0xC0000000 LTDC_Layer1->CFBAR = FRAME_ADDRESS ; // set frame buffor address to 0xC0000000 LTDC->SRCR = LTDC_SRCR_VBR; // or LTDC_SRCR_IMR for immediatelly change uint32_t* pFBaddr ; pFBaddr = (uint32_t *) FRAME_ADDRESS; // 0xC00000000 for(uint32_t i=0; i< (1500*1024/8); i++){ pFBaddr[i] = LCD_COLOR_RED; // fill SDRAM layer buffor at 0xC0000000 as red color pFBaddr[i+192000] = LCD_COLOR_BLUE; // and second half fill as blue color } /* Main infinite loop */ while(1){} } Skompilowany program zajmuje poniżej 30KB: Efekt działania programu: W załączeniu źródła LCD_PicturesFromSDCard.zip Tym sposobem rozpracowany został kolejny zestaw z LCD.
-
Jest taki zestaw oferowany między innymi na Aliexpress: Oprócz tego można dokupić dedykowany do zestawu wyświetlacz, co ma wielki sens: Z wielu powodów ma to sens, choćby że jest to znakomicie dopasowane, a LCD jest dołączone do 16 bitowej magistrali FSMC dzięki czemu wyświetlacz nie jest tragicznie wolny. Schemat zestawu: Podstawowe właściwości zestawu: 1. Procesor STM32F407VET6 - max do 168MHZ, 512KB internal FLASH i 192KB internal static RAM, obudowa LQFP100 2. LCD 3.2 cala, rozdzielczość 320x200, FSMC 16bit sterownik ILI9341 Resztę dodatkowych użytków widać na schemacie i zdjęciu, dlatego pominę ich wyliczanie. Istnieją także inne wersje np. niegdyś sprzedawane przez sklep OMDAZZ, także wersje black z procesorem STM32F407ZE lub ZGT6 (LQFP144). To co jest tutaj głównym celem, to sprawne skonfigurowanie i oprogramowanie wyświetlacza. Nie jest to może super łatwe, ale mając do dyspozycji CUBE, czyli graficzny interfejs do ustalenia ustawień, nie jest to także bardzo trudne. Konfiguracja procesora w CUBE wygląda tak: Procesor jest ustawiony na 168MHZ i taktowanie z zewnętrznego kwarcu. Ustawienia FSMC są widoczne w CUBE, a dla uzupełnienia podam kod wynikowy inicjujący magistralę FSMC: /* FSMC initialization function */ static void MX_FSMC_Init(void) { FSMC_NORSRAM_TimingTypeDef Timing = {0}; FSMC_NORSRAM_TimingTypeDef ExtTiming = {0}; /** Perform the SRAM1 memory initialization sequence */ hsram1.Instance = FSMC_NORSRAM_DEVICE; hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE; /* hsram1.Init */ hsram1.Init.NSBank = FSMC_NORSRAM_BANK1; hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE; hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM; hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16; hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE; hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW; hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE; hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS; hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE; hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE; hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_ENABLE; hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE; hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE; hsram1.Init.PageSize = FSMC_PAGE_SIZE_NONE; /* Timing */ Timing.AddressSetupTime = 15; Timing.AddressHoldTime = 15; Timing.DataSetupTime = 60; Timing.BusTurnAroundDuration = 0; Timing.CLKDivision = 16; Timing.DataLatency = 17; Timing.AccessMode = FSMC_ACCESS_MODE_A; /* ExtTiming */ ExtTiming.AddressSetupTime = 9; ExtTiming.AddressHoldTime = 15; ExtTiming.DataSetupTime = 8; ExtTiming.BusTurnAroundDuration = 0; ExtTiming.CLKDivision = 16; ExtTiming.DataLatency = 17; ExtTiming.AccessMode = FSMC_ACCESS_MODE_A; if (HAL_SRAM_Init(&hsram1, &Timing, &ExtTiming) != HAL_OK) { Error_Handler( ); } } Kolejnym poziomem jest "dogadanie się" z ILI9341 poprzez sterownik. Pominę tu jego program, jest on dostępny w załączonych źródłach. Końcowy program w bin ma poniżej 5000 bajtów, i robi następująco: 1. Inicjalizuje systemclock na 168MHZ 2. Inicjalizuje GPIO i FSMC 3. Inicjalizuje LCD 4. Wypełnia ekran kolorem niebieskim, poniżej zdjęcie: W załączeniu program z STM32CubeIDE: STM32F407VET6-BLACK-ILI9341-INIT.zip Wersja na github z załączonymi plikami dokumentacji: STM32F407VET6-BLACK-ILI9341-INIT Bardziej rozbudowaną wersję można znaleźć na moim githubie: STM32F407VET6-BLACK-ILI9341-BENCHMARK Działanie benchmarku można obejrzeć na youtube:
-
Z powodów sobie znanych mógłby ktoś chcieć uruchomić część kodu w RAM i nie jest to niemożliwe, a dzięki uprzejmości supportowi STM32 MCD Application Team zostało to zaimplementowane do prostego użycia. Programiści MCD Application Team dodali takie dwie ciekawe linie w sekcji ".data" skryptu linkera: *(.RamFunc) /* .RamFunc sections */ *(.RamFunc*) /* .RamFunc* sections */ To oznacza, że napisane funkcje z atrybutem (section(".RamFunc")) będą przez kompilator i linker traktowane jako dane do przepisania z flash'a do RAM. Sporządziłem mały programik dla BLUEPILL'a do blinkania diodą, żeby użyć tego ficzeru. Program jest bardzo mały, stworzony w STM32CubeIDE. W zasadzie składa się z 3 plików: 1. Rozbiegówki w assemblerze (to co się dzieje po resecie) 2. Pliku main.c w którym są zamieszczone 2 funkcje. Funkcja main(), oraz blink(). Funkcja blink jest funkcją przepisywaną do RAM. 3. Skryptu linkera. Wszystko jest okrojone do minimum, pousuwane zbędne includy, nieużywane opcje itd. Widok projektu: Funkcja blink (to uruchamiana w RAM) ma nadane 3 atrybuty: __attribute__ ((section(".RamFunc"), long_call, naked )) void blink(void) 1. Sekcji, wydzielającej ją do RAM 2. Atrybutu naked usuwającego zarządzanie stosem i zapisywaniem rejestrów (w tym konkretnym wypadku nie było to potrzebne) dla skrócenia programu 3. long_call pozwala skrócić wywoływanie procedury z RAM i zapobiega tworzeniu przez linker dodatkowej funkcji pośredniczącej w wywołaniu funkcji umieszczonej w RAM. Z podanych atrybutów obowiązkowy jest tylko pierwszy, pozostałe są użyte przeze mnie, ale nie są niezbędne do prawidłowego działania. Funcja blink() została napisana w assemblerze (raptem 11 linii) i robi dokładnie to, co 2 zakomentowane linie w C, oraz wykonuje powrót do miejsca wywołania z funkcji main() (bx lr). // GPIOB_ODR_REG ^= GPIOB_ODR_ODR2; // for(unsigned long int delay = 500000 ; delay != 0 ; delay--){} Na "rozbieg" po resecie wykonywany jest plik startupowy, również okrojony przeze mnie do niezbędnego minimum - oto jego zawartość: .syntax unified .cpu cortex-m3 .fpu softvfp .thumb .global g_pfnVectors .global .isr_vector .word _sidata .word _sdata .word _edata /******************************************************************************/ .section .text.Reset_Handler .weak Reset_Handler .type Reset_Handler, %function Reset_Handler: /* Copy the data segment initializers from flash to SRAM */ ldr r0, =_sdata ldr r1, =_edata ldr r2, =_sidata movs r3, #0 CopyDataInit: ldr r4, [r2, r3] str r4, [r0, r3] adds r3, r3, #4 LoopCopyDataInit: adds r4, r0, r3 cmp r4, r1 bcc.n CopyDataInit /* Call the application's entry point.*/ b.n main .size Reset_Handler, .-Reset_Handler /******************************************************************************/ .section .isr_vector,"a",%progbits .type g_pfnVectors, %object .size g_pfnVectors, .-g_pfnVectors /******************************************************************************/ g_pfnVectors: .word _estack .word Reset_Handler /******************************************************************************/ Program z tego pliku przepisuje funkcę blink() z pamięci FLAH do RAM i następnie wykonuje skok do funkcji main() Funkcje main() i blink() nie są skomplikowane, a całość razem z definicjami i klamrami zamknęła się w około 40 liniach kodu... (...kodu?...) #define RCC_APB2ENR (*((unsigned long int *)0x40021018)) #define RCC_APB2ENR_IOPBEN (unsigned long int) 0x08 #define GPIOB_BASE (unsigned long int) 0x40010C00 #define GPIOB_CRL_REG (*((unsigned long int *) GPIOB_BASE)) #define GPIOB_CRL_VALUE (unsigned long int) 0x44484244 //#define GPIOB_ODR_REG (*((unsigned long int *) (GPIOB_BASE + 12))) //#define GPIOB_ODR_ODR2 (unsigned long int) 04 //----------------------- __attribute__ ((section(".RamFunc"), long_call, naked )) void blink(void) { // GPIOB_ODR_REG ^= GPIOB_ODR_ODR2; __asm ("ldr r0, gpiob_odr"); __asm ("ldr r1, [r0]"); __asm ("eor r1, #4"); __asm ("str r1, [r0]"); // for(unsigned long int delay = 500000 ; delay != 0 ; delay--){} __asm ("ldr r0, del_limit"); __asm ("calc: subs r0, #1"); __asm ("bne calc"); __asm ("bx lr"); __asm ("gpiob_odr: .word 0x40010C0C"); __asm ("del_limit: .word 500000"); } //---------------------- __attribute__ ((naked)) int main(void) { RCC_APB2ENR |= RCC_APB2ENR_IOPBEN; GPIOB_CRL_REG = GPIOB_CRL_VALUE; while (1) { blink(); } } Całość jest linkowana według skryptu konsolidatora, który również okroiłem do minimum. /* Entry Point */ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */ _Min_Heap_Size = 0x200; /* required amount of heap */ _Min_Stack_Size = 0x400; /* required amount of stack */ /* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 64K } /* Sections */ SECTIONS { /* The startup code into "FLASH" Rom type memory */ .isr_vector : { KEEP(*(.isr_vector)) /* Startup code */ } >FLASH /* The program code and other data into "FLASH" Rom type memory */ .text : { *(.text) /* .text sections (code) */ *(.text*) /* .text* sections (code) */ _etext = .; /* define a global symbols at end of code */ } >FLASH /* Used by the startup to initialize data */ _sidata = LOADADDR(.data); /* Initialized data sections into "RAM" Ram type memory */ .data : { _sdata = .; /* create a global symbol at data start */ *(.data) /* .data sections */ *(.data*) /* .data* sections */ *(.RamFunc) /* .RamFunc sections */ *(.RamFunc*) /* .RamFunc* sections */ _edata = .; /* define a global symbol at data end */ } >RAM AT> FLASH /* Uninitialized data section into "RAM" Ram type memory */ .bss : { /* This is used by the startup in order to initialize the .bss section */ _sbss = .; /* define a global symbol at bss start */ __bss_start__ = _sbss; *(.bss) *(.bss*) *(COMMON) _ebss = .; /* define a global symbol at bss end */ __bss_end__ = _ebss; } >RAM } Cały skompilowany oraz zdeassemblowany program to około 80 linii w assemblerze: Disassembly of section .text: 08000008 <main>: } //---------------------- __attribute__ ((naked)) int main(void) { RCC_APB2ENR |= RCC_APB2ENR_IOPBEN; 8000008: 4b05 ldr r3, [pc, #20] ; (8000020 <main+0x18>) 800000a: 681b ldr r3, [r3, #0] 800000c: 4a04 ldr r2, [pc, #16] ; (8000020 <main+0x18>) 800000e: f043 0308 orr.w r3, r3, #8 8000012: 6013 str r3, [r2, #0] GPIOB_CRL_REG = GPIOB_CRL_VALUE; 8000014: 4b03 ldr r3, [pc, #12] ; (8000024 <main+0x1c>) 8000016: 4a04 ldr r2, [pc, #16] ; (8000028 <main+0x20>) 8000018: 601a str r2, [r3, #0] while (1) { blink(); 800001a: 4b04 ldr r3, [pc, #16] ; (800002c <main+0x24>) 800001c: 4798 blx r3 800001e: e7fc b.n 800001a <main+0x12> 8000020: 40021018 .word 0x40021018 8000024: 40010c00 .word 0x40010c00 8000028: 44484244 .word 0x44484244 800002c: 20000001 .word 0x20000001 08000030 <Reset_Handler>: .weak Reset_Handler .type Reset_Handler, %function Reset_Handler: /* Copy the data segment initializers from flash to SRAM */ 8000030: 4805 ldr r0, [pc, #20] ; (8000048 <LoopCopyDataInit+0xa>) ldr r0, =_sdata 8000032: 4906 ldr r1, [pc, #24] ; (800004c <LoopCopyDataInit+0xe>) ldr r1, =_edata 8000034: 4a06 ldr r2, [pc, #24] ; (8000050 <LoopCopyDataInit+0x12>) ldr r2, =_sidata 8000036: 2300 movs r3, #0 CopyDataInit: 8000038: 58d4 ldr r4, [r2, r3] 800003a: 50c4 str r4, [r0, r3] 800003c: 3304 adds r3, #4 800003e: 18c4 adds r4, r0, r3 8000040: 428c cmp r4, r1 8000042: d3f9 bcc.n 8000038 <CopyDataInit> /* Call the application's entry point.*/ 8000044: e7e0 b.n 8000008 <main> 8000046: 0000 .short 0x0000 8000048: 20000000 .word 0x20000000 _sdata 800004c: 2000001e .word 0x2000001e _edata 8000050: 08000054 .word 0x08000054 _sidata Disassembly of section .data: 20000000 <blink>: 20000000: f8df 0010 ldr.w r0, [pc, #16] ; 20000014 <gpiob_odr> "ldr r0, gpiob_odr" 20000004: 6801 ldr r1, [r0] 20000006: f081 0104 eor.w r1, r1, #4 2000000a: 6001 str r1, [r0, #0] 2000000c: 4802 ldr r0, [pc, #8] ; (20000018 <del_limit>) "ldr r0, del_limit" 2000000e <calc>: 2000000e: 3801 subs r0, #1 20000010: d1fd bne.n 2000000e <calc> 20000012: 4770 bx lr 20000014 <gpiob_odr>: 20000014: 40010c0c .word 0x40010c0c 20000018 <del_limit>: 20000018: 0007a120 .word 0x0007a120 } Gwoli ścisłości dodam, że program testuję na BlePillPLUS, który jest ulepszoną wersją pierwotnego BluePilla. Posiada 128KB wewnętrznej pamięci flash, jest lepiej zaprojektowane PCB i ma możliwość wlutowania szeregowej pamięci, do SPI1 procesora tj. PA5, PA6, PA7 z CS/ na PA4. Jest także drobna różnica, że LED nie jest jak w wersji poprzedniej na PC13, tylko jest zamontowane na PB2. Schemat płytki: Porównanie z poprzednią wersją: W załączniku omawiany program wyprodukowany w STM32CubeIDE BLUEPILL_BLINK.ZIP
-
Micromouse Robot typu micromouse Aether
Wojcik98 opublikował temat w Projekty - DIY w budowie (worklogi)
Cześć wszystkim! Po kolejnej kilkuletniej przerwie w udzielaniu się na forum postanowiłem się podzielić się postępami prac nad moim nowym projektem: robotem typu micromouse Aether. Projekt jest na razie w dość wstępnych etapach i dużo może się jeszcze zmienić. Planuję początkowo pracować sobie w symulacji i obserwować jak się tam robot zachowuje, a potem dopiero zabierać się za prawdziwą konstrukcję. Mikrokontroler Pomysł na projekt pojawił się jak dowiedziałem się, że ESP32 są dwurdzeniowe i posiadają wbudowane moduły Wi-Fi. Marzyło mi się wtedy, żeby na jednym rdzeniu robił się główny kod robota, a drugi rdzeń by publikował wizualizację przez Wi-Fi do ROSa. Jak jednak przyszło mi do programowania ESP, to jednak się nie polubiłem z nim. Ekosystem wydał mi się jeszcze niedojrzały i procesor nie miał takiej dobrej obsługi peryferiów, jak bardziej mi znane STM32, na które się ostatecznie przerzuciłem. Jednym z głównych powodów jest dobra obsługa DMA, przez co będę mógł łatwo pobierać dane ze wszystkich czujników i procesor będzie o tym myślał tylko przez bardzo krótki czas. Ostatecznego modelu jeszcze nie mam wybranego, ale na razie celuję w STM32F446RE, bo mam z nim płytkę Nucleo. Trochę się zastanawiam na dodaniem jakiegoś modułu bezprzewodowego, żeby móc streamować dane z robota na bieżąco, ale nie jestem jeszcze przekonany. Czujniki We wcześniejszych robotach używałem prostych czujników LED + fototranzystor, które na podstawie jasności odbieranego światła szacowały odległość do ścianek. Używanie ich było dość upierdliwe, bo były one wrażliwe na zewnętrzne oświetlenie, odbijalność ścianek, itd. W tym projekcie postanowiłem spróbować czujników Time-of-Flight (ToF), które mierzą czas pomiędzy wysłaniem wiązki światła i jej powrotem (oczywiście planuję używać gotowych modułów, nie podejmuję się mierzenia taki krótkich odcinków czasu). Odczyt z nich zależy już głównie od odległości do obiektu i nie są tak wrażliwe na inne zakłócenia (kolor, oświetlenie). Z drugiej strony, nie widziałem ich też za bardzo w innych robotach tego typu, więc być może mają jakąś inną wadę, która mi sprawi niespodziankę. Obecnie planuję użyć czujników VL53L4CD. Mają zasięg od 1 do 1300 mm z precyzją 1mm, z pomiarami do 100 Hz. Mają dość duży kąt widzenia (FoV) 18°, który może potencjalnie sprawiać problemy z np. wykrywaniem podłogi, ale zobaczę jak będą mi wypadały testy. Zastanawiam się też, czy nie będzie zakłóceń pomiędzy dwoma czujnikami znajdującymi się obok siebie, będę to musiał przetestować. Nie są one turbo drogie, bo kosztują ~12 zł/szt. przy zakupie samych sensorów (bo gotowe płytki z nimi chodzą już po ~90). Jeśli będą źle wypadały, to rozglądnę się za czymś innym. Myślę też o użyciu jakiegoś IMU, głównie do mierzenia prędkości obrotowej żyroskopem, ale być może użyję też akcelerometru. Do testów na razie wziąłem LSM6DS3. Dodatkowo jakieś enkodery na koła, też konkretów jeszcze nie mam, ale pewnie coś typu AS5040. Mechanika Pod tym względem jest chyba jeszcze najmniej ustalone. Na pewno chcę, żeby mysz była w stanie jeździć po przekątnej, co stawia górne ograniczenie na szerokość na ~110 mm. Będę też pewnie celował w konstrukcję czterokołową, bo nie jest bardzo skomplikowana mechanicznie, a dużo dobrych konstrukcji tego używa. Będę prawdopodobnie używał względnie popularnych opon Mini-Z z wydrukowanymi felgami. Silników i przekładni jeszcze nie wybrałem. Jak patrzę na ceny silników Faulhabera czy Maxona, których używają co lepsi zawodnicy, to trochę mnie słabi, więc możliwe że zostanę na razie ze starymi dobrymi Pololu i w kolejnych konstrukcjach będę celował w wyższą półkę. Nie planuję tutaj turbinki przyciągającej do podłoża, za dużo nowego (i drogiego) na raz by było. Oprogramowanie Czyli to, co Wojcik98 lubi najbardziej. Kod, który napiszę, chciałbym móc jakoś przetestować razem z wizualizacją, dlatego główna logika robota będzie zamknięta w bibliotece C++, którą będę mógł potem skompilować do użytku na kompie albo na mikrokontrolerze. Do developmentu używam VS Code z Dockerem, gdzie już mam wszystko poinstalowane (zmuszenie GUI do działania było upierdliwe, ale działa). Całość kodu trzymam w repo na GitHubie: Wojcik98/aether. Na kompie korzystam z frameworku ROS 2 Jazzy, który ułatwia komunikację między programami. Wykorzystuję go głównie dla jego wizualizacji, co umożliwi weryfikację tego co “myśli” mysz o labiryncie i ułatwi debugowanie. Poniżej screen z wizualizacją robota (wstępny model) i pomiarami z czujników. Do symulacji używam Gazebo, głównie ze względu na dobrą integrację z ROSem. Wydaje mi się, że do względnie prostej i wstępnej symulacji jest on wystarczający, a fine-tuning i tak trzeba będzie robić na prawdziwym robocie. Domyślnie nie ma w nim zwykłych jednowymiarowych czujników odległości, dlatego zastępuję je lidarami z tylko jedną wiązką pomiarową, i później po prostu konwertuję typ wiadomości wysyłanej do reszty systemu. Nie pozwala to symulować dużego FoV rzeczywistych czujników, ale na początek chyba wystarczy. Zamierzam dorobić skrypty do tworzenia symulacji z konkretnymi labiryntami i z losowymi. Poniżej screen z symulacji (na razie niezbyt ciekawy). Głównym problemem micromouse nie jest zmapowanie labiryntu, ale potem przejechanie go jak najszybciej. Dlatego zaczynam kodzenie od modułu lokalizacji, zakładając że już znam cały labirynt. Na razie rozważam PF (particle filter) lub UKF (unscented Kalman filter) do śledzenia lokalizacji robota. Dlaczego nie jakiś zwykły filtr Kalmana? Wyobraźmy sobie bardzo długą prostą w labiryncie, bez żadnych zakrętów. Robot dość szybko zgubi pewność, gdzie dokładnie wzdłuż tego korytarza jest i dopiero gdy zauważy pierwszy zakręt będzie mógł się znowu odnaleźć. Zaimplementowanie tego w zwykłym KF byłoby dość trudne, jeśli nie niemożliwe, tymczasem przy metodach samplujących liczę na to, że wtedy jeden z wylosowanych punktów będzie znajdował się przy zakręcie i będzie mógł wyraźnie dać znać, że to on jest prawdziwą lokalizacją. Czy wybiorę UKF czy PF to przetestuję, PF pozwala na większą nieliniowość całego systemu, ale może być cięższe obliczeniowo. Jeśli będę mógł dokładnie śledzić położenie robota w labiryncie, to przejdę do znajdywania najkrótszej ścieżki i przejechania jej. Dużo nad tym jeszcze nie myślałem, zacznę pewnie od prostych zakrętów pod kątem prostym, a potem zacznę się bawić ze skosami i łukami. I na koniec to, co się dzieje na początku, czyli mapowanie. Formalnie, to lokalizacja tutaj może być bardziej skomplikowana, bo jednocześnie się lokalizujemy w mapie i tworzymy mapę na podstawie naszej lokalizacji (SLAM). Ale myślę, że powinna zadziałać prosta strategia jeżdżenia powoli, przez co będę mógł ufać, że odczyty enkoderów pokrywają się z rzeczywiście przejechaną odległością. Problemem tutaj znowu mogą być długie korytarze, ale będę się tym przejmował jak faktycznie coś nie będzie działać. Na razie pozwalam sobie korzystać z dobroci C++ i używam jakichś fajnych konstrukcji typu obiektowość. Mam nadzieję, że nie okaże się to zbyt wolne, bo będę musiał wtedy zacząć optymalizować. Na razie to chyba tyle. Mam nadzieję, że nie znudzi mi się ten projekt po miesiącu i że jednak uda mi się go dokończyć . Będę się starał wrzucać tutaj aktualizacje przy większych postępach. Wszelkie opinie, uwagi i pytania dotyczące projektu mile widziane -
Hej wszystkim! Mam problem z animacją na wyświetlaczu SSD1331 OLED przy użyciu mikrokontrolera STM32F411VET6 Discovery. Głównym problemem jest migotanie animowanego obrazu, które próbowałem rozwiązać, wprowadzając podwójne buforowanie. Niestety, po implementacji tej metody pojawiły się inne problemy: obraz zmienia kolory, a na ekranie pojawiają się dziwne artefakty graficzne. Nie wiem gdzie leży problem jestem początkujący w temacie stm32 jak i w C, dlatego proszę o pomoc. Mój kod: uint8_t ssd1331_check_exposed_pixel(uint8_t x_min, uint8_t x_max, uint8_t y_min, uint8_t y_max, uint8_t pixel_value, uint8_t xppos, uint8_t yppos){ if(xppos > x_max || xppos < x_min || yppos > y_max || yppos < y_min || pixel_value == 0x00){ return 1; } return 0; } void ssd1331_draw_animation(uint8_t new_chXpos, uint8_t new_chYpos, uint8_t old_chXpos, uint8_t old_chYpos, const uint8_t *pchBmp, const uint8_t * pBackBmp, uint8_t chWidth, uint8_t chHeight){ ssd1331_draw_bitmap(new_chXpos, new_chYpos, pchBmp, chWidth, chHeight, 0); uint16_t i, j; int16_t deltaX = (int16_t)(new_chXpos - old_chXpos); int16_t deltaY = (int16_t)(new_chYpos - old_chYpos); for(j = 0; j < chHeight + ((deltaY < 0) ? -deltaY : deltaY); j++){ for(i = 0; i < chWidth + ((deltaX < 0) ? -deltaX : deltaX); i++){ uint16_t x = (deltaX < 0) ? (uint16_t)(new_chXpos + i) : (uint16_t)(old_chXpos + i); uint16_t y = (deltaY < 0) ? (uint16_t)(new_chYpos + j) : (uint16_t)(old_chYpos + j); int16_t local_x = (int16_t)(x - new_chXpos); int16_t local_y = (int16_t)(y - new_chYpos); if (local_x >= 0 && local_x < chWidth && local_y >= 0 && local_y < chHeight) { uint16_t pixel_value = (uint16_t)((pchBmp[(local_y * chWidth + local_x) * 2] << 8) | pchBmp[(local_y * chWidth + local_x) * 2 + 1]); if(ssd1331_check_exposed_pixel( ((deltaX > 0) ? (uint16_t)old_chXpos : (uint16_t)new_chXpos), ((deltaX > 0) ? (uint16_t)new_chXpos : (uint16_t)old_chXpos), ((deltaY > 0) ? (uint16_t)old_chYpos : (uint16_t)new_chYpos), ((deltaY > 0) ? (uint16_t)new_chYpos : (uint16_t)old_chYpos), pixel_value, x, y) ){ uint16_t back_pixel = (uint16_t)((pBackBmp[(y * OLED_WIDTH + x) * 2] << 8) | pBackBmp[(y * OLED_WIDTH + x) * 2 + 1]); ssd1331_draw_point(x, y, back_pixel); } } } } } void ssd1331_draw_bitmap(uint8_t chXpos, uint8_t chYpos, const uint8_t *pchBmp, uint8_t chWidth, uint8_t chHeight, uint16_t hwColor) { uint16_t i, j; for(j = 0; j < chHeight; j++) { for(i = 0; i < chWidth; i++) { uint16_t pixelIndex = (j * chWidth + i) * 2; uint16_t color = (pchBmp[pixelIndex] << 8) | pchBmp[pixelIndex + 1]; if(color != 0x0000) ssd1331_draw_point(chXpos + i, chYpos + j, color); } } } void ssd1331_draw_point(uint8_t chXpos, uint8_t chYpos, uint16_t hwColor) { if (chXpos >= OLED_WIDTH || chYpos >= OLED_HEIGHT) { return; } //set column point ssd1331_write_byte(SET_COLUMN_ADDRESS, SSD1331_CMD); ssd1331_write_byte(chXpos, SSD1331_CMD); ssd1331_write_byte(OLED_WIDTH - 1, SSD1331_CMD); //set row point ssd1331_write_byte(SET_ROW_ADDRESS, SSD1331_CMD); ssd1331_write_byte(chYpos, SSD1331_CMD); ssd1331_write_byte(OLED_HEIGHT - 1, SSD1331_CMD); //fill 16bit colour ssd1331_write_byte(hwColor >> 8, SSD1331_DATA); ssd1331_write_byte(hwColor, SSD1331_DATA); } static void ssd1331_write_byte(uint8_t chData, uint8_t chCmd) { if (chCmd) { __SSD1331_DC_SET(); } else { __SSD1331_DC_CLR(); } __SSD1331_CS_CLR(); __SSD1331_WRITE_BYTE(chData); __SSD1331_CS_SET(); __SSD1331_DC_SET(); } #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "SSD1331.h" #include "grass_texture.h" #include "golf_logo.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; SPI_HandleTypeDef hspi1; TIM_HandleTypeDef htim2; TIM_HandleTypeDef htim10; /* USER CODE BEGIN PV */ /* * ADC_Buffer[0] --> The x coordinate from joystick input * ADC_Buffer[1] --> The y coordinate from joystick input * ADC_Buffer[2] --> The Signal A from encoder input * ADC_Buffer[3] --> The Signal B from encoder input */ uint16_t ADC_Buffer[4]; uint8_t frame_update; /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_ADC1_Init(void); static void MX_SPI1_Init(void); static void MX_TIM2_Init(void); static void MX_TIM10_Init(void); /* USER CODE BEGIN PFP */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ uint8_t level = 0; uint8_t game_is_running = 1; uint8_t i, j = 4; /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_ADC1_Init(); MX_SPI1_Init(); MX_TIM2_Init(); MX_TIM10_Init(); /* USER CODE BEGIN 2 */ //ADC1/TIM2/OLED INICIALIZATION HAL_TIM_Base_Start(&htim2); HAL_TIM_Base_Start_IT(&htim10); if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*) ADC_Buffer, 4) != HAL_OK) Error_Handler(); ssd1331_init(); ssd1331_draw_bitmap(0, 0, image_data_grass_texture, 96, 64, 0); //ssd1331_draw_bitmap(16, 4, image_data_golf_logo, 64, 15, 0); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (game_is_running) { switch (level) { case 0: uint8_t lg_flag = 0; while (level == 0) { if (frame_update) { if ((i < 9) && (lg_flag == 0)) { i++; //ssd1331_draw_animation(16, i, 16, j, image_data_golf_logo, image_data_grass_texture, 64, 15); ssd1331_draw_animation(16, i, 16, j, image_data_golf_logo, image_data_grass_texture, 64, 15); j = i; frame_update = 0; } else { lg_flag = 1; j--; ssd1331_draw_animation(16, j, 16, i, image_data_golf_logo, image_data_grass_texture, 64, 15); frame_update = 0; i = j; if ((i >= 4) && (i < 5)) lg_flag = 0; } } } break; }; /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM10) { frame_update = 1; } }
-
Cześć wszystkim Zauważyłem że ostatnio nie pojawia się zbyt dużo nowych konstrukcji jeśli chodzi o linefollowery, dlatego chciałbym opisać swoją Nie jest to raczej nic odkrywczego – podobne projekty pojawiały się w przeszłości na tym forum, jednak mimo to podzielę się swoim i może zachęcę osoby które chciałby wykonać podobną konstrukcję i zacząć swoją przygodę z elektroniką, programowaniem bądź robotyką. Cały projekt udostępniłem publicznie na GitHubie, więc jeśli ktoś jest zainteresowany, zachęcam do zapoznania się z nim. Nie przedłużając, Linefollower Kwark jest moją drugą konstrukcją. Pierwszą, jakiś czas temu (sam nie pamiętam 6-7lat?) wykonałem jeszcze na 8bitowcu - Atmega32. Obecny projekt jest dosyć świeży, w zasadzie kilkanaście dni temu robot zaczął poruszać się po linii. Pracę nad nim zacząłem końcem sierpnia tego roku i wszystko znajduję się w fazie testów. Linefollower jeszcze nie brał udziału w żadnych zawodach. Krótki filmik pokazowy, wraz z przejazdem udostępniłem na YouTubie. KONSTRUKCJA MECHANICZNA Podwozie Podwozie robota składa się z dwóch płytek PCB połączonych za pomocą dwóch wąskich listew węglowych oraz taśmy FFC. Na końcu głównej płytki zamontowana jest metalowa blaszka, wygięta w sposób zapobiegający podnoszeniu się robota podczas gwałtownego przyspieszania. Jako ślizgacze zastosowałem materiał przypominający filc, choć nie jest to rozwiązanie idealne, materiał odkleja się podczas jazdy oraz wprowadza dodatkowy opór. Planuję zastąpić go czymś twardszym, np. przymocować lub wlutować rezystory THT. Akumulator litowo-polimerowy 2S 7.4V (o pojemnościach od 120 mAh do 220 mAh) montowany jest do płytki PCB za pomocą gumek recepturek - nie miałem jeszcze problemów z takim rozwiązaniem. Najczęściej używam akumulatora 220 mAh podczas testów, a pozostałe akumulatory służą głównie jako zapas podczas ładowania. Do tej pory nie sprawdziłem, czy masa akumulatora wpływa na czas przejazdu robota, nie chciałem zajmować się tym na obecnym etapie projektu. Napęd Napęd robota stanowią dwa szczotkowe silniki HPCB 10:1 z obustronnym wałem, do montażu wykorzystałem mocowania Pololu wydrukowane na drukarce 3d. Do wałów silników zamontowałem magnesy neodymowe (⌀6 mm, wysokość 2,5 mm), namagnesowane wzdłuż średnicy. Zostały one przymocowane za pomocą uchwytów również wydrukowanych na drukarce 3D. Koła wykonano z poliamidu, a opony pochodzą z modelu Mini-Z. Opony są jednak mocno zużyte (pozostałość po wcześniejszej konstrukcji), co negatywnie wpływa na trzymanie trasy. Planuję zakup nowych opon, prawdopodobnie również z modelu Mini-Z. ELEKTRONIKA Komponenty Poniżej załączam ogólny schemat blokowy, opisujący sposób komunikacji poszczególnych elementów robota: Mikrokontroler STM32F722 Wybrałem mikrokontroler STM32 oparty na rdzeniu ARM Cortex M7, taktowany zegarem 216 MHz. Jest wystarczająco wydajny, aby obsługiwać wszystkie obliczenia w rozsądnym czasie. W wyborze procesora nie ma się co ograniczać, projekt budujemy zazwyczaj w maksymalnie kilku sztukach więc cena nie gra tutaj roli. W przyszłości planuję zwiększyć częstotliwość wykonywanych pomiarów. Cortex-M7 umożliwia wykorzystanie pamięci cache oraz pamięci o niskiej latencji, ITCM i DTCM, co zapewnia spory zapas wydajności. 12 x Transoptory KTIR0711S Chyba najbardziej popularna i sprawdzona opcja. Czujniki są rozmieszczone w szeregu, tworząc delikatny łuk. Wyjścia podłączono bezpośrednio do przetwornika ADC mikrokontrolera. Każdemu czujnikowi odpowiada przypisana dioda LED. 2 x Enkodery AMS AS5040 Enkodery magnetyczne pozwalają na precyzyjne pomiary prędkości silników. Informacje te są wykorzystywane w algorytmie PID, zapewniając zamkniętą pętlę sterowania. Dokonują pomiaru kąta z rozdzielczością 10 bitową. Do pomiaru impulsów wykorzystałem wyjście kwadraturowe. 2 x Mostek H Pololu TB6612FNG Jeden mostek odpowiedzialny jest za sterowanie jednym silnikiem. Kanały mostka zostały scalone w celu zapewnienia większej wydajności prądowej. Zastosowałem je w poprzedniej konstrukcji i nie miałem z nimi problemu. Moduł Bluetooth JDY-31 Tutaj mógłby być w zasadzie każdy moduł, który komunikuje się za pomocą magistrali UART. Moduł JDY-31 miałem pod ręką, ale mógłby być również popularny HC-05. PCB Wszystkie płytki PCB zaprojektowałem w programie KiCad w wersji 8. Mogę polecić ten program początkującym – sam nie jestem ekspertem w projektowaniu PCB, a mimo to udało się stworzyć płytki w stosunkowo prosty sposób. Podczas projektowania popełniłem dwa błędy. Pierwszym było błędne przypisanie sygnału z enkodera do pinu mikrokontrolera, co udało się naprawić za pomocą wlutowanego kynara. Drugi błąd to nieprawidłowa kolejności pinów w złączu Bluetooth - sposób naprawy jest widoczny na zdjęciach wyżej . PCB zamawiałem w JLCPCB i byłem zaskoczony szybkością realizacji oraz wysyłki do Polski. Główna płytka jest czterowarstwowa. Początkowo płytka z sensorami również była czterowarstwowa. Podczas sprawdzania cen różnica między płytkami dwu- i czterowarstwowymi wydawała się niewielka. Niestety, przy składaniu zamówienia okazało się, że dla płytek przekraczających 10 cm różnica w cenie jest większa. W rezultacie zdecydowałem się przerobić płytkę z sensorami na dwuwarstwową, natomiast główną płytkę pozostawiłem w wersji czterowarstwowej. Druga i trzecia warstwa głównej płytki stanowią masę, pierwsza to warstwa sygnałowa, a czwarta służy do sygnałów i zasilania. Jeśli chodzi o elementy pasywne, zdecydowałem się na rozmiar 0603 – są stosunkowo łatwe do lutowania i nie zajmują zbyt wiele miejsca na płytce. Sensory ułożone są w delikatny łuk, kształt płytki skopiowałem z poprzedniego robota. Dopełnieniem są dwie małe płytki, na których wlutowany jest sensor AMS AS5040. OPROGRAMOWANIE Kod na mikrokontroler został napisany w całości w języku C. Standardowo, do konfiguracji driverów użyłem programu STM32CubeMX. Do debuggowania kodu korzystałem z programu SEGGER Ozone. Oprócz standardowej aplikacji, przygotowałem projekt bootloadera oraz aplikację na PC, która została napisana w QT. Główny algorytm Sposób działania głównego kodu opiera się na sterowanej zdarzeniami maszynie stanów (ang. event driven state machine). Przetwarzanie zabranych danych oraz aktualizacja sygnału wyjściowego następuje w przypadku zakończenia konwersji ADC. Na ten moment jest to co 5ms. Oprócz standardowego algorytmu PID, dodałem między innymi wykrywanie kątów prostych, zwalnianie w przypadku niestabilności sensorów. Do zapisu danych kalibracyjnych wykorzystałem jedną stronę pamięci FLASH, wszystkie istotne parametry można modyfikować za pomocą aplikacji. Dane są zabezpieczone za pomocą CRC, a w przypadku błędnej sumy kontrolnej wczytywane są dane domyślne. Całość kodu starałem się ładnie podzielić na moduły, których opis jest dostępny tutaj. Bootloader Dzięki bootloaderowi nie musimy nawet podpinać programatora, gdy jest potrzeba aby coś szybko zaktualizować. Przesyłanie aplikacji następuje przez Bluetooth z wykorzystaniem mojego protokołu SCP. Do bootloadera możemy wejść dwiema metodami: 1. Poprzez wysłanie odpowiedniej komendy. 2. Poprzez backdoor tj. wysłanie odpowiedniej komendy podczas włączenia zasilania. Dzięki temu możemy zawsze zaktualizować oprogramowanie (możliwość wejścia do bootloadera, nawet jeżeli w aplikacji jest błąd przez który jednostka cały czas się resetuje). Organizację pamięci FLASH przedstawiłem na zdjęciu poniżej: Aplikacja QT (C++) Aplikacja dostarcza graficzny interfejs, umożliwiający sterowanie, konfigurację oraz monitorowanie linefollowera. Główne funkcjonalności to: Panel połączenia: Kontrola komunikacji przez Bluetooth. Panel sterowania: Funkcje uruchamiania, zatrzymywania, resetowania i kalibracji robota. Oraz opcje takie jak: włączenie trybu debugowania, odczyt/zapis NVM, wyświetlanie danych w czasie rzeczywistym. Panel sensorów: Aktualne wartości, wartości kalibracyjne oraz wagi przypisane do poszczególnych sensorów. Panel konfiguracji: Ustawienie pozostałych parametrów znajdujących się w pamięci nieulotnej mikrkontrolera. Wykresy prezentujące prędkości silników, błędy z sensorów. Bootloader: Obsługa aktualizacji oprogramowania. Logi PODSUMOWANIE Zbudowanie robota zajęło mi około 3 miesiące wykorzystując głównie wieczory po swojej pracy zawodowej. Aby zwiększyć docelową prędkość poruszania się linefollowera wciąż potrzebne jest jeszcze sporo testów i być może zmian w algorytmie. Mam nadzieję tylko że motywacji nie zabraknie
-
- 14
-
-
- Line follower
- linefollower
-
(i 2 więcej)
Tagi:
