Микроконтроллеры, АЦП, память и т.д Темы касающиеся микроконтроллеров разных производителей, памяти, АЦП/ЦАП, периферийных модулей... |
19.03.2010, 20:52
|
|
Временная регистрация
Регистрация: 30.07.2007
Сообщений: 51
Сказал спасибо: 1
Сказали Спасибо 12 раз(а) в 7 сообщении(ях)
|
О программировании AVR на C++
Бытует мнение, что программировать 8-ми битные контроллеры, такие как AVR, ввиду их скромных ресурсов, лучше всего на на ассемблере или в крайнем случае на С. А С++ с его классами, виртуальными функциями, исключениями и прочей объектно ориентированной шелухой для них мало подходит. Так ли это на самом деле. Попробуем разобраться в этом вопросе.
Полные исходные тексты к примерам: http://github.com/KonstantinChizhov/AvrProjects
|
|
|
|
19.03.2010, 20:54
|
|
Временная регистрация
Регистрация: 30.07.2007
Сообщений: 51
Сказал спасибо: 1
Сказали Спасибо 12 раз(а) в 7 сообщении(ях)
|
Re: О программировании AVR на C++.
Для начала выясним, какие возможноси языка С++ можно использовать для эффективного программирования для AVR:
- классы – само собой.
- исключения – сразу про них забудем – знатный пожиратель ресурсов (для AVR их поддержка по умолчанию выключена).
- шаблоны и метапрограммирование.
- полиморфизм (виртуальные функции).
Разбираться будем на конкретных примерах:
- ножкодрыгательство – работа с портами и пинами.
- АЦП.
- структуры данных: массив, стек, кольцевой буффер.
- USART.
- динамический диспетчер с программными таймерами.
- ЖК индикатор (HD44780).
- семисегментный индикатор.
- драйвер шагового двигателя.
|
|
|
|
19.03.2010, 21:45
|
|
Почётный гражданин KAZUS.RU
Регистрация: 01.04.2009
Адрес: Рязань
Сообщений: 1,140
Сказал спасибо: 21
Сказали Спасибо 635 раз(а) в 344 сообщении(ях)
|
Re: О программировании AVR на C++.
neiver - ждем с не терпением
|
|
|
|
19.03.2010, 22:52
|
|
Временная регистрация
Регистрация: 30.07.2007
Сообщений: 51
Сказал спасибо: 1
Сказали Спасибо 12 раз(а) в 7 сообщении(ях)
|
Re: О программировании AVR на C++.
Общие положения.
В язаках С и С++ поддержка модульности развита очень слабо:
заголовочные файлы вставляются простым копированием на место директивы #include, а работа с разными единицами трансляции полностью отдана компоновщику, с которой он не всегда справляется хорошо.
Пример:
В одном *.c или *.cpp файле находится несколько функций. Если функция не используется внутри файла, компилятор не может исключить её из компиляции, поскольку не знает, будет ли она использоваться в других единицах трансляции. Линковщик неиспользуюмую функцию так-же удалить не сможет, поскольку не знает используется ли она внутри своего модуля.
В Си, чтобы избежать таких вещей при написании библиотек, пользуются правилом: одна единица трансляции (читай *.с файл) – одна функция. Тогда линковщик с лёгкостью может отфильтровать неиспользуемые функции. Однако это не очень удобно. Компилятор может проводить оптимизацию только в пределах одной единицы трансляции. Конфигурировать код при этом можно только с помощью препроцессора.
Я предпочитаю использовать другой подход. В программе имеется только одна единица трансляции, например файл «main.cpp», а весь библиотечный код находится в заголовках. Все функции в библиотеках объявлены как inline. Это позволяет компилятору оптимизировать всю программу целиком. Это позволяет получить более компактный и быстрый код. Что очень существенно для маленьких встраиваемых систем. Но увеличивает время компиляции – каждый раз программа перекомпилируется полностью. В данном случае, я думаю можно пожертвовать временем компиляции в пользу более компактного и быстрого кода. Собственно, все приведенные примеры будут следовать этому принципу.
|
|
|
|
19.03.2010, 23:40
|
|
Почётный гражданин KAZUS.RU
Регистрация: 13.12.2004
Сообщений: 3,172
Сказал спасибо: 11
Сказали Спасибо 692 раз(а) в 504 сообщении(ях)
|
Re: О программировании AVR на C++.
Сообщение от neiver
|
В одном *.c или *.cpp файле находится несколько функций. Если функция не используется внутри файла, компилятор не может исключить её из компиляции, поскольку не знает, будет ли она использоваться в других единицах трансляции. Линковщик неиспользуюмую функцию так-же удалить не сможет, поскольку не знает используется ли она внутри своего модуля.
|
Начались "великие открытия". Все эти проблемы проблемами не являются. Путь первый - квалификатор static на функции не используемые в других модулях. Все - компилятор имеет контроль и прекрасно может выкидывать неиспользуемые функции сам. Кроме того это повышает возможности самого оптимизатора - он может инлайнить функции подчистую, не оставляя копий ее на случай вызова из других модулей.
Второе решение - сообщить компилятору ( есть такие смешные штуки - ключи и параметры) что мы требуем от него размещать каждую функцию в отдельной секции.
Ну и попросить у линкера откинуть неиспользуемые секции. И все - проблем нет.
Ничего лишнего в код не попадет. Есть правда смешные исключения - можно потерять например бутлоадер, ![Улыбка](images/smilies/icon_smile.gif) ведь он в коде часто явно не вызывается. Но можно просто собрать его отдельно, хотя это и неудобно. А можно просто для файла с загрузчиком не просить выкидывать неиспользуемое.
Сообщение от neiver
|
Я предпочитаю использовать другой подход. В программе имеется только одна единица трансляции, например файл «main.cpp», а весь библиотечный код находится в заголовках.
|
Надо еще не забыть, что и отлаживаться придется не по простой и логичной структуре, а по свалке "все в одном". На любителя. Вообще в таком подходе минусов больше чем плюсов в десятки раз. А думать о лучшей оптимизации - смешно. Компилятор оптимизирует функциями, а не файлами. Так что выгоды не будет. А вот попотеть с перекрестными связями между модулями придется. Причем в результате потом нельзя будет просто взять модуль и включить его в другой проект, модуль надо править и существенно. Что в С, что в С++ это плохой стиль. Особенно в С++, язык создавался для упрощения ведения больших проектов. А всесто упрощения получим помойку из исходников. ![Улыбка](images/smilies/icon_smile.gif) Вообще странно - принципам раздельной компиляции уже много лет. И все они никак не могут уйти. Может причина в том, что они достаточно эффективны?
|
|
|
|
19.03.2010, 23:57
|
|
Временная регистрация
Регистрация: 30.07.2007
Сообщений: 51
Сказал спасибо: 1
Сказали Спасибо 12 раз(а) в 7 сообщении(ях)
|
Re: О программировании AVR на C++.
Ножкодрыгательство – работа с портами и пинами.
Ножкодрыгательство это основное предназначение маленьких контроллеров и работа с портами составляет большую часть объёма программы. Поэтому хотелось бы с ними работать как можно удобнее.
Традиционно для этого используются битовые операции:
PORTA |= (1 ‹‹ 5); // установить бит 5 в порте А.
PORTA &= ~(1 ‹‹ 5); // сбросить бит 5 в порте А.
Что умело преобразуется компилятором в:
sbi PORTA, 5
cbi PORTA, 5
Что не может не радовать. Порт и номер пина обычно задаются с помощью #define-а. И когда мы хотим подергать пин, надо помнить в каком он порту (а ещё есть DDR и PIN регистры), и какой у него номер. Не очень удобно. А хочется завести переменную для пина, передавать её как параметр в функции и т.д. Некоторые компиляторы предлагают для таких целей нестандартные расширения, но это всё баловство.
Попробуем объединить порт и номер пина в одной сущности:
Код:
|
class Pin{
public:
Pin(volatile unsigned char &port, uint8_t pin)
:_port(port)
{
_pin = pin;
}
void Set()const
{
_port |= (1 ‹‹ _pin);
}
void Set(uint8_t val)const
{
if(val)
Set();
else Clear();
}
void Clear()const
{
_port &= (uint8_t)~(1 ‹‹ _pin);
}
void Togle()const
{
_port ^= (1 ‹‹ _pin);
}
void SetDirWrite ()const
{
*(&_port - 1) |= (1 ‹‹ _pin);
}
void SetDirRead()const
{
*(&_port - 1) &= (uint8_t)~(1 ‹‹ _pin);
}
uint8_t IsSet()const
{
return (*(&_port - 2)) & (uint8_t)(1 ‹‹ _pin);
}
private:
volatile unsigned char &_port;
uint8_t _pin;
}; |
В функциях SetDirRead, SetDirWrite и IsSet мы воспользовались фактом, что DDRX и PINX регистры порта имеют адреса на 1 и 2 меньше соответствующего регистра PORTX.
Теперь напишем маленький пример использования такого класса:
Код:
|
int main()
{
Pin pin1(PORTA, 1);
pin1.SetDirWrite();
pin1.Set();
pin1.Clear();
pin1.Togle();
pin1.SetDirRead();
if(pin1.IsSet())
pin1.Clear();
} |
И его ассемблерный листинг:
Код:
|
main:
sbi 58-32,1 // pin1.SetDirWrite();
sbi 59-32,1 // pin1.Set();
cbi 59-32,1 // pin1.Clear();
in r24,59-32 // pin1.Togle();
ldi r25,lo8(2)
eor r24,r25
out 59-32,r24
.
cbi 58-32,1 // pin1.SetDirRead();
sbic 57-32,1 // if(pin1.IsSet())
cbi 59-32,1 // pin1.Clear();
ldi r24,lo8(0) //return 0; раз уж объявили main как int
ldi r25,hi8(0)
ret |
Уже не плохо.
|
|
|
|
20.03.2010, 00:09
|
|
Почётный гражданин KAZUS.RU
Регистрация: 13.12.2004
Сообщений: 3,172
Сказал спасибо: 11
Сказали Спасибо 692 раз(а) в 504 сообщении(ях)
|
Re: О программировании AVR на C++.
Сообщение от neiver
|
Уже не плохо.
|
А на заказ можете? Все то же самое:
Код:
|
int main()
{
Pin pin1(PORTA, 1);
pin1.SetDirWrite();
pin1.Set();
pin1.Clear();
pin1.Togle();
pin1.SetDirRead();
if(pin1.IsSet())
pin1.Clear();
} |
Кристалл мега 128. Порт F, вывод PF3 (58 ножка)
Хочу увидеть листинг.
|
|
|
|
20.03.2010, 00:17
|
|
Временная регистрация
Регистрация: 30.07.2007
Сообщений: 51
Сказал спасибо: 1
Сказали Спасибо 12 раз(а) в 7 сообщении(ях)
|
Re: О программировании AVR на C++.
Для kison:
Код:
|
.global main
.type main, @function
main:
.LFB55:
.LSM0:
/* prologue: function */
/* frame size = 0 */
.LBB20:
.LBB21:
.LBB22:
.LSM1:
lds r24,97
ori r24,lo8(8)
sts 97,r24
.LBE22:
.LBE21:
.LBB23:
.LBB24:
.LSM2:
lds r24,98
ori r24,lo8(8)
sts 98,r24
.LBE24:
.LBE23:
.LBB25:
.LBB26:
.LSM3:
lds r24,98
andi r24,lo8(-9)
sts 98,r24
.LBE26:
.LBE25:
.LBB27:
.LBB28:
.LSM4:
lds r24,98
ldi r25,lo8(8)
eor r24,r25
sts 98,r24
.LBE28:
.LBE27:
.LBB29:
.LBB30:
.LSM5:
lds r24,97
andi r24,lo8(-9)
sts 97,r24
.LBE30:
.LBE29:
.LBB31:
.LBB32:
.LSM6:
lds r24,96
.LBE32:
.LBE31:
.LSM7:
sbrs r24,3
rjmp .L2
.LBB33:
.LBB34:
.LSM8:
lds r24,98
andi r24,lo8(-9)
sts 98,r24
.L2:
.LBE34:
.LBE33:
.LBE20:
.LSM9:
ldi r24,lo8(0)
ldi r25,hi8(0)
/* epilogue start */
ret |
|
|
|
|
20.03.2010, 00:19
|
|
Временная регистрация
Регистрация: 30.07.2007
Сообщений: 51
Сказал спасибо: 1
Сказали Спасибо 12 раз(а) в 7 сообщении(ях)
|
Re: О программировании AVR на C++.
А теперь попробуем объявить pin1 глобальной переменной:
Код:
|
Pin pin1(PORTA, 1);
int main()
{
pin1.SetDirWrite();
pin1.Set();
pin1.Clear();
pin1.Togle();
pin1.SetDirRead();
if(pin1.IsSet())
pin1.Clear();
} |
И…..
И pin1.SetDirWrite() превращается в:
Код:
|
lds r30,pin1
lds r31,(pin1)+1
ld r18,-Z
ldi r20,lo8(1)
ldi r21,hi8(1)
movw r24,r20
lds r0,pin1+2
rjmp 2f
1: lsl r24
rol r25
2: dec r0
brpl 1b
or r18,r24
st Z,r18 |
Этот код работает с портом через указатель – медленно и громоздко.
Глобальные переменные защищены от посягательств оптимизатора.
Здесь нужен какой-то другой подход…
|
|
|
|
20.03.2010, 00:25
|
|
Временная регистрация
Регистрация: 30.07.2007
Сообщений: 51
Сказал спасибо: 1
Сказали Спасибо 12 раз(а) в 7 сообщении(ях)
|
Re: О программировании AVR на C++.
Сообщение от kison
|
он может инлайнить функции подчистую, не оставляя копий ее на случай вызова из других модулей.
|
А если у него нет доступа к ее исходному коду, только объектный файл?
Незаинлайнит.
|
|
|
|
Ваши права в разделе
|
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения
HTML код Выкл.
|
|
|
Часовой пояс GMT +4, время: 19:13.
|
|