Цифровые порты ввода-вывода. Передача и приём сигнала

Добрый день, господа сторожилы форума. Буквально пару недель назад решил начать осваивать мир Ардуинок.

Без особой предыстории, перейду сразу к делу. Есть 2 Ардуинки. Уно и Нано. На данный момент изучаю принципы работы с портами. В целях обучения и усложнения заданий решил на одной Ардуинке сделать переменный генератор сигнала, а на другой приёмник.

Вот простенький код генератора:

Спойлер
  1. int holl;

  2. int period;

  3. void setup() {

  4. pinMode(7, OUTPUT);

  5. }

  6. void loop() {

  7. holl = analogRead(A0); // потенциометр на 10КОм, для изменения частоты сигнала

  8. holl = map(holl, 0, 1023, 200, 20000);

  9. holl = constrain(holl, 200, 20000);

  10. period = 1000000 / holl; //получили изменяемый период

  11. tone(7, holl);

  12. }

Генератор проверил зуммером. Сигнал есть и корректно меняется. Во всяком случае, мой расчёт действительно попадает в слуховой диапозон.

А это код приёмника:

Спойлер
  1. bool sig;

  2. unsigned long st1;

  3. unsigned long st2;

  4. void setup() {

  5. // put your setup code here, to run once:

  6. Serial.begin(9600);

  7. pinMode(7, INPUT_PULLUP);

  8. }

  9. void loop() {

  10. sig = digitalRead(7);

  11. if (sig == 1) {

  12. st1 = micros();
    
  13. }

  14. st2 = micros()-st1;

  15. Serial.println(st2);

  16. }

По моей логике, переменная st1 должна обновляться только тогда, когда на входе порта есть сигнал. А так как счётчик micros() работает и отсчитывает постоянно, то время между обновлениями, должно как раз быть разницей между постоянно меняющимся micros() и перезаписываемым каждый такт st1, что по формуле считается в переменную st2 и выводиться в порт.

Но в порту какая-то тарабарщина из значений:

Спойлер

3120
9360
4
4
3120
4
3120
9360
15600
22880
30160
37440
4
4
4
3120
4
3120
9360
4
3120
9360
15600
22880
30160
4
3120
9360

Причём, на изменение частоты с генератора, цифры действительно меняются.
Вот кусок данных с монитора при 200Гц:

Спойлер

3120
9360
4
3112
4
3120
9360
4
3120
4
3120
9360
4
3120

А вот кусок из монитора при 20КГц:

Спойлер

3120
9360
15600
4
3120
9368
15600
22880
4

Там где цифра 4, я ещё понимаю, что это данные, когда условие не выполняется (на порт приходит 0), но по ходу программы st2 имея разрядность в 4мкС выдаёт и их. А вот что за другие цифры, я понять не могу. Почему иногда по нескольку значений проскакивает за проход? Почему такие разные значения? По моим подсчётам, минимальная частота в 200Гц, должна иметь период целых 5 миллисекунд и чисто по идее, должно успевать рассчитываться Ардуинками.

Да что же так люди отупели то…

откройте для себя функцию pulseIn()

Да, вы правы, функция прекрасно сама считает период между импульсами. Скетч сократился в два раза, буквально до пары команд. Но если не сложно, объясните, почему мой первоначальный вариант не правильно считает?

З.Ы. У меня нет задачи сделать какой-то конкретный проект или устройство. Я именно хочу изучить платформу, язык, и сам принцип работы контроллеров, взаимодействия как на программном уровне, так и на аппаратном. По этому простите, за возможно дебильные вопросы и прошу отнестись ко мне не как к человеку, который затеял что-то собрать, а как к студенту, который изучает саму платформу.

так для начала хотелось бы увидеть код в стиле кода

bool sig;
unsigned long st1;
unsigned long st2;
void setup() {
Serial.begin(9600);
pinMode(7, INPUT_PULLUP);
}
void loop() {
sig = digitalRead(7);
if (sig == 1) {
st1 = micros();
}
st2 = micros()-st1;
Serial.println(st2);
}

там всё неправильно…в цикле жди пока пин не будет притянут к земле засекаешь первый микрос, далее во втором цикле ждёшь, пока пин не получит единицу, засекаешь второй микрос, из второго вычитаешь первое, имеешь длину импульса, этим ты посчитаешь длину 0 состояния, таких длин набрать к примеру десять и высчитать среднее, далее в цикле ждёшь единицы, засекаешь, переходишь к другому циклу ждёшь 0, засекаешь из второго вычитаешь первое, получаешь длину 1-го импульса, таких импульсов набираешь штук 10, вычисляешь среднее, складываешь первое среднее со вторым получишь временной интервал частоты, ну дальше арифметика

Прочитали состояние пина.
Ждете фронт импульса. // while(sig == digitalRead(7))
Засекаете время1.
Ждете пока не сменится состояние пина.
Ждете пока не сменится состояние пина. // Прошел период импульса.
Смотрим на время2. (запоминаем)
Выводим в сериал разность время2 и время1.
//Конец loop()

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

1 лайк

Я не предлагаю, а прошу лишь подсказать. Выше Pyotr предложил оператор While использовать, а я о нём даже не задумался. Вы не поверите, но я даже реально делаю в тетради конспекты по видеороликам разных блоггеров. Сложно даётся предмет, без вектора (плана) изучения. Сам себе задания придумываешь, сам их пытаешься решить, потом предыдущие уроки пытаешься увязать с новыми, чтобы закрепить темы, сам пытаешься понять почему это так работает а не иначе.

З.Ы. Прежде чем написать на форуме, я с этим приёмом сигнала муздыкался 3 вечера подряд после работы. И так и сяк его ковырял и менял. Да, приторможенный я. Но не ругайтесь, пожалуйста) Я всего 2 недели как занялся изучением Ардуино.

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

у вас фронт импульса должен быть по спаду уровня на пине из HIGH в LOW, а вы его ловите наоборот.

1 лайк

@ForZe , посмотрите как работает функция tone() при вызове с двумя параметрами и тремя. https://arduino.ru/Reference/Tone
Генерировать импульсы и измерять их период или длительность можно на одной ардуино.
При соединении пина как выход с пином как вход, подтяжка на последнем не обязательна. Ни верхняя, ни нижняя.

Плохая идея

а почему?

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

Так я Вам и подсказал, что целесообразно делать.
А Ваша идея “расскажите мне, как работает бездумно составленная последовательность операторов” порочна сама по себе, если даже не говорить о том, что такое предложение бестактно: Вы предлагаете другим сделать бессмысленную, но при этом объемную работу.

Вот в этом вся и проблема.
Учиться нужно по учебникам. И подробно разбирать примеры в конце каждой главы.
Забудьте о блоггерах. Совсем.

Да, еще об учебниках: в названии первых двух-трех учебников, по которым Вам лучше всего заниматься, не должно быть слова “Ардуино”.

С позволения сторожил, продолжу тут изучение вопроса по портам.

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

Есть комманда digitalWrite(); которая выводит на нужный пин логический уровень сигнала.

С тем-же успехом, есть ассемблерный эквивалент этой команды в виде PORTB &= ~(m << n);

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

Если кто-то понимает, объясните пожалуйста, почему комманда digitalWrite(); выполняется в десятки раз дольше? По разным ресурсам, встречается информация, что С-ишная команда отрабатывается в 28 раз дольше, где-то цифра, что в 50 раз дольше, читал инфу, что она отрабатывается за 1.5 сотни тактов и т.д. Короче сколько точно она отрабатывается я не знаю, но при этом ассемблерный аналог срабатывает всего за 1 такт. В С-ишный синтаксис можно конечно подставлять условия, можно подставлять переменные и т.д…Да хоть математические формулы. Но на сколько я понимаю, это ведь лишний геморрой для компилятора при сборке программы, но не для контроллера, с итоговым машинным кодом, где (на сколько мне хватает представления) все сведется к команде вывода в регистр порта нужного логического уровня. И ведь скоростью отработки контроллером команды не сильно зависит от того, прямое ли там указание к выводу на конкретный порт конкретного значения или целое выражение с математикой. Все равно команда отрабатывается в разы дольше чем ассемблерное обращение к регистру.

Так что в итоге делает команда digitalWrite(); , что требует столько тактов обработки и почему это необходимо? Ведь есть же какая-то причина, что компилятор собирает прошивку именно так, именно с такой скоростью работы порта. Я думаю если бы небыло веской причины, давно бы уже исправили это особенность

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

А в итоге она точно так же меняет значение порта. Почему так долго? Это плата за универсальность - на разных платформах - разные имена регистров, соответственно, нужно найти, к какому регистру обращаться и какой бит дергать