Стремясь записать тонну информации на SD-карту как можно ближе к жизни, насколько это возможно
Поэтому я недавно начал проект, в котором я использую акселерометр, а также плату для вывода SD-карты. Я смог правильно записать информацию на SD-карту без каких-либо проблем. Однако моя скорость немного ниже, чем мне бы хотелось, примерно на 40 Гц или около того, где мой акселерометр может регистрировать данные на частоте 260 Гц (MPU-6050). Каждое считывание содержит значения X, Y, Z и метки времени с момента начала записи. Каждое чтение выводится в файл .txt для последующего разделения с помощью сортировки значений через запятую в Excel(я бы хотел) или GNUplot, который намного лучше обрабатывает массивный размер выборки.
Я ссылался https://forum.arduino.cc/t/how-to-write-data-with-high-sampling-rates-to-a-sd-card/281496/9 чтобы попытаться разобраться в этом, но я ничего не мог из этого извлечь. Я не очень хорошо понимаю массивы, кроме того факта, что они являются просто таблицами, как в Excel, и могут быть настроены в 1, 2, 3 или более измерениях.
Я немного почитал, и, похоже, моя проблема в том, что я закрываю и открываю файл каждый цикл записи. Тем не менее, я не знаю, как избежать этого, так как программа, похоже, работает неправильно, если я не закрою файл или если я использую флеш. Мое общее представление о том, как это может происходить без циклов открытия/закрытия каждый раз, было бы следующим:
- Создайте массивы для каждой переменной (X, Y, Z, прошедшее время)
- Возьмите все мои записи за определенное время, может быть, пятьсот показаний или около того, которые составят около 12 секунд, или любое возможное значение, которое я могу выжать из своего ATmega328P и поддерживать стабильность программы, и вставьте их в соответствующие массивы. В идеале, чем дольше, тем лучше, но я понимаю, что у меня ограниченная память.
- За один цикл записи я записываю все значения в форму CSV или даже несколько циклов печати, непрерывно проходя через каждую ячейку массива, пока все значения не будут введены в надлежащем формате.
- Очистите массивы от всей информации, которую они содержали, и вернитесь к шагу 2.
Действительно ценю любую помощь, которую вы, ребята, можете предложить. Спасибо!
//
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
int recordStatus = false; //Default recordStatus is zero.
int recordingLed = 3;
const int chipSelect = 10;
Adafruit_MPU6050 mpu;
void setup(void) {
pinMode(2, INPUT_PULLUP);
pinMode(3, OUTPUT);
Serial.begin(500000);
while (!Serial)
delay(10); // will pause Zero, Leonardo, etc until serial console opens
//SD CARD WRITE CODE BEGIN
{
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
Serial.print("Initializing SD card...");
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
while (1);
}
Serial.println("card initialized.");
}
//SD CARD WRITE CODE END
Serial.println("Adafruit MPU6050 test!"); //MPU6050 ACCELEROMETER INITIALIZATION CODE BEGIN
// Try to initialize!
if (!mpu.begin()) {
Serial.println("Failed to find MPU6050 chip");
while (1) {
delay(10);
}
}
Serial.println("MPU6050 Found!");
mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
switch (mpu.getAccelerometerRange()) {
case MPU6050_RANGE_2_G:
break;
case MPU6050_RANGE_4_G:
break;
case MPU6050_RANGE_8_G:
break;
case MPU6050_RANGE_16_G:
break;
}
mpu.setGyroRange(MPU6050_RANGE_500_DEG);
switch (mpu.getGyroRange()) {
case MPU6050_RANGE_250_DEG:
break;
case MPU6050_RANGE_500_DEG:
break;
case MPU6050_RANGE_1000_DEG:
break;
case MPU6050_RANGE_2000_DEG:
break;
}
mpu.setFilterBandwidth(MPU6050_BAND_260_HZ);
Serial.print("Filter bandwidth set to: ");
switch (mpu.getFilterBandwidth()) {
case MPU6050_BAND_260_HZ:
Serial.println("260 Hz");
break;
case MPU6050_BAND_184_HZ:
Serial.println("184 Hz");
break;
case MPU6050_BAND_94_HZ:
Serial.println("94 Hz");
break;
case MPU6050_BAND_44_HZ:
Serial.println("44 Hz");
break;
case MPU6050_BAND_21_HZ:
Serial.println("21 Hz");
break;
case MPU6050_BAND_10_HZ:
Serial.println("10 Hz");
break;
case MPU6050_BAND_5_HZ:
Serial.println("5 Hz");
break;
}
//MPU6050 ACCELEROMETER INITIALIZATION CODE END
}
unsigned long startMillis;
unsigned long currentMillis;
unsigned long elapsedTime;
int rawMillis;
void loop() {
digitalWrite(recordingLed, LOW);
int buttonRecord = digitalRead(2); //Check button for GND input
/* Get new sensor events with the readings */
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
// make a string for assembling the data to log: //SD CARD VALUES ASSIGN BEGIN
int X = "";
int Y = "";
int Z = "";
String timeofRecord = String(millis()/1000.0, 10);
X = (a.acceleration.x/9.81);
Y = (a.acceleration.y/9.81);
Z = (a.acceleration.z/9.81);
if(buttonRecord == false){ //Toggle record status upon pin 2 going to GND
delay(500);
recordStatus = !recordStatus; //If button is pressed, make recordStatus switch from true to false.
startMillis = millis();
}
if(recordStatus == true){ //While record is toggled, do below
rawMillis = millis()-startMillis;
String elapsedTime = String(rawMillis/1000.00, 3);
File dataFile = SD.open("datalog.txt", FILE_WRITE);
if (dataFile) { // if the file is available, write to it:
digitalWrite(recordingLed, HIGH);
dataFile.print(elapsedTime);
dataFile.print(", ");
dataFile.print(X);
dataFile.print(", ");
dataFile.print(Y);
dataFile.print(", ");
dataFile.print(Z);
dataFile.println("");
dataFile.close();
}
}
// if the file isn't open, pop up an error:
else {
Serial.println("error opening datalog.txt");
}
}
ОБНОВЛЕНИЕ
Так что я некоторое время возился с этим, и мне кажется, что я почти все понял. У меня есть массивы, настроенные для времени, X, Y и Z, и они правильно заполняют значения. Однако, похоже, я не могу попасть в файл своей SD-карты. При инициализации установки нет никаких проблем, как будто она пропускает
если (файл данных){
полностью. Измененный код приведен ниже здесь. Отключений нет, и программа постоянно работает, так что я не думаю, что у меня заканчивается память или возникают какие-либо проблемы с питанием. Красный светодиод на SD - карте мигает, как будто она записывается. Даже если я удалю проверку if(файл данных) и принудительно выполню команды, содержащиеся в инструкции if, мой txt-файл все равно останется пустым. Раньше я писал об этом просто отлично. Проблемы с памятью? У меня доступно менее 500 байт памяти. Возможно, это и есть причина? Или это петля for вызывает у меня проблемы?
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
int recordStatus = false; //Default recordStatus is zero.
int recordingLed = 3;
const int chipSelect = 10;
Adafruit_MPU6050 mpu;
void setup(void) {
pinMode(2, INPUT_PULLUP);
pinMode(3, OUTPUT);
Serial.begin(500000);
while (!Serial)
delay(10);
//SD CARD WRITE CODE BEGIN
{
Serial.print("Initializing SD card...");
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
while (1);
}
Serial.println("card initialized.");
}
//SD CARD WRITE CODE END
Serial.println("Adafruit MPU6050 test!"); //MPU6050 ACCELEROMETER INITIALIZATION CODE BEGIN
// Try to initialize!
if (!mpu.begin()) {
Serial.println("Failed to find MPU6050 chip");
while (1) {
delay(10);
}
}
Serial.println("MPU6050 Found!");
mpu.setAccelerometerRange(MPU6050_RANGE_4_G);
switch (mpu.getAccelerometerRange()) {
case MPU6050_RANGE_2_G:
break;
case MPU6050_RANGE_4_G:
break;
case MPU6050_RANGE_8_G:
break;
case MPU6050_RANGE_16_G:
break;
}
mpu.setGyroRange(MPU6050_RANGE_500_DEG);
switch (mpu.getGyroRange()) {
case MPU6050_RANGE_250_DEG:
break;
case MPU6050_RANGE_500_DEG:
break;
case MPU6050_RANGE_1000_DEG:
break;
case MPU6050_RANGE_2000_DEG:
break;
}
mpu.setFilterBandwidth(MPU6050_BAND_260_HZ);
Serial.print("Filter bandwidth set to: ");
switch (mpu.getFilterBandwidth()) {
case MPU6050_BAND_260_HZ:
Serial.println("260 Hz");
break;
case MPU6050_BAND_184_HZ:
Serial.println("184 Hz");
break;
case MPU6050_BAND_94_HZ:
Serial.println("94 Hz");
break;
case MPU6050_BAND_44_HZ:
Serial.println("44 Hz");
break;
case MPU6050_BAND_21_HZ:
Serial.println("21 Hz");
break;
case MPU6050_BAND_10_HZ:
Serial.println("10 Hz");
break;
case MPU6050_BAND_5_HZ:
Serial.println("5 Hz");
break;
}
//MPU6050 ACCELEROMETER INITIALIZATION CODE END
}
float startMillis;
float elapsedTime[3];
float rawMillis;
float X[3];
float Y[3];
float Z[3];
void loop() {
digitalWrite(recordingLed, LOW);
int buttonRecord = digitalRead(2); //Check button for GND input
/* Get new sensor events with the readings */
if(buttonRecord == false){ //Toggle record status upon pin 2 being pulled down to GND
delay(500);
recordStatus = !recordStatus; //If button is pressed, make recordStatus switch from true to false.
startMillis = millis();
Serial.print("BUTTON PRESSED");
}
if(recordStatus == true){ //While record is toggled, do below
digitalWrite(recordingLed, HIGH);
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
for(int arrayNumber = 0; arrayNumber <= 3; ++arrayNumber){
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
X[arrayNumber] = (a.acceleration.x); //SD CARD VALUES ASSIGN BEGIN/DO MATH TO CALCULATE G'S
Y[arrayNumber] = (a.acceleration.y);
Z[arrayNumber] = (a.acceleration.z);
rawMillis = millis()-startMillis;
elapsedTime[arrayNumber] = rawMillis;
Serial.println(elapsedTime[arrayNumber]);
Serial.println(X[arrayNumber]);
Serial.println(Y[arrayNumber]);
Serial.println(Z[arrayNumber]);
Serial.println(arrayNumber); // diagnostic for checking to make sure arrays are being populated properly
}
File dataFile = SD.open("datalog.txt", FILE_WRITE);
Serial.print("LEFT LOGGING LOOP, ENTERING WRITE LOOP");
if (dataFile) { // if the file is available, write to it
for(int arrayNumber = 0; arrayNumber <= 3; ++arrayNumber){
Serial.print("NOW INSIDE WRITE LOOP"); // confirmation of being inside the write loop, and writing data to the file
String(dataString) = (
elapsedTime[arrayNumber] + String(", " ) + X[arrayNumber] + String(", ") + Y[arrayNumber] + String(", ") + Z[arrayNumber] //Format data for output to the comma separated format for excel/gnuplot
);
dataFile.println(dataString);
}
dataFile.close();
Serial.println("WRITE LOOP DONE");
}
}
}
@Colby Johnson, 👍0
Обсуждение1 ответ
Лучший ответ:
Поэтому я еще некоторое время бился головой о стену и, наконец, нашел решение своей проблемы. @DaveNewton предоставил несколько ссылок, которые ведут к другим ссылкам, и я нашел свой путь сюда, где объясняются некоторые не очень распространенные знания. Похоже, что по какой-то причине функция FILE_WRITE работает медленно и требует много времени для выполнения.
Это можно заменить с помощью
O_CREAT | O_WRITE | O_APPEND
поскольку это сначала проверяет, создан ли файл, и если нет, создает его. Как только проверка или пройдет для раздела O_CREAT, он перейдет к команде O_WRITE. Затем O_APPEND проверяет, есть ли уже какие-либо данные, и если да, перейдите в конец строки, чтобы начать запись новых данных.
Мой готовый код полностью приведен ниже, я надеюсь, что он поможет кому-то в будущем. Я использую карту Micro SD MicroCenter 32 ГБ (класс 10) для хранения данных, эту плату для вывода SD-карты и акселерометр на базе MPU-6050.
// This program is designed to log data to an SD card via an arduino Uno, and SD card breakout board, and a MPU-6050 accelerometer module.
// MPU-6050: https://www.amazon.com/HiLetgo-MPU-6050-Accelerometer-Gyroscope-Converter/dp/B078SS8NQV
// Micro SD Card Breakout: https://www.adafruit.com/product/254
// Micro SD Card I used: https://www.microcenter.com/product/485584/micro-center-32gb-microsdhc-card-class-10-flash-memory-card-with-adapter
// Link to original Arduino StackExchange submission where I worked through this code: https://arduino.stackexchange.com/questions/84961/seeking-to-write-a-ton-of-information-to-an-sd-card-as-close-to-live-as-possible
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
int recordStatus = false; //Default recordStatus is zero.
int recordingLed = 3; //Output to show us whether we are recording or not.
const int chipSelect = 10; //Use pin 10 as our CS pin
Adafruit_MPU6050 mpu;
void setup(void) {
pinMode(2, INPUT_PULLUP); //Set pin 2 to have a default HIGH state using the internal pull up resistor.
pinMode(3, OUTPUT);
Serial.begin(500000);
SPI.begin();
SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0)); //I have no idea if this really does anything, but I figure I'd just leave it since it's not causing any problems.
while (!Serial)
delay(10); // will pause Zero, Leonardo, etc until serial console opens
//SD CARD WRITE CODE BEGIN
{
Serial.print("Initializing SD card...");
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
while (1);
}
Serial.println("card initialized.");
}
//SD CARD WRITE CODE END
Serial.println("Adafruit MPU6050 test!"); //MPU6050 ACCELEROMETER INITIALIZATION CODE BEGIN
// Try to initialize!
if (!mpu.begin()) {
Serial.println("Failed to find MPU6050 chip");
while (1) {
delay(10);
}
}
Serial.println("MPU6050 Found!");
mpu.setAccelerometerRange(MPU6050_RANGE_16_G); //I originally had this at 4 but was capping results, so I moved it up to 16. Feel free to lower this if you know your application will not exceed 4Gs
switch (mpu.getAccelerometerRange()) {
case MPU6050_RANGE_2_G:
break;
case MPU6050_RANGE_4_G:
break;
case MPU6050_RANGE_8_G:
break;
case MPU6050_RANGE_16_G:
break;
}
mpu.setGyroRange(MPU6050_RANGE_500_DEG); //I'm not using the gyro at all, but 500 degrees seems like a good value to use here.
switch (mpu.getGyroRange()) {
case MPU6050_RANGE_250_DEG:
break;
case MPU6050_RANGE_500_DEG:
break;
case MPU6050_RANGE_1000_DEG:
break;
case MPU6050_RANGE_2000_DEG:
break;
}
mpu.setFilterBandwidth(MPU6050_BAND_260_HZ); //Select maximum frequency for recording.
Serial.print("Filter bandwidth set to: ");
switch (mpu.getFilterBandwidth()) {
case MPU6050_BAND_260_HZ:
Serial.println("260 Hz");
break;
case MPU6050_BAND_184_HZ:
Serial.println("184 Hz");
break;
case MPU6050_BAND_94_HZ:
Serial.println("94 Hz");
break;
case MPU6050_BAND_44_HZ:
Serial.println("44 Hz");
break;
case MPU6050_BAND_21_HZ:
Serial.println("21 Hz");
break;
case MPU6050_BAND_10_HZ:
Serial.println("10 Hz");
break;
case MPU6050_BAND_5_HZ:
Serial.println("5 Hz");
break;
SPI.endTransaction();
}
//MPU6050 ACCELEROMETER INITIALIZATION CODE END
}
float X;
float Y;
float Z;
float startMillis;
float currentMillis;
float rawMillis;
int tickMillis = LOW;
void loop() {
int buttonRecord = digitalRead(2); //Check button (pin 2) to see if it is grounded through momentary switch.
// Get new sensor events with the readings.
if(buttonRecord == LOW){ //If buttonRecord switches to GND, switch tickMillis to it's opposite value as a toggle and store startMillis, then wait 300ms as a debounce.
tickMillis = !tickMillis;
startMillis = millis();
delay(300);
}
if(tickMillis == HIGH){
File dataFile = SD.open("datalog.txt", O_CREAT | O_WRITE | O_APPEND); // Create datalog.txt. If already created, move to end of stored data, and begin write function
if (dataFile) { // if the file is available, write to it:
for(uint8_t i = 0; i < 250; i++){ // For i = 0, execute the below code. Then increment i by 1. Once past 250, exit for() loop
digitalWrite(recordingLed, HIGH);
sensors_event_t a, g, temp; // Commands to trigger event sensing and acceleration logging.
mpu.getEvent(&a, &g, &temp);
X = a.acceleration.x/9.81; // Divide normal acceleration return values to calculate G forces
Y = a.acceleration.y/9.81;
Z = a.acceleration.z/9.81;
rawMillis = millis()-startMillis; // Take our current millis value and subtract our beginning startMillis from it to get how long we've actually been recording.
dataFile.print(rawMillis/1000, 3); // Store data in a Comma-Seperated-Value format. The '3' after rawMillis/1000 says print out to 3 decimal places.
dataFile.print(", "); // I just use a txt file since GNUplot can plot from that value, but a CSV library could probably be used here.
dataFile.print(X); // Excel doesn't allow plots beyond 255 datapoints for god knows why. So GNUplot is basically a necessity to plot these accurately to timestamp.
dataFile.print(", ");
dataFile.print(Y);
dataFile.print(", ");
dataFile.print(Z);
dataFile.println();
}
dataFile.flush();
dataFile.close();
}
else {
delay(500);
Serial.println("error opening datalog.txt");
}
} else {
digitalWrite(recordingLed, LOW);
}
}
Большое всем спасибо за комментарии/советы. Это действительно ценно. Окончательная частота записи для этого составляет ~170 Гц, что для меня достаточно хорошо.
- Как увеличить скорость записи на SD-карту в Ардуино
- Понимание того, почему следует избегать «String» и альтернативных решений
- Линейное ускорение от MPU 6050
- Снять гравитацию с акселерометра MPU-6050
- ESP32 сохранение данных на SD-карту в формате .csv не форматируется правильно
- Помощь с MPU-6050
- Как удалить содержимое SD-карты в ардуино?
- Использование MPU-6050 без I2C
Можете ли вы быть более конкретным в отношении "работает неправильно"? В любом случае, есть это, [это](https://forum.arduino.cc/t/data-logging-speed-is-very-slow/624842), и [это](https://forum.arduino.cc/t/increase-sd-data-logging-speed/598624/2). Не зная, в чем проблема/проблемы с массивами, трудно помочь., @Dave Newton
Как насчет записи во внешнюю память, такую как оперативная память или сегнетоэлектрическая оперативная память? Это займет столько времени, сколько вы сможете записать, задержки записи нет. Затем, когда тест будет завершен, перенесите его на SD-карту. Сегнетоэлектрический ОЗУ запомнит, даже если питание отключено., @Gil
Рассматривали ли вы возможность написания двоичного файла вместо текста?, @Edgar Bonet
Почему бы не написать целую строку данных за один раз ? "быстрее" ?, @Antonio51
@DaveNewton Спасибо вам за ссылки. Третий вариант особенно полезен для меня. Ссылка рекомендует выполнить команды SD.open и SD.close вне цикла. Однако, если я попытаюсь это сделать, это вернется к моему заявлению "не выполняется должным образом", поскольку программа, похоже, проходит один цикл и никогда не повторяется. Я не совсем понимаю, почему это так? Я полагаю, что файл должен быть открыт в программе установки и закрыт в любое удобное для вас время, и пока файл открыт, можно вводить новые данные, манипулировать ими и записывать. Останавливает ли открытие файла другие функции или предотвращает зацикливание? Спасибо, @Colby Johnson
@Гил Эй, Гил, ценю информацию о ФРАМ. К сожалению, я не думаю, что это решит мою проблему, так как я считаю, что это скорее проблема с тем, что мой код медленный. Однако я буду иметь это в виду на будущее., @Colby Johnson
@jsotola я даже не заметил. Когда я объединил два примера программ вместе, чтобы сделать это, я, должно быть, оставил его там по ошибке. На данный момент удалено из кода., @Colby Johnson
@EdgarBonet Я бы подумал об этом, но эти данные, скорее всего, будут переданы другим людям, которые просто возьмут устройство, вставят SD-карту, получат данные и вытащат SD-карту. Если я заставлю их возиться с бинарными, я чувствую, что это просто закончится тем, что они не привыкнут. Я мог бы обработать данные для них, но тогда они будут вынуждены обращаться ко мне каждый раз, когда, как я думаю, последует тот же результат., @Colby Johnson
@Antonio51 Антонио, это было отличным дополнением к программе. Теперь я печатаю в файл только один раз, а не несколько раз за каждый цикл. Спасибо!, @Colby Johnson
Логический... для характеристики вы открываете файл, записываете, затем закрываете. Все операции, которые требуют "некоторого" времени ! Мне пришлось решать эту проблему при сохранении параметров температуры, влажности и некоторых других параметров (100 карактеров) каждые 5 секунд, поэтому я знаю ..., @Antonio51
@Antonio51 В идеале я хотел бы выжать как минимум 260 Гц из этих операций записи, так как это скорость моей способности записи MPU-6050s. Я измеряю некоторые действительно высокие ускорения, которые быстро гасятся, поэтому я действительно хочу максимизировать свои показания в секунду, @Colby Johnson
Не добавляйте данные, которые вы действительно не используете ... как ярлыки ... Вы знаете, что и как вы сохраняете свои данные, сохраняйте только их, в конечном итоге в виде двоичных данных вместо строк символов. Вы также можете использовать запись "набор данных". Немного сложнее, но полезно., @Antonio51
@Antonio51 Я также заметил, что без необходимости использую много строк. К сожалению, мне нужны как десятичные знаки, так и положительные/отрицательные, поэтому я предполагаю, что поплавки-единственный способ получить все это, даже если значения ускорения никогда не превысят 64 м/С^2, @Colby Johnson
Если я хорошо помню, я мог читать 8 кбайт в секунду и писать примерно так же, но не измерял это, потому что каждые 5 секунд. (на Arduino 16 МГц). Я думаю, что это было бы лучше на LGT328 (?), который работает на частоте 32 МГц., @Antonio51
@Antonio51 Возможно, мне нужен более быстрый микроконтроллер, но я уверен, что это должно быть возможно. Я просто регистрирую значения XYZ и время. Я думаю, теперь у меня есть какая-то личная привязанность к попыткам решить эту проблему. Я также новичок, так что для меня это хорошая возможность для обучения., @Colby Johnson
Если данные достаточно велики на 2 байта (15 бит + знак), вы можете сохранить все как word ... но при считывании измените или исправьте тип переменных соответствующим образом. Удачи!, @Antonio51