Tag Archives: Язык Си

Управление большим количеством светодиодов через Binary Angle Modulation

Вот приспичило вам сделать себе могучую светодиодную хреновину, чтобы моргала и переливалась. Да еще в RGB и плавненько так. Собрали вы это дело, поглядели на количество каналов которыми нужно рулить и призадумались…
 

▌А что не так с ШИМ?
Да все с ним хорошо, только аппаратных каналов обычно всего несколько штук. А программный ШИМ имеет ряд недостатков. Да, можно взять и на базе алгоритма управления кучей сервомашинок, используя всего один таймер собрать многоканальный ШИМ, но сколько у нас будет вызовов прерываний?
 


 

Каждый отдельный фронт потребует своего прерывания на смену уровня. А представьте, что у нас этих каналов будет не 4, а 40? Или 400? Да контроллер из прерываний вылезать не будет. Прерывания будут налезать друг на друга, порождая джиттер. Не говоря уже о том, что все эти каналы надо будет при любом изменении скважности заново сортировать по длительности. В общем, тупилово будет еще то.
 

▌Нас спасет BAM
Но решение есть. Зовется этот метод BAM. Суть его в том, что мы включаем нагрузку импульсами, поразрядно, с длительностью равной весу разряда.

 

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

 

Интегрируется все аналогично обычному ШИМу. Но есть ряд нюансов:

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

 
(далее…)

Read More »

AVR Studio 4.19 и AVR Toolchain

Обновился я тут недавно до AVR Studio 4.19 и спустя некоторое время обнаружил, что все проекты, что были на Си, отказываются компилироваться. Либо компилятся, но не отлаживаются. После недолгого выяснения и теребления народа из сообщества выяснилось, что WinAVR как самостоятельный проект ныне не существует, а полностью перешел под крыло Atmel и ныне зовется AVR Toolchain. Ставить его нужно отдельно с сайта Atmel, предварительно снеся старый WinAVR, поверх студии 4.19. Ставится он теперь в дебри папки студии. В целом ничего не изменилось, по крайней мере все старые проекты скомпилились без проблем.

Чтобы вам не париться с регистрацией на сайте Atmel я бросил пару файликов:

Read More »

Организация древовидного меню

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

Попутно, из проекта в проект, развивалась своя псевдоОС — таймеры, события, диспетчеры.
Я ее полностью писал на си, оптимизацией и вылизыванием еще не занимался.

Перебирая разные системы, наткнулся на MicroMenu:

Попробуем разобрать ее на части и прикрутить к системе.
(далее…)

Read More »

AVR. Учебный Курс. Кусочно-линейная аппроксимация

Часто бывает так, что приходится обрабатывать жутко нелинейные величины, задаваемые каким-нибудь извращенным законом. Простейший пример — датчики расстояния SHARP GP2D12. Только поглядите на его характеристику:

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

Вариантов тут, на самом деле, всего два. Первый очень быстрый, но жадный до памяти ПЗУ — табличный.
То есть мы просто берем и эту кривулину расписываем в памяти. Например, у нас с 8ми разрядного АЦП идет значение напряжения от 0 до 256, а мы на каждое значение создаем в памяти значение расстояния. Тогда с АЦП сразу гоним в индекс массива, где эти значения хранятся и получаем расстояние:

L=Curve[ADCH];

Недостаток один — прожорливость до памяти, растущая в геометрической прогрессии с ростом разрядности АЦП.

Вариант второй — написать функцию, переводящую одну величину в другую. (далее…)

Read More »

AVR. Учебный Курс. Работа на прерываниях

Одним из серьезных достоинств контроллеров AVR является дикое количество прерываний. Фактически, каждое периферийное устройство имеет по вектору, а то и не по одному. Так что на прерываних можно замутить кучу параллельных процессов. Работа на прерываниях является одним из способов сделать псевдо многозадачную среду.

 
Идеально для передачи данных и обработки длительных процессов.

 
Для примера покажу буфферизированный вывод данных по USART на прерываниях.

 
В прошлых примерах был такой код:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Отправка строки
void SendStr(char *string)
{
while (*string!='\0')
	{
	SendByte(*string);
	string++;
	}
}
 
// Отправка одного символа
void SendByte(char byte)
{
while(!(UCSRA & (1< <UDRE)));
UDR=byte;
}

 
Данный метод, очевидно, совершенно неэффективен. Дело в том, что у нас тут есть тупейшее ожидание события — поднятие флага готовности USART. А это зависит, в первую очередь, от скорости передачи данных. Например, на скорости 600 бод передача каких то 600 знаков будет длиться 9 секунд, блокируя работу всей программы, что ни в какие ворота не лезет.

 
Как быть?
(далее…)

Read More »

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

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

Итак, начну по порядку.
Инфа, любая инфа (команды, данные) лежит в памяти по ячейкам. У каждой ячейки есть порядковый номер — адрес.

Мы можем напрямую сказать процессору — возьми данные из ячейки с адресом 0xA0 и положи его в ячейку с адресом 0x11. Это будет прямая адресация. Здесь адреса 0xA0 и 0x11 содержатся напрямую в машинном коде. Это очень быстро, просто и не требует никаких дополнительных телодвижений. Один минус — адреса 0xA0 и 0x11 нельзя изменить, как мы их впишем в код, так они там и останутся.

Но может быть и другой способ. Когда у нас есть еще две ячейки памяти. Например, А и Б в которые мы предварительно положим числа 0xA0 и 0x11 соответственно. И тогда предыдущая операция будет выглядеть так.

Возьми число из ячейки адрес который лежит в А и положи в ячейку адрес которой узнаешь из Б.

Результат тот же, но возникло множество дополнительных телодвижений. Во первых положить первоначальные адреса 0xA0 и 0x11 в ячейки А и Б. Потом, при совершеннии операции, используая данные ячеек А и Б как адреса, взять уже оттуда нужные нам данные и совершить обмен.

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

А один и тот же кусок кода становится универсальным. Он может работать с любыми данными адреса которых нам укажут переменные А и Б.

А сами эти переменные и будут указателями.
(далее…)

Read More »

AVR. Учебный Курс. Программирование на Си. Атомарные операции.

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

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

Итак, есть у нас такой код (не ищите в нем практического смысла, я его просто как пример работы с разными операндами написал):

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
volatile char flag_byte; 	
/*Просто флаговая переменная, на разные случаи жизни. Разные события там 
выставляют флажки, опираясь на которые потом работает логика программы. 
Один из способов организации псевдомногозадачности. Когда у нас главный цикл 
анализирует флажки и делает переходы на подпрограммы, а вызов подпрограмм 
осуществляется не напрямую, а установкой соответствющих флажков. Своего 
рода диспетчер переходов. О такой архитектуре я скоро расскажу)*/
 
ISR (USART_RXC_vect)	// Обработчик прерывания, самый обычный.
{
flag_byte|=1< <rcv_buff;
...
...
}
 
int main (void)		// Главная программа
{
INIT_ALL();
SEI();
...
...
...
TCCR0A  	|=1<<WGM01;
...
flag_byte 	|=1<<options;
...
PORTB 	&=~(2<&lt;1);
...
}

(далее…)

Read More »

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

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

Зашиваю все через AVR Prog в Pinboard и смотрю на поведение LED1 и LED2.

LED1 мигает как и задумано, но стоит мне попытаться зажечь LED2 отправкой с терминала «1», как первый диод гаснет. И наоборот — зажженый диод LED2 гаснет вместе с первым. Бага! Причем жирная такая. Рассмотрим откуда она взялась.

Вот код мигания первым диодом:

1
2
3
4
LED_PORT=1< <LED1;
_delay_ms(1000);
LED_PORT=0<<LED1; 
_delay_ms(1000);

А вот код работы с вторым диодом:

1
2
3
4
5
6
switch(UDR)
	{
	case '1': LED_PORT = 1< <LED2; break;
	case '0': LED_PORT = 0<<LED2; break;
	default: break;
	}

Как видишь, тут мы пишем в один и тот же порт, но вот только биты разные. Но нельзя вот так просто через операцию «=» изменить один бит! (только если мы используем битовые поля, о них я расскажу позже). Так что операция идет с целым байтом, и в LED_PORT поочередно записывается число 00100000 (1<<LED2) и 00010000 (1<<LED1), перезаписывая друг друга. Поэтому когда происходит запись одного значения мы теряем прердыдущее. А 0<<LED2 это по факту просто 0, потому что как ноль по байту не двигай нулем он и останется .

Особенно часто эта ошибка всплывает у начинающих когда они пытаются инициализировать периферию не всю сразу, а по мере надобности. Включил, например, прерывания от таймера 1, а когда активизировал таймер 2, то затер биты таймера1. Как итог — результат не соответствует ожиданиям.

Как быть? Тут нам помогут битовые маски. Помнишь логические операции AND/OR/NOT/XOR?
(далее…)

Read More »

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

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

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

Когда приходит прерывание то контроллер завершит текущую команду (машинную инструкцию!) сразу же кинется выполнять процедуру обработки прерывания, а как выполнит, то вернется к прерваной фоновой программе.

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

Поскольку прерывание приходит ВНЕЗАПНО, а у нас могут быть несохраненные данные, то обработчик их должен сохранить и при выходе в фоновую программу вернуть все как было.

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

Read More »

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

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

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

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

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

(далее…)

Read More »

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

Я не раз и не два говорил, что изучение МК надо начинать с ассемблера. Этому был посвящен целый курс на сайте (правда он не очень последовательный, но постепенно я его причесываю до адекватного вида) . Да, это сложно, результат будет не в первый день, но зато ты научишься понимать что происходит у тебя в контроллере. Будешь знать как это работает, а не по обезьяньий копировать чужие исходники и пытаться понять почему оно вдруг перестало работать. Кроме того, Си намного проще натворить быдлокода, который вылезет вилами в самый неподходящий момент.

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

С другой стороны у Си сильная сторона это переносимость кода. Если, конечно, писать все правильно. Разделяя алгоритмы работы и их железные реализации в разные части проекта. Тогда для переноса алгоритма в другой МК достаточно будет переписать только интерфейсный слой, где прописано все обращение к железу, а весь рабочий код оставить как есть. И, конечно же, читаемость. Сишный исходник проще понять с первого взгляда (хотя.. мне, например, уже пофигу на что фтыкать — хоть си, хоть асм :) ), но, опять же, если правильно все написать. Этим моментам я тоже буду уделять внимание.

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

Дальше все будет разжевано буквально по шагам для старта с полного нуля. (далее…)

Read More »