95

Программный ШИМ на arduino NANO

Приветствую всех!

Сегодня я хочу затронуть тему реализации программного ШИМ на Ардуино НАНО.

Бывают такие моменты, что надо ШИМ там где его нет, например на Аналоговых пинах.. почему нет?


Что такое ШИМ я затрагивал ТУТ, Напомню в краце: ШИМ - это отношение высокого и низкого сигнала за какой-либо период, который называется частотой ШИМ.

Картинка из интернетов:

Программный ШИМ на arduino NANO Arduino Nano V3, Шим, Программирование

И вот понадобилось мне сделать ШИМ там, где его нет в Ардуине.

Да простит меня сообщество, - не вижу смысла заливать код куда либо, если там несколько строк:

Программный ШИМ на arduino NANO Arduino Nano V3, Шим, Программирование

Котэ работает на прерывании по таймеру2, так как на нем висит Аппаратный ШИМ пинов 3 и 11, то никаких критичных "базовых" функций этим не испортим.

Значит в блоке setup просто переводим таймер в режим работы по CLK, т.е. 16 мГц, и разрешаем прерывание по таймеру.

Для включения ШИМ на каком-либо пину делаем так: в массиве пинов пишем пины какие надо, через запятую,.. ну там {13, A0, A7, 5} и тд...

Затем в Массиве значений ШИМ записываем им всем начальное значение, лучше нули, ... т.е. {0, 0, 0, 0} - 4 Пина юзаем, 4 значения записали,.. хотя наверное можно вообще не писать, они и так при инициализации нулями будут..

Ну а дальше в цикле программы, когда нужно записываем в переменную нужное значение, т.е. надо на пин A0 подать 50%, - пишем PWM_pins[1] = 127; обьясняю: в 1 ячейке массива пинов записан A0,.. 127 - это половина от 255 (0-255 значения). Вот и всё.

Надеюсь кому поможет.

Arduino & Pi

1.5K поста20.8K подписчика

Правила сообщества

В нашем сообществе запрещается:

• Добавлять посты не относящиеся к тематике сообщества, либо не несущие какой-либо полезной нагрузки (флуд)

• Задавать очевидные вопросы в виде постов, не воспользовавшись перед этим поиском

• Выкладывать код прямо в посте - используйте для этого сервисы ideone.com, gist.github.com или схожие ресурсы (pastebin запрещен)

• Рассуждать на темы политики

• Нарушать установленные правила Пикабу

Вы смотрите срез комментариев. Показать все
10
Автор поста оценил этот комментарий

analogWrite - это 2000uS,... получится не ШИМ, а дерьмо с частотой 50Гц.  Пишите в PORTA, в нужный бит ручками и будет вам щастье.

раскрыть ветку (25)
1
DELETED
Автор поста оценил этот комментарий

Самое смешное, что на пинах с поддержкой ШИМа analogWrite использует его, так что мы получим эмуляцию ШИМ посредством ШИМ.

Не то чтобы я утверждал, что автор мало что понимает в предметной области, но таки да, понимает он мало что.

Алсо, весьма советую почитать исходник analogWrite'a и осознать, почему все вокруг крутят пальцами у виска.

https://android.googlesource.com/platform/external/arduino-i...

раскрыть ветку (3)
Автор поста оценил этот комментарий

Если почитать исходники библиотек arduino IDE (Ибо я пишу скетч именно на ней, а не в atmelStudio), то можно сделать вывод, что analogWrite записывает значение скважности в регистр сравнения таймера, и в прерыванию по таймеру просто ставит низкий уровень, именно по этому на каждом из таймеров висит по 2 пина, т.е. таймеров 3, на каждом таймере есть 2 регистра сравнения, A и B, (поэтому всего 6 пинов ШИМ) что явно и прописано в библиотеке. Что бы не быть голословным, вот вырезка из функции analogWrite:

----------------------------------------------

if (val == 0)

{

digitalWrite(pin, LOW);

}

else if (val == 255)

{

digitalWrite(pin, HIGH);

}

......

#if defined(TCCR0A) && defined(COM0A1)

case TIMER0A:

// connect pwm to pin on timer 0, channel A

sbi(TCCR0A, COM0A1);

OCR0A = val; // set pwm duty

break;

#endif


#if defined(TCCR0A) && defined(COM0B1)

case TIMER0B:

// connect pwm to pin on timer 0, channel B

sbi(TCCR0A, COM0B1);

OCR0B = val; // set pwm duty

break;

#endif

----------------------------------------------

И, как видно из этого, если записывать в analogWrite значения 0 или 255, то запись будет не в регистры, а вызываться digitalWrite, который в свою очередь просто пишет в порты. что Так же видно из исходника:

----------------------------------------------

uint8_t oldSREG = SREG;

cli();


if (val == LOW) {

*out &= ~bit;

} else {

*out |= bit;

}


SREG = oldSREG;

----------------------------------------------

Поэтому, можете сколько угодно крутить пальцем у виска, минусить коменты и с пеной у рта доказывать что там медленно, или ШИМ по средствам ШИМ...

Пусть я не прав в ваших глазах, но опровержения своих слов я пока не узрел... для Arduino IDE во всяком случае

раскрыть ветку (2)
1
Автор поста оценил этот комментарий

Можно было просто частоту ШИМ максимальную указать и всё..

Кстати будет гадкий джиитер из-за конфликта с таймером 0, когда ардуина будет время обрабатывать, ШИМ может дать сбой по времени импульса.

1
Автор поста оценил этот комментарий

это не фишка, это костыль, зачем использовать лишнее, если по нормлаьному можно СРАЗУ диджитал использовать? а с пеной у рта это ты доказываешь тут, с тобой нормально говорили, а ты агрился. ты просто не желаешь признавать, что был не прав.

а работает аналог на не шим выводах именно из-за этого костыля.

0
Автор поста оценил этот комментарий

Как говорит референс, это 490 Гц: https://www.arduino.cc/reference/en/language/functions/analo...

Только я не пойму - analogWrite() сам по себе - уже ШИМ. На тех пинах, где он поддерживается. Нахрена ТС вызывает из прерывания таймера analogWrite вообще? Ладно б еще digitalWrite() был бы. Лучше конечно напрямую в регистры порта/пина писать.

Типа "if (counter > duty) PINA |= (1 << PWM_pins[i])" (запись 1 в бит PINA настроенно на выход всегда его "переключает". т.е. было 0 - станет 1, была 1 - станет 0).

раскрыть ветку (5)
0
Автор поста оценил этот комментарий

Ну хватит уже... Ну сложно что ли исходники глянуть, как уже ранее было говорено, у analogWrite есть "костыль", который и используется. analogWrite принудительно переводит ПИН в режим OUTPUT - поэтому не надо это делать в блоке setup, и если значение скважности устанавливать в 0 или 255, то он его тупо перекидывает на digitalWrite, который в свою очередь и так пишет прямо в порты...

Ну ребята, ну хватит.

раскрыть ветку (4)
0
Автор поста оценил этот комментарий

А смысл?! В output переводить надо именно в setup(). И ес-но он ставит в LOW при 0 и в HIGH при 255. Это не костыль. ШИМ при 0% или 100% и есть постоянная составляющая в Vol и Voh (низкий и высокий лог. уровни). Я когда вручную без ардуин пишу код ШИМ для avr или pic - так и делаю когда надо 0 или 100%. Переключений состояний ведь уже нет никаких.

перекидывает на digitalWrite, который в свою очередь и так пишет прямо в порты...

Она помимо этого еще кое-что делает, за это не особо любима не-ардуинщиками.


И ладно бы хрен с ними, с этими потерянными тактами. Дело в том, что ваш пример просто делает ненужную вещь. Это равнозначно написанию функции, которая будет делать единственное - вызывать другую функцию и всё.

раскрыть ветку (3)
0
Автор поста оценил этот комментарий

Вот скажи, что такое блок setup и цикл loop, и почему в output надо переводить именно в setup? Для чего тогда в analogWrite есть принудительный перевод ПИНа в output, а в digitalWrite этого принудительного перевода нет?


Собственно вот из этого принудительного перевода, я когда-то и обломался, выставив значение digitalWrite в HIGH, и не получив нужного результата, хотя ранее было написано через analogWrite 255 и было норм. И уже несколько позже, я нашёл этот прикол, а вот осадочек остался.


На первый вопрос я могу ответить так: В моем понимании, у ардуино есть один основной поток, который выполнился и завершился, и что бы он был циклом, в адруино иде и сделали цикл loop, который с точки зрения реализации программно выглядит как (я так думаю):

void setup(){

}

void lood(){

}

...

void main {

cli()

setup();

sei()

while(1) do loop();

}


И если я все правильно понимаю, то принципиальной разницы, где переводить ПИНы, нет. На счет  cli() и sei() не уверен.


И всё-таки digitalWrite

Она помимо этого еще кое-что делает, за это не особо любима не-ардуинщиками.

Что она делает... она относительно ПИНа который в ней указали определяет порт этого пина, управляющий бит, и дальше либо его в 1 либо в 0... - от этого и получаются всякие паразитные задержки.

Я полностью согласен, что если Я знаю на каком ПИНе мне нужно подать сигнал, то я знаю какой у него бит и какой порт,.. я сразу просто беру и ставлю бит. А если я НЕ знаю какие ПИНы будут использоваться? как быть? Наверное придется выполнять тоже самое, что и digitalWrite - определять порт и бит. И какой тогда смысл писать в порты?

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

раскрыть ветку (2)
0
Автор поста оценил этот комментарий

В Output переводить можно в любой момент времени, когда этого требует задача. Я говорил о конкретном случае - пины мы используем по одному назначению, значит перевести в вывод их надо 1 раз - в начале выполнения программы. А именно для этого и есть функция setup().

На самом деле прога для AVR имеет одну точку входа - функция main(). Функции setup() и loop() арудино - просто "обертки". setup() вызывается один раз вначале main(), loop() вызывается постоянно в бесконечно цикле после. Примерно так, как ты написал.  Только еще до всего этого идет инициализация переферии и прочего, от самого ядра ардуино.

На вопрос про забыл перевести порт в output - это забота программиста. Помнить и продумывать логику программы и в каком состоянии у него порт. С analogWrite ситуация может быть прямо противоположная - ты порт используешь как INPUT, но забыл или перепутал и сделал на него analogWrite - и всё, он теперь OUTPUT и лови глюки потом, почему у тебя данные к нему не приходят.

Что касаемо выбора порта - реальные задачи не требуют изменения пина. Ты заранее знаешь что к какому пину будет подключено. Нет смысла в итоговой программе писать так, чтобы пин был "динамическим". На крайняк это решается макросами препроцессора, которые вычисляются на этапе компиляции.

раскрыть ветку (1)
0
Автор поста оценил этот комментарий

Вклинюсь в дискуссию. В блоке сетап можно написать две строчки:

for (byte i = 0; i<PWM_count; i++)

pinMode( PWM_pins[i], OUTPUT);


тогда все пины, которые хочет использовать пользователь будут на выход.

ещё комментарии
Вы смотрите срез комментариев. Чтобы написать комментарий, перейдите к общему списку