Нестабильность генерации, microsы и такты

(там уже на 32 элемента поменял вместо 100 - ничего не изменилось)

и где тут чтение из массива?

Как Вы делаете, абсолютно непонятн6о, т.к. код неполный.
Но настораживает несколько моментов.

  • Вы отключаете прерывания каждый раз при прохождении цикла. Включаете ли обратно - неизвестно. Если “да”, понятно, откуда “нестабильность”, если “нет” - вообще непонятно, зачем Вы это делаете.
  • любые условные операторы в данном случае - возможный источник нестабильности. А если, к тому же, только с одной веткой - очевидный источник нестабильности.
  • в отрывке вообще отсутствует какой-либо код, обеспечивающий синхронизацию, поэтому вообще о какой-либо стабильности говорить не приходится.

В 17-й строке. Сейчас всё закомментировано, чтобы отключить и после этого импульсы стали стабильными.

Тут еще много что настораживает.
Например, заполнение массива внутри луп. Он, что при каждом проходе заново заполняется?
Или переходы по меткам ВНУТРЬ области с запрещенными прерываниями. ЭТО вообще бред, на мой взгляд.

В таком коде могут быть ляпы на каждой строчке

вы, простите, здоровы?
Показываете код, где нужное место закомментировано?
И как нужно догадаться, что именно надо раскомментировать или убрать, чтобы получить нужный эффект?

Вы бы еще часть кода бумажкой прикрыли и спросили на форуме “Подскажите, что у меня не работает в прикрытом коде?”

Прерывания включаю в конце цикла. Мне их тут посоветовали отключать, чтобы задержка на “nop’ах” была стабильной. Потому что точность micros’ов - 4 мкс, что тоже не вариант. Вот полный код.

// Set up potentiometer pin
 #define POT_PIN A3

// Set up clock out pin
 #define CLC_OUT 9      // PORTB первый бит PB1

// Set up random out pin (выход данных)
 #define RANDOM_OUT 10  // PORTB второй бит PB2

// Set up MFM out pin (выход МЧМ)
 #define MFM_OUT 11      // PORTB третий бит PB3

 byte RND = 0;     // текущий бит данных
 byte RND_OLD = 0; // предыдущий бит данных

 unsigned int POT = 10;
 // unsigned int m=20; // считал, что 20 мкс - это 160 тактов при частоте 8 МГц, но получилось 50 (?)
 // unsigned int POT_PLUS = POT + m; // добавка для выравнивания меандра

 unsigned int l=0;

 // unsigned int arr[32];  // массив случайных чисел
 // int i=0;
 // int j=0;

 void setup() {

// Set up pins as input
  pinMode(POT_PIN, INPUT);
  pinMode(A1, INPUT);

// Set up pins as outputs
  pinMode(CLC_OUT, OUTPUT);
  pinMode(RANDOM_OUT, OUTPUT);
  pinMode(MFM_OUT, OUTPUT);

  // для пинов 9 и 10 (таймер1) - такие установки ни на что не влияют
  // TCCR1A = 0b00000001;  // 8bit
  // TCCR1B = 0b00001001;  // x1 fast pwm - 62.5 кГц - для UNO и 125 кГц - для LGT8F328P

 }

   void loop() {

   // for(i = 0; i < 32; i++){ // в массиве будет 32 элементов 
   // arr[i] = random(65536);   // заполняем массив случайными числами 
   // }   

   noInterrupts();

   label:

   PORTB |= (1 << 1);               // фронт CLC

    //RND = bitRead(arr[i], j);        // чтение битов массива - 1 мкс, почти спокойно

   // digitalWrite(RANDOM_OUT, RND);   // вывод данных на 10-й пин - около 3 мкс, сильный джиттер

   if (RND < 1) PORTB &= ~(1 << 2); // установить 0 в бите 2 (10-й пин)
   else PORTB |= (1 << 2);          // установить 1 в бите 2 (10-й пин)

   if (RND < 1 && RND_OLD < 1) PORTB = PORTB^0b00001000;  // в начале бита данных, если второй 0 подряд

   // j=j+1;               //
   // if(j > 15) {         // последовательный перебор
   // j = 0;               // битов массива
   // i=i+1;               // (быстрее, чем random (2))
   // }                    //
   // if(i > 31) i = 0;    //

   POT = analogRead(POT_PIN);          // чтение POT - 23-24 мкс

   for (l = 0; l < POT; l++) {        // задержка, определяемая потенциометром
   asm ("nop");
   }

   PORTB &= ~(1 << 1);               // спад CLC

   if (RND > 0) PORTB = PORTB^0b00001000;          // в середине бита, т.е. CLC должен быть меандром

   RND_OLD = RND;

   for (l = 0; l < POT; l++) {   // длительность nop зависит от типа переменной
   asm ("nop");
   }

   RND = analogRead(A1)&0b1; // тоже 23-24 мкс, поэтому в конце, чтобы по фронту без задержек

   goto label;

   interrupts();

} // закрыли loop

Сейчас с AnalogRead импульсы стабильные.

Пример простенького генератора шума.
Всего 16 разрядов, поэтому последовательность короткая - 65535, что при частоте дискретизации 40 кГц дает чуть более полутора секунд. Дальше - повторяется, но на слух неотличимо от генератора белого шума.

#include <TimerOne.h>

#define RESOLUTION 25 // разрешение по времени, т.е. период дискретизации

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  long t0 = micros();
  for(int i = 0; i < 1000; i++) {
    LFSR_Ga_16();
  }
  long t1 = micros();
  Serial.println(t1-t0); // здесь мы печатаем в нс время выполнения нашего генератора шума
  for(int i = 0; i < 1000; i++) {
    tick();
  }
  long t2 = micros();
  Serial.println(t2-t1); // а здесь - всего прерывания
  Timer1.initialize(RESOLUTION); // настраиваем таймер на 25 мкс
  Timer1.attachInterrupt(tick); // и сообщаем ему, что вызывать с этим периодом
}

void loop() {}

void tick() { // обработчик прерывания
  if(LFSR_Ga_16()) {
    PORTB |= 1 << 5;
  } else {
    PORTB &= ~(1 << 5);
  }
}

inline int LFSR_Ga_16(void) { // наш генератор шума
  // для полинома 16,15,13,4 => 0,1,3,12 => 0x100b
  static unsigned long S = 0x00000001;
  if (S & 1) {
    S = ((S ^ 0x100b) >> 1) | 0x8000;
    return 1;
  } else {
    S >>= 1;
    return 0;
  }
}

Тут, конечно, можно немного соптимизировать, если перенести работу с портами сразу в генератор и именно его вызывать в качестве прерывания, но теряется универсальность.

Спасибо, само по себе интересно, сохраню. Но тут опять микросы, у которых от рождения нестабильность 4 мкс …

Не нестабильность, а дискретность.
И используются они исключительно, чтобы узнать время выполнения процедур. Т.е. это предварительные измерения, которые дальше ни на что не влияют.
А период, как запрошено, 25 мкс (нацело на 4 не делится).

А как подпрограмму этот полином можно вызывать? Чтобы без прерываний, со сдвигом при каждом вызове.

В общем, инструкцию понял :slightly_smiling_face:

  • Берём однобайтовую переменную GEN (биты 0-7) и присваиваем ей любое значение, отличное от 0.
    1. Получаем бит XOR из битов 5 и 6 по логике исключающего или.
    1. Сдвигаем биты в GEN влево.
    1. Записываем XOR в нулевой бит GENы.
  • Goto 1.

Можно в самой программе. Там будет довольно длинная последовательность. Если 16 бит, то точки подключения будут другие.

Заработало :+1: Стабильность нормальная и длительность полупериода всего 2 мкс. А другой полупериод минимум 24мкс - там потенциометр читается, надо выравнивать, ну ладно.

 int GEN=650;  // любое ненулевое значение (все нули - запрещённая комбинация)
 bool XOR=1; 


label:

   XOR=bitRead(GEN, 14)^bitRead(GEN, 13);
   
   GEN=GEN<<1;

   bitWrite(GEN, 0, XOR);

   RND=bitRead(GEN, 15);

   // RND = analogRead(A1)&0b1;        // 23-24 мкс

   PORTB |= (1 << 1);               // фронт CLC

   if (RND < 1) PORTB &= ~(1 << 2); // установить 0 в бите 2 (10-й пин)
   else PORTB |= (1 << 2);          // установить 1 в бите 2 (10-й пин)

Спасибо всем, кто помог!

Спасибо тут выражается выставлением Решения на тот пост что помог !

Вставилось криво, а редактировать нельзя.

Тут многие чем-то помогали, но нет какого-то конкретного поста, на который можно указать, как на однозначное решение.

Вот окончательный вариант:

// Получение последовательности псевдослучайных данных с регулируемой внешним потенциометром
// стабильной частотой (без джиттера) от 900 Гц до 20 кГц.

// Set up potentiometer pin
 #define POT_PIN A3

// Set up clock out pin
 #define CLC_OUT 9      // PORTB первый бит PB1

// Set up random out pin (выход данных)
 #define RANDOM_OUT 10  // PORTB второй бит PB2

 byte RND = 0;          // текущий бит данных

 int GEN=650;
 bool XOR=1;

 unsigned int POT = 10;
 // unsigned int m=30; // считал, что 20 мкс - это 160 тактов при частоте 8 МГц, но получилось 30 (?)
 // unsigned int POT_PLUS = POT + m; // добавка для выравнивания меандра

 unsigned int l=0;

 void setup() {

// Set up pins as input
  pinMode(POT_PIN, INPUT);

// Set up pins as outputs
  pinMode(CLC_OUT, OUTPUT);
  pinMode(RANDOM_OUT, OUTPUT);

 }

   void loop() {

   noInterrupts();

   label:				   // до метки заполнялся массив случайных данных, пришлось убрать

   XOR=bitRead(GEN, 14)^bitRead(GEN, 13);  //
   GEN=GEN<<1;				   // псевдослучайная последовательность на переменной GEN (аналог LFSR)
   bitWrite(GEN, 0, XOR);		   // более стабильная, чем random (фронты CLC не дрожат)

   RND=bitRead(GEN, 15);		   // "случайные" биты данных

   // RND = analogRead(A1)&0b1;            // 23-24 мкс, тоже без джиттера, но нужен шум на А1 

   PORTB |= (1 << 1);                      // фронт CLC

   // Вывод данных

   // digitalWrite(RANDOM_OUT, RND);       // вывод данных на 10-й пин - около 3 мкс, сильный джиттер

   if (RND < 1) PORTB &= ~(1 << 2); // установить 0 в бите 2 (10-й пин), всего за пару микросекунд
   else PORTB |= (1 << 2);          // установить 1 в бите 2 (10-й пин)


   POT = analogRead(POT_PIN);              // "чтение" потенциометра - 23-24 мкс

   POT=POT/4*3;				   // уменьшил, чтобы приблизить мин. частоту к 1 кГц

   for (l = 0; l < POT; l++) {             // задержка, определяемая потенциометром
   asm ("nop");
   }

   PORTB &= ~(1 << 1);                     // спад CLC

   for (l = 0; l < POT+30; l++) {          // добавка +30, чтобы приблизить CLC к меандру (не обязательно)
   asm ("nop");
   }

   goto label;

   interrupts();

} // закрыли loop

И то … Добавка к POT добавляет примерно 20 мкс и для этого потребовалось всего +30 “nop’ов” … Кто-нибудь может это объяснить?

Напоминаю, что у меня китайский клон LGT8F328P, там 4 таймера, а не 3, больше частота и т.д., так что могут быть всякие накладки.

Без добавки +30 отрицательный полупериод CLC короткий и частота больше 30 кГц. Просто данным всё равно, насколько симметричный CLC.

Можно читать POT после спада CLC и тогда положительный полупериод будет коротким.

“Это” объясняется тем, что вы не знаете, как “это” работает )).
А сколько по вашему длится “пустой цикл”, без “nop”? Нисколько?
А ведь есть ещё оптимизатор, который много чего может сделать на своё усмотрение…

Мне это нужно как небольшая часть макета, поэтому работает и ладно. Я писал о том, что нет какого-то однозначного решения, который любой пользователь может использовать без оглядки, потому что это назвали решением.

Вот это правильно.

Просто, чтобы разобраться, придётся серьёзно углубиться в тему, в целом подучиться. Если это надо лишь для одного проекта - не вижу смысла

Ну да, я и это через год забуду ) Просто тут написали, что надо указать на какой-то пост, как на “решение”, а такого поста нет.