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

Вот теперь объясните владельцам компьютеров с win 7, что если они не догадаются установить Eclipse r3dfox, то на вашем форуме им делать нечего.

А вопрос такой:

Не программист, но хотел заменить микроконтроллером кучку микросхем. Ардуинозаменителем LGT8F328, внутренняя частота 32 мГц.

Нужна тактовая частота от 1 до желательно 100 кГц, желательно меандр на одном из выходов (назначил 8-й). Уже весело (100 кГц - это 10 мкс на период). Регулировка частоты внешним потенциометром.

На другом выходе (9-м) типа случайные данные (не важно какие).

И на третьем выходе (10-й) - PORTB |= (1 << 0); и PORTB &= ~(1 << 0); по фронту и спаду тактов при определённых условиях (if).

Стандартно удалось получить до 10 кГц с явной нестабильностью (джиттер). Там delayMicroseconds, AnalogRead, random и прочие дигиталриды/райты. Потом сначала заполнил случайный массив, потом просто его читал в цикле, запись напрямую в порты (как выше написал, только пока не нашёл, как туда записывать не банальное хайло (High/Low), а элемент массива), без потенциометра и с micros’ом - получил 30 кГц, но джиттер сильно увеличился. Фронты и спады дрыгаются в пределах около 4 мкс (что похоже на точность микроса). Ладно, написал такой тестовый скетч:

// Set up clock out pin
#define CLC_OUT 8 // PORTB нулевой бит PBO

unsigned long i;

void setup() {

pinMode(CLC_OUT, OUTPUT);

}

void loop() {

label:

PORTB |= (1 << 0);

for (int i = 0; i < 180; i++) {
asm (“nop”);
}

PORTB &= ~(1 << 0);

for (int i = 0; i < 180; i++) {
asm (“nop”);
}

// while(micros()-time < 20){
// }

// time = micros();

goto label;

} // закрыли loop

Примерно 22 кГц, но всё равно дрыгается.

И чё делать (если по-простому, без прерываний) ?

Редактировать нельзя?

Не то вставил - на 10-м выходе просто переход уровня - PORTB = PORTB^0b00000100;

Новичкам - нет. И вот еще что

Зачем? Яндекс-браузер прекрасно поддерживается на Windows 7.

А вот если не сможет вставить код правильно - тогда точно на форуме делать нечего!

2 лайка

Чтобы получить точный меандр постоянной частоты, на контроллерах типа ат328 нужно использовать аппаратный таймер. Соответственно, меандр доступен не на любом произвольном пине, а только на пинах выходов таймеров. На атмеге328 это пины 3,5,6,9,10,11. На вашем МК могут быть другие, смотрите даташит.
С регулировкой частоты сложнее. Максимально возможная частота = половине тактовой, но из частот ниже возможны не все, а только с целым делителем от такта. Поэтому чем выше частоты, тем реже они расположены. Например. Если у вас такт 32Мгц - это значит что первые три частоты это 16, 10.66 и 8 МГц. Никакие между ними не получить.
Для частот в районе 100 кГц шаг изменения будет порядка 0.4кгц

1 лайк

Запрет прерываний добавить надо в скетч с нопами и гото !

Второй метод описал @MMM

Треттий метод - специальная микросхема. Там с шагом частоты поинтереснее будет …

2 лайка

Насколько я понял, ТС хочет одновременно и согласовано с меандром выводить на другие пины ещё какие-то данные. На атмеге это будет трудно

Зато на дешёвом блюпиле всё делается просто и точно.

У него даже атмега левая … блюпилл будет то же левый наверное…

Блюпиллу пофиг должно быть на «левость», архитектура то одна.

Неважно какая мега. LGT - замечательная мега. Хочется красивую частоту на выходе - выключай прерывания (“@Komandir) и считай такты. Иначе используй таймер(ы) (”@MMM).

Ну, как-то на прежнем форуме у “новичков”, которые там уже хрен знает сколько лет зарегистрированы, не было необходимости в яндексе, который во все дырки лезет (даже из его почты ушёл, как и многие другие). Про апострофы прочитал, но редактировать нельзя да и сколько там этих строчек кода ….

За подсказки спасибо, попробую что-нибудь сделать. Точность установки частоты не важна, просто хотелось бы чтобы она была стабильная. Да, это тактовая частота для “случайных” данных, но какая-то задержка этих данных тоже допустима. Блюпилл есть, но с стмом я почти не знаком. Как-то попробовал, а там только пины назначаются.

Начальный код, работающий до 10 кГц и, как ни странно, немного более стабильный, был такой:


// Set up potentiometer pin
#define POT_PIN A3

// Set up clock out pin
#define CLC_OUT 8

// Set up free pin (для ловли наводок)
#define FREE_PIN A1

// Set up random out pin (выход данных)
#define RANDOM_OUT 9

// Set up MFM out pin (выход МЧМ)
#define MFM_OUT 10

 int RND = 0;		// текущий бит данных
 int RND_OLD = 0;	// предыдущий бит данных
 int POT = 5;  		// примерно полпериода в микросекундах
 int MFM = 0;		// текущий бит МЧМ

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

 void setup() {
 
// Set up pins as input
  pinMode(POT_PIN, INPUT);
  pinMode(FREE_PIN, INPUT);

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

  digitalWrite(MFM_OUT, MFM);

  randomSeed(analogRead(FREE_PIN));

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

  // Serial.begin (9600);
  
}


  void loop() {  // до 10 кГц со всеми задержками на random и чтение потенциометра

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


  RND = random (2); 		// 0 или 1, вносит задержку примерно 40 мкс

  digitalWrite(CLC_OUT, HIGH);	// переключаем СLC на HIGH

  // RND = bitRead(arr[i], j);							// чтение битов случайного массива

    digitalWrite(RANDOM_OUT, RND);						// вывод данных на 9-й пин, пока не понял, как выводить в регистр

  if (RND < 1 && RND_OLD < 1) digitalWrite(MFM_OUT, !digitalRead(MFM_OUT)); // смена логического уровня
										
  // j=j+1;			        //
  // if(j > 15) {		    // последовательное чтение
  // j = 0;			        // битов из массива
  // i=i+1;			        // (быстрее, чем random (2))
  // if(i > 99) i = 0;		//

   POT = analogRead(POT_PIN);        // чтение потенциометра при CLC = HIGH, чтобы приблизить длительность к LOW
   POT = map(POT, 20, 1000, 1, 512); // масштабируем до диапазона 1 ... 512, тоже вносит задержку
   POT = constrain(POT, 1, 512);     // ограничиваем диапазон, чтобы исключить выбросы

  delayMicroseconds(POT); 

  digitalWrite(CLC_OUT, LOW);	// переключаем СLC на LOW

  if (RND > 0) digitalWrite(MFM_OUT, !digitalRead(MFM_OUT));  // переход уровня в середине бита, т.е. CLC должен быть меандром
    
  RND_OLD = RND;
    
    // POT = analogRead(POT_PIN);         //
    // POT = map(POT, 20, 1000, 1, 512);  // вставлял в разных местах, чтобы приблизить CLC к меандру
    // POT = constrain(POT, 1, 512);      //

  delayMicroseconds(POT);
  
  // Serial.print (POT); Serial.print("\t");
  // Serial.print (PAUSE); Serial.print("\t");
  // Serial.print (FREE); Serial.print("\t");
  // Serial.println (RND);

  // } // закрыли while

} // закрыли loop
/ Терминальный генератор меандра от 1 Гц до 8 Мгц
// Автор dimax 2017 год
// Записать скетч в arduino
// Запустить терминал. Установить скорость 9600.
// Из терминала послать требуемую частоту в герцах. Только цифру.
// В ответ в терминал будет выведена частота в герцах.
// D9 и GND - выход генератора частоты.
void setup() {
Serial.begin(9600);
pinMode (9,OUTPUT); // выход генератора
TCCR1A=0;TCCR1B=0;
}
void loop() {
static uint32_t reqfreq=0; //переменная запроса частоты
uint32_t ocr=OCR1A;  uint16_t divider=1;  float freq;
if (Serial.available() > 0){ reqfreq = Serial.parseInt();
if (reqfreq==0 || reqfreq>F_CPU/2) {return;}
 ocr = (F_CPU / reqfreq /2 /divider);
  byte shifts[] = {3,3,2,2};
   for(byte i = 0; i < 4; i++){
     if (ocr > 65536) { divider <<= shifts[i];
       ocr = F_CPU / reqfreq /2 /divider; }
      else { TCCR1B = (i+1)|(1<<WGM12);  break; }  } //Mode4 (CTC)
     OCR1A=ocr-1; TCCR1A=1<<COM1A0;
    freq= (float) F_CPU/2 / (OCR1A+1) /divider;
  Serial.print(freq,3);Serial.println(" Hz ");
 }
1 лайк

Без прерываний тестовый генератор заработал как надо )


// Set up clock out pin
 #define CLC_OUT 8  // PORTB нулевой бит PBO

 unsigned long i;

 void setup() {

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

}

  void loop() {

   label:

   noInterrupts();

   PORTB |= (1 << 0);

   for (int i = 0; i < 180; i++) {
   asm ("nop");
   }

   PORTB &= ~(1 << 0);

   for (int i = 0; i < 180; i++) {
   asm ("nop");
   }

   goto label;

   interrupts();

} // закрыли loop

Попробую довпихнуть остальное …

В последнем скетче пока не всё понятно и кто-то говорил, что работа с ком-портом тоже медленная, но буду разбираться.

Спасибо.

Зачем вы пользуетесь метками? Тем более в таком странном исполнении?
Если вы не знаете - loop() соответствует своему переводу и является бесконечным циклом.

На стм32 пересядь, там куча таймеров, никаких делей! И все будет вне от процессорного времени. Или синтезатор частоты на внешней микрухе и не нужно мучать мк)))

Я не пользуюсь метками, просто хотел убрать джиттер и встречал такую конструкцию вроде как для ускорения. Тут просто проверял. Ну и вместо while можно после однократного заполнения случайного массива.

А с внешними микрухами я как раз сам не хотел мучиться ) Уже устал паять и там ещё не полный функционал. И пока не до изучения СТМ “в мягких муравах у нас” …

Сейчас один “ноп” получается 0,125 мкс (1/8 МГц).

Блин. Аналогрид с потенциометра длинный (23-24 мкс), но спокойный.

// Set up potentiometer pin
#define POT_PIN A3
// Set up clock out pin
 #define CLC_OUT 9  // PORTB первый бит PBO
// Set up random out pin (выход данных)
 #define RANDOM_OUT 10

 int RND = 0;     // текущий бит данных
 int RND_OLD = 0; // предыдущий бит данных
 unsigned int POT = 5;
 int m=100; // 100 тактов при частоте 8 МГц - 12,5 мкс
 unsigned long l=0;
 unsigned int arr[100];  // массив случайных чисел
  int i=0;
  int j=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() {

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

   noInterrupts();

   label:

   PORTB |= (1 << 1);

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

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

   for (int l = 0; l < POT; l++) {   // 120 тактов без чтения POT равно 15 мкс
   asm ("nop");
   }

   PORTB &= ~(1 << 1);

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

   for (int l = 0; l < POT; l++) { 
   asm ("nop");
   }

   goto label;

   interrupts();

} // закрыли loop

а дигиталрайт случайных данных с чтением из массива в сумме короткие (около 4 мкс), но опять дрыгаются.

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

Всё-таки можно заменить дигиталрайт операцией с портом?

Можно, да и наверное нужно в вашем случае.