Быстрое битуполовое GPIO/SRAM Access

Арпрос
Сб 25 ноября 2017 г. 20:00
Я сделал несколько быстрых макросов GPIO на основе BitBand, которые генерируют быстрый и маленький код ввода-вывода/вывод. Они работают только с номерами PIN -код, четко указанными, как PB13 и PA6. Использование: value = DIGITAL_READ(PB13); DIGITAL_WRITE(PB14,1);

Дэнниф
Солнце 26 ноября 2017 г. 3:32 утра
Я сделал несколько быстрых макросов GPIO на основе BitBand, которые генерируют быстрый и маленький код ввода-вывода/вывод. Просто любопытно: насколько быстрее они по сравнению с стандартными подходами?

Арпрос
Солнце 26 ноября 2017 г. 5:36 утра
[Дэнниф - Солнце 26 ноября 2017 г. 3:32] - Я сделал несколько быстрых макросов GPIO на основе BitBand, которые генерируют быстрый и маленький код ввода-вывода/вывод. Просто любопытно: насколько быстрее они по сравнению с стандартными подходами?
По сути, чтение и письмо становится так же быстро, как чтение/написание UINT32 в местоположение, указанное глобальным указателем UINT32*. Я еще не рассчитывал это.

Обновлено: Не совсем. Процессор, похоже, имеет дополнительные накладные расходы при доступе к локациям памяти, особенно при написании.

Rogerclark
Солнце 26 ноября 2017 г. 6:46 утра
К вашему сведению.

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

Компилятор - странный зверь.

Я бы попробовал время вашего кода и принял во внимание накладные расходы на вызов и посмотрел, намного быстрее он

Пито
Солнце 26 ноября 2017 г. 9:12
1 млн для петли переключающих штифт 1/0 с: digitalWrite 1517ms (591ns/Write) DIGITAL_WRITE 570ms (118ns/WRITE)

Стивестронг
Солнце 26 ноября 2017 г. 9:54
В LIBS широко используется использование ядра, подобных этим: const uint8_t inPin = PB0; const uint8_t outPin = PB10; volatile uint32_t * inPort = portInputRegister(digitalPinToPort(inPin)); uint16_t inMask = BIT(digitalPinToBit(inPin)); volatile uint32_t * setPort = portSetRegister(outPin); uint16_t outMask = BIT(digitalPinToBit(outPin)); ... bool rd = ( (*inPort) & inMask ) ? 1 : 0; // read ... *outPort = outMask; // set the pin *outPort = outMask<<16; // reset the pin

Дэнниф
Солнце 26 ноября 2017 г. 12:58
Я еще не рассчитывал это. Я случайно управлял IAR (7.10 я думаю), когда я задал вопрос.

Поэтому я решил сравнить свои макросы GPIO:
//port/gpio oriented macros #define IO_SET(port, pins) port->ODR |= (pins) //set bits on port #define IO_CLR(port, pins) port->ODR &=~(pins) //clear bits on port //fast routines through BRR/BSRR registers #define FIO_SET(port, pins) port->BSRR = (pins) #define FIO_CLR(port, pins) port->BRR = (pins)

Пито
Солнце 26 ноября 2017 г. 13:18
Здесь, под нашим ядром: //port/gpio oriented macros #define IO_SET(port, pins) port->regs->ODR |= (pins) //set bits on port #define IO_CLR(port, pins) port->regs->ODR &=~(pins) //clear bits on port //fast routines through BRR/BSRR registers #define FIO_SET(port, pins) port->regs->BSRR = (pins) #define FIO_CLR(port, pins) port->regs->BRR = (pins)

Арпрос
Солнце 26 ноября 2017 г. 14:21
Преимущество моих макросов по сравнению с некоторыми другими решениями заключается в том, что мои макросы не требуют никакой настройки - никаких масок или адресов для определения.

Дэнниф
Солнце 26 ноября 2017 г. 14:45
Это не потому, что вы явно определяете их там.

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

Арпрос
Солнце 26 ноября 2017 г. 15:08
Вот время цикла за операцию и размеры кода для моего тестового кода с использованием -O3 и DWT->Cycccnt для времени. Тестовый код использует 400 развернутых операций. Для операций чтения это последовательность из 400 чтений от PB12 и PA7, чередующаяся. Для операций записи это последовательность из 400 записей 1>PB12, 1>PA7, 0>PB12, 0>ПА7. Все тесты имеют одинаковый код настройки, поэтому размеры байтов должны в основном варьироваться из -за фактических операций чтения/записи.

Резюме: для чтения Bitbanded самый быстрый и самый маленький. Для постоянного написания значений gpio_write_bit () намного быстрее всего, а Bitbanded немного меньше.
  • DigitalRead: 52.5 циклов (40568 байтов)
  • gpio_read_bit: 9.5 циклов (39744 байта)
  • Чтение из регистра в Premade Mask: 9 циклов (39360 байтов)
  • Bitbanded Read: 7 циклов (38128 байтов)
  • DigitalWrite: 54 цикла (39760 байтов)
  • gpio_write_bit: 2 цикла (37344 байт)
  • Написание в ODR Регистр в Premadade Mask: 12.5 циклов (41072 байтов)
  • Запись в BSRR Регистр в Premadade Mask: 4 цикла (38144 байта)
  • Bitbanded Напишите: 7 циклов (37328 байтов)
Тем не менее, gpio_write_bit () становится значительно меньше пространства и эффективного времени, когда написанное значение не известно во время компиляции (e.глин., У меня был это летающий uint8, который был перевернут каждую запись). Он по -прежнему бьет битбандированную запись по одному тактовому циклу, за счет многих потерянных пространств.

Дэйв Дж
Солнце 26 ноября 2017 г. 15:20
Преимущества от битов действительно поступают от предварительной разбавления адреса-таким образом, вам просто нужно прочитать или написать, когда вы его используете. Расчет каждый раз, когда вы делаете, теряет основное преимущество техники.

Арпрос
Солнце 26 ноября 2017 г. 15:49
Я сделал новую версию, которая заменяет битбандированную цифровую_WRITE () оптимизированной версией gpio_write_bit (). Оптимизированная версия на один таксовый цикл быстрее, чем gpio_write_bit (), когда записанное значение (которое должно быть либо 0, либо 1; другие значения дают непредсказуемые результаты), неизвестно во время компиляции и имеет одинаковую скорость, что и gpio_write_bit (), когда значение известен во время компиляции. https: // Gist.GitHub.com/arpruss/5be978f ... 7ABF954C68

Новый Digital_Write () торгует пространством для скорости. Если вы хотите торговать скоростью на пространство, используйте Digital_Write_bitband (), что всегда будет быстрее и меньше, чем DigitalWrite ().

Обратите внимание, что мой Digital_Read () имеет преимущество перед GPIO_READ_BIT (), потому что Digital_Read () всегда возвращает 0 или 1, в то время как gpio_read_bit () возвращает 0 или 32-битную маску. Таким образом, можно делать что -то вроде: uint8 value = DIGITAL_READ(PA8); DIGITAL_WRITE(PA8,value);

Арпрос
Солнце 26 ноября 2017 г. 15:53
[Дэйв Дж - Солнце 26 ноября 2017 г. 15:20] - Преимущества от битов действительно поступают от предварительной разбавления адреса-таким образом, вам просто нужно прочитать или написать, когда вы его используете. Расчет каждый раз, когда вы делаете, теряет основное преимущество техники.
Макросы работают только тогда, когда порт явно указан с использованием формата PXXX, а затем все вычисления выполняются компилятором во время компиляции. Я проверил вывод сборки как с -O3, так и без оптимизации. Вот фрагмент без оптимизации (-g): aa=DIGITAL_READ(PB12); 8002466: 6808 ldr r0, [r1, #0] 8002468: 6018 str r0, [r3, #0] aa=DIGITAL_READ(PA7); 800246a: 6810 ldr r0, [r2, #0] 800246c: 6018 str r0, [r3, #0]

Стивестронг
Солнце 26 ноября 2017 г. 16:13
Вы должны добавить к своему эталону инструкции, которые загружают постоянные значения в регистры R1, R2, R3.

Пито
Солнце 26 ноября 2017 г. 16:16
..торгует пространство для скорости. Если вы хотите торговать скоростью на космос,.. Как вы могли бы торговать скоростью на размер и наоборот, манипулируя булавками?
Размер означает часы здесь..

Mrburnette
Солнце 26 ноября 2017 г., 17:03
Я абсолютно "люблю" эти темы; по -настоящему интересно с точки зрения архитектуры чипа.

Но я обычно (делал это много раз раньше) делаю пост, что такие процессы являются анти -ардуино, концептуально. Для кого -то приезжает в STM32Duino Из AVR это Quicksand Paradigm, потому что мы не только STM32_Centric, мы можем даже писать код в конкретный UC в семействе продуктов STM32.

Это хорошее чтение и хорошее освежение для того, почему ядра arduino по своей природе неэффективен PIN_ADDRESS. Все это влияет на проблемы писателей библиотеки о том, пишут ли они общие или используют #ifdef, чтобы расширить привлекательность библиотеки (полезная область.)

По сути, сделанный выбор будет восхищать и смягчить перспективных пользователей; что является еще одним способом сказать: «Вы не можете угодить всем.'


Луча

Олли
Солнце 26 ноября 2017 г., 17:40
Мои причины, чтобы любить ввод/вывода, были
  • Монотонные операции действительно устраняли конфликты с прерывами
  • Самодокументация при использовании имен переменных вместо портов и пин-номеров
  • Доступ к булавкам, которые не были известны во время компиляции
Причина, по которой я отказался
  • Он не поддерживается в F7 и H7

Рик Кимбалл
Солнце 26 ноября 2017 г., 17:53
[Арпрос - Солнце 26 ноября 2017 г. 15:08] - Тестовый код использует 400 развернутых операций. Для операций чтения это последовательность из 400 чтений от PB12 и PA7, чередующаяся.
Я думаю, что это нелепые испытания. Кто это сделает? Типичный случай использования цифровой записи является некоторым условным кодом, затем изменение PIN -кода, затем еще немного условной логики и другой DigitalWrite. Это оттолкнет ваши регистры BitBand из R1, R2, R3, и ему придется перезагрузить их.

Можете ли вы объяснить, почему этот тест имеет значение?

Дэнниф
Солнце 26 ноября 2017 г. 18:22
Основным недостатком моих макросов является то, что вы не можете хранить порт в переменной (хотя вы можете хранить его в макросе). Не такая большая сделка, для большинства программиста C. Однако толпа Arduino, кажется, более сложна.
Одна вещь, которая мне нравится в моем цифровом _*() макросах, это то, что мне не нужно иметь два #defines на порт в моем заголовке эскиза. Если бы я использовал gpio _*_ bit (), мне нужно было бы сделать что -то вроде Вы платите за это (небольшую) цену, однако.

Арпрос
Солнце 26 ноября 2017 г. 8:40 вечера
[Стивестронг - Солнце 26 ноября 2017 г. 16:13] - Вы должны добавить к своему эталону инструкции, которые загружают постоянные значения в регистры R1, R2, R3.
Хороший момент. Если я получаю больше контактов в функции, компилятор в конечном итоге заканчивает регистры и делает такие вещи, как: aa=DIGITAL_READ(PA4); 80028aa: 491e ldr r1, [pc, #120] ; (8002924 <_Z4loopv+0x700>) 80028ac: 6809 ldr r1, [r1, #0] 80028ae: 6019 str r1, [r3, #0]

Арпрос
Солнце 26 ноября 2017 г. 9:21 вечера
Я добавил запасной удар в DigitalRead () и DigitalWrite (), когда аргумент не является постоянной формата PXXX. Это позволяет просто использовать Digital_read () и Digital_Write () в качестве в значительной степени замены замены DigitalRead () и DigitalWrite (), сохранение пространства и времени, когда аргумент является постоянным, и не хуже (при условии, что оптимизация GCC выполняет свою работу ) в противном случае.

Арпрос
Солнце 26 ноября 2017 г., 21:23
[Пито - Солнце 26 ноября 2017 г. 16:16] - ..торгует пространство для скорости. Если вы хотите торговать скоростью на космос,.. Как вы могли бы торговать скоростью на размер и наоборот, манипулируя булавками?
Размер означает часы здесь..
Насколько я могу судить, написание (и, вероятно, чтения, но я не проверял это). Таким образом, у вас может быть меньше инструкций в коде, но с одной из инструкций, занимающих довольно больше времени.