Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'Stm32'.

  • Szukaj wg tagów

    Wpisz tagi, oddzielając przecinkami.
  • Szukaj wg autora

Typ zawartości


Kategorie forum

  • Elektronika i programowanie
    • Elektronika
    • Arduino i ESP
    • Mikrokontrolery
    • Raspberry Pi
    • Inne komputery jednopłytkowe
    • Układy programowalne
    • Programowanie
    • Zasilanie
  • Artykuły, projekty, DIY
    • Artykuły redakcji (blog)
    • Artykuły użytkowników
    • Projekty - DIY
    • Projekty - DIY roboty
    • Projekty - DIY (mini)
    • Projekty - DIY (początkujący)
    • Projekty - DIY w budowie (worklogi)
    • Wiadomości
  • Pozostałe
    • Oprogramowanie CAD
    • Druk 3D
    • Napędy
    • Mechanika
    • Wydarzenia
    • Sprzedam/Kupię/Zamienię/Praca
    • Inne
  • Ogólne
    • Ogłoszenia organizacyjne
    • Dyskusje o FORBOT.pl
    • Na luzie

Kategorie

  • Quizy o elektronice
  • Quizy do kursu elektroniki I
  • Quizy do kursu elektroniki II
  • Quizy do kursów Arduino
  • Quizy do kursu STM32L4
  • Quizy do pozostałych kursów

Szukaj wyników w...

Znajdź wyniki, które zawierają...


Data utworzenia

  • Rozpocznij

    Koniec


Ostatnia aktualizacja

  • Rozpocznij

    Koniec


Filtruj po ilości...

Data dołączenia

  • Rozpocznij

    Koniec


Grupa


Imię


Strona


TempX

  1. 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.
  2. 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 😉
  3. 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
  4. 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.
  5. 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
  6. 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.
  7. 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:
  8. 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
  9. 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 🙂
  10. 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; } }
  11. 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 😄
  12. Hej, Chciałbym wam przedstawić mój ostatni popołudniowy projekt, którego używam w pracy i przy projektach z udziałem mikrokontrolerów STM32. Krótko mówiąc jest to opensourceow'a kopia niewspieranego już STMStudio. Dla tych, którzy nie słyszeli wcześniej o tym programie, był to realtime'owy logger wartości zmiennych, które były odczytywane przy pomocy programatora STlink bezpośrednio z mikrokontrolera przy użyciu interfejsu debugowego. Genialna sprawa do strojenia regulatorów PID, podglądu zmiennych procesowych, konfigów, czy nawet sterowania wykonaniem programu. Narzędzie o tyle fajne, że jest bezinwazyjne (nie zmienia zachowania softu zależnie od tego czy z niego korzystamy czy nie), pozwala na odczyt z częstotliwością dochodzącą nawet do 1kHz, przy niewielkiej ilości logowanych zmiennych, a także pozwala na zapisywanie wartości z poziomu GUI. Niestety od jakiegoś czasu jest niewspierane, a jego następca czyli Cube Monitor jest bardzo ograniczony i wydaje się również być obecnie porzucony. Dodatkowo STMStudio ma problem z dekorowanymi (manglowanymi) nazwami obiektów C++ - wbudowana wyszukiwarka ich po prostu nie widzi, a dodatkowo program działa tylko na Windowsie. Dochodzi do tego kilka wkurzających bugów i w efekcie program przestaje być już tak atrakcyjny. Nie mając innych alternatyw postanowiłem zrobić swoje narzędzie w oparciu o różne projekty dostępne na Githubie. Główne założenia to wsparcie dla Windowsa i Linuxa oraz obsługiwanie C++. W chwili obecnej projekt jest we wczesnym stadium, jest używalny ale nie ma jeszcze wszystkich ficzerów STMStudio. Postanowiłem o nim napisać teraz ze względu na to, że może akurat ktoś zdecyduje się spróbować nim pobawić i wrzuci mi jakiś feedback, a ja będę wiedział co trzeba dopracować/zmienić w pierwszej kolejności. Aktualnie sam projekt to nie są jakieś wyżyny programowania, nad refactorem pracuję w wolnych chwilach - z czasem powinno być lepiej. Zachęcam do uwag i sugestii 😉 Poniżej link do repa na Githubie: https://github.com/klonyyy/STMViewer Na razie testy przeprowadzałem tylko na maszynach wirtualnych i komputerze z Windowsem, ale założenie jest takie, że pobierając installera z strony z Releasami (po lewej stronie) i instalując program powinien on ruszyć 😉 W razie problemów jestem do dyspozycji, tutaj czy na Githubie. Jako, że softwareowe projekty zazwyczaj bywają nudne jeśli chodzi o opis, to wrzucam gifa z działania programu, tak na zachętę 😉 Postaram się wrzucać tutaj ważniejsze update'y jako posty, możecie także zajrzeć na bloga, tam pojawiać się będą posty w j. angielskim.
  13. Cześć, niedawno zakupiłem Nucleo-H743ZI2 (MB1364) głównie w celu uzyskania dostępu jednocześnie do 4 linii I2C oraz interfejsu SDMMC. No i spotkała mnie pewna niespodzianka, ponieważ wyprowadzenia SDMMC_D0 oraz SDMMC_D1 są rozłączone od złącza morpho. Można je podłączyć poprzez 2 zworki (SB14 i SB15). Niby oczywiste, ale zastanawia mnie, dlaczego producent nie podłączył ich fabrycznie. W dokumencie UM2407 znajduje się informacja "These pins are disconnected from ST morpho connector CN12 to avoid stub of SDMMC data signals on PCB". Co to dokładnie oznacza. Czym "grozi" dodanie tych zworek i jak się uchronić przed ewentualnymi problemami. I drugie pytanie - rozumiem, że dotyczy to jedynie interfejsu SDMMC2, czyli pinów PB14 i PB15? Niestety nie znalazłem schematu do MB1364, a na schematach MB1137 zworki te odpowiadają za zupełnie inne piny. Czy możliwe jest w ogóle używanie obu interfejsów SDMMC jednocześnie? Jawnie nigdzie nie definiuję pinów, do których jest podłączony moduł SD, ani nie wskazuję, czy używam interfejsu nr 1 czy 2.
  14. Sprzedam w pełni sprawną płytkę SMT32H5-DK, w skład zestawu wchodzą 3 płytki -main board -wifi board -fan out daughter board Zestaw w pełni sprawny, możliwy odbiór osobisty w Warszawie(metro centrum/stokłosy)lub wysyłka po przedpłacie, alternatywnie poprzez olx(inne portale wchodzą w drogę, ale ewentualne koszty spadają na kupującego) Cena 280+koszt wysyłki.
  15. Cześć, z uwagi na ograniczoną ilość pamięci w STM32H7, w celu pełnego wykorzystania jej zasobów, chciałbym współdzielić jeden duży bufor do dwóch zadań (gdy pierwsze zadanie się skończy, zaczyna się drugie, więc dane "nie kłócą się ze sobą"). Normalnie korzystam z tablic: uint32_t tab1[119256] w jednym programie oraz uint8_t tab2[278528] w drugim ( (round_No=4096)*(liczba_rejestrów=68) ). Przyszedł czas połączyć programy i okazało się, że pamięci brakuje. Stąd pomysł, by wykorzystać jeden bufor (tablicę) do obu celów. Mam jednak problem z poprawnym zapisem danych. Próbowałem bardzo różnych kombinacji, może nie będę ich wymieniał, by nie narzucać (złych) pomysłów. Proszę o pomoc, jak poprawnie skonstruować zapis do tablicy, a następnie zapis tablicy na kartę. Dobrze byłoby, gdybym w pełni wykorzystał tab1 i mógł użyć 2 razy więcej komórek 8-bitowych niż jest komórek 32-bitowych, ale jeśli zostanie mi do dyspozycji owe 119256, to też da radę. Poniżej krytyczne fragmenty programu, czyli akwizycja danych do tablicy oraz ich zapis na kartę pamięci. HAL_I2C_Mem_Read_DMA(&hi2c1, MPU9250_ACC_ADDRESS_A, MPU9250_ACCEL_XOUT_H, 1, &tab[round_No*68 + 0*14], 14); //14 registers measurement HAL_I2C_Mem_Read_DMA(&hi2c2, MPU9250_ACC_ADDRESS_B, MPU9250_ACCEL_XOUT_H, 1, &tab[round_No*68 + 1*14], 14); //14 registers measurement HAL_I2C_Mem_Read_DMA(&hi2c3, MPU9250_ACC_ADDRESS_C, MPU9250_ACCEL_XOUT_H, 1, &tab[round_No*68 + 2*14], 14); //14 registers measurement HAL_I2C_Mem_Read_IT (&hi2c4, MPU9250_ACC_ADDRESS_D, MPU9250_ACCEL_XOUT_H, 1, &tab[round_No*68 + 3*14], 14); //14 registers measurement HAL_I2C_Mem_Read_DMA(&hi2c1, BMP280_ADDRESS_A, BMP280_REG_BAR_MSB, 1, &tab[round_No*68 + 4*14+0*3], 3); //3 registers measurement HAL_I2C_Mem_Read_DMA(&hi2c2, BMP280_ADDRESS_B, BMP280_REG_BAR_MSB, 1, &tab[round_No*68 + 4*14+1*3], 3); //3 registers measurement HAL_I2C_Mem_Read_DMA(&hi2c3, BMP280_ADDRESS_C, BMP280_REG_BAR_MSB, 1, &tab[round_No*68 + 4*14+2*3], 3); //3 registers measurement HAL_I2C_Mem_Read_IT (&hi2c4, BMP280_ADDRESS_D, BMP280_REG_BAR_MSB, 1, &tab[round_No*68 + 4*14+3*3], 3); //3 registers measurement f_write(&fil, readings_from_registers, sizeof(tab), &numread); W tej drugiej części (BMP280) dane pobieram z 3 rejestrów 8-bitowych. Ponieważ może być to kłopotliwe, mogę ograniczyć się do 2 lub rozszerzyć do 4, w którym jeden będzie zawsze zerami. Każda konstruktywna uwaga będzie na dla mnie cenna.
  16. Cześć Zrobiłem wszystko według tego kursu i totalnie nic mi nie działa. Żadnej reakcji. Zainspirowałem się pewnym projektem termometru analogowego na diodach programowalnych i postanowiłem wykonać swój tyle że na diodach WS2812b bo miałem ich sporo. Najpierw żeby potestować to chciałem na Nucleo sprawdzić i totalnie zero reakcji, nawet bufor podmieniałem (do projektu kupiłem smd) i nadal nic. Już drugi dzień nad tym siedzę, a niestety oscyloskopu w domu nie mam. Ani na płytce stykowej nie działa, ani w docelowym projekcie. Ktoś dałby jakieś wskazówki?
  17. Dzień dobry, pacjent to stm32 H723VGT6, dodałem arm_math, deklarację użycia CM7 oraz bibliotekę dla mojego mikrokontrolera tj. M7lfsp. Dane odbieram przez DMA i mam je od razu skonwertowane do float, wysyłane są tak samo z zmiennej typu float przez rzutowanie na uint16_t. Problem jest taki, że przekazywanie parametrów na sucho aby sprawdzić czy same rzutowania stanowią problem działają jak najbardziej w porządku. Problem następuje gdy wykorzystuję funkcję od implementowania filtrów IIR, wówczas wartości wyjściowe zwracają wartość inf. Próbowałem przekazać parametry zarówno w formie wskazania od którego miejsca tablicy ma zacząć odczytywać wartości czyli &l_buf_in[0] jak i wskazać całą tablicę l_buf_in, przy późniejszej deklaracji rozmiaru bloku 256. W obu przypadkach wychodzi na to samo, zarówno nie działa jak i wskazuje na całą tablicę. arm_biquad_casd_df1_inst_f32 mam deklarację dwóch struktur, które później inicjuję. W init mam obie struktury, filtr będzie drugiego rzędu więc wykorzystuje 5 współczynników, stąd iir_coeffs[5], i będą 4 stany - wzorowane na grafice struktury filtru IIR czyli iir_l_state[4]. I numStages czyli drugi parametr inicjalizacji to liczba takich filtrów połączona szeregowo - w tym wypadku jest to pojedyncza filtracja, pojedynczy filtr. Później zapewne jak uda mi się to poprawić to zwiększę liczbę filtrów to będę miał analogicznie 10 współczynników, 8 stanów i numStages = 2, itd itd. Dodatkowo kalibracja i inicjalizacja ADC, mniejsza z nim bo tutaj nic nie robi oraz start DMA dla I2S z rzutowaniem aby otrzymać od razu upragnione wartości float. I potem cała magia - mam dwa stany - halfCplt oraz Cplt callback, i w zależności od tego rozpatruję zakres 0-511 oraz 512-1023 w zmiennej rxBuff. Rozdzielam 512 wartości na dwa bufory zawierające dane dla kanału lewego oraz prawego więc mam 2x 256 próbek w odpowiednich tablicach i próbuję to przefiltrować osobno dla obu kanałów. Wskazuję blok z danymi wejściowymi, blok gdzie mają zostać zapisane i rozmiar bloku. Z racji tego, że wartości l_buf_in oraz r_buf_in są nadpisywane to nie muszę podawać zakresów tak jak w głównej zmiennej rxBuf tylko to co jest w bloku jest gotowe do przetworzenia.Więc na jedno wyjdzie jak wcześniej wspomniałem czy napiszę &l_buf_in[0] czy l_buf_in. I problem jest taki, że na wyjściu dla l_buf_out[0] dostaję inf, tak samo jak wszystkie inne komórki tablicy. Potem obie tablice są sumowane aby utworzyć analogiczny przebieg jak przedtem, nie wiem tylko czy nie zamieniam kolejności dwóch kanałów, ale to wyjdzie w praniu jak zacznie działać normalnie kod. I tutaj moje pytanie, dlaczego arm_biquad_cascade_df1_f32 zwraca wartości inf. /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2024 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "arm_math.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; CORDIC_HandleTypeDef hcordic; FMAC_HandleTypeDef hfmac; I2S_HandleTypeDef hi2s1; DMA_HandleTypeDef hdma_spi1_rx; DMA_HandleTypeDef hdma_spi1_tx; /* USER CODE BEGIN PV */ volatile static uint16_t adc_val[2]; float rxBuf[1024]; float txBuf[1024]; float l_buf_in[256]; float r_buf_in[256]; float l_buf_out[256]; float r_buf_out[256]; float iir_coeffs[5] = { 1.0041645480379269, -1.9797515357244457, 0.9759879669583226, -1.9798515425143588, 0.9800525082063363 }; float iir_l_state[4]; float iir_r_state[4]; uint8_t callback_state = 0; /* 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_I2S1_Init(void); static void MX_CORDIC_Init(void); static void MX_FMAC_Init(void); /* USER CODE BEGIN PFP */ /* 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 */ /* 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_I2S1_Init(); MX_CORDIC_Init(); MX_FMAC_Init(); /* USER CODE BEGIN 2 */ arm_biquad_casd_df1_inst_f32 iirsettings_l, iirsettings_r; arm_biquad_cascade_df1_init_f32(&iirsettings_l, 1, iir_coeffs, iir_l_state); arm_biquad_cascade_df1_init_f32(&iirsettings_r, 1, iir_coeffs, iir_r_state); if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED) != HAL_OK) { Error_Handler(); } HAL_ADC_Start_DMA(&hadc1, (uint32_t*) adc_val, 2); HAL_I2SEx_TransmitReceive_DMA(&hi2s1, (uint16_t*) txBuf, (uint16_t*) rxBuf, 1024); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { if (callback_state != 0) { if (callback_state == 1) { uint16_t x = 0; for (int i = 0; i < 511; i = i + 2) { l_buf_in[x] = rxBuf[i]; r_buf_in[x] = rxBuf[i + 1]; x++; } x = 0; arm_biquad_cascade_df1_f32(&iirsettings_l, l_buf_in, l_buf_out, 256); arm_biquad_cascade_df1_f32(&iirsettings_r, r_buf_in, r_buf_out, 256); for (int i = 0; i < 511; i = i + 2) { txBuf[i] = l_buf_out[x]; txBuf[i + 1] = r_buf_out[x]; x++; } x = 0; } else if (callback_state == 2) { uint16_t x = 0; for (int i = 512; i < 1023; i = i + 2) { l_buf_in[x] = rxBuf[i]; r_buf_in[x] = rxBuf[i + 1]; x++; } x = 0; arm_biquad_cascade_df1_f32(&iirsettings_l, l_buf_in, l_buf_out, 256); arm_biquad_cascade_df1_f32(&iirsettings_r, r_buf_in, r_buf_out, 256); for (int i = 512; i < 1023; i = i + 2) { txBuf[i] = l_buf_out[x]; txBuf[i + 1] = r_buf_out[x]; x++; } x = 0; } callback_state = 0; } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = { 0 }; RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 }; /** Supply configuration update enable */ HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY); /** Configure the main internal regulator output voltage */ __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0); while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) { } /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_DIV1; RCC_OscInitStruct.HSICalibrationValue = 64; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLM = 4; RCC_OscInitStruct.PLL.PLLN = 34; RCC_OscInitStruct.PLL.PLLP = 1; RCC_OscInitStruct.PLL.PLLQ = 3; RCC_OscInitStruct.PLL.PLLR = 2; RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_3; RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE; RCC_OscInitStruct.PLL.PLLFRACN = 3072; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_D3PCLK1 | RCC_CLOCKTYPE_D1PCLK1; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2; RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2; RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK) { Error_Handler(); } } /** * @brief ADC1 Initialization Function * @param None * @retval None */ static void MX_ADC1_Init(void) { /* USER CODE BEGIN ADC1_Init 0 */ /* USER CODE END ADC1_Init 0 */ ADC_MultiModeTypeDef multimode = { 0 }; ADC_ChannelConfTypeDef sConfig = { 0 }; /* USER CODE BEGIN ADC1_Init 1 */ /* USER CODE END ADC1_Init 1 */ /** Common config */ hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1; hadc1.Init.Resolution = ADC_RESOLUTION_16B; hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; hadc1.Init.LowPowerAutoWait = DISABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.NbrOfConversion = 2; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR; hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE; hadc1.Init.OversamplingMode = ENABLE; hadc1.Init.Oversampling.Ratio = 16; hadc1.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_4; hadc1.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER; hadc1.Init.Oversampling.OversamplingStopReset = ADC_REGOVERSAMPLING_CONTINUED_MODE; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } /** Configure the ADC multi-mode */ multimode.Mode = ADC_MODE_INDEPENDENT; if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK) { Error_Handler(); } /** Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_10; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5; sConfig.SingleDiff = ADC_SINGLE_ENDED; sConfig.OffsetNumber = ADC_OFFSET_NONE; sConfig.Offset = 0; sConfig.OffsetSignedSaturation = DISABLE; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } /** Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_16; sConfig.Rank = ADC_REGULAR_RANK_2; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN ADC1_Init 2 */ /* USER CODE END ADC1_Init 2 */ } /** * @brief CORDIC Initialization Function * @param None * @retval None */ static void MX_CORDIC_Init(void) { /* USER CODE BEGIN CORDIC_Init 0 */ /* USER CODE END CORDIC_Init 0 */ /* USER CODE BEGIN CORDIC_Init 1 */ /* USER CODE END CORDIC_Init 1 */ hcordic.Instance = CORDIC; if (HAL_CORDIC_Init(&hcordic) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN CORDIC_Init 2 */ /* USER CODE END CORDIC_Init 2 */ } /** * @brief FMAC Initialization Function * @param None * @retval None */ static void MX_FMAC_Init(void) { /* USER CODE BEGIN FMAC_Init 0 */ /* USER CODE END FMAC_Init 0 */ /* USER CODE BEGIN FMAC_Init 1 */ /* USER CODE END FMAC_Init 1 */ hfmac.Instance = FMAC; if (HAL_FMAC_Init(&hfmac) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN FMAC_Init 2 */ /* USER CODE END FMAC_Init 2 */ } /** * @brief I2S1 Initialization Function * @param None * @retval None */ static void MX_I2S1_Init(void) { /* USER CODE BEGIN I2S1_Init 0 */ /* USER CODE END I2S1_Init 0 */ /* USER CODE BEGIN I2S1_Init 1 */ /* USER CODE END I2S1_Init 1 */ hi2s1.Instance = SPI1; hi2s1.Init.Mode = I2S_MODE_MASTER_FULLDUPLEX; hi2s1.Init.Standard = I2S_STANDARD_PHILIPS; hi2s1.Init.DataFormat = I2S_DATAFORMAT_24B; hi2s1.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE; hi2s1.Init.AudioFreq = I2S_AUDIOFREQ_48K; hi2s1.Init.CPOL = I2S_CPOL_LOW; hi2s1.Init.FirstBit = I2S_FIRSTBIT_MSB; hi2s1.Init.WSInversion = I2S_WS_INVERSION_DISABLE; hi2s1.Init.Data24BitAlignment = I2S_DATA_24BIT_ALIGNMENT_RIGHT; hi2s1.Init.MasterKeepIOState = I2S_MASTER_KEEP_IO_STATE_DISABLE; if (HAL_I2S_Init(&hi2s1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN I2S1_Init 2 */ /* USER CODE END I2S1_Init 2 */ } /** * Enable DMA controller clock */ static void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Stream0_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn); /* DMA1_Stream1_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn); /* DMA1_Stream2_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream2_IRQn); } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { /* USER CODE BEGIN MX_GPIO_Init_1 */ /* USER CODE END MX_GPIO_Init_1 */ /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOH_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /* USER CODE BEGIN MX_GPIO_Init_2 */ /* USER CODE END MX_GPIO_Init_2 */ } /* USER CODE BEGIN 4 */ void HAL_I2SEx_TxRxHalfCpltCallback(I2S_HandleTypeDef *hi2s) { callback_state = 1; } void HAL_I2SEx_TxRxCpltCallback(I2S_HandleTypeDef *hi2s) { callback_state = 2; } /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ Dodaję też archiwum jak by ktoś chciał zobaczyć czy nie namieszałem nic w ustawieniach projektu. IIR_Biquad_ARM.zip
  18. Sprzedam płytkę dicovery B-U585I-IOT02A. Płytka w pełni sprawna, chciałbym za nią 350zł(możliwa jakaś negocjacja ceny). Optymalny byłby odbiór osobisty w Warszawie, ale możliwa wysyłka paczkomatem przy płatności z góry. Pełną specyfikacje płytki można znaleźć na stronie ST W razie pytań zapraszam na pw.
  19. Mam pewien problem. Zakupiłem analogiczny wyswietlacz LCD 2.4 inch module na sterowniku ILI9341. Lecz nie jestem w stanie go skofigurować tak aby jakkolwiek działał. Próbowałem go zrobić analogicznie do jego "mniejszej" wersji 1.8 cala z kursu STM32, lecz ten crushuje się cały czas. Nie wiem w czym tkwi problem i jak skonfigurować to prawidłowo bym mógł na nim pracować. Cięzko mi się dokopać do jakihś plików konkretnie z tym wyswietlaczem i jego podstawową konfiguracją, a jak coś znajdę to nic nie działa prawidłowo/nie jestem w stanie tego przenieść do siebie. Proszę o radę/pomoc albo jakieś przydatne proste linki które pomogą amatorowi stm32. Moja płytka to Nucleo L476RG, ta z kursu. Pozdrawiam PO.
  20. Witam. Realizuję projekt matrycy mikrofonów z wykorzystaniem mikrofonów MEMS ICS-52000 oraz mikrokontrolera, który służy do zbierania danych z mikrofonu w celu dalszej analizy dźwięku np. w MATLAB'ie. Mam problem z komunikacją z mikrofonami. Mikrofony ICS-52000 mają wyjście TDM i mam takich mikrofonów połączonych w łańcuch zgodnie z dokumentacją (zdjęcie poniżej). Celem jest pobranie danych z mikrofonów i zapisanie ich na dysku. Póki co mam gotową konstrukcję i próbuję pisać program, który pobierze dane z każdego mikrofonu. Moje pytanie brzmi: W jaki sposób zrealizować mogę komunikację TDM (Time Division Multiplexing)? Nie ma na ten temat za wiele materiałów. To co udało mi się znaleźć to zastosowanie interfejsu SAI (Serial Audio Interface) i próbowałem ten interfejs wykorzystać jednak podłączając tylko jeden mikrofon w celu jego stestowania nie działało to tak jak powinny. Próbowałem pobrać dane przez HAl_SAI_Receive_DMA() i w funkcji callback void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) przypisać wartość do zmiennej, jednak nie działało to. Szczerze to nie ma zbyt wielu przykładów zastosowania tego interfejsu do rejestracji dźwięku, a z dokumentacji co udało mi się wyczytać to SAI wspiera TDM. Konfiguracja interfejsu SAI Mode: Master Konfiguracja zegara: Po włączeniu SAI w tej konfiguracji mam 3 piny: SAI1_SD_A - wyjście danych SAI1_SCK_A - zegar SAI1_FS_A - frame select (word select) Czy zegar jaki podaje na wyjścia mikrofonów równy jest wartości zegara SAI1 (24MHz)? Czy częstotliwośc SAI1_FS_A równa jest częstotliwości audio (48kHz), czy musze ten pin obsługiwać w jakiś sposób? Według dokumentacji mikrofony są synchronizowane przez sygnał WS, więc mikrofony dzielące ten sam zegar będą samplowane synchronicznie.
  21. Enbio Technology - Potrzebujemy do pomocy średniodoświadczonego programistę embedded, najlepiej który ma doświadczenie w STM32 i ESP32 w rozwiązaniach komercyjnych, albo przynajmniej w projektach wykonanych profesjonalnie, przetestowanych, nieawaryjnych. Dobrze mieć wiedzę w temacie IoT, najlepiej z MQTT i Azure IoT Hub. Praca hybrydowa: zdalnie / biuro + warsztat R&D w Rumii (Pomorskie, tuż obok Gdynii). Zarobki: do 10k brutto. Projekty które robimy to autoklawy parowe z dostępem do internetu. Pracujemy nad nowymi technologiami, a urządzenia, po wdrożeniu będą pracować w krajach na całym świecie. Duża zaleta tej pracy - możliwość prześledzenia projektu od początku do końca i napisanie własnego programu - jest to świetna okazja do nauki. Praca nie ogranicza się do jednego projektu. W tym momencie jest jeden produkt wymagający utrzymania (STM32F4), drugi w trakcie realizacji i trzeci do którego szukamy programisty. Będą też kolejne. W przypadku zainteresowania podam szczegóły.
  22. Hej, zwracam się do Was z prośbą i radą jaki protokół do komunikacji miedzy różnymi mikrokontrolerami najlepiej użyć do mojego wymyślonego projektu. To zaczynając od pomysłu. Chciałem stworzyć kontroler główny, który będzie miał komunikację 2 kierunkową z urządzeniami podłączonymi do niego: mniej więcej tak ja na tym schemacie: Z Założenia chcę, aby urządzenia łączyły się ze sobą kablowo najprawdopodobniej skrętką 8 żyłową(2 lub 4 przewody chciałbym poświęcić na zasilania, reszta na komunikację) a odległość maksymalna to będzie 20m, Dane przesyłane miedzy nimi nie będą duże( najprawdopodobniej string o długości max 30 znaków lub char do identyfikacji typu wiadomości i int16) a częstotliwość komunikacji raz na 1s z każdym urządzeniem wystarczy. Chciałbym także aby te podłączenia można było robić w trakcie używania urządzenia a ich wykrywanie było na zasadzie "do portu 3 zostało coś podłączone" i po przez różnego rodzaju wiadomości urządzanie będzie rozpoznawało z czym konkretnie się połączył. Dodatkowo chciałbym aby można było podłączyć różne mikrokontrolery, takie jak STM32, ESP oraz Arduino. Jest Modbus, SPI, I2C, CAN, UART itd... Ale co najlepiej do takiego zastosowania by się nadało? Wiem, że każdy wybór ma swoje plusy i minusy i zdaję sobie sprawę, że muszę pójść na jakieś kompromisy. Moim wstępnym pomysłem było wykorzystanie do tego RS-485. Tylko nie miałem z nim nigdy styczności i nie jestem przekonany, że się sprawdzi. Dlatego chcę waszej opinii i porady, co byście do takiego zastosowania użyli, a może już ktoś robił coś podobnego i ma w tym doświadczenie.
  23. Cześć wszystkim. Chciałbym skonfigurować akcelerometr mc3479 do pomiaru położenia komunikując się z czujnikiem przy użyciu interfejsu spi. Do tej pory nie wykorzystywałem zbyt tego interfejsu i mam problem z funkcjami do zapisu oraz do odczytu rejestrów. Mianowicie po napisaniu funkcji nie jestem w stanie zapisać/odczytać wartości z czujnika. Próbowałem podać przykładową wartość, ale niestety nie zadziałało. Czy ktoś ma pomysł co mogłem przeoczyć? 🙂 void mc_reg_write(uint8_t reg, uint8_t value) { uint8_t tx[3] = { 0x40 | reg, value, 0x00 }; HAL_GPIO_WritePin(CSACC_GPIO_Port, CSACC_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, tx, 3, HAL_MAX_DELAY); HAL_GPIO_WritePin(CSACC_GPIO_Port, CSACC_Pin, GPIO_PIN_SET); } uint8_t mc_reg_read(uint8_t reg) { uint8_t tx_data[2] = { reg | 0x80, 0x00 }; uint8_t rx_data[2] = { 0x00, 0x00 }; HAL_GPIO_WritePin(CSACC_GPIO_Port, CSACC_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, 2, HAL_MAX_DELAY); HAL_GPIO_WritePin(CSACC_GPIO_Port, CSACC_Pin, GPIO_PIN_SET); return rx_data[1]; } mc_reg_write(0x12, 0x01); printf("In register 0x%02X is: (0x%02X)\r\n",0x12, mc_reg_read(0x12));
  24. Kontynuacja tematu analizy widma audio. To już prawie 2 lata z przerwami 🙂 Niedawno nieco niepewnie wrzuciłem pierwszą wzmiankę o projekcie w wspólny worklog, ale temat się trzyma więc dorzucam osobny worklog. Jak ostatnio link na githuba z projektem PCB, programem, notatnikiem Jupyter z obliczeniami i pomiarami: https://github.com/Gieneq/FFTSlice W tej aktualizacji doszła całkiem dobra metoda generowania sinusoid z DAC. Po tym jak odpaliłem DAC, przypomniałem sobie dlaczego kiedyś odrzuciłem tę opcję. Wygenerowanie dowolnej częstotliwości nie jest takie oczywiste bo trzeba regulować częstotliwość timera i ewentualnie wybrać inną rozdzielczość spóbkowania sinusoidy. Dlatego uprościłem sytuację i pomyślałem że dla potrzeb analizatora widma audio potrzebuję tylko 100 częstotliwości pomiędzy 20Hz a 20kHz. Podzieliłem przedział logarytmicznie dla lepszej percepcji: Mikrokontroler STM32G081RBT nie ma za dużo pamięci, ale 6 spróbkowanych sinusoid zapisanych na uint16_t może być. Wartości narastają od 64 do 2048. Następnie trzeba przyporządkować próbki do częstotliwości jakie chcę wygenerować. Większe rozdzielczości lepiej nadają się dla niskich częstotliwości, a mniejsze rozdzielczości do szybszych przebiegów. Skrypt w Pythonie pomógł mi dokonać takiego przyporządkowania minimalizując czas w którym sygnał pozostaje na tym samym poziomie przy zachowaniu niezadużego odchylenia od oczekiwanej częstotliwości: Błąd poniżej 1% to całkiem dobry wynik. Przyporządkowanie jest dość proporcjonalne: W tabelce widać co by się działo gdyby skorzystać z tej samej rozdzielczości próbek - dla wysokich częstotliwości 1024 próbek ni jak nie pasuje: Pozostały testy, porównałem teoretycznie obliczoną częstotliwość z pomiarami picoscopem i wyniki są zgodne z oczekiwaniami. Tu wybrana wartość 1068Hz: Jestem trochę zaskoczony, że tak dobrze to działa 😅 w kolejnej aktualizacji chcę dodać opcję kolejkowania zmiany częstotliwości, żeby następowała po zakończeniu odtwarzania okresu sinusoidy - żeby nie było ucięcia w środku co wiązałoby się z trzaskiem od tak wygenerowanej wysokiej częstotliwości. Następnie pozostaje przetestowanie PGA i dodanie interfejsu SPI.
  25. Cześć, testuję ostatnio evalboard STM32U5A9J-DK, na pewno ktoś ma większe doświadczenie więc na starcie pytanie: czy -Ofast i 160MHz (max) 480x480 px RGB8888 10ms czyszczenie framebuffera na samym MCU to szybko? Na razie podejście nienajlepsze. Bez optymalizacji wyszło 36ms. void gfx_prepare() { int32_t ltdc_clear_sreen_start_us = microtimer_get_us(); /* Clear screen */ gfx_fillscreen(0xFFFF0000); // RED gfx_draw_fillrect(LCD_WIDTH/2 - 20, LCD_HEIGHT-40, 40, 40, 0xff0000ff); ltdc_clear_sreen_duriation_us = microtimer_get_us() - ltdc_clear_sreen_start_us; } void gfx_draw_fillrect(uint32_t x_pos, uint32_t y_pos, uint32_t width, uint32_t height, uint32_t color) { uint32_t px_address = 0; uint32_t i; uint32_t j; /* Get the rectangle start address */ uint32_t startaddress = (hltdc.LayerCfg[0].FBStartAdress + (4 * (y_pos * PIXEL_PERLINE + x_pos))); /* Fill the rectangle */ for (i = 0; i < height; i++) { px_address = startaddress + (3072 * i); //768 * 4 for (j = 0; j < width; j++) { *(__IO uint32_t *)(px_address) = color; px_address += 4; } } } void gfx_fillscreen(uint32_t color) { gfx_draw_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT, color); } Tu podgląd czasów: Jak można to poprawić? Używanie memset nie poprawiło, pewnie optymalizacja i tak sprowadza to do podobnych operacji. Widziałem że DMA2D ma tryb mem-register do wypełniania kolorem. A może transfer małych kawałów pamieci? Chcę teraz dodać doublebuffer z przerwania od LTDC Live Event, ale najpierw muszę mieć sposób żeby zmieścić się pomiędzy back i front Vporch 😕 To zajmuje te 10ms:
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.