Помогите понять, что творит оптимизатор Си AVR

Обсуждаем контроллеры компании Atmel.
Аватара пользователя
Мikа
Потрогал лапой паяльник
Сообщения: 343
Зарегистрирован: Пн апр 01, 2013 15:13:40
Откуда: Москва

Помогите понять, что творит оптимизатор Си AVR

Сообщение Мikа »

Здравствуйте. Сразу оговорюсь, что создал новую тему а не написал в "Несколько простых вопросов по Си AVR", т.к. вопрос для меня очень важный и не хочу, чтобы он затерялся в той ветке без ответа.

Дело такое: писал я себе на ассемблере и горя не знал. Ну за исключением того, что это ассемблер и надо всё делать ручками.
Потом у меня был долгий перерыв в программировании. Сейчас опять занялся, но решил писать на Си. Всё равно я не пишу сложных программ и все мои устройства носят скорее вспомогательный характер, нежели целевой продукт производства. Что думаю париться со стеком, регистровыми парами и прочими делами, если можно на Си всё по быстрому писать.

Ну так вот. Делаю простенькое устройство. Первое что решил написать – это часть кода, которая будет управлять трехразрядным семисегментным индикатором, схема которого ОА. Индикатор управляется через 2 последовательно соединенных сдвиговых регистра 74HC595D.
Казалось бы, чего тут проще?
На схеме я убрал всё лишнее, чтобы не загромождать. Да, не обращайте внимания, что я как-то странно назвал линии управления сдвиговым регистром. Схему сделал давно (на ассемблере всё работало без каких-либо проблем. Там я вообще по 1 проводу всеми тремя ногами сдвигового регистра управлял).

Короче говоря, сдвиговые регистры подключены последовательно, то есть биты пройдя первый насквозь, попадают во второй. Выводы управления регистрами запараллелены между собой.
Используется :

линия данных (Data) (состояние с которой считывается в регистр),

линия записи лог состояния с Data (ReadData) и

линия вывода считанного байта на порт регистра (ShowData).

Линии управления (Data, ReadData, ShowData) напрямую подключены к микроконтроллеру ATtiny2313A. На всякий случай оговорюсь, что земли
регистров и контроллера, также как и линия питания – общие. Еще один момент – выводы регистра Q0-Q7, которые подключены к катодам индикатора A-H могут не соответствовать тем кодам, которые лежат в массиве кодов индикатора в программе. Это связано с тем, что как я уже говорил, схема была сделана давно, а сейчас просто доработана и переложена на другую плату.

Теперь идём к программе.
Проект С (не С++), Atmel Studio 7.0. Оптимизатор настроен на –О1. Чтобы исключить возможность ошибки или опечатки, я приложу скриншот.

Теперь суть задачи, на которой возникла проблема (вообще конечная цель – динамическая индикация, но проблема возникла на ТУПОМ ВЫВОДЕ БИТ В РЕГИСТР).
Напомню, как управлять регистром.

1.Кладем в 0 линии управления ReadData (SH_CP) и ShowData (ST_CP)

2.На линию Data (DS) выводим тот логический уровень, который должен быть занесен в регистр.

3.Поднимаем ReadData (SH_CP) в ЛОГ1, чуточку ждем, кладем ReadData (SH_CP) в ЛОГ0. Это приводит к считыванию бита с Data (DS) в регистр.

Повторяем пункты 2-3 нужное количество раз. В моем случае 16. Нужно заполнить 2 восьмибитных регистра.

4. Поднимаем ShowData (ST_CP) в ЛОГ1, чуточку ждем, кладем ShowData (ST_CP) в ЛОГ0. Это приводит к выводу записанных в регистр бит на порт регистра.

В принципе, всё очень просто, совершенно тупое дерганье ногами.


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

Я решил сделать следующим образом:

Код: Выделить всё

volatile unsigned char Kod_Znaka = 0;   // Код цифры
volatile unsigned char Kod_Vklucheniya = 0;   // Код разряда
volatile unsigned char i = 0;  // Счетчик, который используется сдвиговым регистром
volatile unsigned int SRData = 0; // 2 байта, выводящиеся в регистр.


volatile я дописал в процессе схождения с ума из-за не понимания происходящего.
Сделать i глобальной переменной я решил по той же причине.

Из массива с кодами символов я получаю какой-то код. Например для цифры 5:

Код: Выделить всё

Kod_Znaka = digits[5];

//Второй байт несет в себе код, который откроет транзистор нужного мне разряда.

Kod_Vklucheniya = 0x01; // 0b00000001

//Из них я складываю двухбайтное число:

SRData = (Kod_Vklucheniya << 8) + Kod_Znaka;

//До этого момента все идет хорошо, я смотрел в симуляторе.
//Если, например,
//Kod_Vklucheniya == 0b11001100
//Kod_Znaka == 0b00001111
//То SRData == 0b1100110000001111

//Далее простецкий цикл вывода SRData по биту в регистр:

// Shift register
#define PORT_DATA PORTD
#define PinData PIND1
#define PinReadData PIND3
#define PinShowData PIND4
// Определения находятся наверху программы. Привожу их чтобы вы не решили, что я их забыл


Далее код вывода бит в регистр:

Код: Выделить всё

for (i=0; i<16; i++) // 16 раз, для каждого бита повторяем одно и то же
   {
if (SRData & 0x80)// Чему равен старший бит, тому будет равен вывод Data (DS)
   {
      PORT_DATA |= (1<<PinData);
   }
   else
   {
      PORT_DATA &= (~(1<<PinData));
   }
   
   _delay_us(100);            // Чуть ждем хз зачем
   PORT_DATA |= (1<<PinReadData);      // Запись бита в сдвиговый регистр
   _delay_us(100);
   PORT_DATA &= (~(1<<PinReadData));
      
   SRData1 <<= 1;// Сдвиг битов влево, чтобы получить следующий бит
      
   }

   PORT_DATA |= (1<<PinShowData);   // Вывод данных в порт регистра
   _delay_us(100);
   PORT_DATA &= (~(1<<PinShowData));


Это, в принципе весь код, который тут должен отработать. Но при просмотре действий программы в симуляторе я видел какой-то бред. Программа то тупо пропускала по 3-4 строки,
то оптимизировалась так, что если нужно было поднять в ЛОГ1 Data (DS), а потом (ПОСЛЕ ЭТОГО, ПРЯМ ПОДОЖДАТЬ НАДО) передернуть ReadData (SH_CP), чтобы записать бит в регистр, программа оптимизировала задержку и одновременно поднимала в ЛОГ1 оба вывода, приводя к непредсказуемому значению, записывающемуся в регистр.

Теперь забегая вперед я скажу, что цели я достиг – динамическая индикация работает. Но что не так в приведенном примере я не понимаю. Решение проблемы следующее:

Делаем не один цикл из 16 повторений с 2 байтным числом, а 2 цикла из 8 повторений с двумя однобайтными числами. И все работает как задумано. То есть делаем вот так 2 раза:

Код: Выделить всё

for (i=0; i<8; i++) // 16 раз, для каждого бита повторяем одно и то же
   {
if (SRData & 0x80)// Чему равен старший бит, тому будет равен вывод Data (DS)
   {
      PORT_DATA |= (1<<PinData);
   }
   else
   {
      PORT_DATA &= (~(1<<PinData));
   }
   
   _delay_us(100);            // Чуть ждем хз зачем
   PORT_DATA |= (1<<PinReadData);      // Запись бита в сдвиговый регистр
   _delay_us(100);
   PORT_DATA &= (~(1<<PinReadData));
      
   SRData1 <<= 1;// Сдвиг битов влево, чтобы получить следующий бит
      
   }


Только меняем значение SRData при втором прогоне этого цикла и после обоих прогонов добавляем

Код: Выделить всё

PORT_DATA |= (1<<PinShowData);   // Вывод данных в порт регистра
_delay_us(100);
PORT_DATA &= (~(1<<PinShowData));


Исходники обеих программ я прикладываю.

Суть всего поста в том, что мне необходимо понять компилятор. Можете подсказать, в каком месте я допускаю ошибку? Я на этот бред убил 2 вечера. И на этот пост около часа. Хочется окупить потерянное время приобретенными знаниями. Заранее большое спасибо!
Вложения
Готовый вариант, где все работает.c
(3.7 КБ) 531 скачивание
Вариант, где всё НЕ работает.c
(2.72 КБ) 405 скачиваний
Схема.png
(36.93 КБ) 590 скачиваний
Screenshot_1.png
(26.61 КБ) 614 скачиваний
Почему я здесь и задаю тупые вопросы?
Потому что хочу научиться.
L.O.D
Встал на лапы
Сообщения: 139
Зарегистрирован: Чт фев 11, 2016 18:35:37

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение L.O.D »

Мikа писал(а):мне необходимо понять компилятор. Можете подсказать, в каком месте я допускаю ошибку?
Если код программы, приведенный выше, честный копипаст, без ошибок, то есть пара предположений.
1. Вы сдвигаете 16-битное число, но достаете из него только 8 неопределенных бит, а еще 8 бит - заведомо нулевые, что если компилятор это просек? :) Ошибка повидимому вызвана метаниями между 8-и- и 16-битной версиями программы - счетчик цикла меняли, а маску старшего бита - нет:

Код: Выделить всё

if (SRData & 0x80)// Чему равен старший бит, тому будет равен вывод Data (DS)
Старший бит двухбайтного - не 0x80, а 0x8000.
2. Видно объявление переменной SRData, как unsigned int, а вот что там крутится 16 раз в цикле, не показано - может однобайтная переменная?

Код: Выделить всё

SRData1 <<= 1;// Сдвиг битов влево, ...
- Из овощей я больше всего люблю пельмени... © Соседский Мальчик
Аватара пользователя
Мikа
Потрогал лапой паяльник
Сообщения: 343
Зарегистрирован: Пн апр 01, 2013 15:13:40
Откуда: Москва

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение Мikа »

Здравствуйте! Большое спасибо за совет. Мне почему-то кажется, что дело как раз в этом. О маске я совсем не подумал. Вечером, как домой приду, проверю эту версию на устройстве, тк гонять в симуляторе будет не очень продуктивно с точки зрения времени. Я не могу моментально в голове переводить числа из десятичных в двоичные и т.п., а делать это с помощью калькулятора Windows будет та еще задача :) Относительно кода - это правда честная копипаста и, для верности я и файлы приложил :)
Насчет переменной SRData1 - она появилась в процессе разбиения 16ти разового цикла на 2 8ми разовых. Здесь я, видимо, скопировал не из той программы (не из 16 битной) :)
Почему я здесь и задаю тупые вопросы?
Потому что хочу научиться.
Аватара пользователя
ARV
Ум, честь и совесть. И скромность.
Сообщения: 18544
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск
Контактная информация:

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение ARV »

По поводу оптимизатора могу сказать пару общих слов, возможно вам, как ранее не знакомому с компилятором Си avr-gcc это окажется полезным.

1. оптимизацию имеет смысл делать либо -Os для получения минимального объема кода, либо -O3 для получения максимального быстродействия программы. лично в моей практике ни разу не было случая, чтобы я использовал что-то отличное от -Os
2. в процессе отладки программы с включенной оптимизацией вы обязательно увидите "чудеса", которые могут проявляться в том, что то цикл начинается не с первого оператора, а с середины, то вообще код исполняется не понятно как... это имеет одно простое объяснение: в процессе оптимизации компилятор создает такой код, ассемблерные команды которого не всегда можно разделить на группы, соответствующие конкретной строке исходника на Си. в итоге получается, что в процессе пошагового исполнения невозможно отобразить строку исходника, соответствующую текущему месту исполнения, а иногда и само место исполнения вообще не попадает на какую-то из строк программы. в этом случае отладочная информация оказывается не соответствующей действительности, что и приводит к странностям. однако, если ошибок в коде нет, истинное поведение программы всегда остается верным - странности чисто вешние в самом отладчике.

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

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

Мой уютный бложик... заходите!
L.O.D
Встал на лапы
Сообщения: 139
Зарегистрирован: Чт фев 11, 2016 18:35:37

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение L.O.D »

Мikа писал(а):Я не могу моментально в голове переводить числа из десятичных в двоичные и т.п., ...
Видимо, Вы и на ассемблере писали не слишком много. :) Каждая шестнадцатиричная цифра переводится строго в одну двоичную тетраду. Ну а соответствие тетрад цифрам от 0 до F даже и запоминать необязательно - легко вычисляется на первых порах. Т.о.:

0x 8'0'0'0 == 0b 1000'0000'0000'0000


Мikа писал(а):... а делать это с помощью калькулятора Windows будет та еще задача
Ничего подобного - переключение между шестнадцатиричной/десятичной/восьмиричной и двоичной системами осуществляется клавишами F5/F6/F7 и F8.
- Из овощей я больше всего люблю пельмени... © Соседский Мальчик
Аватара пользователя
Мikа
Потрогал лапой паяльник
Сообщения: 343
Зарегистрирован: Пн апр 01, 2013 15:13:40
Откуда: Москва

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение Мikа »

ARV, спасибо за совет. Насчет того, что лучше не мудрить и работать по возможности с однобайтными числами мне тоже в голову пришло :) Думаю стоит разобраться с дисассемблером в отладчике.

L.O.D, относительно перевода чисел в уме - я мел ввиду именно десятичные в двоичные. Это относится к моменту, когда в отладчике мы начинаем следить за какой-то переменной (в моём случае я следил за Kod_Znaka, Kod_Vklucheniya и SRData. Просто если там можно было бы отображать двоичные коды, то было бы проще понять, идёт все по плану или нет. Т.к. когда в десятичном виде мы видим, например 170 и 215, которые после загона в один байт дают, например 45837, я не могу понять, правильно ли прошла операция или нет :) (В этом примере я числа написал вылдуманные, не думаю, что 215 в старшем и 170 в младшем байте дадут именно 45837). Но пока я писал про это меня внезапно осенило, что там можно переключить на шестнадцатиричный вид отображения и там все будет видно сразу)) Кстати, что калькулятор можно переключать с помощью кнопок F я правда не знал, спасибо :))
Почему я здесь и задаю тупые вопросы?
Потому что хочу научиться.
L.O.D
Встал на лапы
Сообщения: 139
Зарегистрирован: Чт фев 11, 2016 18:35:37

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение L.O.D »

Мikа писал(а):внезапно осенило, что там можно переключить на шестнадцатиричный вид отображения
Дык!!! :)))
- Из овощей я больше всего люблю пельмени... © Соседский Мальчик
Аватара пользователя
Мikа
Потрогал лапой паяльник
Сообщения: 343
Зарегистрирован: Пн апр 01, 2013 15:13:40
Откуда: Москва

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение Мikа »

Здравствуйте, парни. Сейчас проверил программу, добавив в двухбайтную версию еще один байт в маску (0x80 00) и все работает. Ура!

Однако еще провел эксперимент относительно занимаемого программой места.

Вариант с однобайтными числами в функции вывода код в сдвиговый регистр занимает:

Program Memory Usage : 498 bytes 24,3 % Full
Data Memory Usage : 30 bytes 23,4 % Full

С двухбайтным числом занимает:

Program Memory Usage : 420 bytes 20,5 % Full
Data Memory Usage : 29 bytes 22,7 % Full

Как видно, второй вариант экономит 78 байт флеша и 1 байт оперативки. Для МК с 2К флеш и 120 б оперативки - очень даже неплохо.

Это на оптимизации -O1.

А вот на -Os

1 байтовые числа

Program Memory Usage : 486 bytes 23,7 % Full
Data Memory Usage : 30 bytes 23,4 % Full

2 байтове число.

Program Memory Usage : 414 bytes 20,2 % Full
Data Memory Usage : 29 bytes 22,7 % Full

В общем есть с чем поэкспериментировать. И у меня еще появился вопрос на засыпку:
Что означают эти варнинги?

Warning implicit declaration of function 'SR' [-Wimplicit-function-declaration] Gsxr-Main C:\Users\Михаил\OneDrive\! - Мои проекты\GsxR\Gsxr-Main\Gsxr-Main\main.c 66
Severity Code Description Project File Line
Warning conflicting types for 'SR' Main C:\Users\Mick\\Main\Main\Main\main.c 152


P.S. Вот 1 и 2 байтные функции:

Спойлер

Код: Выделить всё

void SR(void)
{
   SRData = Kod_Vklucheniya;
   SRData1 = Kod_Znaka;
   
   PORT_DATA &= (~(1<<PinReadData));         // Опускаем контрольные выводы в 0
   PORT_DATA &= (~(1<<PinShowData));

   for (i=0; i<8; i++)
   {
      if (SRData & 0x80)               // Выводим логический уровень вывода передачи данных в соответствие со значением старшего бита в Data
      {
         PORT_DATA |= (1<<PinData);
      }
      else
      {
         PORT_DATA &= (~(1<<PinData));
      }
      
      _delay_us(100);
      PORT_DATA |= (1<<PinReadData);      // Запись бита в сдвиговый регистр
      _delay_us(100);
      PORT_DATA &= (~(1<<PinReadData));
      
      SRData <<= 1;                     // Сдвиг битов влево, чтобы обновить выводимые данные.
      
   }
   
   
   
   for (i=0; i<8; i++)
   {
      if (SRData1 & 0x80)               // Выводим логический уровень вывода передачи данных в соответствие со значением старшего бита в Data
      {
         PORT_DATA |= (1<<PinData);
      }
      else
      {
         PORT_DATA &= (~(1<<PinData));
      }
      
      _delay_us(100);
      PORT_DATA |= (1<<PinReadData);      // Запись бита в сдвиговый регистр
      _delay_us(100);
      PORT_DATA &= (~(1<<PinReadData));
      
      SRData1 <<= 1;                     // Сдвиг битов влево, чтобы обновить выводимые данные.
      
   }

   PORT_DATA |= (1<<PinShowData);
   _delay_us(100);
   PORT_DATA &= (~(1<<PinShowData));   // Запись бита в сдвиговый регистр
}


Спойлер

Код: Выделить всё

void SR(void)
   {
   SRData = (Kod_Vklucheniya << 8) + Kod_Znaka;
      
   PORT_DATA &= (~(1<<PinReadData));         // Опускаем контрольные выводы в 0
   PORT_DATA &= (~(1<<PinShowData));
   
   for (i=0; i<16; i++)
   {
         if (SRData & 0x8000)               // Выводим логический уровень вывода передачи данных в соответствие со значением старшего бита в Data
         {
         PORT_DATA |= (1<<PinData);
         }
         else
         {
         PORT_DATA &= (~(1<<PinData));
         }
   
         _delay_us(100);
         PORT_DATA |= (1<<PinReadData);      // Запись бита в сдвиговый регистр
         _delay_us(100);
         PORT_DATA &= (~(1<<PinReadData));
   
         SRData <<= 1;                     // Сдвиг битов влево, чтобы обновить выводимые данные.
   
   }
   PORT_DATA |= (1<<PinShowData);
   _delay_us(100);
   PORT_DATA &= (~(1<<PinShowData));   // Запись бита в сдвиговый регистр
   }



UPD: Щас заметил, что если к вызову функции добавить void, то варнинги пропадают, но функция не работает.
То есть было Encoder(); - функция работала и были варнинги.
Стало void Encoder(void); - функции нет и варнингов нет))

И судя по размеру выходной прошивки - функцию вообще нафиг выкидывает.
Почему я здесь и задаю тупые вопросы?
Потому что хочу научиться.
Аватара пользователя
ARV
Ум, честь и совесть. И скромность.
Сообщения: 18544
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск
Контактная информация:

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение ARV »

с вашим подходом все может быть... я бы делал так

Код: Выделить всё

static void send_byte(uint8_t data){
   for(uint8_t mask=0x80; mask; mask >>= 1){
      if(data & mask)
         PORT_DATA |= _BV(PinData);
      else
         PORT_DATA &= ~_BV(PinData);
      _delay_us(1);
      PORT_DATA |= _BV(PinReadData);
      _delay_us(1);
      PORT_DATA &= ~_BV(PinReadData);
   }
}

static void send_word(uint16_t data){
   send_byte(data); // младший байт
   send_data(data >> 8); // старший байт
}


задержка в принципе вообще не нужна, т.к. регистр 595-й способен работать на 20 МГц запросто, а у вас МК с куда более низкой частотой, причем код изменения бита на Си явно будет иметь длительность в несколько тактов, но если уж очень хочется, хватит и задержки в 1 мкс

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

Мой уютный бложик... заходите!
L.O.D
Встал на лапы
Сообщения: 139
Зарегистрирован: Чт фев 11, 2016 18:35:37

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение L.O.D »

Мikа писал(а):Щас заметил, что если к вызову функции добавить void, то варнинги пропадают, но функция не работает.
Всего проекта не вижу (он вообще выложен где-то?) поэтому могу только предположить:
Если тип функции не указан вовсе, то предполагается int. Если в том месте, где она вызывается, она будет объявлена с типом, отличным от int, то это уже будет другая функция (они объявлены глобальными - сообщение о конфликте типов), а первая останется без вызовов и потому будет удалена из объектного кода цели.
ARV писал(а):я бы делал так
Или по такому принципу:

Код: Выделить всё

void SRSend(uint16_t data){
uint16_t mask=0x8000;

        do {

           DataPort.bData  = ((data & mask) > 0);
           DataPort.bStrob = 1;
           // asm("nop");
           DataPort.bStrob = 0;

        } while (mask >>= 1);
}
- Из овощей я больше всего люблю пельмени... © Соседский Мальчик
Аватара пользователя
Мikа
Потрогал лапой паяльник
Сообщения: 343
Зарегистрирован: Пн апр 01, 2013 15:13:40
Откуда: Москва

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение Мikа »

Я на уровне "дно" нахожусь, т.к. не понимаю 2\3 вашего кода. Но я только учусь Си, даже книжку читаю :)

Весь проект я прилагаю в этом сообщении.

На данный момент программа:

Опрашивает энкодер => меняет значение Koefficient.
Разделяет Koefficient на 3 отдельных числа
Выводит разделенный Koefficient на трехразрядный индикатор.

Обратите внимание, что на данный момент программ пишется (вот прям щас сижу и пишу), поэтому она совсем "не причесана". Но я уверен, что даже если б я её "причесал", выглядела бы она не очень. Поэтому буду благодарен за любую критику и советы, спасибо, что уделяете время.
Вложения
Main.zip
(36.09 КБ) 185 скачиваний
Почему я здесь и задаю тупые вопросы?
Потому что хочу научиться.
L.O.D
Встал на лапы
Сообщения: 139
Зарегистрирован: Чт фев 11, 2016 18:35:37

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение L.O.D »

Мikа писал(а):Я на уровне "дно" нахожусь, ...
Значит впереди много интересного. :)
Мikа писал(а):Весь проект я прилагаю в этом сообщении.
Ну, так и есть - сначала встречается вызов функции (строка 72) и только потом - её объявление и реализация (строка 195). То есть, в строке 72 рождается неявное объявление функции "int Encoder();", которое позже вступает в конфликт с явным объявлением "void Encoder();".
Для начала можно дать явное объявление функции до её первого вызова. А вообще, полезно выделить функции работы с енкодером (или иным периф.устройством) в отдельный .c-модуль со своим .h-файлом и включить .h-файл в main.c.
Если позже эти функции понадобятся в другом проекте, их не придется выдирать из main.c.

P.S. Кстати - по поводу опроса енкодера...
- Из овощей я больше всего люблю пельмени... © Соседский Мальчик
Аватара пользователя
COKPOWEHEU
Говорящий с текстолитом
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение COKPOWEHEU »

Форматирование на удивление неплохое. Местами нелогичные отступы, слишком разреженный, на мой вкус, код, названия переменных транслитом и прочие мелкие недочеты. Но гораздо лучше, чем у многих на форумах.
Вот этот момент стоит переделать. НИКОГДА не используйте goto в коде на языке высокого уровня. Пара исключений, конечно, есть, но сначала научитесь обходиться без эого.
Sotni:
if (KoefficientSEC >= 100)
{
KoefficientSEC = KoefficientSEC - 100;
Sotni++;
goto Sotni;
}

Здесь стоит использовать цикл

Код: Выделить всё

while( koefficientSec >= 100 ){ //Один из стандартов рекомендует именовать переменные со строчной буквы, константы капсом и т.д.
  koefficientSec -= 100; //Оператор -= и записывается короче, и ошибиться труднее, да и для понимания проще
  thousands++; //Именование переменных транслитом - зло.
}

Альтернативный вариант - использовать деление. Что выгоднее по скорости и объему кода не знаю

Код: Выделить всё

thousands = koefficientSec / 100;
tens = (koefficientSec % 100) / 10;
units = koefficientSec % 10;

Функцию SR однозначно стоит переписать. Как минимум, называть метку внутри нее одинаково с самой функцией - плохая идея. Не говоря о том, что goto - всегда плохая идея. Ну и описания назначения этой функции в коде нет, а должно бы. Ну и да, L.O.D правильно пишет - сначала объявляете функцию и только потом используете.
Аватара пользователя
Мikа
Потрогал лапой паяльник
Сообщения: 343
Зарегистрирован: Пн апр 01, 2013 15:13:40
Откуда: Москва

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение Мikа »

L.O.D.

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

Относительно варнингов я так понял, что можно перенести функции наверх, до основного цикла и все будет нормально.

Для начала можно дать явное объявление функции до её первого вызова. А вообще, полезно выделить функции работы с енкодером (или иным периф.устройством) в отдельный .c-модуль со своим .h-файлом и включить .h-файл в main.c.


Вот это МОЯ МЕЧТА! Но пока у меня ничего не получилось. Не дочитался еще до этого, а какой-то вменяемой статьи "как вынести кусок кода в отдельный файл" я не встретил. Может быть не правильно искал, т.к. я упорно называл это библиотекой :)
В ассемблере я просто использовал include и проблем не было, но Си требует какие-то сложности, вроде .h файла, которые мне пока еще неведомы.

COKPOWEHEU

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

Относительно коротких записей вроде a -=a; - это я тоже в курсе, использую и так и так.
Про общепринятые стандарты называния переменных Венгерская нотация и что-то там еще - я тоже читал. Ну вроде называть с маленькой буквы, писать без пробелов и каждое новое слово с большой буквы и похожие варианты.

Вроде:
variableBlack
VariableBlack

На транслите я их начал называть когда поймал себя на том, что в какой-то момент я сам начинаю путаться в красивых названиях на английском языке. По английски я свободно говорю, проблема не в нем, а втом, что я начинаю просто забывать путаться в том, что и как я назвал. Тот же код обработки индикатора. Там можно использовать слова:
Segment
Digit
Dot.
В какой-то момент я могу затупить и не понимать, что я назвал сегментом. То ли часть символа, то ли одну цифру целиком :)

P.S. Если подскажете как в отдельный файл вынести функцию и как ее подключить к проекту (или ссылку на нормальную статью), будет прям очень круто :)

UPD:

Кстати, L.O.D., в вашем комментарии относительно энкодера вы делаете массив из беззнаковых переменных unsigned char, а в самом массиве числа со знаком. Тут какая-то фишка запрятана?

COKPOWEHEU, ваш вариант с заменой if и goto в месте разбиения трехзначного числа на однозначные еще и 8 байт экономт :)

А этот вариант

Код: Выделить всё

thousands = koefficientSec / 100;
tens = (koefficientSec % 100) / 10;
units = koefficientSec % 10;


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

Знаете, должен вам признаться, сегодняшнее общение на форуме мне новые горизонты открывает :)

Еще UPD: когда я убрал goto оттуда, где идет вывод в сдвиговый регистр (goto SR) и заменил это на while, программа увеличилась на 6 байт. И тут я вспомнил, что преподаватель с сайта ITVDN.com рассуждал о goto и очень яростно утверждал, что надо читать какие-то две книги, чтобы понимать, как его использовать и т.п. Мне вообще казалось, что он своему коллеге сейчас в нос даст за то, что тот не аргументируя назвал goto злом :D Мне просто как-то нужно было на работе допилить ПКшную программу на C# и я по быстрому вникал в этот язык :)
Почему я здесь и задаю тупые вопросы?
Потому что хочу научиться.
mrFox
Нашел транзистор. Понюхал.
Сообщения: 190
Зарегистрирован: Пт сен 21, 2007 17:53:23
Откуда: Зарайск

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение mrFox »

L.O.D писал(а):Или по такому принципу:
Спойлер

Код: Выделить всё

void SRSend(uint16_t data){
uint16_t mask=0x8000;
        do {
           DataPort.bData  = ((data & mask) > 0);
           DataPort.bStrob = 1;
           // asm("nop");
           DataPort.bStrob = 0;
        } while (mask >>= 1);
}


Компилятор может не поддерживать битовые поля
так надежнее:
Спойлер

Код: Выделить всё

void SerialSendByte(uint8_t data) {
uint8_t mask=0x80;
  do {
    if( data & mask ) PORT_DATA |= _BV(PinData);
    else                PORT_DATA &= ~_BV(PinData);
    PORT_DATA |= _BV(PinReadData);
    PORT_DATA &= ~_BV(PinReadData);
  } while (mask >>= 1);
}
void SerialSendWord(uint16_t data){
 SerialSendByte(data >> 8); // старший байт
 SerialSendByte(data); // младший байт
}


Mika писал(а):В ассемблере я просто использовал include и проблем не было, но Си требует какие-то сложности, вроде .h файла, которые мне пока еще неведомы.

так и в С просто подключаешь #include и все, надо только не забыть описания функций туда перенести
единственно надо озаботится охраной от повторного включения (нормальные ИДЕ правла сами это делают)

Код: Выделить всё

#ifndef имя_файла
#defina имя_файла
// здесь описания функции
#endif


по поводу опитимизации - включить вывод листинга и смотреть на код
L.O.D
Встал на лапы
Сообщения: 139
Зарегистрирован: Чт фев 11, 2016 18:35:37

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение L.O.D »

Мikа писал(а):По поводу функции опроса энкодера - я опять же не понял, как сработает
Оператор switch и вложенные в case'ы if'ы ставят в соответствие двум значениям (Encoder_NewState и Encoder_State) значение приращения к Encoder_Summ. Массив делает то же самое.
Мikа писал(а):Относительно варнингов я так понял, что можно перенести функции наверх, до основного цикла и все будет нормально.
Это тоже сработает, но избегать строгости - это плохая привычка, поэтому лучше привыкать к порядку.
Мikа писал(а):Но пока у меня ничего не получилось. ... Си требует какие-то сложности, вроде .h файла, которые мне пока еще неведомы.
Мikа писал(а):Если подскажете как в отдельный файл вынести функцию и как ее подключить к проекту (или ссылку на нормальную статью), будет прям очень круто
1. Добавить в проект новый .c-файл, написать в его начале соответствующий #include "..."
2. Перенести в него код функций
3. Создать тот самый заголовочный файл и перенести в него объявления соответствующих переменных и функций.
Например:
Спойлер

Код: Выделить всё

"SR595.c"----------
#include  "SR595.h"

// объявления статических переменных - их не будет видно за пределами модуля SR595
void SR(void)
{
    ...
}

void Encoder(void)
{
    ...
}
----------
"SR595.h"----------
#ifndef  __SR595_H__
#define  __SR595_H__

unsigned char Encoder_State;
signed   char Encoder_Summ;
unsigned char Encoder_NewState;
uint8_t  EncTransIncs[4][4] = {
     0, +1, -1,  0,
    -1,  0,  0, +1,
    +1,  0,  0, -1,
     0, -1, +1,  0
  };
void SR(void);
void Encoder(void);

#endif //__SR595_H__
----------
Мikа писал(а):в вашем комментарии относительно энкодера вы делаете массив из беззнаковых переменных unsigned char, а в самом массиве числа со знаком. Тут какая-то фишка запрятана?
Нет, просто лень было писать с нуля - редактировал тот кусок кода, что был выше, вот и пропустил.
mrFox писал(а):Компилятор может не поддерживать битовые поля
Во-первых - это часть стандарта Си, если компилятор его не поддерживает, то выходит это компилятор с другого языка. Во-вторых - написано же было: "по такому принципу", - это ни о чем не говорит? Отличие того кода совсем в другом. :dont_know:
- Из овощей я больше всего люблю пельмени... © Соседский Мальчик
Аватара пользователя
COKPOWEHEU
Говорящий с текстолитом
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение COKPOWEHEU »

Мikа писал(а):Знаете, должен вам признаться, сегодняшнее общение на форуме мне новые горизонты открывает :)
Когда человек приходит на форум и не грубит, пишет по-человечески (без дурацких орфографических и прочих ошибок хотя бы), по-человечески же форматирует код (вот честно, вы едва ли не первый из новичков, кто этим не пренебрегает), тогда и ему помогут.
На счет разделения на несколько файлов L.O.D. недостаточно подробно расписал.
Вот заготовка:
Спойлерmain.c

Код: Выделить всё

#include "ext_file.h" //эта директива копирует все содержимое файла ext_file.h вместо себя
int main(){
  ext_func();
  return 0;
}

ext_file.h

Код: Выделить всё

#ifndef __EXT_FILE_H__ //имя макроопределения может быть любым. Для простоты можно использовать имя файла
#define __EXT_FILE_H__ //главное чтобы оно было одинаково в этой и предыдущей строках
void ext_func(); //прототип функции. Без него внешний код не будет знать что такая функция вообще существует
#endif

ext_file.c

Код: Выделить всё

#include <avr/io.h>
#include "ext_file.h" //подключаем файл с прототипами функций
void ext_func(){
  PORTB = 0x01;
}
После этого все *.c файлы надо подключить к проекту в среде разработки. Либо в makefile. Впрочем, наверное, вам лазить туда рановато. Вот добавить файл к проекту надо.
Альтернативный вариант - обойтись без *.c файла, разместив код в *.h - файле. Проблема в том, что такой файл нельзя подключать в несколько *.c-файлов. Впрочем, можете поэкспериментировать.
Относительно варнингов
Их надо исправлять. Исключения можно по пальцам пересчитать и вы на них едва ли наткнетесь.
преподаватель с сайта ITVDN.com рассуждал о goto и очень яростно утверждал, что надо читать какие-то две книги, чтобы понимать, как его использовать
Случаи где goto к месту, то есть улучшает читаемость, быстродействие или объем, существуют, но их всего несколько. Единственный случай, который мне вспоминается - выход из нескольких вложенных циклов. Однако лично мне его использовать не пришлось ни разу.
Учиться программировать на ЯВУ надо строго без использования этого оператора. Когда наберетесь опыта и наткнетесь на ситуацию, где его использование могло бы быть выгодно - рассмотрите вариант без goto (где-то читал, что доказали, что это возможно всегда, но не всегда оптимально) и сравните с тем. Вот если обходной вариант уж слишком запутан и это никак не исправить, применяйте. Только задокументируйте, что это действительно оптимальный вариант и что альтернативы оказались хуже.
Про общепринятые стандарты называния переменных ... писать без пробелов
Ну, без пробелов - особенности компиляторов. Рекомендации по стилю это всего лишь рекомендации, стандартов куча и все разные.
Аватара пользователя
Мikа
Потрогал лапой паяльник
Сообщения: 343
Зарегистрирован: Пн апр 01, 2013 15:13:40
Откуда: Москва

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение Мikа »

Так, мечты сбываются, спасибо! Сейчас попробую вынести функции в отдельный файл.

Тут был вопрос, который я уже решил. Из него вынес следующее:

Если вы в симуляторе смотрите Си программу и вас интересует заход программы в обработчик прерывания - следите не за тем, куда программа шагает а за переменными в обработчике.
Как вы вчера говорили и как и было в прошлый раз - симулятор не удосуживается прыгать в обработчик, но действие в нём выполняется :)

Большой UPD относительно выноса кода в отдельные файлы.

Я напишу по порядку что делал.

1. Создаю файлы EncoderExt.c и EncoderExt.h(Include File), файлы лежат в папке проекта вместе с main.c

2. Переношу в них код, оформляя как вы указали (см приложенные файлы).

3. Добавляю файлы в проект (см. скриншот)

4. Пишу в main.c

Код: Выделить всё

#include "EncoderExt.h" // В самом верху, где подключаются остальные файлы.


5. Компилирую и получаю два варианта развития событий:

5.1 Если оставить все, как описано выше, получаются следующие ошибки:

Severity Code Description Project File Line
Error 'Koefficient' undeclared (first use in this function)
Error 'Encoder_NewState' undeclared (first use in this function)
Error 'Encoder_State' undeclared (first use in this function)
Error 'Encoder_Summ' undeclared (first use in this function)

Если в Encoder.h заменить строки

Код: Выделить всё

#ifndef EncoderMacro //имя макроопределения может быть любым. Для простоты можно использовать имя файла
#define EncoderMacro //главное чтобы оно было одинаково в этой и предыдущей строках


На такие

Код: Выделить всё

#ifndef Encoder //имя макроопределения может быть любым. Для простоты можно использовать имя файла
#define Encoder //главное чтобы оно было одинаково в этой и предыдущей строках


То получаются такие ошибки:

Severity Code Description Project File Line
Error expected identifier or '(' before ')' token
Error expected identifier or '(' before ')' token

Двойной клик по первой приводит сюда:

Код: Выделить всё

EncoderExt.c

#include <avr/io.h>
#include "EncoderExt.h" //подключаем файл с прототипами функций

void Encoder(void) // <--- Курсор становится в эту строку между ( и void


Двойной клик по второй ошибке приводит сюда:

Код: Выделить всё

EncoderExt.h

#ifndef Encoder //имя макроопределения может быть любым. Для простоты можно использовать имя файла
#define Encoder //главное чтобы оно было одинаково в этой и предыдущей строках
void Encoder(); Курсор становится в этой строке между скобками


Судя по весему в первом варианте не правильно называю макроопределение и программа вообще не видит ничего.
А во втором случае где-то я допускаю небольшую ошибку. Где-то я не поставил какой-то return?
Вложения
Include.png
(3.93 КБ) 571 скачивание
EncoderExt.h
(295 байт) 534 скачивания
EncoderExt.c
(1.22 КБ) 545 скачиваний
Почему я здесь и задаю тупые вопросы?
Потому что хочу научиться.
Аватара пользователя
COKPOWEHEU
Говорящий с текстолитом
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение COKPOWEHEU »

Если вы в симуляторе смотрите Си программу и вас интересует заход программы в обработчик прерывания - следите не за тем, куда программа шагает а за переменными в обработчике.
Как вы вчера говорили и как и было в прошлый раз - симулятор не удосуживается прыгать в обработчик, но действие в нём выполняется :)
Отключите оптимизацию (флаг -O0). У меня в AVRStudio 4.18, да и протеусе нормально заходил в обработчик прерывания.
3. Добавляю файлы в проект (см. скриншот)
Насчет добавления в раздел library не уверен, может так и надо, хотя выглядит странно. Добавлять туда *.h-файл не нужно, он там будет только мешать.
#ifndef Encoder
#define Encoder
void Encoder(); Курсор становится в этой строке между скобками
Опять ходите по тем же граблям. Определяете одно и то же имя несколькими разными способами. В примере я не зря так над ним извращался - добавлял подчеркивания и капс - чтобы минимизировать шанс что такой набор символов случайно попадется где-то еще.
Кстати, мои комментарии из шаблона могли бы и удалить :-) они особого смысла не несут и написаны только для вас, остальным действия этих команд очевидны.
5.1 Если оставить все, как описано выше, получаются следующие ошибки:
Эти переменные не объявлены в файле EncoderExt.c. Каждый *.c файл компилируется независимо от остальных, поэтому используемые функции и переменные должны быть объявлены хотя бы только прототипами. В вашем случае стоит сделать так:

Код: Выделить всё

signed char encoder_sum = 0; //или как оно у вас объявлено
void Encoder(void){
   char encoder_newstate = PINB & 0x03; //эту переменную можно сделать локальной
   static char encoder_state = encoder_newstate; //модификатор static означает, что после завершения функции переменная останется в памяти и не изменится до следующего вызова
   encoder_newstate = PINB & 0x3;
   if(encoder_state != encoder_newstate)

А в заголовочном файле (*.h) надо добавить объявление переменной encoder_sum:

Код: Выделить всё

extern signed char encoder_sum;

Что с koefficient'ом решайте сами, нужен ли он и какую роль исполняет.
Но, вообще-то, использование разделяемых между несколькими файлами переменных довольно опасная идея. Лучше оформить доступ через функции. Не объявляем encoder_sum в заголовочном файле, зато объявляем следующие прототипы:

Код: Выделить всё

void set_encoder_sum(signed char sum);
signed char get_encoder_sum();

А в файле исходного кода (*.c) описываем реализацию

Код: Выделить всё

void set_encoder_sum(signed char sum){encoder_sum = sum;}
signed char get_encoder_sum(){return encoder_sum;}

Это несколько ухудшит производительность, зато несколько безопаснее. Труднее изменить эту переменную случайно, а если это не нужно, функцию set_encoder_sum() можно вообще убрать. Кстати, прошу прощения за функции-в-одну-строчку, но если они выполняют одно действие, читаемость не ухудшается.
Да, чуть не забыл. Если отлаживаете платформо-независимве особенности, на ПК-версии это обычно проще. Ну и стоит поучить сам язык Си (да и ассемблер). Учебник Кернигана и Ричи - классический старт. Большинство особенностей Си у них описано.
L.O.D
Встал на лапы
Сообщения: 139
Зарегистрирован: Чт фев 11, 2016 18:35:37

Re: Помогите понять, что творит оптимизатор Си AVR

Сообщение L.O.D »

COKPOWEHEU писал(а):На счет разделения на несколько файлов L.O.D. недостаточно подробно расписал.
Прошу прощения, но мое описание совпадает с Вашим, с точностью до имен переменных или файлов. :)))

Мikа писал(а):получаются следующие ошибки:
...
Error 'Koefficient' undeclared (first use in this function)
Error 'Encoder_NewState' undeclared (first use in this function)
Error 'Encoder_State' undeclared (first use in this function)
Error 'Encoder_Summ' undeclared (first use in this function)
Undeclared - необъявленные. А ведь я писал ранее:
L.O.D писал(а):1. Добавить в проект новый .c-файл, написать в его начале соответствующий #include "..."
...
3. Создать тот самый заголовочный файл и перенести в него объявления соответствующих переменных и функций.
Например:
Спойлер

Код: Выделить всё

"SR595.c"----------
#include  "SR595.h"

// объявления статических переменных - их не будет видно за пределами модуля SR595
void SR(void)
{
    ...
}

void Encoder(void)
{
    ...
}
----------
"SR595.h"----------
#ifndef  __SR595_H__
#define  __SR595_H__

unsigned char Encoder_State;
signed   char Encoder_Summ;
unsigned char Encoder_NewState;
int8_t  EncTransIncs[4][4] = {
     0, +1, -1,  0,
    -1,  0,  0, +1,
    +1,  0,  0, -1,
     0, -1, +1,  0
  };
void SR(void);
void Encoder(void);

#endif //__SR595_H__
----------

Правда, в дальнейшем придется несколько нарастить сложность таких объявлений, но об этом позже - от простого к сложному.
- Из овощей я больше всего люблю пельмени... © Соседский Мальчик
Ответить

Вернуться в «AVR»