Przeszukaj forum
Pokazywanie wyników dla tagów 'c'.
Znaleziono 90 wyników
-
Hej, napisałem program, który alokuje tablicę na N osób. Teraz chciałbym to przerobić tak, żeby program miał proste menu (1. Dodaj osobę, 2. Wypisz). Problem mam taki, że nie wiem jak poprawnie użyć realloc, żeby powiększyć tablicę o 1 miejsce przy każdym dodaniu nowej osoby, nie tracąc przy tym starych danych. Czy ktoś mógłby rzucić okiem na kod i podpowiedzieć, jak zmienić funkcję wypelnijListe na taką, która dodaje tylko jedną osobę? #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <math.h> #include <stdlib.h> #include <time.h> #include <string.h> #define OBECNY_ROK 2026 typedef struct { char imie[50]; int rok_urodzenia; } osoba; typedef struct { osoba* adres_tablicy; int N; } listaObecnosci; osoba generujOsobe() { osoba o; printf("Podaj imie studenta: "); scanf("%49s", o.imie); o.rok_urodzenia = rand() % (2020 - 1920 + 1) + 1920; return o; } int wypelnijListe(listaObecnosci* lista) { lista->adres_tablicy = (osoba*)malloc(lista->N * sizeof(osoba)); if (lista->adres_tablicy == NULL) { printf("blad alokacji pamieci\n"); return 0; } for (int i = 0; i < lista->N; i++) { printf("wprowadzenie osoby nr %d\n", i + 1); lista->adres_tablicy[i] = generujOsobe(); } return 1; } void wypiszliste(listaObecnosci lista) { printf("wynki: \n"); for (int i = 0; i < lista.N; i++) { int wiek = OBECNY_ROK - lista.adres_tablicy[i].rok_urodzenia; printf("%s: %d\n", lista.adres_tablicy[i].imie, wiek); } } int main() { srand(time(NULL)); listaObecnosci mojaLista; do { printf("Podaj liczbe osob od 1 do 10: "); scanf("%d", &mojaLista.N); if (mojaLista.N < 1 || mojaLista.N > 10); { printf("bledna liczba\n"); } } while (mojaLista.N < 1 || mojaLista.N > 10); if (wypelnijListe(&mojaLista) == 0) { printf("Blad alokacji pamieci\n"); return 1; } wypiszliste(mojaLista); free(mojaLista.adres_tablicy); mojaLista.adres_tablicy = NULL; return 0; }
-
Cóż - jak zwykle miało być o czymś innym, tylko że ten "czymś" cały czas ma problemy egzystencjonalne typu "a ja nie będę działać po twojemu ale po swojemu". Dlatego dzisiaj o czymś, co powstało w międzyczasie. Dzisiaj przedstawiam bibliotekę poldit, służącą do słownego rozwijania różnych liczb (w tym liczb mianowanych, godzin, dat czy zakresów). Zaczęło się to jakoś 20 lat temu, kiedy potrzebowałem systemu TTS dla Linuxa. Moja Milena działała (i do dziś działa) praktycznie bezproblemowo, tyle że jest przystosowana przede wszystkim do Mbroli, poza tym jest duża, ciężka zbyt specjalizowana (powstała do czytania beletrystyki, więc wiele jej reguł dotyczy właśnie powieści - choćby francuskie rewolucyjne daty, stopnie Reaumura czy wymowa wyrazów obcojęzycznych). Co prawda najnowsze wersje potrafią od biedy wygenerowć tekst dla innych syntezatorów, ale nie jest to jej głównym celem. Dlatego postanowiłem stworzyć coś prostszego, a jednocześnie mającego nieco większe możliwości interpretacji tekstu. Kompletny kod i techniczny opis zamieszczony jest na githubie, nie będę więc się specjalnie powtarzać, postaram się opisać o coś od strony bardziej praktycznej. Jako że główną platformą dla biblioteki jest Linux (w tym RaspiOS) podaję najprostszy sposób instalacji. Zacznę od biblioteki libpoldit i programu poldit wykorzystującego bibliotekę: sudo apt install build-essential git git clone https://github.com/ethanak/poldit.git cd poldit/src make sudo make install Można teraz wypróbować działanie programu, przykładowo: $ poldit napięcie powinno wynosić od 1.5 do 5 V napięcie powinno wynosić od jednego i pięć do pięciu woltów Oczywiście warto wypróbować różne przełączniki, przykładowo: $ poldit -p -n napięcie powinno wynosić od 1.5 do 15 V napięcie powinno wynosić od półtora do pietnastu wolt Jak widać, w tym przypadku poldit zastosował skróconą/potoczną konwersję i skorygował przesadną wymowę. Bieżącą datę i czas można odczytać przykładowo w ten sposób: $ date -Iminutes | cut -f1 -d+ | poldit dwudziesty drugi stycznia dwa tysiące dwadzieścia sześć czternasta pięćdziesiąt siedem Pokazany wyżej sposób będzie działać zarówno na RPi (nawet na maleńkim Zero W), jak i na wszelkich debianobodobnych dystrybucjach Linuksa. Dla innych wystarczy w sposób odpowiedni dla systemu zainstalować pakiety build-essential i git. Oczywiście to co wypisuje poldit nie służy do czytania na konsoli ale do sterowania syntezą mowy. Pomijając rozwiązania wymagające dostępu do internetu, płatne syntezatory (i moją Milenę) dla języka polskiego mamy niewiele praktycznych możliwości: espeak - tu dwie możliwości: albo syntezator Klatta (mechaniczny głos, ale czytelny przy wysokich prędkościach) albo Mbrola (niestety, raczej tylko jako ciekawostka); piper - z założenia głos wysokiej jakości generowany przy użyciu AI. Niestety są to jedynie założenia, bo polskie modele generują coś w stylu mieszanki polsko-rosyjskiej gubiąc po drodze niewygodne spółgłoski; RHVoice - od początku projektowany z myślą o praktycznych zastosowaniach. Po polsku mówi całkiem dobrze, do dyspozycji mamy pięć głosów. Istnieje jeszcze oczywiście korpusowy syntezator mowy (PJWSTK) oraz leciwy Festival, ale to są projekty bardziej akademickie; o ile Festival był w paru aplikacjach używany, o tyle ten polsko-japoński nigdy nie doczekał się praktycznego zastosowania. Aby więc posłuchać efektów trzeba mieć przede wszystkim komputer z wyjściem audio (może być RPi, ale model co najmniej Zero 2 W). Można zacząć od espeaka (jest w repozytoriach) żeby po prostu stwierdzić że to działa. Jednak jeśli ktoś chciałby stworzyć jakąś rzeczywistą aplikację, polecam RHVoice. Dla nowych dystrybucji sprawa jest prosta: $ sudo apt install rhvoice rhvoice-polish Niestety - starsze dystrybucje mogą w repozytoriach mieć wersje bez dostępnego języka polskiego lub w ogóle nie mieć pakietu. W takim przypadku trzeba pobrać go z githuba skompilować samodzielnie. Na szczęście sposób instalacji jest dobrze opisany, wymaga tylko kilku dodatkowych bibliotek z repozytorium (np. libao) i programu scons. Można więc spróbować już czegoś bardziej przydatnego, przykładowo: $ date +'Jest %H:%M, %A, %d.%m.%Y' | poldit -n | RHVoice-test -p natan Taki skrypt można podłączyć np. pod jakiś klawisz i mieć do dyspozycji zegarek, który nie wymaga gapienia się w ekran. Szczególnie w rozwiązaniach bez interfejsu graficznego może to być przydatne. Tak mniej więcej działa czytanie godziny i daty przez różne syntezatory: W załączniku pliki mp3 dla głosów espeak-ng, piper i RHVoice (róœnież z głosem Cezary, który ma tak dziwaczną licencję że nie wiem czy w ogóle można go słuchać i dlatego nie ma go w filmie). Oczywiście czytanie bieżącego roku nie jest w większości przypadków konieczne. Można się bez tego obejść, tyle że trzeba polditowi powiedzieć dokładniej co ma czytać (czyli użyć formatów): $ date +'jest %H:%M, F[DWdM/,:%u %d %m]' | poldit -n | RHVoice-test -p natan W wielu przypadkach programik poldit może być wystarczający, ale na RPi (który jest główną platformą dla której powstała biblioteka) programy pisze się raczej w Pythonie a nie Bashu. Dlatego powstały dwa modułt do Pythona: polditLL - niskopoziomowy moduł, stanowiący wyłącznie interfejs do biblioteki poldit poldit - moduł udostępniający dodatkowe funkcje rozszerzające możliwości polditLL Sposób instalacji jest w miarę prosty: sudo apt install python3-setuptools python3-pip pip3 install build --break-system-packages cd ~/poldit/python/modules python3 -m build --no-isolation pip3 install . --break-system-packages W niektórych dystrybucjach przełącznik --break-system-packages może być niepotrzebny! Oczywiście można moduł zainstalować globalnie, używając sudo w obu przypadkach uruchamiania pip3 Można przetestować moduł np. poleceniem: $ python3 ../demo/kolej.py Wynik powinien wyglądać w ten sposób: Na torze czwartym przy peronie trzecim stoi pociąg do stacji Warszawa Główna. Wagony o numerach jeden do trzy zatrzymują się w sektorze trzecim. Opóźniony o pietnaście minut pociąg intersjity I C dwadzieścia trzy sto czterdzieści cztery "Maruda", ze stacji Warszawa Centralna do stacji Katowice Wschodnie, wjedzie na tor siódmy przy peronie czwartym. No i teraz najważniejsze - co można z tym zrobić? Wyobrażam sobie kilka zastosowań - szczególnie jeśli chodzi o RPi. Jednym z ciekawszych może być modelarstwo kolejowe. RPi Zero 2W obsłuży zarówno jakiś porządny interfejs, ustawi zwrotnice i semafory, będzie sterować lokomotywami i całą infrastrukturą dworca, jak i zasymuluje komumikaty na dworcu. Zamieszczony przykład pokazuje, jak w bardzo prosy sposób można generować komunikaty na dworcu kolejowym czy autobusowym, nie wnikając w prawidłową odmianę (np. "natorze 7 przy peronie 3" czy "autobus odjeżdzą ze stanowiska 7"). Innym typem zastosowań może być automatyka domowa, gdzie czasem infrmacje głosowe są dużo wygodniejsze niż wyświelane na jakimś ekranie, Przykładowo - w mojej domowej instalacji wciśnięcie przycisku na ścianie spowoduje w efekcie przeczytanie na głos aktualnego czasu i temperatury na zewnątrz, albo prognozy pogody na najbliższe dni ściągniętej z openmeteo. Sprawdza się od dłuszego czasu (syntezator RHVoice, głos Alicja), przy czym przycisk jest obok mojego ulubionego fotela Taki programik do prognozy wcale nie jest taki skomplikowany, przykładowe rozwiązanie to: #!/usr/bin/env python3 import requests, os, time, json class weather(object): cachefile = '/dev/shm/prognoza.json' weacodes={ 0: "Czyste niebo", 1: "Przeważnie bezchmurnie", 2: "Zachmurzenie częściowe", 3: "Pochmurno", 45: "Mgła", 48: "Marznąca mgła", 51: "Lekka mżawka", 53: "Umiarkowana mżawka", 55: "Intensywmna mżawka", 56: "Lekka marznąca mżawka", 57: "Intensywna marznąca mżawka", 61: "Niewielki deszcz", 63: "Umiarkowany deszcz", 65: "Intensywny deszcz", 66: "Lekki marznący deszcz", 67: "Silny marznący deszcz", 71: "Słabe opady śniegu", 73: "Uniarkowane opady śniegu", 75: "Silne opady śniegu", 77: "Śnieg ziarnisty", 80: "Przelotne lekkie opady deszczu", 81: "Przelotne umiarkowane opady deszczu", 82: "Przelotne gwałtowne opady deszczu", 85: "Przelotne słabe opady sniegu", 86: "Przelotne intensywne opady sniegu", 95: "Burza z piorunami", 96: "Burza z lekkim gradem", 99: "Burza z silnym gradem" } def __init__(self, latitude, longitude): self.url="https://api.open-meteo.com/v1/forecast?latitude=%.5f&longitude=%.5f&timeformat=iso8601&forecast_days=2&timezone=Europe/Warsaw" + \ "&daily=wind_speed_10m_min,wind_speed_10m_max,weather_code,wind_gusts_10m_max" + \ ",temperature_2m_min,apparent_temperature_min,temperature_2m_max,apparent_temperature_max" + \ ",rain_sum,showers_sum,snowfall_sum" self.url = self.url % (latitude, longitude) self.json = None def getWeatherJSON(self): try: st=os.stat(self.__class__.cachefile) delta = time.time() - st.st_mtime if delta >= 900: raise Exception() return json.loads(open(self.__class__.cachefile,'rb').read()) except: return self.getNetWeather() def getNetWeather(self): try: rc=requests.get(self.url) js=rc.content rc=json.loads(js) open(self.__class__.cachefile,'wb').write(js) return rc except: return None def _getIVals(self, name): return int(self.dly.get(name+'_min')[self.idx]), \ int(self.dly.get(name+'_max')[self.idx]) def _getIVal(self, name): return int(self.dly.get(name)[self.idx]) def getWeatherString(self, tomorrow=False): if self.json is None: self.json = self.getWeatherJSON() if self.json is None: return 'Brak prognozy' self.idx = 1 if tomorrow else 0 self.dly = self.json['daily'] sout = [] wcode = self._getIVal('weather_code') if (wdesc := self.__class__.weacodes.get(wcode)) is not None: sout.append(wdesc) tm1, tm2 = self._getIVals('temperature_2m') to1, to2 = self._getIVals('apparent_temperature') if tm1 == tm2: stm = "temperatura %d °" % tm1 else: stm = "temperatura od %d do %d °" % (tm1, tm2) if to1 == to2: stm += ", odczuwalna %d °" % to1 else: stm += ", odczuwalna %d do %d °" % (to1, to2) sout.append(stm) w1, w2 = self._getIVals('wind_speed_10m') w3 = self._getIVal('wind_gusts_10m_max') if w1 == w2: stm = "wiatr %d km/h" % w1 else: stm = "wiatr %d do %d km/h" % (w1, w2) if w3 > w2: stm += ", w porywach do %d km/h" % w3 sout.append(stm) rain = int(self._getIVal('rain_sum')+self._getIVal('showers_sum')) snow = int(self._getIVal('snowfall_sum')) if rain > 0 or snow > 0: stm = 'przewidywane opady' if rain > 0: stm += ' deszczu %d mm' % rain if snow > 0: stm += ',' if snow > 0: stm += ' śniegu %d cm' % snow sout.append(stm) return '. '.join(sout) latitude = 52.23 longitude = 23.01 w=weather(latitude,longitude) so="Pogoda na dziś: %s. Pogoda na jutro: %s." % ( w.getWeatherString(), w.getWeatherString(True)) import poldit p=poldit.Fonetyzer() print(p.Stringify(so, nat=True)) Należy tylko wpisać do programu właściwe wartości zmiennych latitude i longitude, a wygenerowany tekst wrzucić do syntezatora. No i kolejne zastosowanie, tym razem wymagające posiadania odpowiedniego sprzętu: gadające przystawki do urządzeń pomiarowych. Tu już specjalnie nie mam praktyki (np. mierniki z wyjściem cyfrowym są trochę poza moimi możliwościami finansowymi, ale używam takiego prostego gadającego miernika własnego sumptu). Chyba każdy przyzna, że wtyknięcie końcówek przyrządu w jakąś gęstą płytkę z rastrem rzędu milimetra albo mniej i odwrócenie wzroku żeby popatrzeć na wyświetlacz może skończyć się smętnie. Mogę jednak wyobrazić sobie jakiś interfejs oparty na czymś co ma wejście serial i komunikację WiFi/BT (esp32, RPi Pico czy co kto lubi), czyta dane z miernika wyrzuca np. na smartfona z prostą aplikacją (przykład aplikacji w wątku o suwmiarce) A jeśli chodzi o komunikaty... fajnie by było, aby w pociągach oprócz wyświetlania informacji na wyświetlaczach (do których człowiek albo siedzi tyłem, albo coś/ktoś mu zasłania) były informacje głosowe o bieżących opóźnieniach i przewidywanych czasach przyjazdu na następną stację. Jeśli obsługa może wprowadzić takie dane do wyświetlania - raczej nie byłoby problemu z wygenerowaniem (automatycznym) odpowiedniehgo komunikatu, np. na podstawie rozkładu jazdy i opóźnienia można wygenerować coś w stylu: Następna stacja Trypucie, przyjazd 15:32, opóźnienie 137 min Czytać tego wcale nie musi koniecznie Ivona... Ale chyba za dużo bym od kolei wymagał Oprócz plików mp3 w załączniku źródełko programu do prognozy pogody oraz prosta klasa umożliwiająca użycie RHVoice bezpośrednuio w skrypcie Pythona bez zabawy w (nie zawsze działający) moduł rhvoice-wrapper: poldemo.zip A na koniec konkurs bez nagród: kto wymyśli jakieś sensowne zastosowanie dla poldita? Jak zwykle w razie pytań pozostaję do dyspozycji ethanak
-
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
-
Silnik graficzny dla Raspberry Pi Pico 2
yami_five opublikował temat w Projekty - DIY w budowie (worklogi)
Cześć. O prawie roku pracuję nad demem (https://pl.wikipedia.org/wiki/Demoscena) na Raspberry Pi Pico 2, na potrzeby którego rozwijam silnik graficzny. Projekt bazuje na demach, które w poprzednich dwóch latach napisałem w lua na Pico-8. Projekt jest napisany w C. Mam też kilka dodatkowych tooli napisach w pythonie, np. do zapisania pliku bmp jako array czy do zapisania pliku obj z modelem jako instancję. Feature'y: Renderer 3D Modele podlegają 3 podstawowym transformacjom - translacja, rotacja, skalowanie. Oprócz tego jest texture mapping (jeszcze z dem na Pico-8), zaimplementowałem cieniowanie Gourdauda i zBuffer. Pozycja pikseli na ekranie wyliczana jest użyciem macierzy widoku i perspektywy Arytmetyka stałoprzecinkowa, wektorowa. Coś tam jest też do macierzy, ale i też jakiś czas temu na potrzeby rotacji dodałem funkcje dla kwaternionów. Fabryki do budowania modeli, kamer i źródeł światła. Modele mają materiały, w tej chwili ograniczają się tylko do tekstury i diffuse. Osobna biblioteka do rysowania pikseli na frame bufferze, rysowania spritów na frame bufferze i wysyłanie frame buffera do ekranu. Biblioteki do zarządzania ekranem. Modele, sprity i tekstury są przechowywane w kodzie w const arrayach, żeby jako storage używać pamięci flash. Odtwarzanie muzyki z karty pamięci sd z plików wav. Tutaj było sporo gimnastyki, bo używam modułu, który ma i czytnik i ekran. Do samego grania dźwięku wziąłem kod z pico-extras. Do obsługi systemu plików na sd używam biblioteki FatFs by ChaN. Żeby jakoś tam zgrać czytanie danych z karty z rysowaniem na ekranie używam spinlocków Używam wskaźników do funkcji, żeby potencjalni przyszli użytkownicy mogli wstawiać swój kod, żeby chociażby użyć innego ekranu. Efekty postprocesowe. Na ten moment ze względu na demo mam jako jeden efekt aberrację chromatyczną, barrel distortion i scanline. Funkcja nakładająca efekt może zostać rozszerzona, żeby użyć czegokolwiek. W tej chwili pracuję nad animacją szkieletową 2D. Dodałem już rysowanie spritów i obracanie ich o dowolny kąt. Efektu końcowego na razie nie będę mógł pokazać, bo używam assetów, narysowanych do dema. Części użyte w projekcie: Raspberry Pi Pico 2 - https://botland.com.pl/moduly-i-zestawy-do-raspberry-pi-pico-2/25311-raspberry-pi-pico-2-rp2350-arm-cortex-m33-5056561803951.html Ekran - https://botland.com.pl/raspberry-pi-pico-hat-klawiatury-i-wyswietlacze/19718-ekran-dotykowy-rezystancyjny-lcd-ips-28-320x240px-spi-65k-rgb-do-raspberry-pi-pico-waveshare-19804-5904422347246.html Moduł audio - https://botland.com.pl/pozostale-moduly-do-raspberry-pi-pico/20096-rozszerzenie-audio-2x-glosnik-5w-do-raspberry-pi-pico-waveshare-20167-5904422351847.html Ekspander - https://botland.com.pl/raspberry-pi-pico-hat-ekspandery-wyprowadzen/18872-pico-omnibus-podwojny-ekspander-wyprowadzen-do-raspberry-pi-pico-769894017210.html Niestety tylko daję gifa, bo póki co nie trzymam filmików z projektem na yt. Kodu źródłowego na ten moment nie chcę jeszcze udostępniać, ale jak tylko demo zostanie pokazane w sierpniu na demoparty Xenium to się to zmieni.- 2 odpowiedzi
-
- 7
-
-
- Raspberry Pi
- grafika
-
(i 1 więcej)
Tagi:
-
Operacje plikowe, czy mogę po raz drugi przypisać wartość pod wskaźnik?
_LM_ opublikował temat w Programowanie
Tak jak w tytule plus dodatkowe pytanie, wytłumaczę na podstawie kodu. Mam pamięć która przechowuje parametry transmisji zapisane jako csv (świadomie zrezygnowałem z JSON) następnie po zainstalowaniu systemu plików i odnalezieniu tego z parametrami "reg1.txt"[będzie .csv] robię dwie rzeczy: zliczam ile jest linii(wierszy) w pliku i przesyłam kolejne wiersze do funkcji dekodującej. while ((d = readdir(dh)) != NULL) { printf("DIR %s\n", d->d_name); if(0 == (strcmp(d->d_name,"reg1.txt"))) { FILE * f = fopen("/usb/reg1.txt", "r"); char line[128]; int l = 0; if(mdbTask != NULL){ vTaskSuspend(mdbTask); while(fgets(line, sizeof(line), f)) //where sizeof(buf) is the length of //line you anticipate reading in. { //do something with buf here; //The input of fgets will be NULL as soon //as its input fp has been fully read, then exit the loop l++; } decode_csv(NULL,l); FILE * f = fopen("/usb/reg1.txt", "r"); // printf("test1\n"); while(fgets(line, sizeof(line), f)) //where sizeof(buf) is the length of { decode_csv(line, 0); } Pierwsze otwarcie pliku ma na celu zliczenie linii, jest to później potrzebne gdyż stosuję dynamiczną alokację pamięci. Problem był kiedy chciałem przesłać do funkcji "decode_csv" kolejne linie z pliku. Poradziłem sobie przypisując ponownie wskaźnik file * f nie jestem pewien czy to prawidłowe rozwiązanie? Ewentualnie czy istnieje inny sposób na zliczenie wierszy w pliku tekstowym? Poniżej plik na którym testuję U1,1,4,2,0,1,V I1,1,4,2,6,1,A P1,1,4,2,18,1,W -
Mam do wykonania logger parametrów pewnego urządzenia, wymaganie jest między innymi takie aby po włożeniu pamięci pendrive logowanie zaczynało się automatycznie. Logger buduję w oparciu o esp32s3 który ma wsparcie sprzętowe USB msc, aby zapoznać się z komponentem udostępnionym przez espressif korzystam i ich przykładu https://components.espressif.com/components/espressif/usb_host_msc https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/msc Urządzenie ma być bezobsługowe, czyli użytkownik wkłada pamięć -> esp sprawdza czy istnieje folder ze specyficzną nazwą powiązaną z jego MAC adres, jeśli taki istnieje to tworzy w tym folderze plik z bieżącą datą i czasem oraz zaczyna zapisywanie danych. -> jeśli folder nie istnieje to musi go założyć i przejść powyższe kroki, ścieżkę buduję w taki sposób: uint8_t chipid[6]; esp_efuse_mac_get_default(chipid); char makedir[128]; sprintf(makedir,"/usb/LOGGER_%02X%02X%02X%02X%02X%02X",chipid[0], chipid[1], chipid[2], chipid[3], chipid[4], chipid[5]); bool directory_exists = stat(makedir, &s)==0; if (!directory_exists) { if (mkdir(makedir, 0775) != 0) { ESP_LOGE(TAG, "mkdir failed with errno: %s", strerror(errno)); } } Następnie plik z oznaczony aktualnym czasem np: 22082024_1444.csv i do niego zapisuje kolejne rekordy danych. Sprawa która mnie zastanawia to właśnie ten moment kiedy ktoś podejdzie i wyjmie pendrive z urządzenia, log będzie co sekundę, można liczyć że akurat pendrive zostanie wyjęty pomiędzy kolejnymi zapisami, ale co jeśli trafi akurat w moment dodawania wpisu do pliku? Można w programowy sposób na to zareagować lub muszę wykombinować jakiś mechanizm aby plik został poprawnie zamknięty przed wyjęciem pamięci z gniazda?
-
Witam was serdecznie. Mam pytanie. Chciałbym użyć wyrażeń regularnych regex. Dodaje #include <regex.h> i próbuje użyć funkcji regcomp lub regexec. Niestety po kompilacji otrzymuję błąd "undefined reference to regcomp". Co muszę zrobić aby regex działał w CubeIDE w wersji 1.11.0? Znalazłem taką bibliotekę https://github.com/kokke/tiny-regex-c tylko nie wiem za bardzo jak mam ją wrzucić do CubeIDE? Czy ta biblioteka ma sens i czy są jakieś inne rozwiązania dotyczące wyrażeń regularnych dla CubeIDE?
-
Cześć, mając przetwornik A/C (np. MCP3426) na wyjściu otrzymuję liczbę stałoprzecinkową. Co, jeśli dodam do siebie 4 wyników pomiarów i podzielę ich sumę przez 4. Może się zdarzyć tak, że wynik będzie zmiennoprzecinkowy. Tu, już wchodzimy na arytmetykę zmiennoprzecinkową, często wolniejszą dla mikrokontrolera. Gdy patrzę na ten problem, zastanawiam się, czy jest sens w ogóle uśredniać w takim wypadku. Jak to rozwiązujecie?
- 6 odpowiedzi
-
- mikrokontroler
- C++
-
(i 1 więcej)
Tagi:
-
Potrzebowałem na szybko biblioteki dla wyświetlacza OLED 1.5" Wziąłem pierwszą z brzegu: https://github.com/hexaguin/SSD1327 i przystosowałem ją aby działała na mikrokontroler STM32L476 z HAL: .h #ifndef SSD1327_SSD1327_H_ #define SSD1327_SSD1327_H_ #include "main.h" #include "stdbool.h" #include "spi.h" // Scroll rate constants. See datasheet page 40. #define SSD1327_SCROLL_2 0b111 #define SSD1327_SCROLL_3 0b100 #define SSD1327_SCROLL_4 0b101 #define SSD1327_SCROLL_5 0b110 #define SSD1327_SCROLL_6 0b000 #define SSD1327_SCROLL_32 0b001 #define SSD1327_SCROLL_64 0b010 #define SSD1327_SCROLL_256 0b011 // void writeCmd(uint8_t reg); void writeData(uint8_t data); void setWriteZone(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); uint16_t coordsToAddress(uint8_t x, uint8_t y); void setPixelChanged(uint8_t x, uint8_t y, bool changed); void SSD1327drawPixel(uint8_t x, uint8_t y, uint8_t color, bool display); void SSD1327drawRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color, bool display); void SSD1327drawHLine(int x, int y, int length, uint8_t color, bool display); void SSD1327drawVLine(int x, int y, int length, uint8_t color, bool display); void SSD1327drawLine(int x1, int y1, int x2, int y2, uint8_t color, bool display); void SSD1327drawByteAsRow(uint8_t x, uint8_t y, uint8_t byte, uint8_t color); void SSD1327drawChar(uint8_t x, uint8_t y, char thisChar, uint8_t color); void SSD1327drawChar16(uint8_t x, uint8_t y, char thisChar, uint8_t color); void SSD1327drawChar32(uint8_t x, uint8_t y, char thisChar, uint8_t color); void SSD1327drawCharArray(uint8_t x, uint8_t y, char text[], uint8_t color, int size); void SSD1327drawString(uint8_t x, uint8_t y, char * textString, uint8_t color, int size); void setupScrolling(uint8_t startRow, uint8_t endRow, uint8_t startCol, uint8_t endCol, uint8_t scrollSpeed, bool right); void startScrolling(); void stopScrolling(); void scrollStep(uint8_t startRow, uint8_t endRow, uint8_t startCol, uint8_t endCol, bool right); void fillStripes(uint8_t offset); void SSD1327clearBuffer(); void SSD1327writeFullBuffer(); void writeUpdates(); void SSD1327setContrast(uint8_t contrast); void SSD1327initRegs(); void SSD1327init(SPI_HandleTypeDef * hspi); extern uint8_t frameBuffer[8192]; // Should mirror the display's own frameBuffer. extern uint8_t changedPixels[1024]; // Each bit of this array represets whether a given byte of frameBuffer (e.g. a pair of pixels) is not up to date. #endif /* SSD1327_SSD1327_H_ */ .c /* * ssd1327.c * * Created on: Apr 22, 2022 * Author: dell */ #include "main.h" #include "spi.h" #include "dma.h" #include "ssd1327.h" #include "stdbool.h" #include "stdlib.h" #include "math.h" #include "font8x8_basic.h" #include "font16x16.h" #include "font16x32.h" #define CS_SET HAL_GPIO_WritePin(SSD_CS_GPIO_Port, SSD_CS_Pin,GPIO_PIN_SET) #define CS_RESET HAL_GPIO_WritePin(SSD_CS_GPIO_Port, SSD_CS_Pin,GPIO_PIN_RESET) #define DC_SET HAL_GPIO_WritePin(SSD_DC_GPIO_Port,SSD_DC_Pin,GPIO_PIN_SET) #define DC_RESET HAL_GPIO_WritePin(SSD_DC_GPIO_Port,SSD_DC_Pin,GPIO_PIN_RESET) #define RST_SET HAL_GPIO_WritePin(SSD_RST_GPIO_Port,SSD_RST_Pin,GPIO_PIN_SET) #define RST_RESET HAL_GPIO_WritePin(SSD_RST_GPIO_Port,SSD_RST_Pin,GPIO_PIN_RESET) #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MIN(x, y) (((x) < (y)) ? (x) : (y)) uint8_t frameBuffer[8192]; // Should mirror the display's own frameBuffer. uint8_t changedPixels[1024]; SPI_HandleTypeDef * spix; static void bitWrite(uint8_t * x, uint8_t n, bool b); static uint8_t bitRead(uint8_t x,uint8_t n) { return(x & (1<<n)); } // bitWrite(x, 0, 1); // write 1 to the first bit of x static void ssdDelay(uint32_t d); static void SSD1327writeCmd(uint8_t reg); static void bitWrite(uint8_t * x, uint8_t n, bool b) { b ? (*x|= (1 << n)):(*x&=~(1 << n)); } //void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) //TODO: Find a way to handle the write commands without toggling CS and DC every time void SSD1327writeCmd(uint8_t reg){//Writes a command byte to the driver DC_RESET; // digitalWrite(_dc, LOW); CS_RESET; // SPI.transfer(reg); HAL_SPI_Transmit(spix,®,1,500); CS_SET; // digitalWrite(_cs, HIGH); } void SSD1327writeData(uint8_t data){//Writes 1 byte to the display's memory DC_SET; CS_RESET; HAL_SPI_Transmit(spix,&data,1,100); CS_SET; } void SSD1327setWriteZone(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { //defines a rectangular area of memory which the driver will itterate through. This function takes memory locations, meaning a 64x128 space SSD1327writeCmd(0x15); //Set Column Address SSD1327writeCmd(x1); //Beginning. Note that you must divide the column by 2, since 1 byte in memory is 2 pixels SSD1327writeCmd(x2); //End SSD1327writeCmd(0x75); //Set Row Address SSD1327writeCmd(y1); //Beginning SSD1327writeCmd(y2); //End } uint16_t SSD1327coordsToAddress(uint8_t x, uint8_t y){ //Converts a pixel location to a linear memory address return (x/2)+(y*64); } void SSD1327setPixelChanged(uint8_t x, uint8_t y, bool changed){ uint16_t targetByte = SSD1327coordsToAddress(x, y)/8; bitWrite(&changedPixels[targetByte], SSD1327coordsToAddress(x, y) % 8, changed); } void SSD1327drawPixel(uint8_t x, uint8_t y, uint8_t color, bool display){//pixel xy coordinates 0-127, color 0-15, and whether to immediately output it to the display or buffer it int address = SSD1327coordsToAddress(x,y); if((x%2) == 0){//If this is an even pixel, and therefore needs shifting to the more significant nibble frameBuffer[address] = (frameBuffer[address] & 0x0f) | (color<<4); } else { frameBuffer[address] = (frameBuffer[address] & 0xf0) | (color); } if(display){ SSD1327setWriteZone(x/2,y,x/2,y); SSD1327writeData(frameBuffer[address]); SSD1327setPixelChanged(x, y, false); // We've now synced the display with this byte of the buffer, no need to write it again } else { SSD1327setPixelChanged(x, y, true); // This pixel is due for an update next refresh } } void SSD1327drawRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color, bool display){//Draws a rectangle from x1,y1 to x2,y2. uint8_t xMin = MIN(x1, x2); // TODO: double performance by writing whole bytes at a time uint8_t xMax = MAX(x1, x2); uint8_t yMin = MIN(y1, y2); uint8_t yMax = MAX(y1, y2); for (uint8_t x = xMin; x <= xMax; x++) { for (uint8_t y = yMin; y <= yMax; y++) { SSD1327drawPixel(x, y, color, display); } } } void SSD1327drawHLine(int x, int y, int length, uint8_t color, bool display){ for (uint8_t i = x; i < x+length; i++) { SSD1327drawPixel(i, y, color, display); } } void SSD1327drawVLine(int x, int y, int length, uint8_t color, bool display){ for (uint8_t i = y; i < y+length; i++) { SSD1327drawPixel(x, i, color, display); } } void SSD1327drawLine(int x0, int y0, int x1, int y1, uint8_t color, bool display){ //Bresenham's line algorithm int deltaX = abs(x1-x0); int deltaY = abs(y1-y0); int signX = x0<x1 ? 1 : -1; int signY = y0<y1 ? 1 : -1; int error = (deltaX>deltaY ? deltaX : -deltaY)/2, error2; while (true) { SSD1327drawPixel(x0, y0, color, display); if (x0==x1 && y0==y1) break; error2 = error; if (error2 >-deltaX) { error -= deltaY; x0 += signX; } if (error2 < deltaY) { error += deltaX; y0 += signY; } } } void SSD1327drawByteAsRow(uint8_t x, uint8_t y, uint8_t byte, uint8_t color){//Draws a byte as an 8 pixel row for (int i = 0; i < 8; i++) { if(bitRead(byte, i)){ SSD1327drawPixel(x+i, y, color, false); } } } void SSD1327drawChar(uint8_t x, uint8_t y, char thisChar, uint8_t color){ for (size_t i = 0; i < 8; i++) { SSD1327drawByteAsRow(x, y+i, font8x8_basic[(unsigned char)thisChar][i], color); } } void SSD1327drawCharArray(uint8_t x, uint8_t y, char text[], uint8_t color, int size){ const char* thisChar; uint8_t xOffset = 0; if(size==16){ for (thisChar = text; *thisChar != '\0'; thisChar++) { SSD1327drawChar16(x+xOffset, y, *thisChar, color); xOffset += 8; } } else if(size==32){ for (thisChar = text; *thisChar != '\0'; thisChar++) { SSD1327drawChar32(x+xOffset, y, *thisChar, color); xOffset += 16; } } else { for (thisChar = text; *thisChar != '\0'; thisChar++) { SSD1327drawChar(x+xOffset, y, *thisChar, color); xOffset += 8; } } } void SSD1327drawString(uint8_t x, uint8_t y, char * textString, uint8_t color, int size){ // char text[64]; // textString.toCharArray(text, 64); SSD1327drawCharArray(x,y, textString, color, size); } void SSD1327drawChar16(uint8_t x, uint8_t y, char thisChar, uint8_t color){ for (size_t row = 0; row < 16; row++) { SSD1327drawByteAsRow(x, y+row, font16x16[(unsigned char)thisChar][row*2], color); SSD1327drawByteAsRow(x+8, y+row, font16x16[(unsigned char)thisChar][(row*2)+1], color); } } void SSD1327drawChar32(uint8_t x, uint8_t y, char thisChar, uint8_t color){ for (size_t row = 0; row < 32; row++) { SSD1327drawByteAsRow(x, y+row, font16x32[(unsigned char)thisChar][row*2], color); SSD1327drawByteAsRow(x+8, y+row, font16x32[(unsigned char)thisChar][(row*2)+1], color); } } void SSD1327fillStripes(uint8_t offset){ //gradient test pattern for(int i = 0; i < 8192; i++){ uint8_t color = ((i+offset) & 0xF) | (((i+offset) & 0xF)<<4); frameBuffer[i] = color; } for (uint16_t i = 0; i < 1024; i++) { changedPixels[i] = 0xFF; // Set all pixels to be updated next frame. fillStripes should not be used without a full write anyways, but just in case } } void SSD1327setupScrolling(uint8_t startRow, uint8_t endRow, uint8_t startCol, uint8_t endCol, uint8_t scrollSpeed, bool right){ uint8_t swap; if (startRow > endRow) { // Ensure start row is before end swap = startRow; startRow = endRow; endRow = swap; } if (startCol > endCol) { // Ditto for columns swap = startCol; startCol = endCol; endCol = swap; } SSD1327writeCmd(0x2E); // Deactivate scrolling before changing anything if (right) { SSD1327writeCmd(0x26); // Scroll right } else { SSD1327writeCmd(0x27); // Scroll left } SSD1327writeCmd(0); // Dummy byte SSD1327writeCmd(startRow); SSD1327writeCmd(scrollSpeed); SSD1327writeCmd(endRow); SSD1327writeCmd(startCol); SSD1327writeCmd(endCol); SSD1327writeCmd(0); // Dummy byte }; void SSD1327startScrolling(){ SSD1327writeCmd(0x2F); } void SSD1327stopScrolling(){ SSD1327writeCmd(0x2E); } void SSD1327scrollStep(uint8_t startRow, uint8_t endRow, uint8_t startCol, uint8_t endCol, bool right){ setupScrolling(startRow, endRow, startCol, endCol, SSD1327_SCROLL_2, right); startScrolling(); ssdDelay(15); stopScrolling(); } void SSD1327clearBuffer(){// for(int i = 0; i < 8192; i++){ if (frameBuffer[i]) { // If there is a non-zero (non-black) byte here, make sure it gets updated frameBuffer[i] = 0; bitWrite(&changedPixels[i/8], i%8, 1); // Mark this pixel as needing an update } } } void SSD1327writeFullBuffer(){ //Outputs the full framebuffer to the display SSD1327setWriteZone(0,0,63,127); //Full display for(int i = 0; i < 8192; i++){ SSD1327writeData(frameBuffer[i]); } for (uint16_t i = 0; i < 1024; i++) { changedPixels[i] = 0; // Set all pixels as up to date. } } void SSD1327writeUpdates(){ // Writes only the pixels that have changed to the display for (size_t y = 0; y < 128; y++) { bool continued = false; // If we can continue with the write zone we're using for (size_t x = 0; x < 128; x++) { uint16_t address = coordsToAddress(x, y); if ( bitRead(changedPixels[address/8], address % 8) ) { // If we need an update here if (!continued) { // Just write the byte, no new write zone needed continued = true; setWriteZone(x/2, y, 63, 127); // Set the write zone for this new byte and any subsequent ones } writeData(frameBuffer[address]); bitWrite(&changedPixels[address/8], address % 8, 0); } else { continued = false; // The chain of pixels is broken } } } } void SSD1327setContrast(uint8_t contrast){ SSD1327writeCmd(0x81); //set contrast control SSD1327writeCmd(contrast); //Contrast byte } void SSD1327initRegs(){ //Sends all the boilerplate startup and config commands to the driver SSD1327writeCmd(0xae);//--turn off oled panel SSD1327writeCmd(0x15); //set column addresses SSD1327writeCmd(0x00); //start column 0 SSD1327writeCmd(0x7f); //end column 127 SSD1327writeCmd(0x75); //set row addresses SSD1327writeCmd(0x00); //start row 0 SSD1327writeCmd(0x7f); //end row 127 SSD1327writeCmd(0x81); //set contrast control SSD1327writeCmd(0x80); //50% (128/255) SSD1327writeCmd(0xa0); //gment remap SSD1327writeCmd(0x51); //51 (To my understanding, this is orientation SSD1327writeCmd(0xa1); //start line SSD1327writeCmd(0x00); SSD1327writeCmd(0xa2); //display offset SSD1327writeCmd(0x00); SSD1327writeCmd(0xa4); //rmal display SSD1327writeCmd(0xa8); //set multiplex ratio SSD1327writeCmd(0x7f); SSD1327writeCmd(0xb1); //set phase leghth SSD1327writeCmd(0xf1); SSD1327writeCmd(0xb3); //set dclk SSD1327writeCmd(0x00); //80Hz:0xc1 90Hz:0xe1 100Hz:0x00 110Hz:0x30 120Hz:0x50 130Hz:0x70 01 SSD1327writeCmd(0xab); //Enable vReg SSD1327writeCmd(0x01); SSD1327writeCmd(0xb6); //set phase leghth SSD1327writeCmd(0x0f); SSD1327writeCmd(0xbe); //Set vcomh voltage SSD1327writeCmd(0x0f); SSD1327writeCmd(0xbc); //set pre-charge voltage SSD1327writeCmd(0x08); SSD1327writeCmd(0xd5); //second precharge period SSD1327writeCmd(0x62); SSD1327writeCmd(0xfd); //Unlock commands SSD1327writeCmd(0x12); SSD1327writeCmd(0xAF); ssdDelay(100); } void SSD1327init(SPI_HandleTypeDef * hspi){ spix = hspi; RST_SET; ssdDelay(10); // digitalWrite(_rst, LOW); RST_RESET; //delay(100); ssdDelay(10); RST_SET; ssdDelay(10); SSD1327initRegs(); } static void ssdDelay(uint32_t d) { HAL_Delay(d); }; bibliotekę uruchomiłem, działa, ale widzę że jest sporo bajtów kopiowanych z ram do wyświetlacza, próbowałem to zrobić za pomocą DMA, ja używam SPI2 więc DMA pracuje na kanale 5 tryb normal. W pierszej kolejności próbowałem podmienić zawartość funkcji void SSD1327writeFullBuffer(){ //Outputs the full framebuffer to the display SSD1327setWriteZone(0,0,63,127); //Full display for(int i = 0; i < 8192; i++){ SSD1327writeData(frameBuffer[i]); } for (uint16_t i = 0; i < 1024; i++) { changedPixels[i] = 0; // Set all pixels as up to date. } } W taki sposób: void SSD1327writeFullBuffer(){ //Outputs the full framebuffer to the display SSD1327setWriteZone(0,0,63,127); //Full display // for(int i = 0; i < 8192; i++){ // SSD1327writeData(frameBuffer[i]); // } DC_SET; CS_RESET; HAL_SPI_Transmit_DMA(&spi2,frameBuffer,8192); CS_SET; for (uint16_t i = 0; i < 1024; i++) { changedPixels[i] = 0; // Set all pixels as up to date. } } W main jest przykładowy kod z biblioteki while (1) { static uint32_t lastTimer = 0; if(HAL_GetTick() > lastTimer) { HAL_GPIO_TogglePin(LD2_GPIO_Port,LD2_Pin); lastTimer = HAL_GetTick() + 200; // SSD1327clearBuffer(); // SSD1327drawString(16, 16, "Oled", 0x1, 32); // SSD1327drawString(16, 48, "TEST 1", 0x2, 32); // SSD1327drawString(20, 48+48, "16:32:55", 0xF, 16); // SSD1327setContrast(50); // SSD1327writeFullBuffer(); framecount++; SSD1327clearBuffer(); for (int x = 0; x < 128; x++) { for (int y = (sin(((float)x+framecount)/16)*32)+64; y < 128; y++) { SSD1327drawPixel(x, y, 3, false); } } SSD1327drawCharArray(24, 0, timeText, 0xF, 32); SSD1327drawString(0, 112, "-12.5C", 0xF, 16); SSD1327drawString(84, 112, "52.1%", 0xF, 16); SSD1327writeFullBuffer(); } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } Tyle że zamiast delay, użyłem timera programowego. Niestety DMA nie wysyła danych na SPI. Czy jeśli SPI jest skonfigurowane do pracy z DMA to mogę używać go też "normalnie" tj: część danych np ustawienia wysyłać z funkcji a część za pomocą DMA?
-
Taki prosty kod: #include <avr/io.h> #include <avr/pgmspace.h> const __flash char tab[] = "test"; int main(void) { for(;;); } Kompiluje się ekstra, ale w edytorze mam wciąż błąd składni który nawet nie jest opisany: Zaś konsolka jest czysta 22:01:15 **** Incremental Build of configuration Release for project constFlasz **** make all 'Building file: ../main.c' 'Invoking: AVR Compiler' avr-gcc -Wall -Os -fpack-struct -fshort-enums -ffunction-sections -fdata-sections -std=gnu99 -funsigned-char -funsigned-bitfields -mmcu=atmega16 -DF_CPU=1000000UL -MMD -MP -MF"main.d" -MT"main.o" -c -o "main.o" "../main.c" 'Finished building: ../main.c' ' ' 'Building target: constFlasz.elf' 'Invoking: AVR C Linker' avr-gcc -Wl,-Map,constFlasz.map -mmcu=atmega16 -o "constFlasz.elf" ./main.o 'Finished building target: constFlasz.elf' ' ' 'Invoking: AVR Create Extended Listing' avr-objdump -h -S constFlasz.elf >"constFlasz.lss" 'Finished building: constFlasz.lss' ' ' 'Create Flash image (ihex format)' avr-objcopy -R .eeprom -R .fuse -R .lock -R .signature -O ihex constFlasz.elf "constFlasz.hex" 'Finished building: constFlasz.hex' ' ' 'Create eeprom image (ihex format)' avr-objcopy -j .eeprom --no-change-warnings --change-section-lma .eeprom=0 -O ihex constFlasz.elf "constFlasz.eep" 'Finished building: constFlasz.eep' ' ' 'Invoking: Print Size' avr-size --format=avr --mcu=atmega16 constFlasz.elf AVR Memory Usage ---------------- Device: atmega16 Program: 114 bytes (0.7% Full) (.text + .data + .bootloader) Data: 0 bytes (0.0% Full) (.data + .bss + .noinit) 'Finished building: sizedummy' ' ' 22:01:16 Build Finished. 0 errors, 0 warnings. (took 1s.344ms) Ma ktoś pomysł jak wyłączyć tego syntaxa?
-
Temat związany z biblioteką menu ale założyłem osobny bo wiem że "gryzie" początkujących kolegów. Do rzeczy, mam funkcję która na podstawie podanych wartości węzła i id ma zwrócić wskaźnik do zmiennej która ma akurat być wyświetlona: proszę nie zwracać uwagi na stałe przypisania do iteratorów 'i' i 'j' to w tej chwili niema znaczenia. xx_t * get_props(nodeType_t n,idType_t id) { for(uint8_t i = 0; i < 2 ; i++ ){ if(props[i].nod == n){ // spr czy wezel na liscie for(uint8_t j = 0; j<2;j++){ if(props[i].val[j].id == id)return(xx_t*)&props[i].val[j]; // spr id i zwrot wskaznikka } } } return NULL; // jesli niema } tutaj struktury na których operuje ww funkcja: typedef enum{char_t,uint8t,uint16t,uint32t,int8t,int16t,int32t,rtc_timetypedef,rtc_datetypedef}retTypeEnum; typedef struct{ idType_t id; void * propVal; retTypeEnum retType; }xx_t; typedef struct{ nodeType_t nod; xx_t * val; }menu_properites_t; uzupełnione: xx_t nodeTime[] = { {.id = ZEGAR,.propVal = (RTC_TimeTypeDef*)&time,.retType = rtc_timetypedef}, {.id = DATA,.propVal = (RTC_DateTypeDef*)&date,.retType = rtc_datetypedef} }; xx_t nodeLcd[] = { {.id = LCD_JASNOSC,.propVal = (uint8_t*)&j,.retType = uint8t}, {.id = LCD_KONTRAST,.propVal = (uint8_t*)&b,.retType = uint8t} }; menu_properites_t props[] = { {.nod = 2,.val = nodeTime}, {.nod = 3,.val = nodeLcd} }; I teraz to, o co chcę zapytać: Jak widać powołałem do życia typ enum który określa na jaki typ ma być rzutowana zwracana zmienna, w programie wygląda to tak: if(NULL != get_props(node, id)){ p->flocate(0,5); xx_t * mp = get_props(node, id); if(mp->retType == uint8t) { uint8_t * xa =(uint8_t*)mp->propVal; p->fint(*xa); // wyswitl } if(mp->retType == rtc_timetypedef) { } Kod działa tak że kiedy po odpytaniu za pomocą get_props wynik jest różny od null, mogę wyświetlić zawartość zmiennej spod zwracanego wskaźnika. Warunkiem poprawnego wyświetlenia tej zmiennej jest poprawne rzutowanie na nią. Jak widać w zwracanej wartości jest informacja o tym w jaki sposób program ma ją traktować(enum). No i pytanko do fachowców, jak postrzegacie taki zapis? Czy można to jakoś inaczej, lepiej zapisać? A może przykład jest przekombinowany? Proszę pamiętać że pytanie dotyczy języka C, na C++ kompletnie się nie znam. Liczę że pytanie jest zrozumiałe
-
gcc: warning: missing braces around initializer [-Wmissing-braces]
_LM_ opublikował temat w Programowanie
Niby już taki początkujący nie jestem, ale czasem życie ostro to weryfikuje dostaję warning taki jak w temacie wątku, szukałem troszkę jak to rozwiązać i okazuje się że jest to jakiś błąd kompilatora. Kod - mocno uproszczony - który mi go generuje: .c xx_t x[] = { {.id = 1,.p = (void*)NULL}, {.id = 2,.p = (void*)NULL}, }; prop_t props = {.idx = 0,.val = x}; // <---- w tej linii jest warning nie wiem o którą klamerkę się rzuca. .h typedef struct{ uint8_t id; void * p; }xx_t; typedef struct{ uint8_t idx; xx_t * val[]; }prop_t; Drażni mnie ten warning, może ktoś mi wytłumaczyć jak się go pozbyć? -
Piszę sobie takiego małego libsa do obsługi enkodera, do testów używam najzwyklejszego impulsowego oraz płytki nucleo L476. Problem polega na tym że po pewnym czasie kręcenia, funkcja enkodera zacina się. Ponieważ nie mam pomysłu jak to dalej debugować przedstawiam kod testowy. Użyłem timera4 w trybie encodera i pracuje on w pełnej swojej pojemności zliczania. Podejrzewam że problem tkwi w maszynie stanów, być może ktoś zauważy miejsce błędu. #include "main.h" #include "tim.h" #include "stm32l4xx_hal.h" #include "enco.h" typedef enum {idle, count, runf,end}encoState_t; static uint8_t encodir = 0; static uint16_t encoInterval = 50; static void(*enc_event_callback)(void); void enco_init(const uint16_t interval) { encoInterval = interval; } void register_enc_event_callback(void(*callback)(void)){ if(callback)enc_event_callback = callback; } void ENCODER_EVENT() { static uint16_t encoval = 0; static uint32_t tickStart = 0; static encoState_t enc = idle; static uint16_t roznicaPlus, roznicaMinus; if (encoval != __HAL_TIM_GET_COUNTER(&htim4) && enc == idle) { // zaczeto krecic enc = count; // liczenie encoval = __HAL_TIM_GET_COUNTER(&htim4); // roznicaPlus = __HAL_TIM_GET_COUNTER(&htim4) + (ENCO_STEP - 1); roznicaMinus = __HAL_TIM_GET_COUNTER(&htim4) - (ENCO_STEP - 1); tickStart = HAL_GetTick() + encoInterval; // timeout return; } if (enc == count) { if (roznicaPlus == __HAL_TIM_GET_COUNTER(&htim4)) { encodir = encoRight; encoval = __HAL_TIM_GET_COUNTER(&htim4); enc = idle; if (enc_event_callback)enc_event_callback(); }else if (roznicaMinus == __HAL_TIM_GET_COUNTER(&htim4)) { encodir = encoLeft; encoval = __HAL_TIM_GET_COUNTER(&htim4); enc = idle; if (enc_event_callback)enc_event_callback(); // wykonanie przypisanej f } } else if (enc == count && HAL_GetTick() > tickStart) { // timeout encoval = __HAL_TIM_GET_COUNTER(&htim4); // przypisanie aby nie zmenial statusu bez potrzeby enc = idle; } } encoDir getEncoDir(void){ return encodir; // zwraca kierunek obracania } .h #ifndef INC_ENCO_H_ #define INC_ENCO_H_ #define ENCO_STEP 4 //4 or 2 typedef enum{encoLeft = 16,encoRight = 32}encoDir; encoDir getEncoDir(void); void enco_init(const uint16_t interval); void ENCODER_EVENT(); void register_enc_event_callback(void(*callback)(void)); #endif /* INC_ENCO_H_ */ main.c /* USER CODE BEGIN 2 */ enco_init(500); register_enc_event_callback(encoclb); HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_1); HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { ENCODER_EVENT(); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } Oraz zarejestrowana funkcja obsługi callbacka: void encoclb(){ HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); }
-
Czy jest możliwość tak obudować typ wyliczeniowy aby był widoczny tylko w zakresie struktury, lub nowego powołanego typu danych? Np: #include<stdio.h> typedef struct { enum {aa,ii,dd}cci ;}cc_t; int main() { printf("enum = %i", ii); } Chciałbym aby wartość "ii"(w ogóle to co znajduje się w enum) była widoczna tylko w zakresie typu cc_t, tymczasem program potrafi "printować wartość ii
-
Osobliwy wiersz kodu w języku C pochodzący z SDK Raspberry Pi Pico
VisualLab opublikował temat w Programowanie
Od kilku dni przeglądam SDK Raspberry Pi Pico. W pliku nagłówkowym o nazwie "address_mapped.h", który znajduje się w katalogu (ścieżka względna): "...\pico-sdk-master\src\rp2_common\hardware_base\include\hardware", natrafiłem na wiersz kodu, którego nie mogę rozgryźć. Oto on: *(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask; Plik "address_mapped.h" jest dostępny pod adresem: https://raspberrypi.github.io/pico-sdk-doxygen/address__mapped_8h_source.html. Przytoczony wiersz pochodzi z funkcji: "hw_set_bits". Jej kod to: __force_inline static void hw_set_bits(io_rw_32 *addr, uint32_t mask) { *(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask; } W tym wierszu występuje "funkcja" o nazwie "hw_set_alias_untyped", która w rzeczywistości jest makrem (jest umieszczone w tym samym pliku). Pytanie jest następujące: do czego jest przypisywana wartość argumentu "mask"? Po lewej stronie występuje makro "hw_set_alias_untyped", które zostanie zastąpione przez: "((uintptr_t)(addr))". Po jego podstawieniu i rozwinięciu powstanie wyrażenie (o ile nie popełniłem błędu): *(io_rw_32 *) ((uintptr_t)(volatile void *) addr) = mask; Argument "addr" jest adresem rejestru RP2040, odwzorowanego w pamięci. Ten adres ("addr") jest rzutowany na wskaźnik amorficzny ("volatile void *") a następnie na wskaźnik "uintptr_t" (znów: o ile nie popełniłem błędu). Żeby analiza była łatwiejsza można to uprościć do: wynik_makra = ((uintptr_t)(volatile void *) addr) // uproszczenie, które mam na myśli *(io_rw_32 *) wynik_makra = mask; // wynikowe wyrażenie po uproszczeniu Czy wynik makra to jakaś zmienna anonimowa (bez nazwy)? Czy lewa część wyrażenia, to dereferencja wskaźnika typu "io_rw_32", którego zawartość jest wyliczana z tego wyrażenia? Czy oznacza to, że zawartość argumentu "mask" jest przypisywana do "tego czegoś", co powstaje po dereferencji? Czy ktoś potrafi to wyjaśnić? Bo ja chyba jednak błędnie wytłumaczyłem sobie ten wiersz kodu. -
Cześć, poszukuję przykładów w języku C dla tablic, gdzie do pól mogę się odwołać przez klucz. W javie istnieje coś takiego jak typ map, gdzie zarówno klucz jak i wartość mogą przyjmować dowolny typ danych. W C trzeba to napisać od podstaw, lub skorzystać z gotowych przykładów ale coś mało jest w tym temacie materiałów, a jeśli już są to korzystają z C++ gdzie jest nawet biblioteka wbudowana <map.h> EDIT zapomniałem dorzucić link do tego co już znalazłem: https://ucgosu.pl/2019/07/tablice-przyspieszajace-wyszukiwanie-elementow/ oraz link z artykułu https://github.com/jamesroutley/write-a-hash-table
-
Cześć, chciałbym zlecić napisanie programu sterującego modelem łódki. Łódka dwusilnikowa (silniki szczotkowe klasy 500 + regulatory ESC). Program ma mieć możliwość wpisania na sztywno koordynatów GPS czyli załóżmy: 50.365044428151755, 18.562147930177403 Po uruchomieniu łódki i urządzenia łódka ma za zadanie przepłynąć do celu w linii prostej. I tyle nie chce żadnych fajerwerków sterowania itd. tylko sam algorytm wyznaczenia i utrzymywania kierunku przez łódkę tak by do celu płynęła w linii prostej Wymagane źródła najlepiej w C (ale nie upieram się ), procesor STM32F1xx lub STM32F4xx do tego GPS RadioLink M8N SE100 i jakiś IMU jest mi obojętne może być GY-91 lub inne wskazane. Oferty tylko poważne, proszę kierować na mail [email protected] Budżet 1500zł.
-
Mam dwie tablice char: char t1[100]; char t2[2048]; oraz wskaźnik który zależnie od potrzeb wskazuje na t1 lub t2 char * ptr; ptr = t1; czy jest możliwość aby za pomocą sizeof pobrać rozmiar tablicy na którą aktualnie ustawiony jest wskaźnik?
-
Pisząc drobne programiki na AVR w C nie był możliwy taki konstrukt: int w = 10; // przykład, w może oznaczać rozmiar ciągu danych np: z uart char str[w]; Tymczasem działając teraz z programem pod esp8266 tak z ciekawości napisałem: int packetSize = Udp.parsePacket(); char packetBuffer[packetSize]; Pytanie teraz do ekspertów: czy powyższy zapis jest prawidłowy? to się kompiluje i działa ale czy nie powinienem zastosować jakichś funkcji do dynamicznej alokacji danych?
-
O ile wydrukowanie stringu z flasch jest dla mnie oczywiste proste i zrozumiałe Serial.print(F("test")); to nie bardzo wiem jak tego makra(PGMP) użyć w funkcjach z końcówką _P np jak zapisać wersję tego: strcmp("Audyt N. 7 ",s) Tak aby string "Audyt N. 7 " był pobierany bezpośrednio z flasch? np: gdy próbuję zapisać (to jest cały warunek) if((lineMem == 0) && (0 == strcmp_P("Audyt N. 7 ",s))) Kompilacja przebiega prawidłowo jednak zajętość RAM nie zmienia się, a chyba powinna być pomniejszona o ilość znaków napisu Audyt..... ?
-
CUDA na kiju (a nawet GPU) cz. 1 Wstęp Tym artykułem chciałbym zapoczątkować cykl artykułów na temat programowania równoległego z wykorzystaniem kart graficznych (GPU) w języku CUDA C. W części pierwszej przedstawię krótki wstęp teoretyczny tzn. omówię dlaczego równoległe wykonywanie obliczeń może istotnie przyspieszyć działanie programu, opiszę budowę procesorów graficznych, a w dalszych częściach będą przedstawione praktyczne wskazówki z fragmentami kodu programu do mnożenia macierzy. Zapraszam do lektury! Treść artykułu została usunięta, ponieważ użytkownik publikujący ten poradnik wykorzystał w nim materiały, do których nie posiadał odpowiednich praw. Usunięcie nastąpiło na prośbę autora zapożyczonych materiałów.
- 2 odpowiedzi
-
- 11
-
-
- programowanie
- C
-
(i 3 więcej)
Tagi:
-
Czołem, jako osoba mniej ścisła, męczę się od wczoraj z próbą ustawienia dwóch zmiennych pobranych z pliku txt zlokalizowanym na zdalnym serwie, korzystając z biblioteki HTTPClient.h. Plik tekstowy ma zawartość "640, 1530" (ale może być też dowolny separator lub nawet w osobnej linii, mam dostęp do tego pliku i mogę go zrobić jak chcę, wazne aby działało). http.begin("http://192.168.1.2/zmienne.txt"); int httpCode2 = http.GET(); if (httpCode2 > 0) { //Check for the returning code String payload2 = http.getString(); } else { Serial.println("<br>Blad pobrania danych z serwa</br>"); } http.end(); Za cholerę nie potrafię przerobić tych dwóch wartości na 2 osobne zmienne liczbowe czyli np.: zmienna1 = 640; zmienna2 = 1530; Kombinowałem i przerobieniem tego na tablicę ale wówczas zmienna[0] = 640 a zmienna[1] = 0. Próbowałem używać funkcji konwertujacych string na int, ale rezultaty podobne. Większość przykładów na necie to operowanie pobieranym plikiem z np. karty SD i użycie funkcji fscanf, ale przypadku pobrania zawartości pliku przez http.GET() to już nie działa. Myślałem też o przerobieniu tego na XML, ale to bez sensu skoro to tylko 2 liczbowe wartości. Będę wdzięczny za naprowadzenie. Pozdro
-
Z konieczności utworzenia małego mobilnego systemu pomiarowego, pojawiła się potrzeba połączenia mikrokontrolera z telefonem pracującym na Androidzie, tak aby ten drugi otrzymywał dane z pierwszego. Wybór podstawowej platformy (Android) padł z uwagi na jej powszechność, a także brak konieczności zakupu płytek, czujników itp. Dlatego też chciałbym ośmielić tym artykułem osoby, które noszą się z zamiarem tworzenia projektów, które wymagają dużej ilości dostępnych w telefonach czujników, interfejsów komunikacyjnych i możliwości obliczeniowych, które czasem się marnują, a samo przedsięwzięcie nie przewiduje trwałego montażu telefonu w projektowanym urządzeniu (choć pewnie dużo osób i tak ponownie wykorzystuje te same mikrokontrolery/płytki z czujnikami itd. do kolejnych projektów). Jedną z wad takiego rozwiązania, jest brak zestawu pinów ogólnego przeznaczenia (GPIO) w telefonach. Ten artykuł bierze udział w naszym konkursie! Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » W kilku słowach niniejszy poradnik przeprowadzi przez proces tworzenia aplikacji na Androida (Java), która będzie komunikować się poprzez wirtualny port szeregowy za pomocą przewodu USB z całkiem nowym Raspberry Pi Pico. Oczywiście, omówiony zostanie również kod w języku C, który będzie sterował stanami logicznymi pinów mikrokontrolera (włączony/wyłączony) na podstawie odczytu wysłanych z telefonu danych. Ponadto będzie on również na żądanie telefonu odczytywał stan logiczny wybranych wejść i wysyłał te informacja, które wyświetlone zostaną w aplikacji mobilnej. Dodatkowo zaznajomi on z przygotowaniem środowiska do kompilacji programów w C/C++ na Raspberry Pi Pico jak i podpowie jak do pewnego stopnia zautomatyzować proces kompilacji i wgrywania programu do mikrokontrolera. Przygotowanie aplikacji na telefon z Androidem Po pierwsze należy pobrać i zainstalować zintegrowane środowisko programistyczne (IDE) Android Studio. Po utworzeniu nowego projektu z domyślną opcją "Empty Activity" i następnie wybranym "API21: Android 5.0 (Lollipop)" w pozycji "Minimum SKD" można zacząć od utworzenia pliku o nazwie "usb_devices.xml" o następującej zawartości (widoczne w pliku wartości wyjaśnione zostaną później): <?xml version="1.0" encoding="utf-8"?> <resources> <usb-device product-id="10" vendor-id="11914" /> </resources> Należy skopiować go do katalogu "app\res\xml" tak jak na zrzucie poniżej. Albo skopiować plik w Eksploratorze Windows i wkleić w Android Studio (może być wymagane utworzenie katalogu "xml" - patrz opcje menu kontekstowego), albo utworzyć bezpośrednio w edytorze Android Studio, czy znaleźć jego lokalizację na dysku (domyślnie "Litera:\Users\Użytkownik\AndroidStudioProjects\NazwaProjektu\app\src\main\res\xml"). Następnie można przystąpić do konfiguracji pliku "AndroidManifest.xml" (do znalezienia jak na zdjęciu powyżej w katalogu "manifests"). Należy wkleić poniższe linijki kodu: <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/usb_device" /> tak jak pokazano to na zrzucie ekranu: Aplikacja będzie korzystała z zewnętrznej biblioteki usb-serial-for-android, którą należy podłączyć do aplikacji. Zrobić można to edytując najpierw plik "build.gradle (Project)", następnie - "build.gradle (Module)" tak, jak przedstawiono na zdjęciach poniżej: wklejając poniższe linijki: maven { url 'https://jitpack.io' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } implementation 'com.github.mik3y:usb-serial-for-android:3.3.0' Teraz można zsynchronizować projekt klikając na "Synch Now" (ostatni rysunek powyżej). Teraz należy wkleić poniższy kod do pliku "app\res\layout\activity_main.xml". Będzie opisywał on wygląd ekranu aplikacji - pole tekstowe i dwa przyciski. <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/tekst" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="vendorID productID" android:textSize="29sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/on" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="50dp" android:layout_marginTop="50dp" android:text="ON" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/off" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:layout_marginEnd="50dp" android:text="OFF" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> Teraz przyszła pora na kod programu. Należy otworzyć plik "MainActivity.java" z katalogu "app\java" i zastąpić jego zawartość (poza pierwszą linijką rozpoczynającą się od "package...") tą podaną poniżej. Komentarz zawarte w kodzie opisują "co jest od czego" i ich przestudiowanie da pogląd na to, jak to wszystko działa. // Zaimportowanie używanych przez aplikację klas import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbManager; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.WindowManager; import android.widget.Button; import android.widget.TextView; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; // Zaimportowanie zewnętrzenej biblioteki "usb-serial-for-android" import com.hoho.android.usbserial.driver.CdcAcmSerialDriver; import com.hoho.android.usbserial.driver.ProbeTable; import com.hoho.android.usbserial.driver.UsbSerialDriver; import com.hoho.android.usbserial.driver.UsbSerialPort; import com.hoho.android.usbserial.driver.UsbSerialProber; import com.hoho.android.usbserial.util.SerialInputOutputManager; public class MainActivity extends AppCompatActivity { // Deklaracja zmiennych globalnych - opis w dalszej części kodu TextView poleTekstowe; BroadcastReceiver broadcastReceiver; UsbSerialPort usbSerialPort; SerialInputOutputManager serialInputOutputManager; ScheduledFuture co100Ms; ExecutorService rx; Button on; Button off; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Załadowanie pliku układu okna i elementów aplikacji setContentView(R.layout.activity_main); // Ekran będzie włączony tak długo, jak aplikacja będzie widoczna na głównym planie getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // Przypisanie pola tekstowego z pliku układu do zmiennej w kodzie aplikacji poleTekstowe = findViewById(R.id.tekst); // Definicja obiektu, który będzie nasłuchiwał zdarzeń związanych z podłączeniem i odłączeniem // miktokontrolera do telefonu broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Przełącznik "switch" - program nie musi sprawdzać po kolei wszystkich "if'ów" czy // "else if'ów", tylko kieruje się do właściwego miejsca za pierwszym podejściem switch (intent.getAction()) { // Obsługa akcji podłączenia urządzeni USB do telefonu case "android.hardware.usb.action.USB_DEVICE_ATTACHED": UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); // Wartości, które należy umieścić we wspomnianym wcześniej pliku "usb_device.xml" poleTekstowe.setText(usbDevice.getVendorId() + " " + usbDevice.getProductId()); ProbeTable probeTable = new ProbeTable(); // Przypisanie odpowiedniego dla Pico sterownika probeTable.addProduct(usbDevice.getVendorId(), usbDevice.getProductId(), CdcAcmSerialDriver.class); UsbSerialProber usbSerialProber = new UsbSerialProber(probeTable); UsbSerialDriver usbSerialDriver = usbSerialProber.probeDevice(usbDevice); UsbDeviceConnection usbDeviceConnection = usbManager.openDevice(usbSerialDriver.getDevice()); // Przypisanie wcześniej zdefiniowanego sterownika do wirtualnego portu // szeregowego USB usbSerialPort = usbSerialDriver.getPorts().get(0); try { // Próba otwarcia portu szeregowego dla mikrokontrolera usbSerialPort.open(usbDeviceConnection); // Zdefiniowanie parametrów komunikacji usbSerialPort.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); // Sygnał Data Terminal Ready - Pico i Android rozpocznął komunikację usbSerialPort.setDTR(true); // Sygnał Request To Send - wymaga go np. Arduino do rozpoczęcia komunikacji z Androidem usbSerialPort.setRTS(true); } catch (Exception ignored) {} // Obiekt nasłuchujący danych przychodzących SerialInputOutputManager.Listener serialInputOutputListener = new SerialInputOutputManager.Listener() { @Override public void onRunError(Exception ignored) {} @Override public void onNewData(byte[] data) { runOnUiThread(() -> poleTekstowe.setText(new String(data))); } }; serialInputOutputManager = new SerialInputOutputManager(usbSerialPort, serialInputOutputListener); serialInputOutputManager.setReadTimeout(0); // Definicja pozyższego obiektu jako oddzielnego wątku programu... rx = Executors.newSingleThreadExecutor(); // ...i jego uruchomienie rx.submit(serialInputOutputManager); // Zdefiniowanie osobnego wątku, który będzie wywoływał się do 100 ms wysyłając // porcję danych co100Ms = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { try { usbSerialPort.write("o".getBytes(), 0); } catch (Exception ignored) {} }, 0, 100, TimeUnit.MILLISECONDS); break; // Obsługa akcji podłączenia urządzeni USB do telefonu case "android.hardware.usb.action.USB_DEVICE_DETACHED": // Sprzątanie po ustanowionej wcześniej komunikacji do odłączeniu Pico od Androida if (co100Ms != null && rx != null) { co100Ms.cancel(false); serialInputOutputManager.stop(); rx.shutdown(); poleTekstowe.setText("Odłączono"); } break; } } }; // Definicja filtrów podłączone/odłączone urządzenie USB IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); // Uruchomienie nasłuchiwania podłączenia/odłączenia urządzenia USB this.registerReceiver(broadcastReceiver, intentFilter); // Definicja przycisków "ON" i "OFF" on = findViewById(R.id.on); on.setOnClickListener(v -> { try { // Wysłanie ciągu znaków do mikrokontrolera usbSerialPort.write("n".getBytes(), 0); } catch (Exception ignored) {} }); off = findViewById(R.id.off); off.setOnClickListener(v -> { try { usbSerialPort.write("f".getBytes(), 0); } catch (Exception ignored) {} }); } // Sprzątanie po ustanowionej komunikacji w momencie zamknięcia (strzałką wstecz) aplikacji @Override protected void onDestroy() { if (co100Ms != null && rx != null) { co100Ms.cancel(false); serialInputOutputManager.stop(); rx.shutdown(); } this.unregisterReceiver(broadcastReceiver); super.onDestroy(); } } Ostatnim krokiem w tej części będzie skompilowanie pliku *.apk aplikacji, który to będzie można zainstalować w telefonie. Wybieramy z menu "Build" pozycję "Generate Signed APK...", wybrać "APK", utworzyć nowy klucz ("Create new..."), wybrać opcję "Build Variants" "release" oraz "Signature Versions" "V2..." i zakończyć kreatora przyciskiem "Finish" - zrzuty poniżej. W powiadomieniu wyświetli się lokalizacja do pliku *.apk. Za jego pomocą można zainstalować aplikację w telefonie. Gotowe! Przynajmniej androidowa aplikacja Pliki do znalezienia tutaj: Android.zip Program dla Raspberry Pi Pico Za wszelką cenę chciałem uruchomić Pico SDK, aby móc zaprogramować "Małą Malinkę" w C i finalnie kopiując plik *.uf2 do widocznej pamięci masowej po podłączeniu jej do komputera trzymając przyciśnięty przycisk "BOOTSEL". Było to zanim powstał skrypt dla Windowsa, a że na Windowsie 7 nie chciał współpracować żaden instalator Microsoftu, to rozwiązałem to w inny sposób. Jak to mówią "the hard way". Po pierwsze nigdy nie zrobiłem nic w C, co więcej oficjalna instrukcja do C/C++ mówiła np. o łatwej konfiguracji chociażby CLion'a. Nie wspominała tylko, że na Linuksie... A więc do rzeczy. Pobieramy następujące składniki: ARM GCC Compiler CMake MinGW Python 3 Raspberry Pi Pico SDK TinyUSB Następnie wypakowujemy je w swojej ulubionej lokalizacji (ja utworzyłem oddzielny folder, gdzie przechowuję binarki programów do "devovania"). Nie ma instalatorów, więc otwarcie mogę się przyznać, że mam niechęć do nich, jak ja coś zrobię, to wiem, gdzie naśmieciłem i łatwiej później posprzątać. Można się przyznawać się, kto też tak ma Co zrobić... A nie, jest MinGW, instalujemy architekturę x86_64. Pierwsze trzy pozycje będą zawierały podkatalog "bin", których ścieżki należy umieścić w systemowej zmiennej "PATH" (Komputer>Właściwości>Zaawansowane ustawienia systemu>Zmienne środowiskowe>Zmienne systemowe) oddzielając je średnikiem. Python 3, tutaj należy podać ścieżkę katalogu głównego. Zawartość TinyUSB kopiujemy do podkatalogu "lib" folderu gdzie wypakowano Raspberry Pi Pico SDK. Można się też pokusić o utworzenie zmiennej użytkownika "PICO_SDK_PATH" zawierającej ścieżkę do wspomniajego w poprzednim zdaniu katalogu. Na zdjęciu tak to wygląda: Pora przygotować właściwe pliki. Najpierw "CMakeLists.txt", który będzie miał zawartość jak poniżej. Należy wspomnieć, że plik"pico_sdk_import.cmake" należy umieścić w katalogu projektu, a znaleźć można go w katalogu zawierającym Raspberry Pi Pico SDK w podfolderze "external". cmake_minimum_required(VERSION 3.17) include(pico_sdk_import.cmake) project(project_name) # nazwa projektu set(CMAKE_C_STANDARD 11) pico_sdk_init() add_executable(project_name file_name.c) # dodanie plików wykonywalnych projektu target_link_libraries(project_name pico_stdlib) pico_enable_stdio_usb(project_name 1) # włączenie połączenia szeregowego USB pico_enable_stdio_uart(project_name 0) # wyłączenie UART pico_add_extra_outputs(project_name) Teraz tworzymy długo wyczekiwany plik *.c taki jak wskazany w "CMakeLists.txt" plik wykonywalny. Jego zawartość będzie jak poniżej. Komentarze rozjaśnią sprawę. #include <stdio.h> #include "pico/stdlib.h" int main() { stdio_init_all(); // Użycie pinu GP25 (dioda LED na płytce) gpio_init(25); // Ustawienie GP25 jako wyjścia gpio_set_dir(25, GPIO_OUT); gpio_init(0); // Ustawienie GP0 jako wejścia gpio_set_dir(0, GPIO_IN); gpio_init(1); gpio_set_dir(1, GPIO_IN); // Podciąga GP1 poprzez wbudowany w czip RP2040 opornik - stan wysoki (1), gdy nie podłączony do masy (GND) gpio_pull_up(1); // Bufor dwóch znaków. Efektywnie jednego, drugi to znak terminalny "\0" // Jeśli chcesz odczytać 5 znaków, ustaw należy ustawić bufor na 6 char wiadomosc[2]; while (true) { // Odczytuje dwa znaki (łącznie z terminalnym) w wejścia. Jeśli chcesz odczytywać wiadomości // ze zmienną ilością znaków, użyj "gets(wiadomosc)" zamiast "fgets(wiadomosc, 2, stdin)" // i zakończ wysyłane wiadomości znakiem kowej linii - "\n" fgets(wiadomosc, 2, stdin); // Odczytuje pierwszy znak (indeks [0]) wektora zawierającego wiadomość. // Java może porównać cały ciąg w instrukcji "switch" ;P switch(wiadomosc[0]) { case 'n': // Włącza diodę led przy wiadomości "n" ze standardowego wejścia gpio_put(25, 1); break; case 'f': // Wyłącza diodę led przy wiadomości "f" ze standardowego wejścia gpio_put(25, 0); break; case 'o': // Odczytuje wartości stanów GP0 i GP1 - wysoki (1) lub niski (0) // i wysyła je przez połączenie szeregowe printf("GP0: %d, GP1: %d", gpio_get(0), gpio_get(1)); break; } } } Do kompilacji przygotujemy plik wsadowy *.bat, który ułatwi zautomatyzuje kompilację. Jego zawartość będzie następująca: @echo off mode con: cols=130 lines=32 for %%i in (%1) do (set sciezka=%%~pi) for %%i in (%1) do (set litera=%%~di) cd /d "%litera%%sciezka%" cmake.exe -DCMAKE_BUILD_TYPE=Release -G "CodeBlocks - MinGW Makefiles" "%litera%%sciezka%" cmake.exe --build "%litera%%sciezka%" robocopy "%litera%%sciezka% " "G:\ " "*.uf2" /nfl /ndl /njh /njs /np pause Należy zmienić literę "G:\ " na tą, pod którą pojawi się dysk wymienny Pico. Spacje nie są błędem. Dziwna sprawa, ale inaczej nie chce działać, przynajmniej na Siódemce, a dodanie spacji na końcu było rozwiązaniem. Przygotowany plik chowamy przed wzrokiem i np. programem Default Programs Editor tworzymy pozycję w menu kontekstowym pliku *.c a jeszcze lepiej tworzymy nowe rozszerzenie dla plików C Pico. Albo kompilujemy wykorzystując dwie linijki zaczynające się od "cmake.exe", albo korzystamy ze szpanerskiego menu, a efekt kompilacji będzie następujący: Pliki do znalezienia tutaj: Pico.zip Pierwsze uruchomienie Po uruchomieniu aplikacji na telefonie i podłączeniu Pico przez kabel USB i adapter OTG należy zgodzić się, aby aplikacja uruchamiała się wraz z podłączeniem mikrokontrolera do smartfona. Nada to uprawnienia do korzystania z połączenia USB aplikacji. Pola "vendorID" i "productID" zmienią swoje wartości na te, które trzeba było umieścić we wspomnianym wcześniej pliku "usb_devices.xml". Po odłączeniu i ponownym podłączeniu (w trakcie działania aplikacji mobilnej, gdyż w androidowej aplikacji odczyt od cykliczne nadawanie zakodowane jest po podłączeniu urządzenia, ale tym zdefiniowanym w pliku "MainActivity.java", który wykonuje się po jej uruchomieniu) Malinki będzie można włączyć i wyłączyć jej diodę LED (GP25) i co 100 ms (na żądanie telefonu) będzie odczytywany stan wejść GP0 i GP1. Jak to działa, na zdjęciach poniżej. Zwarcie pinów GP0 (1) i 3V3OUT (36) ustawi stan wysoki na wejściu GP0, a połączenie GP1 (2) z GND (3) przesteruje normalnie wysoki stan do niskiego. Gotowe pliki wykonywalne tutaj: Binarki.zip Podsumowanie Niniejszy artykuł nie jest projektem od A do Z jakiegoś przedsięwzięcia, tylko przedstawia zagadnienie komunikacji portem szeregowym pomiędzy mikrokontrolerem (tutaj Raspberry Pi Pico, choć nie musi to być on) a telefonem komórkowym z Androidem wykorzystując natywne rozwiązania tworzenia programów na te platformy, tym samym nie będąc na "łasce" np. rozwiązań modułowych tworzenia aplikacji z klocków, wykorzystując wszystkie możliwości jakie daje Android, Pico, Java i C. Oczywiście to tylko zalążek, ale nie znalawszy gotowego rozwiązania, zmotywowało mnie to, żeby takie zrobić i zaprezentować, ktoś może mieć łatwiej. Ja już mam łatwiej, bo takiego potrzebuję PS. Nie spodziewałem się, że tak długo zajmie mi napisanie tego tekstu... PS2. Nazwa aplikacji mobilnej to taka losowo wklepana na klawiaturze. PS3. Pierwszy obrazek, drugi kod i trzy następne obrazki to trochę ludzik, trochę android
- 1 odpowiedź
-
- 3
-
-
- Android
- Raspberry Pico
-
(i 3 więcej)
Tagi:
-
CUDA na kiju (a nawet GPU) cz 2. Wstęp W poprzedniej części omówiliśmy sobie dlaczego warto wykorzystać procesory graficzne do obliczeń oraz po krótce zapoznaliśmy się z ich budową. W tej części omówimy sobie wszystko to co należy zrobić przed napisaniem programu: podam kilka informacji przydatnych podczas rozważania programu równoległego, przygotujemy środowisko do pisania programu w CUDA C, a następnie przygotujemy podwaliny pod pierwszy program tzn. opracujemy sobie teorię mnożenia macierzy oraz napiszemy prototyp programu na CPU. Gotowi? No to jazda!!! Treść artykułu została usunięta, ponieważ użytkownik publikujący ten poradnik wykorzystał w nim materiały, do których nie posiadał odpowiednich praw. Usunięcie nastąpiło na prośbę autora zapożyczonych materiałów.
- 8 odpowiedzi
-
- 8
-
-
- konkurs2021
- CUDA C
- (i 3 więcej)
-
Praca Programista embedded poszukiwany - Śląsk
mic1248 opublikował temat w Sprzedam/Kupię/Zamienię/Praca
Cześć, szukam osób, których pasją jest programowanie mikrokontrolerów i które chciałyby zająć się tym tematem na poważnie, lub już się tym zawodowo zajmują. Wymagana podstawowa znajomość języka C lub C++. Oferuję pomoc w przygotowaniu do wymaganego stanowiska. Wymagana jest również znajomość języka angielskiego na poziomie komunikatywnym. Miejsce pracy: Gliwice. Branża: motoryzacyjna. Zainteresowanych proszę o wiadomość prywatną.
