Страница 1 из 4

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

Добавлено: Пт апр 01, 2016 00:21:18
М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 вечера. И на этот пост около часа. Хочется окупить потерянное время приобретенными знаниями. Заранее большое спасибо!

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

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

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

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

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

SRData1 <<= 1;// Сдвиг битов влево, ...

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

Добавлено: Пт апр 01, 2016 08:54:54
Мikа
Здравствуйте! Большое спасибо за совет. Мне почему-то кажется, что дело как раз в этом. О маске я совсем не подумал. Вечером, как домой приду, проверю эту версию на устройстве, тк гонять в симуляторе будет не очень продуктивно с точки зрения времени. Я не могу моментально в голове переводить числа из десятичных в двоичные и т.п., а делать это с помощью калькулятора Windows будет та еще задача :) Относительно кода - это правда честная копипаста и, для верности я и файлы приложил :)
Насчет переменной SRData1 - она появилась в процессе разбиения 16ти разового цикла на 2 8ми разовых. Здесь я, видимо, скопировал не из той программы (не из 16 битной) :)

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

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

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

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

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

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

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

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

Мikа писал(а):... а делать это с помощью калькулятора Windows будет та еще задача
Ничего подобного - переключение между шестнадцатиричной/десятичной/восьмиричной и двоичной системами осуществляется клавишами F5/F6/F7 и F8.

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

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

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

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

Добавлено: Пт апр 01, 2016 11:46:26
L.O.D
Мikа писал(а):внезапно осенило, что там можно переключить на шестнадцатиричный вид отображения
Дык!!! :)))

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

Добавлено: Сб апр 02, 2016 09:56:17
М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); - функции нет и варнингов нет))

И судя по размеру выходной прошивки - функцию вообще нафиг выкидывает.

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

Добавлено: Сб апр 02, 2016 10:16:25
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 может быть надо в обратном порядке байты выводить - это вам виднее...

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

Добавлено: Сб апр 02, 2016 10:56:24
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);
}

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

Добавлено: Сб апр 02, 2016 12:22:01
Мikа
Я на уровне "дно" нахожусь, т.к. не понимаю 2\3 вашего кода. Но я только учусь Си, даже книжку читаю :)

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

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

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

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

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

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

P.S. Кстати - по поводу опроса енкодера...

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

Добавлено: Сб апр 02, 2016 13:17:08
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 правильно пишет - сначала объявляете функцию и только потом используете.

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

Добавлено: Сб апр 02, 2016 13:52:01
М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# и я по быстрому вникал в этот язык :)

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

Добавлено: Сб апр 02, 2016 16:09:09
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
по поводу опитимизации - включить вывод листинга и смотреть на код

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

Добавлено: Сб апр 02, 2016 16:41:27
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:

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

Добавлено: Сб апр 02, 2016 17:28:58
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 (где-то читал, что доказали, что это возможно всегда, но не всегда оптимально) и сравните с тем. Вот если обходной вариант уж слишком запутан и это никак не исправить, применяйте. Только задокументируйте, что это действительно оптимальный вариант и что альтернативы оказались хуже.
Про общепринятые стандарты называния переменных ... писать без пробелов
Ну, без пробелов - особенности компиляторов. Рекомендации по стилю это всего лишь рекомендации, стандартов куча и все разные.

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

Добавлено: Сб апр 02, 2016 17:51:41
М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?

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

Добавлено: Сб апр 02, 2016 20:13:00
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() можно вообще убрать. Кстати, прошу прощения за функции-в-одну-строчку, но если они выполняют одно действие, читаемость не ухудшается.
Да, чуть не забыл. Если отлаживаете платформо-независимве особенности, на ПК-версии это обычно проще. Ну и стоит поучить сам язык Си (да и ассемблер). Учебник Кернигана и Ричи - классический старт. Большинство особенностей Си у них описано.

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

Добавлено: Вс апр 03, 2016 03:33:18
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__
----------------------------------------------------------------
Правда, в дальнейшем придется несколько нарастить сложность таких объявлений, но об этом позже - от простого к сложному.