Как "перебрать" циклом for структуру struct

подозреваю что вопрос простой, но не пойму как вопрос сформулировать и не могу решение найти. Перешел от массива массивов к структуре для удобочитаемости кода, но не пойму теперь как вывести данные циклом, когда был массив массивов

for(int i=0; i<150; i++){  //временной ряд
  for(int n=0; i<10; i++){ //переменная
    Serial.print(arr[n][i]); Serial.print(",")  
  }
  Serial.println();
}

и еще одна задача есть, если это не решается одним методом. необходимо передать в функцию несколько переменных из структуры(там одиночные переменные не массивы), как это делать правильно, сейчас прописываю

myFunc1(var, myStruct.param1, myStruct.param2, myStruct.param3, myStruct.param4)

При добавлении датчика приходится все копировать и внимательно менять ссылку на другую структуру, если код меняю, соответственно по всем этим “блокам кода” надо пройти. както получается деревянно. Опять таки при массиве можно создатьмассив датчиков и подмассивы параметров этих датчиков и в функцию передавать только номер массива, а в самой функции брать параметр из подмассива

struct sUser {
  uint16_t  uid;
  char[10]  username;
  uint8_t   age;
};

sUser[5] usermass;

// Заполняем массив
/*
 * ---------------------
 */

// Печатаем:
void printUser () {
  Serial.print(" ================= ");

  for (uint8_t i = 0; i < 10; i++) {
  
    Serial.print("Username: ");
    Serial.println(usermass[i].username);
    Serial.print("User ID: ");
    Serial.println(usermass[i].uid);
    Serial.print("Age: ");
    Serial.println(usermass[i].age);
    Serial.print(" ================= ");
  }
}

эээ

А то, что это и компилироваться не будет - нормально? :smiley:
Он спросил “как выводить” - я показал. Уж разберется чай…
А так да, зачем-то десятку влепил - сам не знаю )))

как то так и я делаю, думал есть более простое решение. когда структура имеет 10 переменных,а ты решил добавить еще одну, приходиться по всему коду ходить и исправлять на этапах сбораи вывода как минимум

Ну брат… А как ты хотел?
Я в этом случае могу только это порекомендовать:

Правильное проектирование структуры программы - залог успеха её работы.

есть ли способ собрать имя переменной из string значений? т.е. берем название структуры и название переменной и соединяем, получаем переменную string, а как из нее “имя переменной” сделать? есть ли способ?

Зачем тебе этот геморрой?

… и сохранность нервов программиста )))

Если все данные одного типа, можно обойтись и без структур, поможет enum. Можно обращаться к элементу массива как по индексу, так и по имени:

Спойлер

enum{
  sens1,
  sens2,
  sens3,
  sens4,
  sens5
};

uint8_t mass[] = {5, 4, 3, 2, 1};

void setup() {

 Serial.begin(115200);
 for(uint8_t i = 0; i< sizeof(mass);i++)
{
 Serial.println(mass[i]);
}
Serial.println(mass[sens3]);
}

void loop() {
 

}
Спойлер

нет

Это классика…
Запишите число переменных в константу или макрос и используйте этот макрос во всех циклах. Если захотите добавить еще переменную, будет достаточно исправить только в одном месте, а не по всей программе.
А еще число переменных можно хранить в самой структуре, например в виде статического члена.

Мне непонятны цели Ваших телодвижений, а потому я не знаю, что Вам предложить.

Элементы массивов не имеют собственных имён и все имеют один и тот же тип и потому массив легко обрабатывается циклом.

Вы решили от массива перейти к структуре, чтобы каждый элемент имел своё имя и имел право иметь любой тип. Но при этом хотите продолжать обработку при помощи цикла (где одинаковый тип - жёсткое требование, а имена не нужны). Зачем? Если Вы не собираетесь иметь данные разных типов и для обработки Вам не нужны имена, зачем Вы от удобного массива перешли к неудобной для циклов структуре? Пока я не пойму зачем Вам это, мне трудно Вам что-то предлагать, т.к. я не понимаю выгод за которые Вы боролись пре переходе к структуре.

Т.е. поля структуры у Вас отвечают за разные датчики. Тогда я совсем не понимаю, для чего Вы ушли от массива - массив датчиков - это так естественно?

Если сильно надо, то поля структуры можно перебирать по индексу (типа там kaka[i]), в принципе я могу показать как это делается. Но при этом требования, чтобы эти перебираемые поля имели один и тот же тип никто не отменял и делается это, мягко говоря “через Альпы”, а с массивом - просто и естественно.

Потому, попробуйте подумать и сформулировать для себя - а для чего Вы ушли от массива и сделали структуру. Как ответите себе, озвучьте и для меня, мож чего и придумаем.

Так у них же имена разные! У полей-то структуры!

В проекте есть структура, которая содержит настройки всех модулей.

Сама структура состоит из вложенных структур.

Когда в функцию нужно передать параметры относящиеся к тому или иному модулю - я передаю ссылку на ту часть структуры, которая нужна.

Примерно так:

#ifndef GLOBAL_H
#define GLOBAL_H

#include <Arduino.h>

#define DBG_NONE 0
#define DBG_NORMAL 1
#define DBG_PID_M0 3
#define DBG_PID_M1 4
#define DBG_PID_A  5
#define DBG_PID_P  6
#define DBG_MOTION 7
#define DBG_TASK 10
#define DBG_CPU 11

// Настройки PID регулятора
struct PID_t {
  float kp;     // пропорциональный коэф
  float ki;     // интегральный коэф.
  float kd;     // дифференциальный коэф.
  float ilim;   // ограничение интеграла от переполнения
  float lim; // ограничение выхода ПИД регулятора
};

// Настройки модуля bno055
/** A structure to represent offsets **/
struct bnoConfig_t {
  int16_t isUpdate; // 0 - не требуется калибровка
  int16_t accel_offset_x; /**< x acceleration offset */
  int16_t accel_offset_y; /**< y acceleration offset */
  int16_t accel_offset_z; /**< z acceleration offset */
  int16_t mag_offset_x; /**< x magnetometer offset */
  int16_t mag_offset_y; /**< y magnetometer offset */
  int16_t mag_offset_z; /**< z magnetometer offset */
  int16_t gyro_offset_x; /**< x gyroscrope offset */
  int16_t gyro_offset_y; /**< y gyroscrope offset */
  int16_t gyro_offset_z; /**< z gyroscrope offset */
  int16_t accel_radius; /**< acceleration radius */
  int16_t mag_radius; /**< magnetometer radius */
};

struct motor_t {
  PID_t pid;        // настройки ПИД регулятора мотора М0
  uint  deadzone; // настройка мертвой зоны
};

struct motion_t {
  float faultAngle;   // Максимальный угол отклонения робота при котором произойдет отключение моторов
  motor_t m0; // настройки мотора М0
  motor_t m1; // настройки мотора М1
};

struct arms_t {
  int     HandUpL;  // угол поднятой левой руки
  int     HandUpR;  // угол поднятой правой руки

  int     HandDnL;  // угол опущенной левой руки
  int     HandDnR;  // угол опущенной правой руки

  int     ClampL;  // угол захвата левого зажима
  int     ClampR;  // угол захвата правого зажима

  int     unClampL;  // угол освобождения левого зажима
  int     unClampR;  // угол освобождения правого зажима
  
  int     delta;    // градусов в сеек
};


struct dbg_t {
  int select; // что дебажить?
  int dt; // период запуска задачи Task_PrintDebur
};

struct gy_t {
  int k;
};

// Основная структура содержит все настройки
struct config_t {
  bnoConfig_t bno;
  PID_t       pidA;
  PID_t       pidP;
  motion_t    motion;
  arms_t      arms;
  gy_t        gy_r;
  gy_t        gy_l;
  dbg_t       dbg;
  uint32_t    version;
  uint32_t    crc; //контрольная сумма CRC32
};

const config_t &getConfigReadOnly(); // константная ссылка только для чтения
config_t &getConfig(); // константная ссылка для записи

#endif

Передача параметров

// ПИД регулятор вычисляет движение моторов с целью удержать целевой угол
C_PID AnglePID(getConfigReadOnly().pidA);

// ПИД регулятор вычисляет целевой угол наклона с целью достич целевую позицию
C_PID PositionPID(getConfigReadOnly().pidP);

C_PID pid_m0(getConfigReadOnly().motion.m0.pid);
C_PID pid_m1(getConfigReadOnly().motion.m1.pid);

CMotor MotorL(MOTOR_L_P0, MOTOR_L_P1, ENC_L_PA, ENC_L_PB, getConfigReadOnly().motion.m0, pid_m0);
CMotor MotorR(MOTOR_R_P0, MOTOR_R_P1, ENC_R_PA, ENC_R_PB, getConfigReadOnly().motion.m1, pid_m1);

CMotion Motion(MotorL, MotorR, AnglePID, PositionPID, getConfigReadOnly().motion);

Не знаю на сколько это правильно. Но пришел к такому не сразу

Вы бы для начала определились, какая у Вас во сложенном цикле переменная цикла, n или i.

Error: Invalid architecture.

Шёл , шёл…
Вы бы написали полный пример такой одной функции , который можно запустить, и посмотреть, а то опять обрывки.
Или весь код целиком выкладывайте.

Если поможет - пожалуйста.

Но весь код большой очень стал. Выкладываю 1 модуль (ПИД контроллер)

Передача настроек (коэф) происходит по ссылке в конструкторе.

В основной программе объявлена структура и методы доступа


config_t config;  // Конфигурационная структура

const config_t &getConfigReadOnly() {  // константная ссылка только для чтения
  return config;
}
config_t &getConfig() {  // константная ссылка для записи
  return config;
}

pid.h

#ifndef PID_H // include guard
#define PID_H

#include <arduino.h>
#include <global.h>


class C_PID {
private:
    const PID_t &conf;  // ссылка на структуру PID_t регулятора

    // Локальные поля для внутренней логики ПИД
    float target;
    float current;
    float vP, vI, vD;
    float prev_error; // предыдущая ошибка
    float integral;       // интегральная ошибка
    float diff;     // скорость изменения ошибки
    float out;         // pid

public:
    C_PID(const PID_t &ref_conf);
    float calc(float tar, float cur, float dt);
    void reset();
    float getCurrent(void);
    float getTarget(void);
    float getP(void);
    float getI(void);
    float getD(void);
    float getOut(void);
    // коэфициенты полученные в регулятор
    float getKp(void);
    float getKi(void);
    float getKd(void);
    float getILim(void);
    float getLim(void);

    void print(void);
    
};

#endif /* PID_H */


pid.cpp

#include "pid.h"
#include "xprint.h"

C_PID::C_PID(const PID_t &ref_conf)
  : conf(ref_conf),
    integral(0.0f),
    prev_error(0.0f){};

float C_PID::calc(float tar, float cur, float dt) {
  target = tar;
  current = cur;
  float error = target - current;

  // пропорциональная составляющая
  vP = error * conf.kp;

  // интегральная составляющая
  integral += error * dt;

  //float i_limit = (conf.lim * 0.5) / conf.ki;
  integral = constrain( integral, -conf.ilim, conf.ilim);
  //constrain( integral, -i_limit, i_limit);
  vI = integral * conf.ki;

  if (dt > 0.0f) {
    diff = (error - prev_error) / dt;
  } else {
    diff = 0.0f;
  }
  vD = diff * conf.kd;
  prev_error = error;  // сохраним ошибку
  // ограничить рамками предельного значения параметра
  out = constrain((vP + vI + vD), -conf.lim, conf.lim);

  return out;
}

float C_PID::getCurrent(void) {
  return current;
};

float C_PID::getTarget(void) {
  return target;
};

float C_PID::getP(void) {
  return vP;
};
float C_PID::getI(void) {
  return vI;
};
float C_PID::getD(void) {
  return vD;
};

float C_PID::getOut(void) {
  return out;
};

// коэфициенты полученные в регулятор
float C_PID::getKp(void) {
  return conf.kp;
};

float C_PID::getKi(void) {
  return conf.ki;
};

float C_PID::getKd(void) {
  return conf.kd;
};

float C_PID::getILim(void) {
  return conf.ilim;
};

float C_PID::getLim(void) {
  return conf.lim;
};


void C_PID::reset() {
  integral = 0.0f;
  prev_error = 0.0f;
}

void C_PID::print(void) {
  xPrint("cur=");
  xPrint(current, 4);
  xPrint("\ttar=");
  xPrint(target, 4);
  xPrint("\tkp=");
  xPrint(conf.kp, 4);
  xPrint("\tki=");
  xPrint(conf.ki, 4);
  xPrint("\tkd=");
  xPrint(conf.kd, 4);
  xPrint("\tP=");
  xPrint(vP, 4);
  xPrint("\tI=");
  xPrint(vI, 4);
  xPrint("\tD=");
  xPrint(vD, 4);
  xPrint("\tO=");
  xPrint(out, 4);
  xPrint("\n");
};

может подойдет

// В структуре данных
struct PID_t {
    float kp, ki, kd;
    float ilim, lim;
    
    void print() {
        Serial.print(kp); Serial.print(",");
        Serial.print(ki); Serial.print(",");
        Serial.print(kd); Serial.print(",");
        Serial.print(ilim); Serial.print(",");
        Serial.print(lim);
    }
};

// Использование
for(int i = 0; i < 10; i++) {
    pidArray[i].print();
    Serial.println();
}

есть тепловой процесс, на разных его этапах датчиками измеряю температуру, на этих этапах помимо датчиков есть другие переменные НО они в разных точках разные (к примеру в одной точке поток перенаправляется в разные контуры и необходимо фиксировать какой контур работает). Некоторые структуры 2-4 переменные, некоторые >10. Конечно они имеют разный тип. Обьединил я их в структуры по признаку в какой точке процесса они находятся, мне так было удобнее.
Но если простого решения текущей конструкции нет, проще сделать массив для датчиков индекс будет соответствовать индексу аналогового входа, просто надо будет помнить какая точка процессса к какой ножке подключена

Обычно из первой части этого утверждения и следует вторая.
Скажите, Вам данный механизм (перебор членов структуры как элементов массива) нужен только для вывода структуры в сериал? Потому что дальше у вас разные функции для разных полей (что, в общем-то, логично). Если да, то лучше унаследовать вашу структуру от Printable (кажется так называется), или хотя бы просто добавить в нее метод void print() и вызывать его вместо вашего цикла.

Наверное, правильно передать в функцию ссылку на всю структуру. А уже в самой функции использовать те поля, которые ей нужны.