SoftPWM через DMA и без циклов процессора

Universam10
Пт, 07 июля 2017 г., 11:31
Привет, это скорее соревнования по мозгу, которой я хотел поделиться, если бы было возможно выполнить программное ШИМ с DMA, таким образом, не требуя загрузки процессора, чем надежная реализация до сих пор.
Вот доказательство концепции того, что она на самом деле работает, и крутая вещь - это действительно (почти) не требуется циклов процессора, когда поезжи. : P Прерывание принимает некоторые циклы честно, но почти ни один, если обновлений не произойдет. Так что не имеет значения, если я делаю PWM на одном или 16 булавках.

Я использую двойную буферизацию 32 -битной для всех 16pins порта, поэтому требуемый буфер составляет 2*разрешение*4 байта.

Я успешно протестировал на F1 все 16 контактов Portc до импульса 4US, который является разделением периода частоты и разрешения. Так, например, для разрешения 8 -бит 980 Гц будет работать.

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

КСТАТИ., Интересно, это программное программное плане ... : mrgreen: #include #include #include #define RESOLUTION 255 // PWM resolution #define FREQUENCY 500 // PWM frequency #if 1000000 / RESOLUTION / FREQUENCY < 4 #error did not work for me #endif class DMASoftPWM { public: DMASoftPWM(); void begin(gpio_dev *port); void setPinMode(uint8_t pin, bool enable); void writePWM(uint8_t pin, uint16_t val); uint32_t buffer[RESOLUTION * 2]; private: static DMASoftPWM *anchor; static void marshall() { anchor->DMAEvent(); } inline void fillBuffer(uint16_t ptr); uint16_t pinVal[16]; uint16_t pinmask; uint8_t refresh; void DMAEvent(); dma_tube_config tube_config; }; DMASoftPWM::DMASoftPWM() { anchor = this; } void DMASoftPWM::begin(gpio_dev *port) { refresh = 2; dma_init(DMA1); tube_config.tube_src = buffer; tube_config.tube_src_size = DMA_SIZE_32BITS; tube_config.tube_dst = (uint32_t *)&GPIOC->regs->BSRR; // Load pointer to porta clear/set tube_config.tube_dst_size = DMA_SIZE_32BITS; tube_config.tube_nr_xfers = RESOLUTION * 2; tube_config.tube_flags = DMA_CFG_SRC_INC | DMA_CFG_CIRC | DMA_CFG_CMPLT_IE | DMA_CFG_HALF_CMPLT_IE; // Source pointer increment,circular mode tube_config.target_data = 0; tube_config.tube_req_src = DMA_REQ_SRC_TIM2_CH3; // DMA request source. dma_set_priority(DMA1, DMA_CH1, DMA_PRIORITY_VERY_HIGH); dma_tube_cfg(DMA1, DMA_CH1, &tube_config); // Attach the tube to channel 1 (timer2 ch3) dma_attach_interrupt(DMA1, DMA_CH1, DMASoftPWM::marshall); dma_enable(DMA1, DMA_CH1); //TIMER setup Timer2.pause(); Timer2.setPeriod(10000000UL / FREQUENCY / RESOLUTION); Timer2.setChannel3Mode(TIMER_OUTPUT_COMPARE); Timer2.setCompare(TIMER_CH3, 1); Timer2.refresh(); TIMER2_BASE->DIER = TIMER_DIER_CC3DE; Timer2.resume(); } void DMASoftPWM::fillBuffer(uint16_t ptr) { for (uint16_t step = 1; step <= RESOLUTION; step++) { buffer[ptr] = pinmask << 16; for (uint8_t p = 0; p < 16; p++) { if (pinmask & (BIT(p)) && pinVal[p] >= step) buffer[ptr] |= BIT(p); } ptr++; } refresh--; } void DMASoftPWM::DMAEvent() { dma_irq_cause event = dma_get_irq_cause(DMA1, DMA_CH1); if (refresh == 0) // no update so just keep the mem return; switch (event) { case DMA_TRANSFER_COMPLETE: // now setting the upper half fillBuffer(RESOLUTION); break; case DMA_TRANSFER_HALF_COMPLETE: //now setting the lower half fillBuffer((uint16_t)0); break; case DMA_TRANSFER_ERROR: ASSERT(0); break; case DMA_TRANSFER_DME_ERROR: ASSERT(0); break; case DMA_TRANSFER_FIFO_ERROR: ASSERT(0); break; } } void DMASoftPWM::setPinMode(uint8_t pin, bool enable) { pinMode(pin, OUTPUT); if (enable) pinmask |= digitalPinToBitMask(pin); else pinmask &= ~digitalPinToBitMask(pin); } void DMASoftPWM::writePWM(uint8_t pin, uint16_t val) { pinVal[pin] = val; refresh = 2; } DMASoftPWM *DMASoftPWM::anchor = NULL; DMASoftPWM softPWMPortC; void setup() { Serial.begin(115200); Serial.println("starting usb serial"); softPWMPortC.begin(GPIOC); softPWMPortC.setPinMode(PC13, true); } void loop() { if (Serial.available()) { int pin = Serial.parseInt(); int val = Serial.parseInt(); while (Serial.available()) Serial.read(); softPWMPortC.writePWM(pin, val); Serial.print(pin); Serial.print(':'); Serial.println(val); #ifdef DEBUGBUFFER delay(100); for (int u = 0; u < RESOLUTION * 2; u++) Serial.println(softPWMPortC.buffer[u], BIN); #endif } static uint32_t sweep; static uint16_t t = 0; if (millis() - sweep > 1000 / RESOLUTION) { sweep = millis(); t = ++t % RESOLUTION; softPWMPortC.writePWM(13, t); } }

Пито
Пт, 07 июля 2017 г., 11:37
Я успешно протестировал на F1 все 16 контактов Portc до импульса 4NS, который является разделением периода частоты и разрешения. Так, например, для 8 -битного разрешения 3900 Гц будет работать. Нет способа получить импульс 4NS с F1..

Universam10
Пт, 07 июля 2017 г., 11:46
Извините, опечатка, я имел в виду 4US, и я имел в виду 980 Гц :рулон:

Universam10
Пт, 07 июля 2017 12:29
Выглядит как таймер2.setPeriod () делает немного странно, если оно приходит ниже 4.

С Timer2.setPrescaleFactor(F_CPU / RESOLUTION / FREQUENCY); Timer2.setOverflow(1);

victor_pv
Пт, 07 июля 2017 г. 14:13
Почему вы используете 2 буфера с способностью разрешения?
Не должно быть достаточно, так как вы используете круговой режим?
Я предполагаю, что это так, чтобы вы могли обновить рабочий цикл PWM в одном, пока DMA отправляет другой, но вы также можете обновить выполненный цикл, который отправляется, и избежать получения артефактов, если вы обновляете значения, которыми является DMA DMA. Отправка, но если вы заполняете его сверху вниз для обновлений, я думаю, что вы не должны получать артефакты и максимум в течение 1 цикла, рабочее цикл может быть между оригиналом и обновленным.

Это хорошая идея. Я думал о чем -то подобном, но просто отправить импульсы без определенного рабочего цикла.
Я также использовал DMA для выполнения реального аппаратного ШИМ в таймере и отлично работает, что каждое значение в буфере, представляющее рабочее цикл для 1 импульса на 1 млн. Шм. Это для аудио, поэтому каждому пульсу нужен другой. Но для чего -то, что нуждается в определенной частоте, генерируемой в нескольких булавках с разными пошлинами циклами, я думаю, что ваша идея великолепна.

Universam10
Пт, 07 июля 2017 г. 14:33
[victor_pv - Пт, 07 июля 2017 г. 14:13] - Почему вы используете 2 буфера с способностью разрешения?
Не должно быть достаточно, так как вы используете круговой режим?
Я предполагаю, что это так, чтобы вы могли обновить рабочий цикл PWM в одном, пока DMA отправляет другой, но вы также можете обновить выполненный цикл, который отправляется, и избежать получения артефактов, если вы обновляете значения, которыми является DMA DMA. Отправка, но если вы заполняете его сверху вниз для обновлений, я думаю, что вы не должны получать артефакты и максимум в течение 1 цикла, рабочее цикл может быть между оригиналом и обновленным.
О, очень интересно, я явно предполагал, что у меня возникнут серьезные проблемы, если я пойду в гоночное состояние при одновременном доступе к той же памяти. На самом деле, я понятия не имею, что может произойти, вы?
Если это не проблема, не могли бы вы объяснить немного больше, почему наполнение сверху здесь лучше?

Если мое измерение точное, процесс заполнения занимает около 50us, так что, скажем, менее 20 шагов. Не уверен, сколько времени займет прыжок в ISR, но это означает, что наполнение буфера, вероятно, наполнится довольно скоро, при условии, что вышесказанное - это действительная ситуация.

Олли
Пт, 07 июля 2017 г. 15:02
Это очень актуальная технология. На практике это единственный способ реализовать цифровую связь DSHOT600 и DSHOT1200 с электронными контроллерами скорости для двигателей BLDC, используемых в быстрых коптерах. Классический ШИМ аналоговый и довольно медленный - раньше было 20 мс, но он все еще ограничен определением серво -сигнала 1 - 2 мс. Помимо быстрого и точного, DSHOT имеет функцию обнаружения ошибок, которая делает его очень надежным.

Ура, Олли

victor_pv
Пт, 07 июля 2017 г. 15:44
[Universam10 - Пт, 07 июля 2017 г. 14:33] -
[victor_pv - Пт, 07 июля 2017 г. 14:13] - Почему вы используете 2 буфера с способностью разрешения?
Не должно быть достаточно, так как вы используете круговой режим?
Я предполагаю, что это так, чтобы вы могли обновить рабочий цикл PWM в одном, пока DMA отправляет другой, но вы также можете обновить выполненный цикл, который отправляется, и избежать получения артефактов, если вы обновляете значения, которыми является DMA DMA. Отправка, но если вы заполняете его сверху вниз для обновлений, я думаю, что вы не должны получать артефакты и максимум в течение 1 цикла, рабочее цикл может быть между оригиналом и обновленным.
О, очень интересно, я явно предполагал, что у меня возникнут серьезные проблемы, если я пойду в гоночное состояние при одновременном доступе к той же памяти. На самом деле, я понятия не имею, что может произойти, вы?
Если это не проблема, не могли бы вы объяснить немного больше, почему наполнение сверху здесь лучше?

Если мое измерение точное, процесс заполнения занимает около 50us, так что, скажем, менее 20 шагов. Не уверен, сколько времени займет прыжок в ISR, но это означает, что наполнение буфера, вероятно, наполнится довольно скоро, при условии, что вышесказанное - это действительная ситуация.
Автобус будет арбитражным доступом. Поскольку ни DMA на этих частотах, ни ЦП, написанный в буфере. Если это произошло, DMA и процессор попытаются получить доступ к памяти в одно и то же время, шина будет разделять доступ на 50% для каждого. Таким образом, запрос на доступ к одному из них может содержать для цикла или около того, чтобы завершить транзакцию другого. Racemaniac написал отдельную нить, где он раздвинул границы ЦП и DMA, выполняя несколько переводов одновременно. Он мог засорить его, используя 2 порта SPI на полной скорости + mem2mem DMA Access + ЦП делал что -то еще. Я думаю, что в его результатах доступ emm2mem (который идет настолько быстро, как и оперативная память) и 1 порт SPI на полной скорости, все еще работает нормально, не сильно влияя на процессор, но в любом случае проверьте этот поток для деталей. В результате, если вы не запускаете DMA на полном 72 МГц.

О наполнении сверху вниз я думал о ситуации, в которой изменяется период, и DMA может переворачивать один и тот же бит дважды за один и тот же период. Но, как я писал и примечал, я понял, что заполнение сверху вниз может иметь такой же эффект только в другой ситуации, но все же привести к тому, что один и тот же сигнал дважды переворачивается в один и тот же период. Так что это не было бы решением.

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

victor_pv
Пт, 07 июля 2017 г. 15:47
[Олли - Пт, 07 июля 2017 г. 15:02] - Классический ШИМ аналоговый и довольно медленный - раньше было 20 мс, но он все еще ограничен определением серво -сигнала 1 - 2 мс.
Что вы имеете в виду, что классический ШИМ аналоговый и медленный? Вы имеете в виду STM32F1 или что -то еще?

victor_pv
Пт, 07 июля 2017 г., 16:02
[Universam10 - Пт, 07 июля 2017 12:29 вечера] - Выглядит как таймер2.setPeriod () делает немного странно, если оно приходит ниже 4.

С Timer2.setPrescaleFactor(F_CPU / RESOLUTION / FREQUENCY); Timer2.setOverflow(1);

Пито
Пт, 07 июля 2017 г., 16:08
[victor_pv - Пт, 07 июля 2017 г. 15:47] -
[Олли - Пт, 07 июля 2017 г. 15:02] - Классический ШИМ аналоговый и довольно медленный - раньше было 20 мс, но он все еще ограничен определением серво -сигнала 1 - 2 мс.
Что вы имеете в виду, что классический ШИМ аналоговый и медленный? Вы имеете в виду STM32F1 или что -то еще?
Он ссылается на стандарт, используемый в системах RC, начиная с шестидесятых - канал «ШИМ» период 20 мс (50 Гц), с активной длина импульса «ШИМ» 1-2 мс, где 1500US - средняя позиция сервопривода в конкретном канале. Люди говорят, что это аналоговое, даже сегодня не сегодня, аналоговое - сервоприводный цикл. ESC (он преобразует длину импульса сервопривода в фактическую мощность двигателя BLDC) также питается этим сигналом. Но ребятам, летящим в Acro и подобных вещах, нужно что -то быстрее, так как петля управления 50 Гц на канал медленно для них. Им нужно что -то вроде 1 мс и меньше, так как полеты с квадрокоптером 100 узлов среди деревьев в лесу требуют быстрых ответов в цепочке управления :)

victor_pv
Пт, 07 июля 2017 г., 17:11
[Пито - Пт, 07 июля 2017 г. 16:08] -
[victor_pv - Пт, 07 июля 2017 г. 15:47] -
[Олли - Пт, 07 июля 2017 г. 15:02] - Классический ШИМ аналоговый и довольно медленный - раньше было 20 мс, но он все еще ограничен определением серво -сигнала 1 - 2 мс.
Что вы имеете в виду, что классический ШИМ аналоговый и медленный? Вы имеете в виду STM32F1 или что -то еще?
Он ссылается на стандарт, используемый в системах RC, начиная с шестидесятых - канал «ШИМ» период 20 мс (50 Гц), с активной длина импульса «ШИМ» 1-2 мс, где 1500US - средняя позиция сервопривода в конкретном канале. Люди говорят, что это аналоговое, даже сегодня не сегодня, аналоговое - сервоприводный цикл. ESC (он преобразует длину импульса сервопривода в фактическую мощность двигателя BLDC) также питается этим сигналом. Но ребятам, летящим в Acro и подобных вещах, нужно что -то быстрее, так как петля управления 50 Гц на канал медленно для них. Им нужно что -то вроде 1 мс и меньше, так как полеты с квадрокоптером 100 узлов среди деревьев в лесу требуют быстрых ответов в цепочке управления :)
Аааа, это имеет смысл. HW PWM в STM не должен иметь никаких проблем с 1 мс периодами, и, похоже, DMA/Software тоже не является.

Пито
Пт, 07 июля 2017 г., 17:46
В то время как стандартная аналоговая длина импульса ШИМ составляет 1000-2000, более новая и быстрая аналоговая мультисшись составляет 5-25US.
Новый DSHOT600 представляет собой фиксированную кадр 26US, а новейший DSHOT1200 - фиксированный кадр 13US - эти два называются «цифровыми», так как они отправляются в «кадре» (из блока управления) кодированное бинарное число по длине импульса (кадр - 16 бит с 11 бит фактического значения мощности и 4 -битного CRC) в контроллер ESC. ESC содержит MCU (т.е. STM32), и он декодирует число из рамки и соответственно устанавливает мощность. Они могут делать обновления ~ 30 тысяч в секунду.. STM32 внутри ESC должен а) декодировать раму, б) генерировать 3x 30 кГц (11 бит) ШИМ для управления 3 фазами независимого от н.ц.
Таким образом, типичная настройка 4-8Copter потребует F4 в блоке управления (IMU+RC Signals Fusion) и 4-8x F3 (4) в ESSES (один ESC на двигатель). Много кремния..
Q: Вы можете генерировать IE. При 8 выходах GPIO 8x DSHOT600/1200 кадров через DMA (все 8 кадров выстрелили параллельно) ?
https: // github.com/cleanflight/cleanfli ... m_output.в

Universam10
Пт, 07 июля 2017 г. 22:01
Спасибо за объяснение условий гонки, я попробую с одним буфером.
[victor_pv - Пт, 07 июля 2017 г., 16:02] - Является ли проблема, связанная с таймером, или, возможно, это из -за быстрой скорости ISR и времени, необходимого для заполнения буфера?
Это беспокоит меня, что ниже 1US Timer запускает F1 просто сбивается с самого начала. К сожалению, у меня сейчас нет отладчика, так что я в темноте, почему это происходит. Afaik и в соответствии с вашей цитатой, DMA и порт не должны быть ограничены на уровне 1 МГц, поэтому мне интересно, какой предел здесь. Любые идеи?

victor_pv
Пт, 07 июля 2017 г., 11:21
[Universam10 - Пт, 07 июля 2017 г. 22:01] - Спасибо за объяснение условий гонки, я попробую с одним буфером.
[victor_pv - Пт, 07 июля 2017 г., 16:02] - Является ли проблема, связанная с таймером, или, возможно, это из -за быстрой скорости ISR и времени, необходимого для заполнения буфера?
Это беспокоит меня, что ниже 1US Timer запускает F1 просто сбивается с самого начала. К сожалению, у меня сейчас нет отладчика, так что я в темноте, почему это происходит. Afaik и в соответствии с вашей цитатой, DMA и порт не должны быть ограничены на уровне 1 МГц, поэтому мне интересно, какой предел здесь. Любые идеи?
Они не должны быть ограничены, и странно, что это падает. Если бы DMA собирался поститься для оперативной памяти, это просто не отставала от частоты, но не сбои, так как Racemaniac проверил, что выталкивает границы, и он не сбои.

Если у меня будет некоторое время сегодня, я пропущу ее на доску и проверю с отладчиком.

Только что протестировано, я могу получить до 8 кГц с 255 -битным разрешением.
Это около 2 МБ, если я рассчитаю правильно.
На 10 кГц обработчик DMA сбой. Я думаю, что это спотыкает ISR до завершения предыдущего.

Было 800 Гц с 255 сзади. Смотрите мою записку о разделении SetPeriod.
С разрешением 100 шагов я могу пройти по частоте до 2.5 кГц.

Из отладчика это не удалось в этом утверждении: dma_irq_cause dma_get_irq_cause(dma_dev *dev, dma_channel channel) { /* Grab and clear the ISR bits. */ uint8 status_bits = dma_get_isr_bits(dev, channel); dma_clear_isr_bits(dev, channel); /* If the channel global interrupt flag is cleared, then * something's very wrong. */ ASSERT(status_bits & 0x1)

Universam10
Пн 10 июля 2017 г., 7:30 утра
[victor_pv - Пт, 07 июля 2017 г. 11:21 вечера] - Я думаю, что ISR занимает слишком много времени, чтобы быть обслуживаемым. Прямо сейчас начинка выполняется внутри ISR, поэтому возможное решение - не делать этого, а вместо этого установите флаг и у вас есть петля, ожидая, пока флаг заполнит DMA.
Это предотвратит проблему с гнездованием ISRS, но, вероятно, DMA завершит цикл до того, как функция FillBuffer сможет полностью заполнить буфер.
Отлично, спасибо за отладку!
Я взял ваш совет и парадигму, чтобы обновить буфер за пределами ISR, так как условие гонки не произойдет... см. ниже
[victor_pv - Пт, 07 июля 2017 г. 11:21 вечера] - Я думаю, что было бы лучше использовать 2 буфера для независимости, и вы заполняете каждый буфер за пределами ISR, поэтому не нужно завершать до следующей передачи DMA. На самом деле должен начать наполнять буфер, как только он начнет использовать другой для DMA. К тому времени, когда DMA запустил X -циклы, функция, которая заполняет буфер, имела X раз больше времени для завершения заполнения второго буфера, затем ISR просто переключает буфер и запускает DMA.
Мне это не ясно, так как я использую и F1, где нет буферов для пинг-понга для DMA. Если я поэтому переключу буфер, мне нужно остановить - запустить DMA, который, я не могу измерить прямо сейчас, скорее всего, будет более длинной остановкой ШИМ. Есть ли методы, чтобы сделать это в течение одного/нескольких циклов?

[victor_pv - Пт, 07 июля 2017 г. 11:21 вечера] - Поэтому, если вы хотите значительно увеличить скорость, разгрузка Fillbuffer для запуска за пределами ISR, и, возможно, использование большего количества буферов, поэтому можно заполнить в течение периода дольше, чем DMA для запуска цикла, должен быть хорошим решением.
Отлично, поэтому я собрал предложения:

Как сказано. Могут быть некоторые глюки :?:
Поэтому я мог бы _completelly_ удалить целую часть ISR, так как это стало ненужным.
Следующим было удалить двойной буфер, а также ненужный.

В то время как дважды думать другая идея пришла мне в голову, что с помощью writepwm () я обновляю только одну булавку, но в Fillbuffer () я переписываю весь порт, так что все 16 пин. Поэтому я создал вертикальный залив, который меняет только вывод, который был обновлен, который в результате работает на 16 раз быстрее.

Сделав выше изменения, теперь триггер DMA (рабочее цикл) я могу спуститься до прескалера 1, который составляет тактовую скорость 72 МГц! : o
Конечно, это технически невозможно, но на самом деле F1 больше не сбивается с панмировкой 280 кГц и 8 -битного разрешения.

Теперь мне было бы очень любопытно, что за настоящий скорость на булавках, если у вас есть оборудование, чтобы измерить это? :)
#include #include #include #define RESOLUTION 255 // PWM resolution #define FREQUENCY 200000 // PWM frequency // #if F_CPU / RESOLUTION / FREQUENCY < 120 // #error did not work for me // #endif class DMASoftPWM { public: DMASoftPWM(); void begin(gpio_dev *port); void setPinMode(uint8_t pin, bool enable); void writePWM(uint8_t pin, uint16_t val); uint32_t buffer[RESOLUTION]; uint16_t pinmask; private: void fillBufferVert(uint8_t channel); // static DMASoftPWM *anchor; // static void marshall() { anchor->DMAEvent(); } inline void fillBuffer(uint16_t ptr); uint16_t pinVal[16]; // uint8_t refresh; // void DMAEvent(); dma_tube_config tube_config; }; DMASoftPWM::DMASoftPWM() { // anchor = this; } void DMASoftPWM::begin(gpio_dev *port) { dma_init(DMA1); tube_config.tube_src = buffer; tube_config.tube_src_size = DMA_SIZE_32BITS; tube_config.tube_dst = (uint32_t *)&GPIOC->regs->BSRR; // Load pointer to porta clear/set tube_config.tube_dst_size = DMA_SIZE_32BITS; tube_config.tube_nr_xfers = RESOLUTION; tube_config.tube_flags = DMA_CFG_SRC_INC | DMA_CFG_CIRC; // | DMA_CFG_CMPLT_IE | DMA_CFG_HALF_CMPLT_IE; tube_config.target_data = 0; tube_config.tube_req_src = DMA_REQ_SRC_TIM2_CH3; // DMA request source. dma_set_priority(DMA1, DMA_CH1, DMA_PRIORITY_VERY_HIGH); dma_tube_cfg(DMA1, DMA_CH1, &tube_config); // Attach the tube to channel 1 (timer2 ch3) // dma_attach_interrupt(DMA1, DMA_CH1, DMASoftPWM::marshall); dma_enable(DMA1, DMA_CH1); //TIMER setup Timer2.pause(); Timer2.setPrescaleFactor(F_CPU / RESOLUTION / FREQUENCY); Timer2.setOverflow(1); Timer2.setChannel3Mode(TIMER_OUTPUT_COMPARE); Timer2.setCompare(TIMER_CH3, 1); Timer2.refresh(); TIMER2_BASE->DIER = TIMER_DIER_CC3DE; Timer2.resume(); } void DMASoftPWM::fillBufferVert(uint8_t channel) { for (uint16_t step = 0; step < RESOLUTION; step++) { if (pinVal[channel] >= step) buffer[step] |= BIT(channel); else buffer[step] &= ~ BIT(channel); } } void DMASoftPWM::setPinMode(uint8_t pin, bool enable) { pinMode(pin, OUTPUT); if (enable) pinmask |= digitalPinToBitMask(pin); else pinmask &= ~digitalPinToBitMask(pin); // pre fill the reset buffer for (uint16_t step = 0; step < RESOLUTION; step++) { buffer[step] = (uint32_t)pinmask << 16; } } void DMASoftPWM::writePWM(uint8_t pin, uint16_t val) { pinVal[pin] = val; // refresh = 2; fillBufferVert(pin); // fillBuffer((uint16_t) 0); } // DMASoftPWM *DMASoftPWM::anchor = NULL; DMASoftPWM softPWMPortC; void setup() { Serial.begin(115200); Serial.println("starting usb serial"); softPWMPortC.begin(GPIOC); softPWMPortC.setPinMode(PC13, true); } // #define DEBUGBUFFER 1 void loop() { if (Serial.available()) { int pin = Serial.parseInt(); int val = Serial.parseInt(); while (Serial.available()) Serial.read(); softPWMPortC.writePWM(pin, val); Serial.print(pin); Serial.print(':'); Serial.println(val); #ifdef DEBUGBUFFER delay(100); for (int u = 0; u < RESOLUTION; u++) Serial.println(softPWMPortC.buffer[u], BIN); #endif } static uint32_t sweep; static uint16_t t = 0; if (millis() - sweep > 1000 / RESOLUTION) { sweep = millis(); t = ++t % RESOLUTION; softPWMPortC.writePWM(13, t); } }

ZMEMW16
Пн 10 июля 2017 г. 10:53
Я не понимаю термин буферизации пинг-понга, вы имеете в виду, что «настроить два адреса старта DMA, и DMA будет чередовать между ними» ?

ISR -пожары на DMA завершены?? Так почему вы не можете чередовать начальную точку DMA в ISR и повторно запустить DMA??

Стивен

Universam10
Пн 10 июля 2017 г. 11:14
Сколько времени / циклов потребуется, чтобы остановиться, реконфигурировать, запустить DMA для каждого изменения рабочего цикла? Не уверен, но в любом случае ШИМ будет не синхронизировать. С другой стороны, если я изменяю буфер, может быть образец, не точный, если обязанность ШИМ действительно «прыгает», но я сомневаюсь в прежнем изменении, что воздействие намного более драматично!

В любом случае, это работает очень хорошо, нужно подтвердить, что есть из первых рук сбой.

victor_pv
Пн 10 июля 2017 г. 16:16
[Universam10 - Пн 10 июля 2017 г. 11:14] - Сколько времени / циклов потребуется, чтобы остановиться, реконфигурировать, запустить DMA для каждого изменения рабочего цикла? Не уверен, но в любом случае ШИМ будет не синхронизировать. С другой стороны, если я изменяю буфер, может быть образец, не точный, если обязанность ШИМ действительно «прыгает», но я сомневаюсь в прежнем изменении, что воздействие намного более драматично!

В любом случае, это работает очень хорошо, нужно подтвердить, что есть из первых рук сбой.
Не должно быть слишком много, в зависимости от того, как вы это делаете. Если вы делаете непосредственную урагатирование регистрации (и я не вижу причины, чтобы не делать этого), должно быть лишь несколько инструкций.
Вам нужно только отключить канал, изменить адрес источника, снова установить размер передачи (я считаю, что он переходит на 0, необходимо снова прочитать справочное руководство, чтобы подтвердить) и включить канал.
Поскольку вы не меняете целевой адрес, настройки прерывания, функция обратного вызова... Это не должно быть так много времени.
Но, конечно, в зависимости от того, как быстро вы идете по запросам таймера, может быть длиннее, чем вы хотите. и привести к тому, что импульс длится дольше, чем должен.
Другая возможность, которая использует отдельный буфер и изменяет его, когда его отправляют DMA, всегда есть вероятность, что DMA догоняет с процессором, написавшим новую таблицу, и вы получаете булавку, чтобы спуститься и вверх по одному в одном цикле, если DMA достаточно быстрый, поэтому я думаю, что это зависит от приложения, но я думаю, что для большинства приложений может быть лучше, чтобы получить импульс, который на несколько дольше, чем следовало бы, чем получить 2 быстрых импульса вместо 1.
DMA F4 Double Buffering DMA будет отлично подходит для этого до предела :)

У меня есть небольшой 8 -канальный анализатор, я думаю, что максимальная скорость его 10 МГц или 24 МГц, я посмотрю, получу ли у меня шанс измерить импульсы, и посмотрю, смогу ли я заставить PWM «прыжки» и увидеть их. Вы обновили код в первом посте с помощью Fillbuffer, отделенного от DMA ISR?

Universam10
Пн 10 июля 2017 г., 17:45
Круто, дай мне знать, какого результата. Обновленный код находится в приведенном выше сообщении.

victor_pv
Пн 10 июля 2017 г., 17:58
Кстати, есть функция, чтобы сделать это:
Timer2_base->Dier = timer_dier_cc3de;

Поскольку он не должен принимать много инструкций и делать код более портативным между таймерами /** * @brief Enable a timer channel's DMA request. * @param dev Timer device, must have type TIMER_ADVANCED or TIMER_GENERAL * @param channel Channel whose DMA request to enable. */ static inline void timer_dma_enable_req(timer_dev *dev, uint8 channel) { *bb_perip(&(dev->regs).gen->DIER, channel + 8) = 1; }