Очень худой сериал.printf ()

Сжимать
Ср 13 апреля 2016 г. 11:58
Я очень скучаю по функции старой школы printf ()....
Я устал писать снова и снова сериал.Команды print () для отображения некоторых значений. К сожалению, типичная функция Printf очень большая, версия LIBC может занять почти половину или более памяти F103C8. Это не только сама функция, так это количество функций, которые линкер прикрепляет при вызове этой библиотеки (плавающая, помощники, Stdio Stuff и т. Д.)
Я помню, много лет назад были очень оптимизированные версии printf () для использования с 8051, которые занимали не более 2 КБ. По этой причине я открыл свои старые ноутбуки и нашел несколько версий этих функций. Я пытался принять их в среде STM32, внутри класса печати. Я протестировал более 6-8 функций, и я в конечном итоге на версию компилятора SDCC. Я удалил все конкретные вещи MCU, и после некоторых изменений код работает на STM32.

Что у меня есть до сих пор:
- Функция печати, которая поддерживает целые (длинный, короткий, байт), строки, символ и указатель.
- '', -, +, B, L, C, S, P, D, I, O, U и X -спецификаторы формата
- Нет статических переменных, без динамического распределения, нет глобалов, просто простые функции, работающие с локальными переменными.
- Печать.Функция члена Printf
- Очень маленький размер: 1080 байтов (для 3 -яйных функций) и 64 байта для печати.инкапсуляция printf ().
- До сих пор я не могу избавиться от объявления временного буфера внутри печати.printf (), но я уверен, что буфер не нужен (WIP...). По этой причине вывод функции ограничен размером этого буфера, теперь составляет 64 байта (нет теста на переполнение!!!).
- Нет плавающей запятой, но это возможно включить эту функциональность, я должен изучить влияние этого.

Я думаю, что код нуждается в некоторой очистке, но это хорошее начало.

Код включен в один файл с именем print_format.C, это включает в себя основную функцию и некоторые помощники.
Два файла наших основных файлов должны быть изменены.
Сначала отпечаток.H Для объявления функции у Роджера уже есть функция с заголовками, поэтому я написал новую линию int printf(const char * format, ...);

Ахулл
Чт 14 апреля 2016 г., 11:52
Это выглядит гораздо более компактным, чем любой другой варианты printf (), которые я видел в Arduinoland. : D Хорошая работа.

Сжимать
Чт 14 апреля 2016 12:22
Я думаю о некоторых оптимизациях, чтобы уменьшить память больше... например.
- Удалить выделенную печать указателя (можно печатать указатели, как длинные не знаковые целые числа с целочисленными спецификаторами)
- Прямой вызов обратно в печати.Функции записи без буферизации (очень важно для последовательной или ЖК -печати)

Я также изучаю некоторые алгоритмы для преобразования номера точки поплавка в строку без функций с плаванием. Это важно для поддержки поплавок без накладных расходов библиотек с плавающей запятой. Конечно, есть некоторые ограничения, потому что эти разделенные функции не поддерживают научные обозначения, имеют фиксированное количество точности и т. Д
Лучше всего до сих пор около 1500 байтов... Но это WIP....

Mrburnette
Чт 14 апреля 2016 12:27
Хорошо выглядит! На STM32F103 1K Flash не является чрезмерным количеством хранения, чтобы потерять полезную функцию. Мне придется поиграть с этим позже и получить компонент SRAM, но с Bootloader 2.0, 20K SRAM, как правило, является достаточным количеством, даже когда на UC предъявлены большие требования.

Но когда SRAM и Flash очень плотные (может быть, Uno, Nano...) тогда потоковое макрос Микала Харта-это способ обработки форматирования вывода печати. http: // arduiniana.орг/библиотеки/потоковая передача/
Можно даже снять простую «логику» в потоке:

Пример Роба Тиллаарта: #include // ..... int h = 14; int m = 6 Serial << ((h<10)?"0":"") << h << ":" << ((m<10)?"0":"") << m << endl;

Сжимать
Чт 14 апреля 2016 г. 12:39
Кроме буфера 64 байта в печати.Функция члена printf (), функция Core Используйте локальные переменные в стеке. Если я правильно измеряю байты, это около 26 байтов...
Кроме того, если только серийный.printf используется в программе без разных версий серийных.print ()/serial.println () возможно, что общий размер программы будет меньше.

Mrburnette
Чт 14 апреля 2016 12:55
Сламмер написал:Кроме буфера 64 байта в печати.Функция члена printf (), функция Core Используйте локальные переменные в стеке. Если я правильно измеряю байты, это около 26 байтов...
Кроме того, если только серийный.printf используется в программе без разных версий серийных.print ()/serial.println () возможно, что общий размер программы будет меньше.

Martinayotte
Чт 14 апреля 2016 г. 13:45
Поздравляю ! Вы должны подать пиар для этого !

(В ESP он есть с некоторое время, но это было намного проще, так как было достаточно места, чтобы использовать vsnprintf () ...)

Рик Кимбалл
Чт 14 апреля 2016 г. 14:17
Вниз по печати - это то, что форматирование выполняется с использованием кода времени выполнения. Если вы используете подход потоковой передачи или просто серийный.Распечатайте все решения о том, как форматировать, выполняются во время компиляции. Если вы ищете лишь такую ​​дополнительную скорость, вы можете придерживаться стандартных вещей.

На форуме.43OH.com, мы уже давно обсуждали Printf. У одного пользователя, Opossum, был уникальный подход, который привел к небольшому коду, который не использовал буферизацию. Смотрите этот пост: http: // форум.43OH.com/topic/1289-printf-c-версия/

Сжимать
Чт 14 апреля 2016 г. 14:51
Спасибо за информацию
Я оцениваю почти 6-8 различных реализаций функций Small Printf () с дополнительными модификациями.

Я хочу что -то с небольшим количеством следов (около 1K в порядке), без статических переменных (нам нужна повторная доходность), длинная поддержка Int, 0 и (пространство) спецификаторы, полная поддержка всех типов целочисленных целых (Хорошо, я могу дать 1-1.Еще 5 КБ для этого), интеграция в класс печати.

Сжимать
Чт 14 апреля 2016 г., 11:20 вечера
Пытаясь решить проблему с буфером в printf (), я понял, что должен двигаться в другом направлении.
Вместо того, чтобы пытаться инкапсулировать функцию C, а обратный класс в классе Print (который фактически не имеет функции write (), является лишь виртуальной), лучше написать printf () как собственная функция C ++ Print (aka arduino стиль). Тот же подход в любом случае используется для других функций печати.

Теперь нет C -кода, все функциональность printf выполняются в функции в классе печати, нет причин делать обратный вызов, чтобы написать что -то, это очень просто, вызывая виртуальную функцию write () печати. Мне также нужна небольшая внутренняя функция для расчета цифр численных значений.

Общий размер этих 2 функций составляет 0x22+0x318 = 0x33a = 826 байтов (без буферов, без статических переменных)

Чтобы использовать добавить этот код в конце печати.CPP
//------------------------------------------------ #ifdef toupper #undef toupper #endif #ifdef tolower #undef tolower #endif #ifdef islower #undef islower #endif #ifdef isdigit #undef isdigit #endif #define toupper(c) ((c)&=0xDF) #define tolower(c) ((c)|=0x20) #define islower(c) ((unsigned char)c >= (unsigned char)'a' && (unsigned char)c <= (unsigned char)'z') #define isdigit(c) ((unsigned char)c >= (unsigned char)'0' && (unsigned char)c <= (unsigned char)'9') typedef union { unsigned char byte[5]; long l; unsigned long ul; float f; const char *ptr; } value_t; size_t Print::printDigit(unsigned char n, bool lower_case) { register unsigned char c = n + (unsigned char)'0'; if (c > (unsigned char)'9') { c += (unsigned char)('A' - '0' - 10); if (lower_case) c += (unsigned char)('a' - 'A'); } return write(c); } static void calculateDigit (value_t* value, unsigned char radix) { unsigned long ul = value->ul; unsigned char* pb4 = &value->byte[4]; unsigned char i = 32; do { *pb4 = (*pb4 << 1) | ((ul >> 31) & 0x01); ul <<= 1; if (radix <= *pb4 ) { *pb4 -= radix; ul |= 1; } } while (--i); value->ul = ul; } size_t Print::printf(const char *format, ...) { va_list ap; bool left_justify; bool zero_padding; bool prefix_sign; bool prefix_space; bool signed_argument; bool char_argument; bool long_argument; bool lower_case; value_t value; int charsOutputted; bool lsd; unsigned char radix; unsigned char width; signed char decimals; unsigned char length; char c; // reset output chars charsOutputted = 0; va_start(ap, format); while( c=*format++ ) { if ( c=='%' ) { left_justify = 0; zero_padding = 0; prefix_sign = 0; prefix_space = 0; signed_argument = 0; char_argument = 0; long_argument = 0; radix = 0; width = 0; decimals = -1; get_conversion_spec: c = *format++; if (c=='%') { charsOutputted+=write(c); continue; } if (isdigit(c)) { if (decimals==-1) { width = 10*width + c - '0'; if (width == 0) { zero_padding = 1; } } else { decimals = 10*decimals + c - '0'; } goto get_conversion_spec; } if (c=='.') { if (decimals==-1) decimals=0; else ; // duplicate, ignore goto get_conversion_spec; } if (islower(c)) { c = toupper(c); lower_case = 1; } else lower_case = 0; switch( c ) { case '-': left_justify = 1; goto get_conversion_spec; case '+': prefix_sign = 1; goto get_conversion_spec; case ' ': prefix_space = 1; goto get_conversion_spec; case 'B': /* byte */ char_argument = 1; goto get_conversion_spec; // case '#': /* not supported */ case 'H': /* short */ case 'J': /* intmax_t */ case 'T': /* ptrdiff_t */ case 'Z': /* size_t */ goto get_conversion_spec; case 'L': /* long */ long_argument = 1; goto get_conversion_spec; case 'C': if( char_argument ) c = va_arg(ap,char); else c = va_arg(ap,int); charsOutputted+=write(c); break; case 'S': value.ptr = va_arg(ap,const char *); length = strlen(value.ptr); if ( decimals == -1 ) { decimals = length; } if ( ( !left_justify ) && (length < width) ) { width -= length; while( width-- != 0 ) { charsOutputted+=write(' '); } } while ( (c = *value.ptr) && (decimals-- > 0)) { charsOutputted+=write(c); value.ptr++; } if ( left_justify && (length < width)) { width -= length; while( width-- != 0 ) { charsOutputted+=write(' '); } } break; case 'D': case 'I': signed_argument = 1; radix = 10; break; case 'O': radix = 8; break; case 'U': radix = 10; break; case 'X': radix = 16; break; default: // nothing special, just output the character charsOutputted+=write(c); break; } if (radix != 0) { unsigned char store[6]; unsigned char *pstore = &store[5]; if (char_argument) { value.l = va_arg(ap, char); if (!signed_argument) { value.l &= 0xFF; } } else if (long_argument) { value.l = va_arg(ap, long); } else { // must be int value.l = va_arg(ap, int); if (!signed_argument) { value.l &= 0xFFFF; } } if ( signed_argument ) { if (value.l < 0) value.l = -value.l; else signed_argument = 0; } length=0; lsd = 1; do { value.byte[4] = 0; calculateDigit(&value, radix); if (!lsd) { *pstore = (value.byte[4] << 4) | (value.byte[4] >> 4) | *pstore; pstore--; } else { *pstore = value.byte[4]; } length++; lsd = !lsd; } while( value.ul ); if (width == 0) { // default width. We set it to 1 to output // at least one character in case the value itself // is zero (i.e. length==0) width = 1; } /* prepend spaces if needed */ if (!zero_padding && !left_justify) { while ( width > (unsigned char) (length+1) ) { charsOutputted+=write(' '); width--; } } if (signed_argument) { // this now means the original value was negative charsOutputted+=write('-'); // adjust width to compensate for this character width--; } else if (length != 0) { // value > 0 if (prefix_sign) { charsOutputted+=write('+'); // adjust width to compensate for this character width--; } else if (prefix_space) { charsOutputted+=write(' '); // adjust width to compensate for this character width--; } } /* prepend zeroes/spaces if needed */ if (!left_justify) { while ( width-- > length ) { charsOutputted+=write( zero_padding ? '0' : ' '); } } else { /* spaces are appended after the digits */ if (width > length) width -= length; else width = 0; } /* output the digits */ while( length-- ) { lsd = !lsd; if (!lsd) { pstore++; value.byte[4] = *pstore >> 4; } else { value.byte[4] = *pstore & 0x0F; } charsOutputted+=printDigit(value.byte[4], lower_case); } } } else { charsOutputted+=write(c); } } va_end(ap); return (size_t)charsOutputted; }

Mrburnette
Чт 14 апреля 2016 г., 11:32 вечера
Сламмер написал: <...>
Никакая другая реализация не так мала, я пробовал почти все, есть меньшие реализации, но они не поддерживают все типы целых чисел или спецификаторов ширины, или они используют статические переменные....

Сжимать
Пт 15 апреля 2016 г. 12:21
И для тех, кто говорит, что STM32 тратит больше памяти, чем AVR, та же функция в UNO нуждается в 0x40c+0x2a = 1078 байт

Mrburnette
Пт 15 апреля 2016 г. 14:31
Mrburnette написал: <...>
Теперь, как быстро это?

Луча

Сжимать
Пт 15 апреля 2016 г., 21:21
На самом деле этот метод не измеряет сам printf.... Но время, которое MCU нужно написать персонажам в UART.
Наша реализация записи в Serial (USART_PUTC) не основана на прерывании, ни буфера для поддержки, в зависимости от этого, MCU во время написания персонажа для сериала, просто ожидая, чтобы закончить передачу (я не знаю внутренних участников STM32, но любого ожидания текущий символ, чтобы вытащить или предыдущий... Но результат почти одинаковый, если вы хотите подтолкнуть несколько байтов в UART)
В типичном приложении необходимо использовать кольцевой буфер, USART_PUTC обычно является точкой входа в кольцевой буфер, но не блокирует. TX прерывание запускает отправку следующего символа, пока буфер не станет пустым.
Время, когда персонаж должен покинуть UART, не маленькое. В 115200 персонаж нуждается в почти 1/11520 с = 87 usec, это действительно много времени для 72 МГц MPU (в 9600 - вечность....)

Mrburnette
Пт 15 апреля 2016 г., 21:36
Сламмер написал:На самом деле этот метод не измеряет сам printf.... Но время, которое MCU нужно написать персонажам в UART.
Наша реализация записи в Serial (USART_PUTC) не основана на прерывании, ни буфера для поддержки, в зависимости от этого, MCU во время написания персонажа для сериала, просто ожидая, чтобы закончить передачу (я не знаю внутренних участников STM32, но любого ожидания текущий символ, чтобы вытащить или предыдущий... Но результат почти одинаковый, если вы хотите подтолкнуть несколько байтов в UART)
В типичном приложении необходимо использовать кольцевой буфер, USART_PUTC обычно является точкой входа в кольцевой буфер, но не блокирует. TX прерывание запускает отправку следующего символа, пока буфер не станет пустым.

Сжимать
Пт 15 апреля 2016 г., 21:56
Nodemcu - это действительно зверь 2 $
Память программы настолько огромна для приложений MCU, что полная версия Printf 50K-60K почти ничего.... Вот почему печать.printf () включен в ядро ​​ESP8266.
С другой стороны, наиболее используемые MPUS за последние 25 лет моей профессиональной жизни - ATMEGA8 и 89C52... На этих машинах даже класс печати - роскошь... Вы должны жить с базовым ITOA и LTOA....
В любом случае, может быть более полезным для нашего сообщества, чтобы попытаться улучшить некоторые основные функции, такие как Buffered Transmitt на UART... (LOL, я хочу, чтобы что -то не было заняты ночью....)

PS: я боюсь, что измерение времени передачи с серийным.Написать более сложный. Мой предыдущий пост о UART технически правильный, но сериал.Функции XXX используют не реальный UART, а эмулированное устройство через USB, которое действует как UART. Время этого устройства не является легкой задачей...
Концепция такая же из -за блокирующей природы uart_putc, но время неизвестно.

Mrburnette
Пт 15 апреля 2016 г. 22:10
Сламмер написал: <...>
С другой стороны, наиболее используемые MPUS за последние 25 лет моей профессиональной жизни - ATMEGA8 и 89C52... На этих машинах даже класс печати - роскошь... Вы должны жить с базовым ITOA и LTOA....
В любом случае, может быть более полезным для нашего сообщества, чтобы попытаться улучшить некоторые основные функции, такие как Buffered Transmitt на UART... (LOL, я хочу, чтобы что -то не было заняты ночью....)

Сжимать
Пт 15 апреля 2016 г., 11:00 вечера
Посмотрите на результаты этого кода (только цикл):
long uScount; digitalWrite(BOARD_LED_PIN, HIGH); delay(500); // LED_on + half-second uScount = micros(); Serial1.println("This is my big fat text.... 50 characters long...."); uScount = micros() - uScount; Serial.print("\t\t\t\t printf Serial1 : uS="); Serial.println( uScount); uScount = micros(); Serial.println("This is my big fat text.... 50 characters long...."); uScount = micros() - uScount; Serial.print("\t\t\t\t printf USB : uS="); Serial.println( uScount); digitalWrite(BOARD_LED_PIN, LOW); delay(500); // LED_off + half-second

Mrburnette
СЕТ 16 апреля 2016 г. 2:12
Результат раскрывает правду о времени UART/USB Да, согласен.

Луча

victor_pv
Солнце 15 января 2017 г. 16:44
Slammer, я только что использовал вашу функцию Printf в маленькой библиотеке SWO, надеюсь, вы не возражаете, я дал вам кредит ;)

Теперь, о Printf в классе печати, я понимаю, что Рэйс точка зрения, что добавление 1 КБ кода здесь, 1 там, в конечном итоге добавляет к хорошему количеству, и людям это может не понадобиться, но я подумал, что если эскиз не использует printf , этот Printf не будет включен компилятором/линкером, поэтому, если он фактически не используется эскизом, нет никакой разницы, присутствует ли функция в классе печати или нет.
Я что -то упускаю?

Кроме того, еще одно наблюдение: я протестировал, используя Sprintf, чтобы преобразовать сообщение в строку в строку, чтобы он мог распечатать его с помощью Println, в тестовом эскизе для SWO, и он увеличивает эскиз на 15 КБ вспышки и 1.5 КБ ОЗУ +/-. Для сравнения Slammers Printf увеличивает размер примерно на 1 кб или около того, что.
Учитывая, что если printf не займет место, когда не используется, и добавление его в ядро ​​может спасти людей от необходимости использовать Sprintf, мой голос будет включать его в ядро.


РЕДАКТИРОВАТЬ: Я собрал свой эскиз для теста SWO с помощью Println и с помощью printf. В обоих случаях Printf является частью класса SWO. На самом деле кажется, что это не добавляет к коду, если не используется.
Затем я пошел на 1 шаг вперед и вместо этого включил его в класс печати, и я получаю аналогичный результат, размер кода не расти, когда не используется. Поэтому я не вижу вреда в добавлении в ядро. Я не уверен, почему тест Рэя показывал увеличение размера, когда не использовался. printf in SWO class but not used in the sketch (only println): Sketch uses 15,940 bytes (3%) of program storage space. Maximum is 524,288 bytes. Global variables use 2,952 bytes of dynamic memory. printf in SWO class and used: Sketch uses 16,900 bytes (3%) of program storage space. Maximum is 524,288 bytes. Global variables use 2,952 bytes of dynamic memory. printf in print class and used: Sketch uses 16,908 bytes (3%) of program storage space. Maximum is 524,288 bytes. Global variables use 2,952 bytes of dynamic memory. printf in print class and not used: Sketch uses 15,940 bytes (3%) of program storage space. Maximum is 524,288 bytes. Global variables use 2,952 bytes of dynamic memory.

Управление портом DMA