Tag Archives: Язык Си

Реализация функции задержки меньше 1мс на FreeRTOS с помощью таймера и Task Notification

Есть в FreeRTOS встроенная функция vTaskDelay которая на N тиков системного таймера отдает управление другим задачам. В результате можно делать тупые циклы с ожиданием чего-либо и не париться по поводу процессорного времени. Очень удобно. Но есть проблема, минимальное время которая эта задержка может организовать составляет 1 тик системного таймера. Обычно это около 1 миллисекунда. Но иногда требуются задержки меньше. Да, можно повысить скорость тиков системного таймера. Даже в 10 или 100 раз, при 72 Мегагерцах какого-нибудь STM32 это вполне себе работает. Правда на переключение контекста будет уходить больше процессорного времени. Впрочем, всегда можно работать в кооперативном режиме, а не вытесняющем. Тут в принципе нет вытеснения, а управление передаешь вручную через функцию taskYIELD или любую другую с ожиданием. Те же Delay, Очереди, Семафоры и мало ли что еще.

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

Таймер взял самый бомжовскйи. На STM32F103C8T6 нет, к сожалению, Basic Timers ТIM6 и ТIM7 — это самые простые, самые примитивные считалки. В них нет ни завата, ни регистров сравнения для ШИМ и их не жалко отдать под такое дело, но они есть либо в самых жирных, либо в самых нищих вариация серии F10x. В моей нету. Ну окей, возьмем другой таймер. Общего назначения. Я взял Timer 2.

Настраиваются таймеры элеменатрно, тут не нужны даже никакие библиотеки. Главное понять откуда берется тактирование, какая величина и что надо включить. Смотрим в RM0008 структуру тактирования таймера 2. Раздел 7.2 Clocks

У меня в системе предделители обычно настроены на максимальную частоту и на этой шине 36 мегагерц. Тактирование нашего дополнительного таймера я хочу видеть с частотой 10 килогерц. Та что делим 36 мегагерц на 36, а потом еще на 100. И получим искомое.

(далее…)

Read More »

MISRA C

MISRA — это Motor Industry Software Reliability Association группа разработавшая стандарт языка Си для ответственных встраиваемых систем. Для таких как автомобильная или авиационная техника, разных опасных производств. Где цена программной ошибки может быть очень высока. Это набор ужесточающих правил для языка Си. Есть версия и для С++, но не суть. Вся прелесть в том, что MISRA C поддерживается некоторыми компиляторами и если добавить соответствующие ключи, то они будут проверять код еще и на соответствие MISRA C, например IAR. Есть и отдельные анализаторы кода на соответствие стандарту.

Ну, а сама суть стандарта в том, чтобы осложнить жизнь разным быдлокодерам и любителям красиво повыебываться в коде :) Там, например, запрещено пихать в заголовок for все кроме стандартных опций жизнеобеспечения цикла. Запрещены стандартные типы вроде char или int которые зависят от архитектуры. Только жестко описанные типы вроде uint8_t. Запрещены switch без default, а case и if/else без заключения содержимого в { } блоки. Запрещена указательная арифметика и все в таком духе. Таких правил там штук под двести. Настоящий концлагерь для кодера, где ничего нельзя :) Зато сделать тупую ошибку становится сложней.

Так вот, к чему я. Недавно наткнулся на хороший перевод правил MISRA C для IAR. От Андрея Шлеенкова. Вот, делюсь :)

Read More »

Управление большим количеством светодиодов через 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 »