AVR. Учебный Курс. Программирование на Си. Часть 2.

Вторая часть марлезонского балета, точнее введения в программирование на Си под микроконтроллеры.

Структура программы
Ну вот, МК у тебя уже кое что сделал. И хоть внешне ничего не видно, но внутри у него произошли изменения — передатчик стал готов к работе! Пора выдавать очередную порцию информации. Касательно того как вообще пишется программа для МК, не обязательно на Си. На чем угодно.

Компоновка любой программы такая:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Фунцкции 
 {
 }
 
Прерывания
 {
 }
 
main()
{
инициализация;
 
 Главный БЕСКОНЕЧНЫЙ цикл.
   {
    собственно программа 
   }
}


Инициализацию, по крайней мере UART мы уже сделали. Осталось добавить бесконечный цикл. Я люблю делать так:

1
2
3
4
while(1)
{
i++;
}

Просто и наглядно. while вначале проверяет условие в скобках, если оно не 0 то выполняет кодовый блок, потом возвращается обратно и проверяет снова. Ну а раз там по дефолту 1, то цикл будет бесконечным. Правда мне тут подсказывают, что по феншую кошерней будет сделать for( ; ; ), мол так корректней. Не буду спорить, возможно они правы. Однако у меня while(1) никогда варнингов не давала и выглядит, ИМХО, логичней .

i++ это всего лишь увеличение i на единицу. Должны же мы в цикле что нибудь сделать. Не пустым же оставлять, да и переменная у нас была сделана по приколу.

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

Открываешь ты листинг, думаешь найти там новые ништяки… А ништяков то и нету!(с)

+0000003E: CFFF RJMP PC-0x0000 Relative jump

Вот это, наш while(1) и есть — глухое зацикливание.

Замечательно, а где же наша переменная i??? А это назвается восстание машин. Компилятор решил что он умней тебя и взял ее и похерил. В самом деле, решил он, нафига эта переменная вообще тут тикает? Полезной работы то от нее ноль! Нигде не применяется, нигде не сравнивается, нигде не используется. Только тикает тут себе в уголочке. Дай ка я ее грохну. И грохнул. Но мы то хотим чтобы тикало! Дать компилятору по башке и сказать кто тут главный можно. Для этого есть директива volatile — переменные с этой директивой оптимизатор не имеет права трогать.
Берем и приписываем ее нашей i, там где она определяется.

Получается:

1
volatile unsigned char i;

+00000043: 8189 LDD R24,Y+1 Load indirect with displacement
+00000044: 5F8F SUBI R24,0xFF Subtract immediate
+00000045: 8389 STD Y+1,R24 Store indirect with displacement
+00000046: CFFC RJMP PC-0x0003 Relative jump

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

Вот только что такое Y? Даташит нам скажет, что это регистровая пара R29:R28 и используется она для косвенной адресации. А откуда эта регистровая пара знает что там переменная «i»??? Начинаем штудировать код и в самом начале, где SREG обнуляется, находим:

+0000002C: E5CF LDI R28,0x5F Load immediate
+0000002D: E0D4 LDI R29,0x04 Load immediate

Теперь все понятно. Компилятор в самом начале, хитрый, занычил адрес Y, а потом заюзал его как только потребовалось.

Лепота!

Но пора бы добавить еще фишек. А конкретно диодиками мы там собирались мигать в фоновой программе. Оке, добавим в инициализацию порты диодов. На Pinboard все уже равзедено, поэтому там только перемычки поставить, что мы уже сделали.

Это будет PD4 и PD5. Единичка — горит, ноль не горит.

За настройку порта D отвечют регистры PORTD и DDRD — DDR это направление работы, а PORT состояние. Про порты и как их настраивать я уже писал ранее.

Для зажигания LED1 нам надо выставить бит 4 в DDRD в единицу и дальше рулить битом 4 в регистре PORTD, для LED2 то же самое, только с битом 5

Так что добавляем после инициализации UART еще и инициализацию нашего порта

1
2
3
4
5
#define LED1 4
#define LED2 5
#define LED_PORT PORTD
#define LED_DDR DDRD
LED_DDR = 1<<LED1;

Можно было конечно просто написать DDRD = 0b00010000, но как я уже говори. НИКАКИХ МАГИЧЕСКИХ ЧИСЕЛ! Код должен быть понятным.

А вообще, все эти дефайны вообще по хорошему надо собрать в одну кучу и вынести в отдельный файл, подключив через include.
Захочешь все переделать — переделаешь только этот файл, а не будешь ковырять весь исходник. После, когда проект еще немного усложнится, мы это обязательно сделаем.

А наш цикл добавим команды установки и сброса бита в порту. Именно им будет определятся уровень напряжения на выходе.

1
2
3
4
5
6
while(1)
 {
 i++;
 LED_PORT=0<<LED1;
 LED_PORT=1<<LED1; 
 }

Во как! Если будешь гонять в отладчике, то увидишь как в I/O View будет менятся бит PORTD.4

Если заглянешь в дизасм, то увидишь как там хитро компилятор выкрутился — он при выполнении команды LED_PORT=1<<LED1;
Еще до входа в цикл загружает в регистр заранее число 0х10, а потом в цикле просто перекидывает его из регистра в порт. А ноль берет из R1 — R1 у нас по дефолту 0 всегда, это не закон, но прихоть компилятора.

32: LED_PORT=1<<LED1;
+00000045: E190 LDI R25,0x10 Load immediate

30: i++;
+00000046: 8189 LDD R24,Y+1 Load indirect with displacement
+00000047: 5F8F SUBI R24,0xFF Subtract immediate
+00000048: 8389 STD Y+1,R24 Store indirect with displacement

31: LED_PORT=0<<LED1;
+00000049: BA12 OUT 0x12,R1 Out to I/O location

32: LED_PORT=1<<LED1;
+0000004A: BB92 OUT 0x12,R25 Out to I/O location
+0000004B: CFFA RJMP PC-0x0005 Relative jump
32: LED_PORT=1<<LED1;

Прога получается целиком такой:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <avr/io.h>
int main(void)
{
volatile unsigned char i;
 
#define XTAL 8000000L
#define baudrate 9600L
#define bauddivider (XTAL/(16*baudrate)-1)
#define HI(x) ((x)>>8)
#define LO(x) ((x)& 0xFF)
 
UBRRL = LO(bauddivider);
UBRRH = HI(bauddivider);
UCSRA = 0;
UCSRB = 1<<RXEN|1<<TXEN|1<<RXCIE|0<<TXCIE;
UCSRC = 1<<URSEL|1<<UCSZ0|1<<UCSZ1;
 
 
#define LED1 4
#define LED2 5
#define LED_PORT PORTD
#define LED_DDR DDRD
 
LED_DDR = 1<<LED1;
 
while(1)
{
i++;
LED_PORT=0<<LED1;
LED_PORT=1<<LED1; 
}
 
return 0;
}

Пора пихнуть ее в реальный контроллер и посмотреть что будет.

Тыкай в квадратную кнопку Reset и пока горит LED2 тут же запускай AVR PROG. Он должен определить бут и сказать, что плата есть (подробная инструкция в картинках как заливать прошивку через бутлоадер PinBoard есть в четвертой части документации из рассылки).

Заливай получившийся hex файл.

Жми RESET на плате и наблюдай что происходит.

Те же у кого моей демоплаты нет — выкручивайтесь как хотите :) Сразу могу подсказать, что вам потребуется программатор. Например, такой или такой.

На PinBoard после сброса картина будет такой:
Вначале загорится LED2 — это работает BootLoader. Через пять секунд она погаснет, но загорится наш LED1.

Вот только какого черта он горит? Он ведь должен вроде бы мигать! Диод мигать то будет, но вот уж больно быстро он будет это делать.
Если сейчас тыкнуться осцилом в ножку PD4 то увидишь такую картину:

Узкий провал это интервал между

1
2
LED_PORT=0<<LED1;
LED_PORT=1<<LED1;

Широкий — весь остальной цикл. Нетрудно увидеть разницу исполнения кода по времени. =)

Но нам то надо мигание! Надо бы замедлить. Очевидно что между включением и выключением надо воткнуть задержку.

Пока я не буду тебя грузить программированием задержек. Поэтому подключай хидер библиотеки delay.h Можешь поискать ее в потрохах WinAVR

1
#include <avr/delay.h>

В самое начало засунь, в компанию к уже имеющемуся.

Добавил? Сразу же скомпиль. Опа, а там два варнинга.
Варнинги это хуже ошибок! Забьешь на них — замучаешься отлаживать!

Смотрим что там есть:
c:/winavr-20090313/lib/gcc/../../avr/include/avr/delay.h:36:2: warning: #warning «This file has been moved to
Ну это фигня. Компилятор нас предупреждает, что разработчики WinAVR поместили Delay.h в другую папку, а в папке /avr осталась затычка стрелочник. Но лучше подправить, чтобы не мозолила глаз

1
#include <util/delay.h>

Остался другой варнинг:
c:/winavr-20090313/lib/gcc/../../avr/include/util/delay.h:85:3: warning: #warning «F_CPU not defined for «

Это посереьзней. Компилятор говорит нам, что переменная препроцессора F_CPU не определена.

Длительность задержки зависит от частоты процессора, а частоту то мы не указали. Не вопрос! Добавляй куда нибудь в начало, но обязательно ПЕРЕД

1
#include <util/delay.h>

Иначе тебе еще три варнига будет — компилятор читает программу сверху вниз, а именно в delay.h ему потребовалась эта переменная. А как же частота которую мы выставляли в настройках проекта? А она видимо передается через командную строку, но вот delay требует ее раньше чем она передается в проект. Поэтому добавим в код.

У меня теперь начало вот так:

1
2
3
#define F_CPU 8000000L
#include <avr/io.h>
#include <util/delay.h>

Да, помнишь мы там дефайнили XTAL? Мне лень было переписывать старый добрый макрос, а тебе будет полезно. Чтобы было однообразие в коде, замени все XTAL на F_CPU — ближе к стандарту.

Теперь компилятор ругается на то, что у него F_CPU два раза встречается:

../Pinboard_1.c:1:1: warning: «F_CPU» redefined

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

Все, теперь не ругается. Без ошибок и варнингов компилится.

Потрошим хидеры
Библиотеку то мы добавили, а как ей пользоваться? Можно, конечно поискать мануал на WinAVR, но этот путь не для нас. Настоящие джедаи не пользуются мануалами, а вырывают информацию силой! По крайней мере на этапе обучения. Тяжело в учении легко в бою!(С)

Есть библиотека, надо узнать что у ней внутри. Сама библиотека эта в виде скомпилированого и заоптимизированного наглухо бинарика, а описание ее функций находится в delay.h лезь туда и не пугайся кучи барахла что там есть. Тебе надо найти прототип функции.

В Си нужно четко указать компилятору параметры функции (что на входе, имя, и что на выходе) перед тем как он ее встретит в тексте. Делается это либо посредством прототипа — просто копия заголовока функции без тела, либо просто саму функцию пишут перед всем кодом (обычно так, ибо писать прототипы всем лень).

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

Лезь в delay.h и ищи в этой помойке что либо похожее на прототип или функцию. Да, октрывай этот файл не блокнотом, а в самой студии — будет подсветка кода. Как только ты его заинклюдил то он сразу же появится в дереве проекта. Ты должен нарыть что нибудь вроде:

1
2
static inline void _delay_us(double __us)
static inline void _delay_ms(double __ms)

Как видишь, в этой либе всего две фунции delay_us и delay_ms, дедуктивный метод мне подсказывает, что одна это выдержка в миллисекундах, а вторая в микросекундах. А в качестве передаваемого параметра будет число формата double.

Отрывай от нее все причиндалы, они нужны только на этапе описания, и вставляй в код, не забыв указать задержку. Вот так вот:

1
2
3
4
5
6
7
8
while(1)
{
i++;
LED_PORT=0<<LED1;
 _delay_ms(1000);
LED_PORT=1<<LED1;
 _delay_ms(1000); 
}

Погасили, подождали секунду, зажгли, подождали секунду, перешли в начало цикла.

Скомпилили, прошили, нажали RESET, дождались пока бутлоадер перейдет к исполнению… Мигает. На глазок примерно 1 секунда.

Ок, надо бы проверить вообще то у нас получилось или не то? Задержка точно выдерживается?

В студии есть вкладка Processor, а в ней есть графа StopWatch — это время работы процессора. По ней можно вычислять как долго работает та или иная функция. Изначально значения там в микросекундах, что не удобно. Тыкни на нее мышой и переключи в миллисекунды. 1000мс = 1 секунда.

Чуть выше тактовая частота процессора, на которой идет симуляция. Наверняка там 4Мгц стоит :), а у нас 8. Это не страшно, зайди потом как нибудь во время активного процесса отладки в меню Debug ->AVR Simulator options и выбери там нужную частоту.

Теперь дошагай до строки перед задержкой, а потом поставь курсор после задержки и сделай «выполнить до курсора» (Ctrl+F10) После чего, студия задумается и спустя какое то время проскочит через задержку,а в графе StopWatch будет пройденное время.

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

—- c:\WinAVR\avr\include\util\delay_basic.h ——————————————————
105: __asm__ volatile (
+00000045: EC28 LDI R18,0xC8 Load immediate
+00000046: E030 LDI R19,0x00 Load immediate

Загружаем первую половину задержки 0xС800

= тут немного другого кода — компилятор порой размазывает функции по всему исходнику =

+0000004C: E180 LDI R24,0x10 Load immediate
+0000004D: E297 LDI R25,0x27 Load immediate

Загружаем вторую половину задержки 0x2710

+0000004E: 01F9 MOVW R30,R18 Copy register pair
+0000004F: 9731 SBIW R30,0x01 Subtract immediate from word
+00000050: F7F1 BRNE PC-0x01 Branch if not equal

—- c:\WinAVR\avr\include\util\delay.h ————————————————————
124: __ticks —;
+00000051: 9701 SBIW R24,0x01 Subtract immediate from word
120: while(__ticks)
+00000052: F7D9 BRNE PC-0x04 Branch if not equal

А вот и сама задержка. Если приглядеться и скинуть комментарии, которые только в глаза лезут, то выглядит это так, в псевдокоде:

1
2
3
4
5
6
7
8
9
10
11
12
	R18_R19 = 0xС800		//Первая половина задержки - база
	R25_R24 = 0x2710		// Вторая половина задержки - задержка
 
 
M0:	R30_R31 = R18_R19	    ---------------------
М1: 	R30_R31 - 1				|	|
	R30_R31 равно нулю?			|	|
	Нет = переход на метку М1 -------------	|
							|
	R25_R24 -1					|
	R30_R31 равно нулю?				|
	Нет = переход на метку М0	 ----------------

Как видишь, получается цикл двойной вложенности. В результате внутренний цикл выполняется 0xС800*0x2710 раз. Казалось бы, возможная выдержка в таком случае составляет 0xFFFF*0xFFFF итераций — ведь в первом цикле слово декрементируется и во втором цикле слово. А вот нифига! Си требует совместимости и универсальности либ. В данном случае на входе ВРЕМЯ, а функция оперирует только числом итераций, поэтому для любой тактовой частоты должно быть четко отмерено время. Поэтому значения базовой загрузки хитро вычисляются еще на этапе компиляции препроцессором Си и подставляются в функцию уже заточенные под конкретное значение кварца.

Так какая же максиальная выдержка у этой фунции? А стоит порыться в том же delay.h чтобы найти ответ:

The maximal possible delay is 262.14 ms / F_CPU in MHz.

When the user request delay which exceed the maximum possible one,
_delay_ms() provides a decreased resolution functionality. In this
mode _delay_ms() will work with a resolution of 1/10 ms, providing
delays up to 6.5535 seconds (independent from CPU frequency). The
user will not be informed about decreased resolution.

А по русски — 262.14 миллисекунд/частоту в мегагерцах. Это если в точном режиме, а если надо больше, то идет уменьшение дискретности, в итоге на точности 1/10 миллисекунды мы можем выжать из нее максимум 6.5535 секунды вне зависимости от частоты кварца.

Стоить запомнить эту ситуацию на будущее, если вдруг будешь писать «Универсальную библиотеку на все случаи жизни». Чтобы не получить вилы из-за переполнения потому что кварц не тот :)

Кстати, обрати внимание в прототипе на директиву inline — это значит, что функция будет не вызываться, а тупо подставляться в код. Словно макрос. Два раза написали мы инлайновую функцию delay_ms — получили два экземпляра этой функции в коде. Можешь сам убедиться. Обычно это не очень оптимально с точки зрения расхода ПЗУ, но зато работает быстрей. Впрочем, сам же видишь что функция тут пара команд. Так что, ИМХО, inline тут более чем оправдана — вызов ее потребует передачу параметра double, сохранение и восстановление адреса возврата и прочий сопутствующий код, а это будет не намного короче.

Как обычно, проект на данной стадии.

Продолжение следует….

З.Ы.
Не пугайтесь что продвижение идет в час по чайной ложке, у меня на этот счет шкала логарифмическая :) А пока вжевывайте в то как работает контроллер на простых примерах, потому что когда в бой пойдет многозадачность и разделение процессов отвлекаться на фигню будет некогда :)

175 thoughts on “AVR. Учебный Курс. Программирование на Си. Часть 2.”

    1. GCC как минимум полезно знать. Хотя бы для того, чтобы в наглую передирать алгоритмы из сишных либ и добивать их кувалдой ассемблера до уберкомпактности и скорости :)

  1. Тема отличия постфиксного и префиксного инкремента/декремента не раскрыта (важно же!).
    Тема юзания структур для портов не раскрыта (удобно же!).

    1. 1. Это особенности языка. Потом дам ссылочку на страницу справочника.
      2. А нафига? Это же дополнительный расход на память и время

  2. Чёрт! :)
    Почему нет возможности править посты?
    Вторая редакция:

    1. Мне? Мне-то зачем, я Си вдоль и по диагонали знаю ;)
    2. Нет. Если компилятор писался не пьяным студентом за пиво, то никакой разницы не будет, зато будет ощутимая разница в красоте и читаемости кода..

    То есть (по крайне мере, по моему) вместо

    while(PORTB & (1 << MYPIN_DRIVE_LOCK))

    гораздо приятнее писать

    while(ControlBus.DriveLock)

    Я так себе вообще написал удобные макросы, с помощью которых порты определяю так:

    BEGIN_PORT_DEFINITION
    	DEFINE_PORT_PIN(RowFeedback)
    	DEFINE_PORT_PIN(RowForwarder)
    	DEFINE_PORT_PIN(RowDataline)
    	DEFINE_PORT_PIN(ColFeedback)
    	DEFINE_PORT_PIN(ColForwarder)
    	DEFINE_PORT_PIN(AnodeSwitch)
    END_PORT_DEFINITION(Matrix, A)
    

    инициализирую так:

    	HalConfigurePin(Matrix,		RowFeedback,	IN,		PULLUP	);
    	HalConfigurePin(Matrix,		RowForwarder,	OUT,			);
    	HalConfigurePin(Matrix,		RowDataline,	OUT,			);
    	HalConfigurePin(Matrix,		ColFeedback,	IN,		PULLUP	);
    	HalConfigurePin(Matrix,		ColForwarder,	OUT,			);
    	HalConfigurePin(Matrix,		AnodeSwitch,	IN,		Z		);
    

    А юзаю примерно так:

    	Matrix.Out->RowDataline = 1;
    	ImpulseP(Matrix.Out->RowForwarder);
    	Matrix.Out->RowDataline = 0;
    
    
    	if(Matrix.In->RowFeedback)
    	{
    		//
    		// Отсутствует отклик с RCM-шины. Нет ни одного RCM
    		// или сбой в первом RCM
    		// 
    
    		PostErrorSound(POSTERROR_NO_RCM_FEEDBACK);
    	}
    

    ИМХО это выразительнее, чем побитовые опрерации, при равной производительности и компактности кода.

    1. Хм. прикольно. Можешь макросы замылить на dihalt@dihalt.ru? Любопытно поглядеть.

      Мне, как ассемблерщику, привычней битмаски. А с таким подходом еще не сталкивался.

    2. Всегда поражался, как Сишники из простой вещи могут такооооооооое накрутить… Из простого 8 битного порта здоровенную обьектную модель нагородил. Осталось к этому Matrix прикрутить конструктор, деструктор, полиморфизм и инкапсулирование… А потом удивляемся, почему это VISTE двух гигов памяти мало, когда WIN98 и на 128 мегобайтах то же самое делала…
      Вместо 2-3 строчек — целая страница писанины, набивать задолбаешься, да еще и помнить все это, вместо того, что, к примеру, RowFeedback — это PORTB.0… Определил, и пользуйся.

      1. Нет здесь никакой объектной модели, вы что!

        Я как раз из тех людей, кто зверствующе негодует, когда задачи решаются используя тонны дополнительных, лишних сущностей, которые создают нагрузку, тратят ресурсы, в то время как всё решается гораздо более проще ( != примитивнее).

        Поэтому меня бесит dotnet, бесит статья «Привязываем AVR к интернету», бесит когда чтобы выделить отельный байт из DWORD-а, DWORD конвертируют в hex-строку, берут из hex-представления числа два символа, и обратно конвертируют их в число, вместо того, чтобы юзать побитовые операции.

        Но в данном случае НЕТ никакого усложнения на уровне машинного кода. Это просто удобная внешняя абстракция на уровне исходного кода.

        1. Я, в общем — то, пошутил, но доля правды в этом есть. На код это не влияет, но увеличивает текст программы, да и программисту приходится больше и набирать текста, и запоминать все эти лишние конструкции. Ведь гораздо проще просто ввести, если надо, переменную с коротким конкретным именем, назначить ей бит порта, и спокойно пользоваться. Не буду говорить про все компиляторы, но, например, компиляторы от Микроэлектроники это допускают, так же как и использование в выражениях обозначений типа PORTB.3. Также и указание через точку конкретного бита переменной. Просто и удобно.

          1. >> Ведь гораздо проще просто ввести, если надо,
            >> переменную с коротким конкретным именем,
            >> назначить ей бит порта,

            >>но, например, компиляторы от Микроэлектроники это допускают, так же как и использование в выражениях обозначений типа PORTB.3.

            Это же не по стандарту языка.

            Что же касается номера бита после точки: я изначально расчитывал на то, что когда я буду разводить плату, окажется, что всё очень хорошо разводится, если поменять эти два пина местами.

            И что мне в этом случае, менять в везде PORTB.3 на PORTB.2, а PORTB.2 на PORTB.3?

            У себя же просто поменяю местами две строчки DEFINE_PORT_PIN в одном заголовочном файле.

            1. Компилеры микроэлектроники вообще слабо дружат со стандартами. такая вещь в себе с собственными форматами библиотек, объектников и т.д. По крайней мере с полпинка оно с ob-dev usb не срослось.

              1. Зато они очень эффективны, дают код малого размера. И вполне самодостаточны, особенно версии PRO. Глюков пока не заметил. И имеют очень удобную среду разработки, понятную с первого включения. И массу примеров, по которым легко понять, если что не ясно, и очень хороший Help, опять же с примерами использования функций. Я много чего перепробовал, и теперь для микроконтроллеров пользуюсь только ими, в основном МикроПаскалем ПРО.

                1. Симпатичная среда, да. Похоже сдирали с той среды, в которой писали :) Хотя Galileo немного погибче в плане настройки интерфейса.
                  Ну а не будь они самодостаточны, кому бы они были нужны такие, не сильно с чужими библиотеками дружащие.
                  Микропаскаль штука интересная, у него конкурентов (почти) нету, а вот вместо С лучше использовать что-то более стандартное. Тырить — так IAR )

                  1. P.S. А еще у микроС препроцессор стырен из GCC и какой-то кривой — не работает на русской системе, жалуется на незнакомую кодировку. Пришлось колупать его гексэдитором. А в MinGW’е он же нормально робит.

            2. «Это же не по стандарту языка.» — Ну, Си и стандарты — вещи не совместимые. Если пользоваться хотя бы стандартами ANSI языка С, так там нет и половины того, что реально используется. А если взять первоначальный вариант, так там вообще почти ничего не было. И тем более нет смысла придерживаться стандартов, разрабатывавшихся еще в эпоху IBM 360/370, и для тех машин, при программировании современных микроконтроллеров.

              1. Ну запись вида PORTC.1 это нонсенс и непонятно что.

                Это атомарное выражение или же составное? Если атомарное, то меня корёжит от использования символа «точка» в идентификаторе.

                Если же это не атомарное выражение, а составное, и здесь точка это действительно member-access-operator, то меня корёжит, что идентификатор member-name состоит из одного лишь числа.

                Впрочем, легко проверить: если компилятор допускает запись вида «(PORTC).1» и считает её равносильной записи «PORTC.1», то это всё-таки составное выражение.

                Только вне зависимости от результата, меня одинакого отталкивают эти варианты.

                  1. А мне понравилось. Особенно когда я обнаружил, что в МикроПаскале точно так же можно работать с любыи битом и переменных, не только байтовых, но и типа word. По крайней мере, для PIC, на AVR еще не пробовал. У PIC вообще с битами работать проще. Есть команды проверки, установки, условных переходов по битам, причем без разницы, портов или регистров (ОЗУ). Здорово упрощает программу.
                    Если в МикроПаскале после имени переменной ставишь точку и номер бита, работаешь просто с битом этой переменной. Причем можно сделать новую переменную, которая будет являться битом другой переменной. Удобно работать с флажками, собранными в байты, для управления ходом выполнения программы. (я часто их использую).

      2. и не говори
        никак не пойму смысла указателя ->
        года 2-3 назад еще как-то понимал, а щас и совсем забыл

        для мк с памятью в 8-16к такие абстракции ни к чему и вполне хватает макросов

        1. Глупость. Это же вещь такого же порядка, как возможность писать «x[y]» вместо «*(x+y)». Какая связь между сложностью абстракцией и памятью МК, если дальше компилятора эти вещи не идут и компилируются в одинаковый машинный код, одинакового размера и производительности?

      1. Тут сложно все. Движок мы же не сами пишем, а собираем из подручного хлама. Был плагин позволявший править посты, но сука был тормозной и глючный. От него была просто колоссальная нагрузка на сервак. Снесли его нафиг. Кнопку удалить тоже так просто не прикрутить. А по поводу предпросмотра, тут чистый хтмл так то. Некоторые теги запрещены, вроде картинок (их только я могу ставить). Ссылки и все похожее на ссылки (вроде бы по двойному слешу определяет) улетает на премодерацию.

        1. >>А по поводу предпросмотра, тут чистый хтмл так то.
          Не чистый. Параграфы он сам расставляет. Да ещё и зачем-то побил в моём посте все строки (перенёс что-ли?) и после этого их оформил как отдельные параграфы.

          Но вообще, надо же что-то делать с этим.

    3. ОК, сейчас объясню.

      Фишка в том, что в Си мы имеем возможность юзать bit-field’ы (кто не знает что это такое, ищите сами, я здесь

      объяснять не буду).

      У нас порт это (обычно) 8 пинов и мы работаем с каждым пином дёргая соответствующие биты в соответствующих портах.

      Ну а доступ к отдельным битам можно получать по разному: можно с помощью bitwise-операций, а можно с помощью

      bit-field’ов. Адекватному компилятору не должно быть никакой разницы, написали ли мы

          foo |= 2

      или же у нас

          foo.SecondBitField = 1

      он в обоих случаях генерирует один и тот же машинный код.

      Так вот, есть у нас, скажем, PORTC, у которого первые три пина — выходы, на которых висят светодиоды, потом один

      пулап-вход и 2 высокоимп. входа.

      Мы могли бы объявить 6 констант и работать с регистрами порта с помощью обычных побитовых операторов, примерно так:

          #define MY_PIN_RED_LED     0
          #define MY_PIN_YELLOW_LED  1
          #define MY_PIN_GREEN_LED   2
          #define MY_PIN_BUTTON      3
          #define MY_PIN_AUX1        4
          #define MY_PIN_AUX2        5
          
          ...
      
          DDRC = 0;
          // Делаем первые три пина выходами
          DDRC |= ((1 << MY_PIN_RED_LED) | (1 << MY_PIN_YELLOW_RED) | (1 << MY_PIN_GREEN_LED));
          
          // Включаем подтяжку на нужном пине
          PORTC = 1 << MY_PIN_BUTTON;
          ...
          
          // Зажигаем красный и зелёный сиды:
          PORTC |= ((1 << MY_PIN_RED_LED) | (1 << MY_PIN_GREEN_LED));
      
          // Проверяем вход последнего пина:
          if(PINC & (1 << MY_PIN_AUX2)) ...
      

      но могли бы объявить такую структуру:

          typedef struct
          {
              char RedLed:1;
              char YellowRed:1;
              char GreenLed:1;
              char Button:1;
              char Aux1:1;
              char Aux2:1;
          } MY_PORT_PINS;

      и делать то же самое, но кастуя тип регистров к этой структуре:

          // Делаем первые три пины выходами
          ((MY_PORT_PINS)DDRC).RedLed = 1;
          ((MY_PORT_PINS)DDRC).YellowRed = 1;
          ((MY_PORT_PINS)DDRC).GreenRed = 1;
              
          // Включаем подтяжку на нужном пине
          ((MY_PORT_PINS)PORTC).Button = 1
          ...
      
          // Зажигаем красный и зелёный сиды:
          ((MY_PORT_PINS)PORTC).RedLed = 1;
          ((MY_PORT_PINS)PORTC).GreenLed = 1;
              
          Проверяем вход последнего пина:
          if(((MY_PORT_PINS)PINC).Aux2) ...

      Как видите, здесь структура MY_PORT_PINS выступает чем-то вроде контейнера для бит-фиелдов и отражает реальную

      структуру порта.

      Это выглядит чуть лучше, но всё ещё плохо: куча кастования. Кроме того, этот код вообще не скомпилируетя. По крайней

      мере AvrGCC отказался кастовать PORTC к структуре. Да и у нас тут везде всё те же PORTC, PINC, DDRC. Если мы захотим

      сменить порт на B, это что же нам, везде менять? Даже если мы заюзам макросы, придётся использовать три макроса (ну

      и менять соответственно 3 строчки).

      Негоже! :)

      Поэтому окончательное решение состоит в том, чтобы создать глобальную переменную для всего порта, имя которой будет

      отражать предназначение порта. Типом этой глобальной переменной будет структура с трёмя полями-указателями на PORTX,

      PINX и DDRX.

      Важно (!!!) поставить квалификатор const в объявления указателей. Использование константных указателей компилятор

      оптимизирует, поэтому никакой разницы в коде между предыдущим примером не будет, зато внешне всё будет выглядеть

      красивее:

      (Ахтунг! Я советую вам чем раньше, тем лучше подробно разобраться с CV-квалификаторами. Крайне важно понимать,

      чем указатели на константу (const int * a) отличаются от константных указателей (int * const a), очень важно, чтобы

      ваш мозг не взорвался когда вы увидите сочетание const- и volatile- квалификаторов (это абсолютно нормальное

      сочетание, на самом деле))

          typedef struct
          {
              char RedLed:1;
              char YellowRed:1;
              char GreenLed:1;
              char Button:1;
              char Aux1:1;
              char Aux2:1;
          } MY_PORT_PINS;
      
          typedef struct
          {
              MY_PORT_PINS* const In;
              volatile MY_PORT_PINS* const Out;
              MY_PORT_PINS* const DDR;
          } MY_PORT_CONTROL_BUS ControlBus = {
                                                  (MY_PORT_PINS*)&PINC,
                                                  (MY_PORT_PINS*)&PORTC,
                                                  (MY_PORT_PINS*)&DDRC,
                                             };
      
          // Всё, теперь доступ к битам регистра PIN порта С осуществляетсчя так:
          //         ControlBus->In.xxxxxx
          // доступ к битам регистра PORT порта C, так:
          //         ControlBus->Out.xxxxxx
          // доступ к битам регистра DDR порта C, так:
          //         COntrolBus->DDR.xxxxxx
          // где xxxxx -- логический пин, он же поле из структуры MY_PORT_PINS,
          //              например GreenLed.
      

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

          #define BEGIN_PORT_DEFINITION  typedef struct {
          #define DEFINE_PORT_PIN(name) char name:1;
          #define END_PORT_DEFINITION(portname, hardport) } SYSPORTPINS_##portname; \
                                                  typedef struct \
                    { \
                     SYSPORTPINS_##portname* const In; \
                     volatile SYSPORTPINS_##portname* const Out; \
                     SYSPORTPINS_##portname* const DDR; \
                    } SYSPORT_##portname; \
                    SYSPORT_##portname portname = {(SYSPORTPINS_##portname*)&PIN##hardport,     
      
      (SYSPORTPINS_##portname*)&PORT##hardport, (SYSPORTPINS_##portname*)&DDR##hardport};
      

      С применением этих макросов вместо такого объявления:

          typedef struct
          {
              char RedLed:1;
              char YellowRed:1;
              char GreenLed:1;
              char Button:1;
              char Aux1:1;
              char Aux2:1;
          } MY_PORT_PINS;
      
          typedef struct
          {
              MY_PORT_PINS* const In;
              volatile MY_PORT_PINS* const Out;
              MY_PORT_PINS* const DDR;
          } MY_PORT_CONTROL_BUS ControlBus = {
                                                  (MY_PORT_PINS*)&PINC,
                                                  (MY_PORT_PINS*)&PORTC,
                                                  (MY_PORT_PINS*)&DDRC,
                                             };

      мы можем писать так:

          BEGIN_PORT_DEFINITION
              DEFINE_PORT_PIN(RedLed)
              DEFINE_PORT_PIN(YellowRed)
              DEFINE_PORT_PIN(GreenLed)
              DEFINE_PORT_PIN(Button)
              DEFINE_PORT_PIN(Aux1)
              DEFINE_PORT_PIN(Aux2)
                  END_PORT_DEFINITION(ControlBus,C)

      Осталось только сделать удобное конфигурирование портов.

      Как известно, если в DDR бит установлен, то пин — выход, в противном случае вход.
      Если порт выход, то бит в регистре PORT это его состояние, если же вход — то бит в регистре PORT определяет,

      включена подтяжка или нет.

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

      Сам макрос (+ прилагающиеся к нему константы) выглядит так:

          #define __HALCONF_   0
          #define __HALCONF_OUT   1
          #define __HALCONF_IN  0
          #define __HALCONF_PULLUP 1
          #define __HALCONF_Z   0
               
          #define HalConfigurePin(__port, __pin, __in_out, __pullup_val) __port.DDR->__pin = __HALCONF_##__in_out;     
      
      __port.Out->__pin = __HALCONF_##__pullup_val

      Соответственно, код для конфигурирования наших 6 пинов будет выглядеть так:

          HalConfigurePin(ControlBus, RedLed        , OUT ,        );
          HalConfigurePin(ControlBus, YellowRed     , OUT ,        );
          HalConfigurePin(ControlBus, GreenLed      , OUT ,        );
          HalConfigurePin(ControlBus, Button        , IN  , PULLUP );
          HalConfigurePin(ControlBus, Aux1          , IN  , Z      );
          HalConfigurePin(ControlBus, Aux2          , IN  , Z      );

      Ну и первоначальный код соответственно будет выглядеть так:

          BEGIN_PORT_DEFINITION
              DEFINE_PORT_PIN(RedLed)
              DEFINE_PORT_PIN(YellowRed)
              DEFINE_PORT_PIN(GreenLed)
              DEFINE_PORT_PIN(Button)
              DEFINE_PORT_PIN(Aux1)
              DEFINE_PORT_PIN(Aux2)
          END_PORT_DEFINITION(ControlBus,C)
      
          ...
      
      
          HalConfigurePin(ControlBus, RedLed        , OUT ,        );
          HalConfigurePin(ControlBus, YellowRed     , OUT ,        );
          HalConfigurePin(ControlBus, GreenLed      , OUT ,        );
          HalConfigurePin(ControlBus, Button        , IN  , PULLUP );
          HalConfigurePin(ControlBus, Aux1          , IN  , Z      );
          HalConfigurePin(ControlBus, Aux2          , IN  , Z      );
          
          // Зажигаем красный и зелёный сиды:
          ControlBus->Out.RedLed = 1;
          ControlBus->Out.GreenLed = 1;
              
          //Проверяем вход последнего пина:
          if(ControlBus->In.Aux2) ...
      1. Чето не догоняю где в твоем макросе идет привязка к конкретному ддр или порту? Т.е. тебе в любом случае надо задавать вручную структуру под каждый порт и прописывать там все названия пинов. Как то перегружено получается.

        А по поводу смены порта и переписки всего кода — я так всегда задефайниваю все эти PORTC и DDRC на символические имена. Так что потом разве что HAL подправить и все.

        1. К конкретному порту идёт привязка в макросе END_PORT_DEFINITION. Там вторым параметром указывается буковка. Я ступил и забыл написать в этом посте-примере, но в самом первом посте (где Matrix) это есть.

          А в этом посте, если у тебя есть возможность, погравь
          END_PORT_DEFINITION
          на
          END_PORT_DEFINITION(ControlBus, X)
          где X — поставь букву любого порта, к которому душа лежит :)

        2. >>Т.е. тебе в любом случае надо задавать вручную структуру под каждый порт и прописывать там все названия пинов.

          Почему же вручную? Структуры (их две на каждый порт) создаются автоматически. Прописывать названия пинов надо вручную (а как иначе?) в макросе DEFINE_PORT_PIN.

      2. Что — то не похоже на упрощение. Это как левой пяткой правое ухо чесать… Чуть ли не сотня лишних строк, а выигрыш невелик… (да и есть ли он?)

        1. Где лишние строки-то? Тем более? Определения макросов у меня одни на все проекты. Да и там строк 10.

          Определение же портов занимает на 2+n строк (где n — число используемых пинов). При вашем подходе оно занимает 1+n строк.

          Работа с портами происходит аналогично.

          Конфигурирование пина занимает 1 строку.

          Где сотни лишних строк?

      3. Красивое решение. Разве что конфигурация требует довольно много букаф.
        А почему только Out volatile? Из того, что я только что про volatile почитал, они вроде все три должны быть такими (или хотя бы In и Out).

        1. В принципе, зависит от хитрости компилятора. То есть по идее действительно лучше volatile везде поставить.

          Но у меня работало нормально и с таким кодом.

          Я в первую очередь поставил volatile на Out из-за того, что болся что код вроде
          foo.Dataline = 1
          foo.Clock = 1
          скомпилилируется в одну инструкцию (вернее как если бы было foo |= 3), а код вида
          foo.Clock = 1
          foo.Clock = 0
          упростится до последней строчки.

            1. Ну вообще компиляторы не имеют права оптимизировать таким способом условия циклов, т.к. переменные, участвующие в condition-expression могут измениться в самом цикле.

              Но так как цикл пустой, фиг знает.

      4. понравилось…. очень все прозрачно
        интересно, как пришли к такой идее? наверное опыт с MFC тоже повлиял?
        а может это плод большого опыта?

          1. понятно, не повлиял… дело в том, что там message map именно в таком похожем варианте задается, вот я решил, что изначально толчком послужил опыт с MFC

            так все-таки, как вы пришли к такой идее? просто интересно узнать ход эволюции идеи

            1. Ааа, в этом смысле.

              На самом деле «похожий вариант» где-только не используется: не только в MFC для задания message map, а, например, в ATL, при написании COM-серверов юзаются макросы BEGIN_OBJECT_MAP, OBJECT_ENTRY, END_OBJECT_MAP.

              Если глубоко копать, то именно эту тройку макросов я давным-давно увидел первой.

              Но неправильно говорить, что именно ATL или MFC привели меня к этой идее: у меня у самого begin/entry/end-макросы уже давно всюду используются; этот случай не первый случай их использования.

              Вот кусочек из одного моего исходника компилятора:

              typedef unsigned long FBKEYWORD;
              int ___dummy_function1(const char *) {return 0;}
              #ifdef DEFINE_FULL_FB_KEYWORDS_TABLE
               #define BEGIN_KEYWORD_TABLE unsigned long const __first_keyword_definition_line = __LINE__;
               #define DEFINE_FB_KEYWORD(xxx) const char* ____kwd_text_##xxx= #xxx;const FBKEYWORD FbKwd_##xxx = __LINE__ - __first_keyword_definition_line;const int ___kwd_anti_optim_##xxx = ___dummy_function1(____kwd_text_##xxx);
               #define END_KEYWORD_TABLE unsigned long const FB_KWD_TABLE_SIZE = __LINE__ - __first_keyword_definition_line -1; const char** FbKeywords = (&____kwd_text________);
              #else
               #define BEGIN_KEYWORD_TABLE unsigned long const __first_keyword_definition_line = __LINE__;
               #define DEFINE_FB_KEYWORD(xxx) const FBKEYWORD FbKwd_##xxx = __LINE__ - __first_keyword_definition_line;
               #define END_KEYWORD_TABLE 
              #endif
              

              Тот же принцип.

              А эволюция описана в моём же посте: изначально мне пришла идея использовать битфиелды для руления конкретными битами, потом я просто нашёл подходящий способ их использовать: простое кастование не получилось же использовать.

      5. не стоит определять пины портов через bitfield потому что
        The ANSI standard does not define how bitfields are packed into the byte, i.e., a bitfield
        placed in the MSB (Most Significant Bit) with one compiler can be placed in the LSB
        (Least Significant Bit) in another compiler. With bitmasks the user has complete control
        of the bit placement inside the variables.

      6. Сложно, неэффективно и к тому же неверно сделано.

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

        Чтобы вылечить эту багу, переменную ControlBus необходимо обьявлять как static const (и соотвецтвенно держать её в хидерах, а не в .c файлах). Иначе компилятор не может оптимизировать обращения к ней.

        Во-вторых, такой подход не даёт возможности щёлкать сразу несколькими битами. Например, если я хочу одновременно выключить Red, Yellow, Green LED при данной парадигме это будет сделано тремя командами и получится не очень одновременно. При стандартной парадигме это сделается одной командой. Вылечить это можно только добавлением соответствующей peep-hole оптимизации в avr-gcc. Any volunteers, как говорят буржуи?

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

  3. Надеюсь, напишешь потом «для чайников» — по шагам — как собсна прошить МК? т.е. реально, а не на симуляторе)

  4. Конструкция переменная = 0<<n; затирает все биты, и если остальные биты интересуют надо писать переменная &= ~(1<<n);, тоже самое и для переменная = 1<<n; — переменная |= (1<<n);

      1. Да я понимаю, что ты знаешь, это я для новичков, а то у меня однокурсники спрашивали, — что это за конструкция, нафига она нужна и будет ли работать 0<<n.

        1. Для них тоже будет подробное объяснение. Надо же на этапе проектирования заложить в программу баг, чтобы потом его в педагогических целях найти и расстрелять :) Наглядней будет видно на что это влияет и к чему может привести :)

    1. Пример не верен. В нашем случае операторша должна была достать IDAPro и распотрошить программу по винтику

        1. А где я его игнорирую? Я говорю что пока стоит поучиться добывать инфу из подножного корма, полезно.

          мануал там доксигеновский, так что да, он в хидерах уже весь есть :)

  5. DI HALT, когда появятся новые статьи про соединение с интернетом?? У меня VDS сожрал уже больше 100р, а продолжения нету(( Реверант, ведь, уже отдал Вам все 4 части, хватит тянуть!

  6. бля, а я никак не могу скачать студию 4… она должна весить 40 с чем то метров, это как бы полная версия.

  7. А интересно, какой порядок байт в AVR?

    Узнать можно так:

    #define BE 1 //Big Endian — порядок байт от старшего к младшему
    #define LE 0 //Little Endian — порядок байт от младшего к старшему

    char endian(void)
    {
    /* Подразумевается что short int — больше char, а char — один байт
    */
    short int x = 0x0100;
    /* Берем крайний левый байт.
    Соответственно, вернет 0x01 для BE и 0x00 для LE
    */
    return *((char*) &x);
    }

    /* Где то внутри main:
    */
    if(endian()==BE)
    LED_PORT=1<<LED1;
    else
    LED_PORT=0<<LED1;

        1. какой порядок байт вообще может быть у восьмибитного контроллера?
          ну, можете считать что Little Endian

          1. Контроллеру как-то нужно хранить в памяти слова и двойные слова, к примеру short int — 2 байта, long int — 4 байта. В зависимости от способа хранения, операции приведения типа, а также взятия определенных байт из слов и двойных слов — будут отличаться. Это необходимо знать чтобы не допустить ошибок.

            Возьмем к примеру реальный пример: Контроллеру необходимо что-то пересылать по бинарному протоколу по сети например через Ethernet модуль. Как известно, сетевой порядок байт — Big Endian. Если наш контроллер использует Little Endian, придется делать преобразования, чтобы удаленная сторона могла корректно обработать полученные пакеты.

            Так что, анализ контроллера на порядок байт все же необходим. Кстате говоря, для двойных слов порядок байт может быть вообще нестандартным (т.к. это не ПК), т.е. например 0x12345678 может храниться как 0x34127856. В случае чего, и с таким порядком тоже придется считаться.

    1. Еще бы такой сайт был для всех либ написанных под WinAVR =)

      Там, кстати, далеко не все по русски. Скорей даже большая часть не переведена.

      1. Ну, по первости-то очень полезно. А английский знать надо, конечно:)

        Ди, а ты импульсными преобразователями плотно не занимался случайно?

        1. Ну я их проходил в универе как часть программы электропривода. Так что обрывочные данные в голове есть. Правда на мощности от 5кВт Но не более того. На практике эти знания не применял еще никак.

  8. Исходники avr-gcc свободно доступны по адресу http://www.nongnu.org/avr-libc/

    Функция _delay_ms() намеренно написана с использованием плавающей точки и расчитана на то, что компилер отоптимизирует всю эту херотень. Поэтому вызывать её с не-константным аргументом вредно для желудка :) То есть если настоящий джедай напишет:

    void my_delay (int ms)
    {
    _delay_ms (ms);
    }

    то после компиляции он внезапно удивится почему это программа стала занимать вместо 5 байт — 5 килобайт.

    1. да, кстати, проверял — так и есть. Зато не всегда надо настраивать таймеры:)
      Если серьезно, то весь arv-libc для некритичных во времени задач, особенно без прерываний, это как скальпель. В каких руках окажется — так и будет..

  9. в Proteuse можно осцилограф подрубить, или только реальным можно наблюдать?

    P.S.
    читаю с интересом, только все реализую на асме (обещал себе никогда не учить Си)
    вот как уменя получилось:

    .include "m16def.inc"
    .CSEG
    .ORG 0x0000
    JMP Reset

    Reset:
    SBI DDRD,2
    LDI R17,0b00000100
    Loop:
    IN R16,PIND
    EOR R16,R17
    OUT PORTD,R16
    JMP Loop

    1. для примера, было-бы интересно/полезно повесить мигание на таймер! сложного нет, зато многим новичкам будет полезно на простом примере освоить таймер!

      В тексте упоминалось про изучение RTOS там тока Си или не принципиально?

    2. обещал себе никогда не учить Си

      интересно, почему? вычисления с участием плавающей точки, просто 32х битная арифметика, использования сложных структур данных итд реализовать на С гораздо проще и быстрее. на асме это можно писать только в случае если ваше время и труд вообще ничего не стоят.
      а насчет таймера — согласен.

      1. ну это только в отношении Си у меня так сложилось, знаю Паскаль, Бейсик и т.п., ну и под .net на С# замарачиваюсь.
        первым языком был Фортран (книжку до сих пор храню), затем асм на Z80 (зацепило, примитивы это классно!)… потом был спор с одним челом по возможностям Си и других языков, так вот после того как я себе (ну и ему) доказал, что можно не зная Си реализовать все тоже самое, вот тогда и понеслась…

        в моём листинге метку Loop нужно но одну строку ниже спустить, зачем постоянно читать из порта? ;)

        1. дело ваше. С — это стандарт для подобных вещей.

          в моём листинге метку Loop нужно но одну строку ниже спустить, зачем постоянно читать из порта? ;)

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

          1. даже если из прерывания измениться состояние порта, все будет нормально, а вот если регистр R16 или R17 подправят то да!

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

  10. DI HALT, ну типа такого:
    объявлять надо как,например, -unsigned char mydata[11][10] PROGMEM = { {0x00,0x01,0x02,0x03…} },
    а чтобы получить элемент массива:
    byte = pgm_read_byte(&(mydata[i][j]));
    Да, это именно если во флеши расположено.
    И наверняка подобных «извратов» ещё немало :)

    1. да-да. вот поэтому в CVAVR вроде как проще получается :D От чего, как говорят, все новички и не любят винавр (а то — надо было мне в первой части ответиь, перепутал немного, здесь пост написал)

  11. Подскажите в инете видел программу VMLAB
    для эмуляции работы AVR
    Не подскажете, как она в работе?
    Ведь можно предварительно посмотреть работу МК прямо на экране
    и отстроить прграмму без прошивки.

    1. Честно говоря хз. Я ее видел, даже ставил. Но мне заломало разбираться с тем как там нарисовать схему (там надо вручную нетлист в текстовом файле описывать). Эмулятор как эмулятор. У него одна прикольная фича есть — он показывает где код выполнялся, а где еще нет.

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

        1. Я эмуляторами вообще почти не пользуюсь. На начальном этапе это еще может пригождается. потом лишь потеря времени, т.к. в эмуляторе всегда все работает не настолько точно.

          Отлаживаю всегда все в реальном железе. На демоплате (если что то новое для себя изучаю), потом на реальном устройстве. алгоритмы прогоняю в эмуляции AVR Studio

    2. У меня с появлением нормального осциллографа тяга к этим эмуляторам сразу отпала. А когда жтаг собрал так вообще стало шоколадно все.

      1. У меня как у начинающего такой продвинутой макетки нет.
        А хочется что нибудь собрать для начала простенькое, но своё.
        Поэтому и хотелось испытать сначала на компе.

        1. достаточно просто МК с подачей на него питания. И осциллографа. Без осцилла тяжко порой отлаживать скоростные процессы.

          1. Да есть у меня осциллограф.
            Только я имел в виду, что-бы поменять какой-нибудь параметр в программе например длительность импульсов светодиода, нужно прошить МК.
            А так имея эмулятор меняешь программу и тут-же видишь результат на экране (устраивает он тебя или нет). Помоему грамотно, можно оперативно результат отрегулировать.

              1. Насколько я понял VMLAB дает полную наглядность, то есть кнопочки, светодиоды, осциллограммы и т.д., получается компьютерный аналог вашей макетки. Для начинающего вроде меня это единственный вариант проверить и обкатать своё устройство до сборки. Я понимаю, что ваша макетка даёт большую надёжность, только получается, что начинающему нужно начинать со сборки или приобретения этого девайса. А если человек занимается этим как хобби?

                1. Можешь попробовать юзать симуляторы. Я что отговариваю чтоль :) Сам потом поймешь, что фигня все это :)

                  А по поводу как хобби… любое хобби вообще обычно это дорогое удовольствие.

    3. Это тебе поможет человек, который курс по авр писал, там есть подробное разъяснение :) Кстати это и протеус может, только весит он гораздо больше

    4. VMLAB достойная программа, часто ею пользуюсь. Можно отлаживать многое – например, подключать ноги МК к кнопкам, переменным резисторам, 7-ми сегментным индикаторам и т.п. и смотреть, как все это работает (по содержимому регистров, по состоянию сигнала на ножках и пр.).
      Да, согласен, не идеальный эмулятор (у меня были глюки с частотой ШИМ), но если нету живой платы и осциллографа, то весьма пригодится.

      1. Рад слышать. Как начинающий хочу попробовать с неё. Наверное всё-таки это наиболее грамотный подход. Сначала надо обкатать программу на эмуляторе. Затем переходить к железу. Сранно что уважаемый DI HALT этого не понимает. Думаю, что VMLAB (или какой другой эмулятор) даст 100очков вперед любой макетке по наглядности и оперативности. Впрочем каждый имеет право на свои пристрастия.

        1. «Сранно что уважаемый DI HALT этого не понимает.»
          Как раз наоборот. Он уже ДОСТАТОЧНО понимает, чтобы не делать лишнюю работу. При наличии достаточного опыта «эмуляция» идет в голове, параллельно с отработкой принципиальной схемы и написанием программы. Достаточно опытный разработчик заранее знает, что он должен получить, а мелкие недочеты и ошибочки можно отладить уже в готовом железе.
          Другое дело — новички. Недостаток опыта не позволяет им заранее предвидить последствия тех или иных решений, изменений в программе, работы устройства в целом. Цена ошибки у них слишком велика, часто вынуждает переделывать не только программу, но и плату. Вот для них и пишутся всевозможные симуляторы и отладчики, делаются отладочные платы. Именно у них эти методы дают реальный эффект, позволяя избежать переделок реального железа, и посмотреть, что происходит внутри процессора.
          Для опытного разработчика же это — только потеря времени, двойная работа, да еще и борьба с кривизной и багами самих симуляторов.

          1. Уважаемый если вы читали дискусию, то речь как раз и идёт о начинающих.
            И заголовок «AVR. Учебный Курс…»

                1. Опечатка действительно забавная.
                  А SWG ещё раз постараюсь обьяснить Эта статья для НАЧИНАЮЩИХ.
                  Соответственно разговор идёт о методах обучения.
                  И ещё: возможно я соберу одну, две схемы и на этом остановлюсь.
                  Поэтому я не планирую изначально тратить деньги на оборудование, которое возможно мне потом не пригодится.
                  Немножко подумайте что я пытаюсь вам донести.
                  А к чести DI HALT ваш учебный курс написан легко и доходчиво.

                  1. «возможно я соберу одну, две схемы и на этом остановлюсь.
                    Поэтому я не планирую изначально тратить деньги на оборудование, которое возможно мне потом не пригодится.»
                    Тогда вам тем более нет никакого смысла, например, изучать С, микроконтроллуры, и тратить время на освоение симуляторов. В Интернете полно готовых конструкций на любой вкус, повторите пару — другую, попробуйте слегка их доработать… На двух схемах вы все равно толком ни С, ни программирование вообще, ни электронику не освоите с таким подходом. Чтобы чего — то реально достичь хоть в электронике, хоть в программировании, нужно заниматься этим всерьез и надолго. Если такого желания и интереса нет — тогда это действительно трата времени и денег. Проще пойти и купить по дешевке готовое китайское изделие, их сейчас в избытке. А хобби выбрать другое. Ну, хотя бы марки лизать…

        2. Наглядней — да. Удобней и оперативней тоже. Но вот биться головой об стол и пытаться понять что же глючит — твоя прога или симулятор это особое извращение. Я их использую до сих пор. Но не для того, чтобы пытаться отлаживать в них устройство, а проверить по быстрому какую нибудь идею, причем чаще аппаратную — тут обычню юзаю Мультисим. Он, в отличии от протеуса, глючит меньше и интерфейс у него вменяемей. Правда с МК он плохо дружит. А с МК мне достаточно АВР студии. Поначалу да, от того же протеуса я лихо кипятком проссался. Только быстро мне надоела эта погремушка. Да и далеко не все МК он поддерживает.

            1. Его там и нет. Я же говорил что мультисим плохо дружит с МК. Я его больше для хардвера использую.

              А МК удобней всего прогонять в AVR Studio там и все флаги, точки останова, состояние регистров, памяти, стек, все биты периферии. Да все вообще.

              1. Имхо, тема спора похожа на другую — «прототипировать на макетке или ЛУТом». Когда я первый раз прочитал про ЛУТ, подумал, что это страшное шаманство, и освоить его — это как пройти курс черной магии:-) Однако первый же опыт оказался удачным(PinBoard) — теперь макетку достаю из коробки с хламом только чтобы по быстрому вкурить в какойнить транзюк или нехитрую аналоговую схемку. А тем временем читая разные радиолюбительские ресурсы вижу, что большинство лепит самоделки на макетках. А я как вспомню
                как делал на ней прогер Громова — аж волосы шевеляться:-) Не говоря уже,что SMD + макетка — это нонсенс, а от DIP и прочей выводнухи я почти отказался в пользу безусловного удобства SMD.

                Ну а что касается отладки кода — может быть тебе, Ди, надо уделить отдельную статью методикам отладки, объяснить гражданам про всякие StepOver, StepInto, Watch, MemoryView, способы отладочного вывода, методикам использования подручных средств (типа С1-72, как у меня:-))) и т.п.? Может тогда народ и не будет донимать с игрушками-эмуляторами.

                1. «Ну а что касается отладки кода — может быть тебе, Ди, надо уделить отдельную статью методикам отладки, объяснить гражданам про всякие StepOver, StepInto, Watch, MemoryView, способы отладочного вывода»

                  Обязательно! Это уже запланировано в ближайших двух трех статьях!

          1. Вот уж точно, с Протеусом не все гладко. На «казусе» по нему куча глюков уже набежала к рассмотрению. Там протеусовский представитель «Тень» по ним разборки ведет. Я так со следящей собакой нарыл баг с Аттини45 (не работает она как надо, сейчас уже не помню, но вроде время сработки собаки невозможно было поменять, оно фиксированное в протеусе причем что-то в районе 4мс вроде). Думал уже с головой что-то совсем плохо стало, пока в «железо» не закинул программку. В железе все хорошо, в протеусе все с собакой плохо. Так что побыстрому идейку проверить, это да. А всерьез в нем что-либо отлаживать — это испорченные нервы себе и близким (и дальним, если до них дотянешься)

  12. Уважаемые Сишники!Такой вопрос:

    int i=0x1234, k ;
    k = i<<4 ; /* k=»0x0234″ */

    ведь к должно быть равно «0x2340»
    И после выподнения операции k = i<<4 ; и изменит своё значение, или останется прежним(0x1234)?

    1. ведь к должно быть равно “0×2340″

      k не просто должно быть равно 0x2340, оно таки равно 0x2340
      строка printf(«%x»,k); поможет легко в этом убедиться
      либо я не понял вопрос…

  13. Ди Халт, а где ты пишеть текст программы? я пишу в студии, но у меня почему то нету подсчета строк, видишь, у тебя есть, строчки обозначены,
    1
    2
    3
    4
    5
    6
    7
    8
    9

    как ты это сделал? что то у меня не получается.

  14. понятно. А когда надо посчитать сколько строк программа занимает, как это сделать? или такого нету?

    1. Обрати внимание на самую нижнюю строку — строку статуса. Там есть что-то типа Ln 279, Col 1
      Это и есть номер текущей строки на которой находится курсор и номер текущего символа.

  15. Наткнулся сегодня на: SUBI R24,0xFF; и что то не въеду- в чем прикол такого подхода,
    и почему нельзя обойтись простым: INC R24; ?

    1. От контекста зависит. INC не влияет на флаг переноса. Поэтому не годится для увеличения многобайтных чисел, где все заемы-переносы делаются через С

      1. Спасибо за ответ и за замечательный сайт =)

        Тутъ у меня еще один ламерский вопрос наметился: mov r24, r24; это как трактовать? Я лично понимаю это как NOP, но там где я его встретил он вообще не к месту..

  16. А откуда эта регистровая пара знает что там переменная “i”??? Начинаем штудировать код и в самом начале, где SREG обнуляется, находим:

    +0000002C: E5CF LDI R28,0×5F Load immediate
    +0000002D: E0D4 LDI R29,0×04 Load immediate

    Теперь все понятно. Компилятор в самом начале, хитрый, занычил адрес Y, а потом заюзал его как только потребовалось.

    В этом месте помойму ошибочка. Ещё в первой части изучения Си написано что здесь инициализируется стек. А адресс переменной он в другом месте нычит.

    +00000039: B7CD IN R28,0x3D In from I/O location
    +0000003A: B7DE IN R29,0x3E In from I/O location

  17. Приветствую.
    Сорри, за нуубский вопрос, но что есть такое — «хидер»? Это любой файл с расширением *.h, подключаемый к проекту, и содержащий изобретенные ранее кем-то велосипеды (библиотеки)? Или?
    Спасибо. )

    1. Да.

      Но это не чужие библиотеки (хотя и они тоже) это файлы определений для всяких макросов, переменных которые должны быть видны в других модулях, констант всяких. Прототипов функций Т.е. всякая информационная тряхомудия которая не код :) Код, кстати, размещать в хидерах можно, но это считается моветоном.

  18. Эмм, мигание бита ересь если честно :) Писать надо так:

    while(1)
    {
    i++;
    /*** Ересь
    LED_PORT=0<<LED1;
    _delay_ms(1000);
    LED_PORT=1<<LED1;
    _delay_ms(1000);
    *****/
    LED_PORT ^= 1<<LED1;
    _delay_ms(1000);
    }

    1. Я знаю. Читай дальше курс. Там подробно разьясняется почему то что написал я не верно. Это терапевтическая ошибка :)

  19. Пишу
    #define F_CPU 8000000L
    #include
    #include
    ругается что ../C_nachalo.c:2:1: warning: «F_CPU» redefined

    Коментирую 1ю строчку , все нормально, компилит без варнингов.
    Как так ? откуда берет частоту контроллера?

  20. А у меня такая трабла…. Студия доходит до конца кода, и! когда вроде бы в while все должно гоняться по кругу она тупо тушит все кнопки брекпоинтов, шагов… короче всего, кроме остановки симуляции. Прям не знаю что делать. Еще кстати ей пофигу на volatile. Как игнорила переменную, так и игнорит.
    AVRStudio 4.19 b730

  21. Подскажите, пожалуйста, у меня после компиляции, во время дебага когда доходит до DELAY начинают откравыться хедер файлы и дебаг происходит в них. Как сделать так чтобы AVR STUDIO 4 их не открывала и дебаг был только в основной программе(сишной)?

  22. Офигенно тупой вопрос: как пользовать оператор While? Потому что мои знания программирования не дают мне понять смысла while(1) поясните пожалуйста

    1. Выполнять ПОКА true.

      while(1) это просто бесконечный цикл, т.к. 1 всегда true :)

      Есть еще do while там чуток по другому. Первый раз выполняется, а потом проверяется условие.

  23. Почему то откомпилилось нормально, без варнингов, в железе работает…..т.е. секунда получается. А при разборке в асм пишет:
    ..
    —- D:\Projects\Clock\default/c:/winavr-20100110/lib/gcc/../../avr/include/util/delay_basic.h —-
    105: File not found
    ..
    хотя файл delay_basic.h в папке есть и в проект он подхватывается вместе с delay.h
    Почему так ?

  24. И еще … при симуляции реальности ))))))))) …..не может пройти функцию _delay_ms(1000). Может студия глючная, юзаю версию 4.18

    1. А там как то хитро эти библиотеки сделаны, в них уже скомпиленный код. В результате студию на них глючит, т.к. исходников на них нет, а код внезапно есть.

  25. Подскажите минимально компактный код на Си сдвига по кольцу только четырёх старших разрядов восьми-битного числа .
    Можно ли объявить четырёх-битный тип данных ???

    1. Например так:

      // LEFT
      #define rolb(x) ( ((x < < 1) | (x >> 3)) & 0x000F )
      
      // RIGHT
      #define rorb(x) ( ((x >> 1) | (x < < 3)) & 0x000F )
      
      Ну и крутим:
      zz = rorb(zz);
      zz = rorb(zz);
      zz = rorb(zz);	
      

      3 - это разрядность сдвига. 000F - маска которой давишь ненужные разряды, у меня тут для 32х разрядного типа (иначе оно туда поплывет). Для целых типов (скажем ROR по байту в uint8_t) маску можно упразднить, тут сам тип отрежет.

    2. Если надо вращать только 4 бита, не трогая старшие вообще, то надо через &0000 с последующим OR. Но там тривиально все.

  26. Привет, Di!
    В одном из листингов есть строчка:

    UCSRB = 1<<RXEN|1<<TXEN|1<<RXCIE|0<<TXCIE;

    Имеет ли смысл писать последнее слагаемое 0<<TXCIE, ведь оно не влияет на флаг TXCIE?

    1. Конечно имеет. Когда тебе захочется вдруг включить TXCIE ты просто возьмешь и заменишь там нолик на единичку и не надо будет вспоминать как точно зовется этот бит и в каком регистре он находится.

Добавить комментарий

Ваш e-mail не будет опубликован.

Перед отправкой формы:
Human test by Not Captcha