Проверьте код ниже:
#include <avr/io.h>
const uint16_t baudrate = 9600;
void setupUART( void ) {
uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 );
UBRRH = ubrr >> 8;
UBRRL = ubrr & 0xff;
}
int main( void ) {
setupUART();
}
Эта команда используется для компиляции кода:
avr-gcc -g -DF_CPU=4000000 -Wall -Os -Werror -Wextra -mmcu=attiny2313 -Wa,-ahlmns=project.lst -c -o project.o project.cpp
ubrr
рассчитывается компилятором как 25, пока все хорошо. Однако, чтобы проверить, что рассчитал компилятор, я заглянул в лист разборки.
000000ae <setupUART()>:
ae: 12 b8 out UBRRH, r1 ; 0x02
b0: 89 e1 ldi r24, 0x19 ; 25
b2: 89 b9 out UBRRL, r24 ; 0x09
b4: 08 95 ret
Можно ли сделать avr-gcc
распечатать промежуточный результат во время компиляции (или извлеките информацию из файла .o), поэтому, когда я компилирую код, он печатает строку вроде (uint16_t) ubbr = 25
или похожие? Таким образом, я могу быстро проверить правильность вычислений и настроек.
GCC имеет параметры командной строки, чтобы запросить вывод промежуточного представления после любого этапа компиляции. Дампы «дерева» имеют синтаксис псевдо-C и содержат необходимую информацию. За то, что вы пытаетесь сделать, -fdump-tree-original
а также -fdump-tree-optimized
дампы происходят в полезных точках в конвейере оптимизации. У меня нет компилятора AVR, поэтому я изменил ваш тестовый пример, чтобы он был автономным и компилируемым с моим компилятором:
typedef unsigned short uint16_t;
const int F_CPU = 4000000;
const uint16_t baudrate = 9600;
extern uint16_t UBRRH, UBRRL;
void
setupUART(void)
{
uint16_t ubrr = ((F_CPU / (16 * (float) baudrate)) - 1 + .5);
UBRRH = ubrr >> 8;
UBRRL = ubrr & 0xff;
}
а потом
$ gcc -O2 -S -fdump-tree-original -fdump-tree-optimized test.c
$ cat test.c.003t.original
;; Function setupUART (null)
;; enabled by -tree-original{
uint16_t ubrr = 25;
uint16_t ubrr = 25;
UBRRH = (uint16_t) ((short unsigned int) ubrr >> 8);
UBRRL = ubrr & 255;
}
$ cat test.c.149t.optimized
;; Function setupUART (setupUART, funcdef_no=0, decl_uid=1728, cgraph_uid=0)
setupUART ()
{
<bb 2>:
UBRRH = 0;
UBRRL = 25;
return;
}
Вы можете видеть, что свертывание константных выражений выполняется настолько рано, что это уже произошло в «оригинальном» дампе (который является самым ранним доступным дампом, который вы можете иметь), и что оптимизация еще больше свела операции сдвига и маски в операторы, записывающие в UBRRH и UBRRL.
Числа в именах файлов (003t и 149t), вероятно, будут отличаться для вас. Если вы хотите увидеть все свалки «дерева», используйте -fdump-tree-all
, Есть также дампы «RTL», которые не похожи на C и, вероятно, бесполезны для вас. Если вам интересно, -fdump-rtl-all
включит их. Всего существует около 100 древовидных дампов и 60 дампов RTL, так что это хорошая идея сделать это в чистом каталоге.
(Psssst: Каждый раз, когда вы ставите пробелы внутри скобок, Бог убивает котенка.)
Может быть решение для печати промежуточных результатов, но для его реализации потребуется некоторое время. Так что это стоит только для довольно большой базы исходного кода.
Вы можете настроить свой компилятор GCC; либо через плагин (болезненно закодированный в C или C ++) или через ПЛАВИТЬСЯ расширение. MELT — это высокоуровневый, похожий на Lisp, предметно-ориентированный язык для расширения GCC. (Он реализован как [meta-] плагин для GCC и переведен на C ++ код, подходящий для GCC).
Однако такой подход требует, чтобы вы поняли внутреннюю часть GCC, а затем добавили свой собственный «оптимизационный» проход, чтобы сделать аспектно-ориентированное программирование (например, используя MELT), чтобы распечатать Соответствующий промежуточные результаты.
Вы также можете посмотреть не только сгенерированную сборку (и использовать -fverbose-asm -S
в качестве опций для GCC), но также, возможно, в сгенерированных представлениях Gimple (возможно, с -fdump-tree-gimple
). Для некоторого интерактивного инструмента, рассмотрите графический MELT зонд.
Возможно добавление вашего собственного встроенного (с расширением MELT), как __builtin_display_compile_time_constant
может быть актуальным.
Я сомневаюсь, что есть простой способ определить, что делает компилятор. В gcc могут быть некоторые инструменты, специально предназначенные для вывода промежуточной формы языка, но это определенно будет непросто прочитать, и если вы ДЕЙСТВИТЕЛЬНО не подозреваете, что компилятор делает что-то не так (и имеете ОЧЕНЬ маленький пример, чтобы показать это) Маловероятно, что вы можете использовать его для чего-то значимого — просто потому, что слишком много работы, чтобы следить за тем, что происходит.
Лучше всего добавить временные переменные (и, возможно, распечатать) в ваш код, если вы беспокоитесь о его корректности:
uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 );
uint8_t ubrr_high = ubrr >> 8
uint8_t ubrr_low = ubrr & 0xff;
UBRRH = ubrr_high;
UBRRL = ubrr_low;
Теперь, если у вас есть неоптимизированная сборка и вы пройдете через нее в GDB, вы сможете увидеть, что она делает. В противном случае добавление каких-либо распечаток в код для отображения значений …
Если вы не можете распечатать его в целевой системе, потому что вы находитесь в процессе настройки UART, который вы будете использовать для печати, скопируйте код на локальную хост-систему и отладьте его там. Если компилятор не очень глючит, вы должны получить те же значения из одной и той же компиляции.
Вот хак: просто автоматизируйте то, что вы делаете сейчас, вручную.
-ahlms=
output.lst). В качестве альтернативы, используйте свой собственный метод разборки в качестве шага после компиляции в вашем make-файле.out UBRRH
а также out UBRRL
линий. Они будут загружены из регистров, поэтому ваш скрипт может извлекать непосредственно предшествующие назначения для регистров, которые будут загружены в UBRRH
а также UBRRL
, Затем сценарий может собрать UBRR
значение из значения, загруженного в регистры общего назначения, которые используются для установки UBRRH
а также UBRRL
,Это звучит проще, чем Василий Старынкевичочень полезное предложение ПЛАВИТЬСЯ расширение. Теперь, учитывая, что это решение кажется хрупкий, на первый взгляд, давайте рассмотрим эту проблему:
out UBRR_, r__
появится в листинге разборки: просто нет другого способа установить регистры / записать данные в порт. Одна вещь, которая может измениться, — это расстояние между этими строками, но это может быть легко обработано вашим скриптомout
инструкции могут поступать только из регистров общего назначения, поэтому мы знаем, что в качестве второго аргумента out
строка инструкции, так что это не должно быть проблемой.out
инструкция. Здесь мы должны учесть некоторую изменчивость: вместо LDI
(загрузить немедленно), avr-gcc может выдать какой-то другой набор инструкций для установки значения регистра. Я думаю, что в качестве первого прохода скрипт должен быть в состоянии проанализировать немедленную загрузку и в противном случае вывести любую последнюю найденную инструкцию, включающую регистр, в который будет записана запись. UBRR_
порты.Сценарий, возможно, придется изменить, если вы меняете платформы (некоторые процессоры имеют UBRRH1
/2
регистрирует на месте UBRRH
Однако в этом случае ваш код в бодах должен будет измениться. Если скрипт жалуется, что не может разобрать разборку, то вы по крайней мере узнаете, что ваша проверка не была выполнена.