Skocz do zawartości

STM32H7 Zapis na kartę SD z użyciem FatFs - wykorzystanie DMA


radek04

Pomocna odpowiedź

Cześć,
powoli muszę kończyć projekt, z którym walczę od kilku miesięcy. Dotyczy on akwizycji danych z czujników inercyjnych (i mikrofonów) oraz ich zapis na kartę SD.

Dla zainteresowanych przedstawiam tematy bezpośrednio związane z projektem:  STM32 UART wysyłanie danych typu uint16_t, STM32F7 Zbyt mała szybkość zapisu danych z czujników I2C na kartę SD, Nucleo-H7 - odłączone piny SDMMC i dwa związane pośrednio: Gdzie opłaca się zamawiać PCB?, Oscyloskop - pomoc w wyborze.

Zmontowałem układ na PCB, co rozwiązało kilka problemów związanych z wyprowadzeniami pinów uC na płytce Nucleo.

Zapis na kartę działa już sprawnie, ale wciąż w trybie blokującym. Przez to nawet z użyciem dwóch buforów i naprzemiennego odczytu/zapisu wciąż tracę niektóre pomiary, których nie mogę realizować podczas zapisywania danych na SD.

Czy ktoś z Was robił coś podobnego? Szukam rozwiązań w sieci, ale nie mogę niczego konkretnego się doszukać. W CubeIDE jest informacja, by wygenerować kod bazujący na sd_diskio_dma_template.*files. Gdzie znajdę takie takowe szablony?

SDIO_DMA.png

Link do komentarza
Share on other sites

(edytowany)
9 godzin temu, matsobdev napisał:

pewnie bardziej to

Tak, widziałem to. Ale właśnie nie mogłem się doszukać, czym te wersje się różnią. No i na razie nie udało mi się żadnej uruchomić. Mam dziesiątki błędów przy kompilacji, więc to nie jest trywialna zamiana plików...

 

Edit: Pozbyłem się błędów. Testuję.

Edytowano przez radek04
Link do komentarza
Share on other sites

Rozumiem, że nawet jeżeli zapis na kartę z bufora dokonuje się z wykorzystaniem DMA, to w tym samym czasie nie mogę odczytywać tego bufora i do niego zapisywać? Potrzebuję tutaj 2 buforów pracujących naprzemiennie, tak?

Na razie testuję 1 bufor i w czasie zapisu na kartę tracę zawsze 6 odczytów pomiędzy kolejnymi "okrążeniami". Czyli zapisuje mi pierwsze 200 odczytów (rozmiar bufora), następnie jest 6 przerwań timera (12 ms, f=500 Hz), w czasie których zapis do bufora się nie realizuje, kolejna zapisana próbka ma numer 207 i tak w kółko.

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

17 minut temu, radek04 napisał:

Rozumiem, że nawet jeżeli zapis na kartę z bufora dokonuje się z wykorzystaniem DMA, to w tym samym czasie nie mogę odczytywać tego bufora i do niego zapisywać? Potrzebuję tutaj 2 buforów pracujących naprzemiennie, tak?

Tak lub zastosować bufor okreżny. 

1 godzinę temu, radek04 napisał:

Pozbyłem się błędów. Testuję.

Edytowano 59 minut temu przez

Wiem, że teraz jest presją czasowa, ale jak już skończysz podsumuj proszę wszystkie te wyzwania, bo są bardzo ciekawe. 

Link do komentarza
Share on other sites

(edytowany)
46 minut temu, pmochocki napisał:

Tak lub zastosować bufor okreżny. 

No właśnie przy FatFs nie ma w Cube takich ustawień DMA, jak np. przy interfejsach I2C, gdzie w łatwy sposób mogę zrobić bufor okrężny.

Natomiast przy samodzielnym podziale na 2 bufory wciąż występuje ten sam problem. Zawsze gubię po 6 odczytów. Tak, jakby DMA nie działało...

Wrzucę fragment kodu, może zauważycie jakiś błąd.

while (1)
{
	  while (previous_timer_tick == current_timer_tick) //jeśli timer nie zmienił jeszcze wartości current_timer_tick
	  {
		  HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, RESET);	// 0
		  HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, RESET);	// 0
		  HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, RESET);	// 0
		  HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, RESET);	// 0
	  }

	if (previous_timer_tick < current_timer_tick - 1) //jeśli timer zmienił wartość current_timer_tick więcej niż 1 raz (program nie wyrabia czasowo) program się nie zatrzymuje
	  {
		  //printf("Uklad nie wyrabia czasowo (karta)\n");
		  HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, RESET);	// 0
		  HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, SET);		// 1
		  HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, RESET);	// 0
		  HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, RESET);	// 0
		  current_timer_tick = previous_timer_tick; //naprawa sytuacji, by dalej działało
	  }

	  if (current_timer_tick == previous_timer_tick + 1)
	  {
			while 		//dopóki którykolwiek I2C nie jest gotowy
					(
						HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY
					|| 	HAL_I2C_GetState(&hi2c2) != HAL_I2C_STATE_READY
					|| 	HAL_I2C_GetState(&hi2c3) != HAL_I2C_STATE_READY
					|| 	HAL_I2C_GetState(&hi2c4) != HAL_I2C_STATE_READY
					)
				{
						//printf("I2C nie jest gotowe\n");
				  HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, RESET);	// 0
				  HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, RESET);	// 0
				  HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, SET);		// 1
				  HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, RESET);	// 0
				}
				//printf("Wszystkie I2C sa gotowe, normalna praca\n");
				previous_timer_tick = current_timer_tick;
				HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, RESET);	// 0
				HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, RESET);	// 0
				HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, RESET);	// 0
				HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, SET);	// 1

				HAL_I2C_Mem_Read_DMA(&hi2c1, MPU9250_ACC_ADDRESS_A,	MPU9250_ACCEL_XOUT_H, 1, MPU9250_Data_A, 14); //14 pomiarów od razu
				HAL_I2C_Mem_Read_DMA(&hi2c2, MPU9250_ACC_ADDRESS_B,	MPU9250_ACCEL_XOUT_H, 1, MPU9250_Data_B, 14); //14 pomiarów od razu
				HAL_I2C_Mem_Read_DMA(&hi2c3, MPU9250_ACC_ADDRESS_C,	MPU9250_ACCEL_XOUT_H, 1, MPU9250_Data_C, 14); //14 pomiarów od razu
				HAL_I2C_Mem_Read_IT(&hi2c4, MPU9250_ACC_ADDRESS_D,	MPU9250_ACCEL_XOUT_H, 1, MPU9250_Data_D, 14); //14 pomiarów od razu

				if (ktory_bufor == 0) /* wybór buforów */
				{
					sprintf(&my_string_0[(ASCII_char_No + 7)*(round_No-1)], /* zapis z czujników do bufora nr 0 */
					"%6d "
					"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
					"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
					"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
					"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",

					real_timer_tick,
                            MPU9250_Data_A[0],MPU9250_Data_A[1],MPU9250_Data_A[2],MPU9250_Data_A[3],MPU9250_Data_A[4],MPU9250_Data_A[5],MPU9250_Data_A[8],MPU9250_Data_A[9],MPU9250_Data_A[10],MPU9250_Data_A[11],MPU9250_Data_A[12],MPU9250_Data_A[13],
                            MPU9250_Data_B[0],MPU9250_Data_B[1],MPU9250_Data_B[2],MPU9250_Data_B[3],MPU9250_Data_B[4],MPU9250_Data_B[5],MPU9250_Data_B[8],MPU9250_Data_B[9],MPU9250_Data_B[10],MPU9250_Data_B[11],MPU9250_Data_B[12],MPU9250_Data_B[13],
                            MPU9250_Data_C[0],MPU9250_Data_C[1],MPU9250_Data_C[2],MPU9250_Data_C[3],MPU9250_Data_C[4],MPU9250_Data_C[5],MPU9250_Data_C[8],MPU9250_Data_C[9],MPU9250_Data_C[10],MPU9250_Data_C[11],MPU9250_Data_C[12],MPU9250_Data_C[13],
                            MPU9250_Data_D[0],MPU9250_Data_D[1],MPU9250_Data_D[2],MPU9250_Data_D[3],MPU9250_Data_D[4],MPU9250_Data_D[5],MPU9250_Data_D[8],MPU9250_Data_D[9],MPU9250_Data_D[10],MPU9250_Data_D[11],MPU9250_Data_D[12],MPU9250_Data_D[13]);

					if (round_No == rounds)//jeśli skończą się pomiary w danym etapie (np. przez 5 sekund 500*5)
					{
						ktory_bufor = 1; //zmiana bufora
						round_No = 0;
						lap++; //kolejne okrążenie

						HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, SET); //włączenie diody
						if(f_write(&fil, my_string_0, sizeof(my_string_0), &numread) != HAL_OK) printf("f_write ERROR\n"); /* zapis na kartę z bufora nr 0 */
						HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, RESET); //wyłączenie diody - koniec zapisu na kartę
					}else if (ktory_bufor == 1) /* wybór buforów */
					{
						sprintf(&my_string_1[(ASCII_char_No + 7)*(round_No-1)], /* zapis z czujników do bufora nr 1 */
							"%6d "
							"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
							"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
							"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
							"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",

							real_timer_tick,
                                MPU9250_Data_A[0],MPU9250_Data_A[1],MPU9250_Data_A[2],MPU9250_Data_A[3],MPU9250_Data_A[4],MPU9250_Data_A[5],MPU9250_Data_A[8],MPU9250_Data_A[9],MPU9250_Data_A[10],MPU9250_Data_A[11],MPU9250_Data_A[12],MPU9250_Data_A[13],
                                MPU9250_Data_B[0],MPU9250_Data_B[1],MPU9250_Data_B[2],MPU9250_Data_B[3],MPU9250_Data_B[4],MPU9250_Data_B[5],MPU9250_Data_B[8],MPU9250_Data_B[9],MPU9250_Data_B[10],MPU9250_Data_B[11],MPU9250_Data_B[12],MPU9250_Data_B[13],
                                MPU9250_Data_C[0],MPU9250_Data_C[1],MPU9250_Data_C[2],MPU9250_Data_C[3],MPU9250_Data_C[4],MPU9250_Data_C[5],MPU9250_Data_C[8],MPU9250_Data_C[9],MPU9250_Data_C[10],MPU9250_Data_C[11],MPU9250_Data_C[12],MPU9250_Data_C[13],
                                MPU9250_Data_D[0],MPU9250_Data_D[1],MPU9250_Data_D[2],MPU9250_Data_D[3],MPU9250_Data_D[4],MPU9250_Data_D[5],MPU9250_Data_D[8],MPU9250_Data_D[9],MPU9250_Data_D[10],MPU9250_Data_D[11],MPU9250_Data_D[12],MPU9250_Data_D[13]);

					if (round_No == rounds)//jeśli skończą się pomiary w danym etapie (np. przez minutę 500*60)
					{
						ktory_bufor = 0; //zmiana bufora
						round_No = 0;
						lap++; //kolejne okrążenie

						HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, SET); //włączenie diody
						if(f_write(&fil, my_string_1, sizeof(my_string_1), &numread) != HAL_OK) printf("f_write ERROR\n"); /*zapis na kartę z bufora nr 1 */
                      	HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, RESET); //wyłączenie diody - koniec zapisu na kartę
					}


					if (lap == laps) //koniec wszystkich operacji
					{
						HAL_TIM_Base_Stop_IT(&htim7); //uruchomienie timera generującego przerwanie
						HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, RESET); //wyłączenie diody
						printf("Zatrzymuje przerwania TIM7\n");
							if (f_close(&fil) == HAL_OK) printf("f_close OK\n");
							else printf("f_close ERROR\n");
							unmount_sd();
							printf("previous = %d, current = %d, real = %d, round_No = %d, lap = %d\n", previous_timer_tick, current_timer_tick, real_timer_tick, round_No, lap);
							printf("done\n");
							return 0;
					}
	}
}


void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) /* przerwanie timera */
{
	if (htim->Instance == TIM7)	// przerwanie pochodzi od timera 7
	{
		current_timer_tick++;
		round_No++;
		real_timer_tick++; //wartość ta nigdy nie jest zerowana
	}
}

Edit: Zrobiłem też 2 komplety tablic do zapisu z czujników.
Zamiast MPU9250_Data_A[] mam MPU9250_Data_A_0[] oraz MPU9250_Data_A_1[] (i tak dla każdego czujnika), ale efekt bez zmian.

Edytowano przez radek04
Link do komentarza
Share on other sites

To może teraz jakbyś odpuścił sobie ten sprintf i zamiast stingow zapisywał "raw data". To może byś się wyrobił. Taki pomysł, bo wiem, że jest presja czasu. 

Link do komentarza
Share on other sites

Powiedzmy, że zapisujesz sobie binarnie do pliku 0x05, to w kodzie ASCII, czy UTF-8 to będzie coś (literka) lub nic (krzaczek), bo to będzie znak "zakazany" (jakiś kontrolny itp.). Otwórz w hexedytorze, to będzie miało większy sens. Ten "sprintf" na tych danych z hexedytora zrobisz na konkuterze.

Edytowano przez matsobdev
  • Lubię! 2
Link do komentarza
Share on other sites

1 godzinę temu, radek04 napisał:

Pomysł jest niezły, aje jak wykorzystać takie dane?

No piszesz skrypt w Pythonie i już... 

Możesz tego zrobić wykresy, podsumowanie statystyczne, średnia, mediana, itd... 

Edytowano przez pmochocki
  • Lubię! 2
Link do komentarza
Share on other sites

OK, fajny pomysł.
W jakiej formie to jest wtedy faktycznie zapisywane? Zera i jedynki?
Najłatwiej by mi było to obrabiać w MATLAB-ie, bo i tak tam będę później przetwarzał te dane.

Link do komentarza
Share on other sites

Zasadniczo jest lepiej, bo zapisuje mniej danych, ale i tak występują opóźnienia. W zasadzie jeśli i tak mam przerwy w odczytach, to większej różnicy nie robi mi wielkość tej przerwy, a postać po konwersji funkcją sprintf() jest dla mnie wygodniejsza.

Wciąż zależy mi na zapisie ciągłym, to niestety nie jest rozwiązanie.

Ale oczywiście dziękuję za propozycję, bo jest bardzo sensowna.

  • Lubię! 1
Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • 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.