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 запрещен)

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

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

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

А вот ещё вопрос, у вас запись есть 0<<CS22, она же не нужна. Там и так ноль в этой ячейке, а такой формой записи мы больше ничего не добьемся, да она и не нужна.

TCCR2B = 0<<CS22 | 0<<CS21 | 1<<CS20

1<<CS20  - мы создаем маску, у нас есть число в двоичной системе 00000001, мы его сдвигаем на величину CS20, допустим на 1 влево, тогда будет 00000010. А вот 0<<CS22 нам ничего не дает, мы берем число 00000000 и сдвигаем влево на CS22, в результате все равно ноль будет.

раскрыть ветку (1)
0
Автор поста оценил этот комментарий
Да, иногда делаю так, когда экперементирую, что бы бысто вкл/выкл делать.
1
Автор поста оценил этот комментарий

Я ещё не закончил с программой вашей копаться, но:

1. код в посте дает частоту сигнала ШИМ 122,6 Гц.

2. время выполнения команды analogwrite 8 мкс, digitalwrite 4 мкс.

3. digitalwrite и команда записи в порт напрямую PORTB |= (1 << 4) дают одно и тоже время.

4. переписал код, он теперь дает частоту 331.2 Гц

boolean t1=0;

unsigned long timeStart,timeEnd;

const byte PWM_pins[]={12};

#define PWM_count sizeof(PWM_pins)

volatile byte pwm_value[PWM_count]={20};

byte pwm_counter;

void setup() {


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


TCCR2B=0;

TCCR2A=0;

//TCNT2 = 0;

TCCR2A |= 1<<COM2B0 | (1<<WGM21);

OCR2A=1;

TCCR2B = 1<<CS20;

TIMSK2 = (1<<OCIE2A);

}

ISR (TIMER2_COMPA_vect)

{

pwm_counter++;

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

{

pwm_counter > pwm_value[i] ? analogWrite(PWM_pins[i],0): analogWrite(PWM_pins[i], 255);

}

}

void loop() {

}


5. переписал ещё раз, избавившись от аналограйт, теперь частота стала 1.305 кГц, чувствуете прирост, по сравнению с оригиналом? (хотя частота исходного ШИМ ардуиновского 490 Гц).

boolean t1=0;

unsigned long timeStart,timeEnd;


const byte PWM_pins[]={12};


#define PWM_count sizeof(PWM_pins)


volatile byte pwm_value[PWM_count]={20};


byte pwm_counter;



void setup() {


//pinMode(3, OUTPUT);


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


pinMode( PWM_pins[i], OUTPUT);



TCCR2B=0;


TCCR2A=0;


//TCNT2 = 0;



TCCR2A |= 1<<COM2B0 | (1<<WGM21);


OCR2A=1;



TCCR2B = 1<<CS20;


TIMSK2 = (1<<OCIE2A);



}


ISR (TIMER2_COMPA_vect)


{


pwm_counter++;


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


{


pwm_counter > pwm_value[i] ? PORTB &= ~(1 << 4) : PORTB |= (1 << 4);


}



}


void loop() {


}

6. Допилить можно дальше, так как номер пина для записи я указал вручную и есть ещё идейка как оптимизировать, но мне далеко вот до этого: http://codius.ru/articles/214 .


PS ваше объяснение причины использования analogwrite прочел, но всё равно не согласен с этим.

раскрыть ветку (1)
0
Автор поста оценил этот комментарий
Спасибо за статейку, я даже не думал, что switch так много жрет. Что касается оптимизации кода, то по факту у меня получилось +/- типа твоего... но трава уже отпустила.. =D выкинул на гитхаб. Мало ли пригодится.
по аналогу - теперь я понял что из чего сделано, и да, соглашусь, т.к. нашёл ответы на свои вопросы и всё встало на места. Надо было еще раньше исходники проштудировать =D.
показать ответы
0
Автор поста оценил этот комментарий

Во-первых, частота ШИМ к относительной длительности высокого импульса никакого отношения не имеет. Отношение времени высокого сигнала к общему периоду ШИМ именуется скважностью.

Во-вторых, аналоговые порты в принципе используют ШИМ для выдачи нужного сигнала. Таким образом, когда Вы подаете на аналоговый пин 2,5В, фактически Вы подаете 5В со скважностью 50%. К чему все эти индусские методы?

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

Расскажи, пожалуйста, подробнее о том, что такое ШИМ, и про работу аналоговых портов. Мне очень важно твое мнение. И, большая просьба, покажи дураку, как надо было сделать.

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

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

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

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


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

раскрыть ветку (1)
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 байта,... И в прерывании опять приходим к тому, надо сначала определить номер бита, записать его в переменную, так пройтись по всем пинам, и после залпом записать все байты в порты... только скорости это не прибавит, а вот сложности даст точно... Имеет ли смысл - вопрос.

показать ответы
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).

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

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

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

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

Только щас заметил! В коде есть маленькая ошибка, значения записываются конечно же не в PWM_pins, а в PWM_value[1] = 127.

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

Омг, там от ардуино ide синтаксис и язык подойдет?

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

Ну там всё-таки Си.. немного не то

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

Не нужен нам этот пример тут, он портит красоту всю, лишний код.

Не открыться могли из-за неправильно работающей программы. Аналог Райт не будет работать на некоторых пинах! И фактически вы, используя эту команду, убиваете напрочь полезность вашей задумки!

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

Спецом проверил, работает на ВСЕХ пинах и цифровых и аналоговых

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

меня совместимость языков и библиотек больше интересует.

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

Ну я бы сказал, что AtmelStudio - это более низкоуровневый .. я думаю не прокатит совместимость.. если только зарыться и переписывать библиотеки (не уверен точно), а вот скечи - точно не покатят

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

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

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

Ээээм... а AtmelStudio не?

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

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

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

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

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

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

Если почитать исходники библиотек 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 во всяком случае

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

Вы этого хотели? да?

Специально для желающих, 50 Гц кому-то было мало?, ок... держите 16мГц... надеюсь хватит.

analogWrite - не нравится, хочется писать в ПОРТЫ? - Да ради бога...

Иллюстрация к комментарию
показать ответы
Автор поста оценил этот комментарий

У меня нет возможности сейчас с железом поработать, но я постараюсь ответить с пруфами как только так сразу.

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

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

Это не нервы, - это конструктив.

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

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

Спецом проверил, работает на ВСЕХ пинах и цифровых и аналоговых

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

И вообще,.. Вот вы тут все кричите, что analogWrite и digitalWrite все такой медленный,.. и тд и тп,.. что все это дерьмо, давай в порты писать.....

Уважаемые Профессионалы! А вы сами смотрели исходники библиотек? А вы никогда не задумывались, почему эти функции такие медленные?

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

Пруфы - c:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\wiring_analog.c и wiring_digital.c


Разница между digitalWrite и analogWrite по факту только в том что analogWrite записывает значение в счетчик, а digitalWrite в порты.


И хватит лить это дерьмо!

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


Если надо больше частоту, то уже куда логичнее будет использовать digitalWrite(), и если смотреть в глубь этой функции, то там то же самое определение порта и установка нужного бита. Какой смысл писать костыли, Которые вы, уважаемый @Begemot911 привели в своем сообщении?

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

PS

порт B - выводы с 8 по 13, биты 6, 7 используются для кварцевого генератора, не используются.

порт D - выводы от 0 до 7, 0 и 1 используются для передачи данных, лучше не трогать их.


Для записи единицы в бит под номером n (где n=[0, 1, .., 7]):

PORTB|= (1 << n);

Для записи нуля в бит под номером n (где n=[0, 1, .., 7]):

PORTB &= ~(1 << n);


можно ещё проще:

Инвертировать бит под номером n (где n=[0, 1, .., 7]) можно следующим образом:

PORTB ^= (1 << n);


PORTB - регистр состояния порта B, если хотим писать в порт D - PORTD.

в блоке setup лучше изначально задать состояние выводов порта:

PORTB=B00000000;

PORTD=B00000000;


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

раскрыть ветку (1)
Автор поста оценил этот комментарий
1. Блок loop - для примера, не более.
2. 16мГц - именно из-за analogWrite
3. Почему не digitalWrite? Потому что при использовании на шим-пинах analogWrite даёт больше силу тока (я хз как так). От digitalWrite оптопары мои не открылись
показать ответы
2
Автор поста оценил этот комментарий

хехе, тогда нужно писать подробно про строчку с заданием битов таймера, что за команды |, <<, почему именно так. в общем, имхо, порты проще.

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

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

И да, эти команды я кое-как но пытался описывать в предыдущем посте.

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

После использования прерываний и команд управления таймером запись в порты сущая ерунда)

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

Для меня да, а если начинающий прогер?.. думаю что разобраться с таймерами несколько проще чем с портами... ведь на НАНО их помойму 3, и считать на каком порту какие пины, переводить биты нужного порта в зависимости от значения пина... ну хз...

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

Нет, я википидию не цитировал. Я без неё знаю что такое ШИМ. А твое определение - полная херня. Которая вообще ничего не объясняет, а лишь путает читателя, лучше бы вообще не объяснял что такое ШИМ, чем писать такую чушь ! И да короче не получилось.

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

Бестолку спорить. Доеб*ся можно и до столба.

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

Чё бля ?

ШИМ - Широно Импульсная Модуляция т.е управление мощностью путем изменения скважности управляющего сигнала.

Если не знаете таких терминов как мощность и скважность - осваивайте курс электротехники.

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

Википедию процетировал? молодец... может стоило мне пост посвятить определению ШИМа? Действительно, зачем писать вкраце как оно работает, если можно написать 3 страницы текста с определениями, графиками и прочей никому не нужной информации...

Написал же - ВКРАЦЕ

Иди учи читать.

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

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

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

Никто не запрещает писать в порты, однако реализовать в таком виде - проще для понимания, всё-таки пикабу - это не профильный сайт по программированию ардуины

показать ответы