Светодиодные фонари и световые приборы. Всё о светотехнике.
Вернуться   Форум FONAREVKA.RU Лаборатория Электроника и схемотехника Электроника Микроконтроллеры
Расширенный поиск
Забыли пароль? Регистрация

  • О нашем проекте
  • Светотехника и световые приборы
  • Правила форума
Проект FONAREVKA.RU специализируется на предоставлении всей необходимой информации по светотехнике:

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

Если у вас есть вопросы по выбору фонарей, аккумуляторов и зарядных устройств ознакомьтесь с FAQ от наших экспертов:

F.A.Q. по выбору фонарей различных типов;
F.A.Q. по выбору аккумуляторов;
F.A.Q. по выбору зарядных устройств.
Ответ  Создать новую тему
Просмотров в теме 163386   Ответов в теме 119   Подписчиков на тему 0   Добавили в закладки 0
Опции темы Поиск в этой теме
Старый 19.10.2010, 15:59 Автор темы   1
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию Основы программирования контроллеров AVR на Си

По просьбам посетителей форума пишу мини-учебник по началам программирования.

Предполагается, что на компьютере уже установлен компилятор Си для AVR (avr-gcc, он же в Windows называется WinAVR) с прилагаемой к нему стандартной библиотекой avr-libc. Также предполагается, что читатель умеет запускать его и делать из файла *.c файл *.hex, а потом прошивать его в микроконтроллер. Все это здесь объясняться не будет, а рассмотрим мы только написание собственно программы, то есть файла на Си.

1. Основы синтаксиса Си. Простейшие программы.

Начнем с того, что напишем на Си программу, которая ничего не делает. Вот она:
Код:
int main(void)
{
    /* не делаем ничего */
    return 0;
}
Здесь мы уже сталкиваемся с таким понятием языка Си, как функция. В языке Си функция - примерно то же самое, что функция в математике, а в других языках она может называться подпрограммой или процедурой. Функция в Си является кусочком программы, содержащим некоторую последовательность действий. В этом фрагменте кода мы объявляем функцию под названием main, тело которой состоит из единственной строки return 0;, требуемой лишь стандартом Си. Позднее мы поймем, что это значит, а пока просто запомним, что:
основное тело программы в Си пишется в функции main

Знаками /* ... */ в Си обозначаются комментарии.

Теперь следовало бы написать программу "Hello World". Но у нас нет экрана, куда можно было бы вывести такое сообщение. Поэтому мы просто включим светодиод. Подключим светодиод к выводу PORTB0 нашего микроконтроллера через резистор так, чтобы он горел при появлении единицы на этом выводе. Программа, включающая его, будет выглядеть так:
Код:
#include <avr/io.h>

int main()
{
    DDRB = 0x01;
    PORTB = 0x01;
    return 0;
}
Первая строка #include означает подключение библиотечного файла к нашей программе. В программе мы воспользовались портами ввода-вывода процессора. Это не часть языка Си, это библиотечные имена, и содержатся они в файле <avr/io.h> стандартной библиотеки.

2. Работа с портами ввода-вывода AVR.

Для каждого порта микроконтроллера в библиотеке <avr/io.h> определены три специальных имени, через которые с ним можно работать из программы. Это PORTx, PINx и DDRx, где x - какая-то буква. Например, DDRB и PORTB. Каждый из них представляет собой 1 байт (число из 8 битов, то есть от 0 до 255), каждый бит соответствует одному из проводов порта (нумерация битов справа налево от 0 до 7).

Регистр DDR отвечает за направление порта: 0 - вход, 1 - выход.
Регистр PORT в режиме выхода просто выдает 0 или 1 на соответствующий вывод.
Регистр PORT в режиме входа включает (1) или выключает (0) резистор подтяжки вывода на плюс.
Регистр PIN во всех режимах отражает реальное состояние нулей и единиц на ножках контроллера. Он только для чтения, пытаться изменять его бесполезно. У порта на выходе его содержимое просто повторяет содержимое регистра PORT (если только на выводах контроллера не КЗ), а у порта на входе это и есть регистр, через который читают входы.

Обращаются к регистрам из Си просто - подставляют их в арифметические выражения. Чтобы зажечь светодиод на PORTB0, нам надо сделать две вещи - переключить PORTB0 в режим выхода (поставить DDRB в двоичное 00000001, то есть B7-B1 входы, а B0 выход), а потом в PORTB поднять 0-1 бит (поставить тоже в 00000001, то есть на B7-B1 выключить резисторы подтяжки [они входы], а B0 сделать единицей). Оба числа в десятичной системе - просто 1. Можно было бы написать:
Код:
DDRB = 1;
PORTB = 1;
и это работало бы. Но из двоичной системы в десятичную переводить в уме трудно. Гораздо проще переводить в шестнадцатеричную - это легко научиться делать в уме. Поэтому мы в шестнадцатеричной системе и запишем: 01. Чтобы Си понял, что это не десятичная система, нужно перед числом написать специальный магический код "0x" - признак 16-ричности. И получается:
Код:
DDRB = 0x01;
PORTB = 0x01;
Подставив это в main, получим программу, которая написана выше.

Если мы хотим зажечь два светодиода сразу, на PORTB0 и на PORTB7, то мы напишем это так:
Код:
int main()
{
    DDRB = 0x81;
    PORTB = 0x81;
    return 0;
}
Упражнение. Почему эта программа зажигает PORTB0 и PORTB7?
Упражнение. Что надо написать, чтобы зажечь PORTB4, PORTB0 и PORTB7 одновременно?
Gall вне форума   Ответить с цитированием Вверх
Старый 19.10.2010, 17:42 Автор темы   2
Gall
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

3. Условия и циклы.

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

Пусть на PORTB подключены 8 светодиодов, а на PORTA - 8 кнопок на землю (при отпущенной кнопке на выводе 1 за счет резистора подтяжки, при нажатой - 0). Светодиоды подключены с выводов на плюс питания через резисторы - при 0 горят, при 1 гаснут. Задача: сделать, чтобы при нажатии кнопки соответствующий светодиод загорался, а при отпускании - гас.

Совершенно очевидно, что основная часть кода программы будет выглядеть так:
Код:
PORTB = PINA;
Эта строчка сделает именно то, что надо - прочитает 8 входных битов от 8 кнопок и подаст их на 8 выходов. Прежде чем ее выполнить, нам потребуется проинициализировать железо - сделать PORTB выходом, а PORTA входом, причем включить на PORTA подтяжку. Это просто:
Код:
DDRB = 0xFF;
DDRA = 0xFF;
PORTA = 0xFF;
Напоминаю, что 0xFF (шестнадцатеричное FF) - это 11111111 в двоичной, то есть все единицы. Теперь надо сделать, чтобы строка PORTB = PINA повторялась бесконечно. Но как? А очень просто:

Код:
#include <avr/io.h>

int main()
{

    DDRB = 0xFF;
    DDRA = 0xFF;
    PORTA = 0xFF;

    while (1)
    {
         PORTB = PINA;
    }

    return 0;
}
Оператор while - это оператор цикла. В круглых скобках указывается условие повторения, а затем в фигурных - тело цикла. (Тело цикла из единственной команды разрешается указывать без фигурных скобок, но мы так делать не будем.) Условие в данном случае - "всегда да", то есть "1". Условие "никогда" записывалось бы "0" (но зачем?), а еще можно было бы записать другие условия. Подробнее условия мы рассмотрим, когда будем разбирать оператор if.

Следующая задача. В той же схеме переделаем прошивку так, чтобы при нажатии любой кнопки все светодиоды загорались, а при отпускании - гасли. Зажечь все - это PORTB = 0xFF, а погасить - это PORTB = 0x00. Если нажата хотя бы одна кнопка, то PINA будет не равен 0xFF.

В Си условия "больше", "меньше" и т.п. пишутся так:

< меньше
> больше
>= больше или равно
<= меньше или равно
== равно
!= не равно

Обратите внимание, "равно" пишется двумя знаками равенства. Один знак равенства - это присваивание, а не сравнение! Это частая ошибка.

Если надо составить сложное условие из простых, то используются операторы:

&& и
|| или
! не

а также простые круглые скобки, как в арифметике, для указания порядка действий. Например: ! (x > 3) || (y < 4) означает "x не больше 3 или y меньше 4".

И еще одна особенность: если в качестве условия подставить просто число, без всяких сравнений, это будет то же самое, что "число не равно 0". Именно этим мы пользовались, когда мы писали while (1) - это было то же самое, что написать while (1 != 0).

Осталось понять, как пишется "если". Очень просто - if. Программа выглядит теперь так:

Код:
#include <avr/io.h>

int main()
{

    DDRB = 0xFF;
    DDRA = 0xFF;
    PORTA = 0xFF;

    while (1)
    {
        if (PINA != 0xFF)
        {
            PORTB = 0x00;
        }
        else
        {
            PORTB = 0xFF;
        }
    }

    return 0;
}
Тут все просто: "если, то, иначе". Если нам не надо "иначе", то else и скобок после него не будет.
Gall вне форума   Ответить с цитированием Вверх
Старый 19.10.2010, 18:30   3
SviMik
Завсегдатай Фонарёвки
 
Аватар для SviMik
 
Регистрация: 26.02.2010
Последняя активность: 18.08.2015 18:47
Сообщений: 810
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

Отправить сообщение для SviMik с помощью ICQ Отправить сообщение для SviMik с помощью MSN
По умолчанию

Получилось немного в кучу В идеале, "синтаксис Си" и "вещи, специфичные для МК", надо написать отдельными пособиями. И то и другое интересно, но кто уже имеет представление о самом Си, тому будет немного скучно читать про циклы и условия
SviMik вне форума   Ответить с цитированием Вверх
Старый 19.10.2010, 18:36 Автор темы   4
Gall
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

Цитата:
Посмотреть сообщение Сообщение от SviMik :
Получилось немного в кучу В идеале, "синтаксис Си" и "вещи, специфичные для МК", надо написать отдельными пособиями. И то и другое интересно, но кто уже имеет представление о самом Си, тому будет немного скучно читать про циклы и условия
Тому, кто знаком с Си, вместо всего этого достаточно сказать лишь:

#include <avr/io.h>
Регистры периферии отбражаются на
volatile uint8_t-переменные, имена которых совпадают с именами регистров в даташите микроконтроллера.
avr-gcc -mmcu=процессор -Os -Wall -Wextra --pedantic -o file.bin file.c
avr-objcopy -O ihex file.bin file.hex


Ну и про прерывания отдельно объяснить.

Я решил специально написать одновременно про AVR и про Си, потому что многим требуется "по-быстрому" написать прошивку, не вникая в тонкости языка. Я хочу дать рецепт, как делать не содержащие сложных алгоритмов прошивки, способные мигать светодиодами, делать ШИМ, крутить шаговые двигатели и т.п. Для более сложных задач вроде взаимодействия с компьютером знания одного лишь Си все равно недостаточно, там уже алгоритмы всерьез задействуются.
Gall вне форума   Ответить с цитированием Вверх
Старый 07.11.2010, 18:35 Автор темы   5
Gall
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

4. Переменные.

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

В языке Си переменная обязана иметь тип. Тип переменной указывает, какого рода хранится информация в ней. Переменные бывают беззнаковые и знаковые; беззнаковые хранят число в виде просто скольки-то битов, а знаковые используют дополнительный код.

Основные типы переменных:
Код:
char - целое число для хранения кода символа, обычно 1 байт (8 бит)
short - маленькое целое число, обычно 2 байта (16 бит)
int - обычное целое число, обычно 2-4 байта (16-32 бита)
long - большое целое число, обычно 32 бита
long long - очень длинное число (64 бита), поддерживается не всеми системами
Любой из названных типов может быть как знаковым (signed), так и беззнаковым (unsigned). По умолчанию тип знаковый.

Простой пример работы с переменными:
Код:
int a;
int b;
int c;
a = 3;
b = 4;
c = a + b;
Арифметические операции пишутся как обычно: + - * / (четыре действия), % (остаток от деления). Следует иметь в виду, что деление округляет целые числа вниз. Кроме этих операций, есть еще побитовые & (и), | (или), << (сдвиг влево), >> (сдвиг вправо) и др.

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

Нетрудно заметить, что стандарт Си не гарантирует точных размеров переменных. Это может быть неудобно, поэтому в стандартной библиотеке существуют предопределенные типы фиксированных размеров. Для их использования надо включить stdint.h.
Код:
#include <stdint.h>
int8_t - знаковый 8 бит
uint8_t - беззнаковый 8 бит ("просто байт")
int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t - числа от 16 до 64 бит
Там же объявлены и другие типы, но мы пока не будем их затрагивать. Еще два важных типа объявлены в stddef.h:
Код:
#include <stddef.h>
size_t - беззнаковое число достаточного размера,
        чтобы в него влез размер любого блока памяти
ssize_t - то же, знаковое
Gall вне форума   Ответить с цитированием Вверх
Старый 07.11.2010, 18:47 Автор темы   6
Gall
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

5. Оператор asm.

Попробуем помигать светодиодом.
Код:
#include <avr/io.h>

int main()
{
    DDRB = 0x01;
    while (1)
    {
        PORTB = 0x01;
        PORTB = 0x00;
    }
    return 0; /* программа никогда не попадет сюда */
}
Зажигаем светодиод и снова гасим. Пробуем запустить. Оп-па... светодиод горит "вполнакала"! Посмотрим осциллографом - нет, мигает, но очень быстро (около 300 кГц при тактовой частоте процессора 1 МГц). Надо поставить задержку.

Мы пока не хотим учиться работать с аппаратными таймерами AVR, предназначенными для этой задержки, поэтому просто заставим процессор ничего не делать (точнее, делать нечто бесполезное). Например, так:
Код:
#include <avr/io.h>

int main()
{
    unsigned int c;
    DDRB = 0x01;
    while (1)
    {
        PORTB = 0x01;
        c = 0;
        while (c < 50000)
        {
            c = c + 1;
        }  
        PORTB = 0x00;
        c = 0;
        while (c < 50000)
        {
            c = c + 1;
        }
    }
    return 0; /* программа никогда не попадет сюда */
}
С выключенной оптимизацией это работает. Но если оптимизацию не выключать, будет сюрприз - компилятор удалит из программы "бесполезный" код, и задержки не станет. Надо поставить в цикл что-нибудь, например ассемблерную команду "nop", чтобы они не оптимизировались. Скажем, вот так:
Код:
#include <avr/io.h>

int main()
{
    unsigned int c;
    DDRB = 0x01;
    while (1)
    {
        PORTB = 0x01;
        c = 0;
        while (c < 50000)
        {
            asm volatile ("nop");
            c = c + 1;
        }  
        PORTB = 0x00;
        c = 0;
        while (c < 50000)
        {
            asm volatile ("nop");
            c = c + 1;
        }
    }
    return 0; /* программа никогда не попадет сюда */
}
Теперь все работает как надо.

Оператор asm позволяет вставить в программу кусочек, написанный на ассемблере. Слово "volatile" здесь означает "не оптимизировать ассемблерную вставку". В данном случае мы вставили просто nop (команду ничегонеделания), но можно вставить и довольно длинный код. Ассемблерные вставки в Си достаточно хитрые - в них можно доверить компилятору самому выбрать свободные регистры и даже небольшую оптимизацию. На практике же asm используется только для вызова таких вещей, как nop, sleep, cli и sei, недоступных из Си напрямую.
Gall вне форума   Ответить с цитированием Вверх
Старый 07.11.2010, 18:54 Автор темы   7
Gall
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

6. Цикл for.

В нашей программе дважды встретилась конструкция:
Код:
x = 0;
while (x < 50000)
{
    ...
    x = x + 1;
}
Язык Си позволяет записать то же самое короче. Во-первых, вместо
Код:
x = x + 1;
можно написать
Код:
++x;
, что означает то же самое - прибавление единички (инкремент). Вычитание единички можно записать --x, а еще есть операторы x++ и x--, которые делают примерно то же самое (отличия разберем позже). Получаем:
Код:
x = 0;
while (x < 50000)
{
    ...
    ++x;
}
Но можно записать еще короче. В Си есть замечательный оператор for, который пишется так:
for (с чего начать ; до каких пор продолжать ; что делать после каждого прохода)
С его использованием код будет выглядеть следующим образом:
Код:
for (x = 0; x < 50000; ++x)
{
   ...
}
Знающие Бейсик или Паскаль могут считать эту запись аналогом "for x:= 0 to 49999".
Gall вне форума   Ответить с цитированием Вверх
Старый 07.11.2010, 19:01   8
SviMik
Завсегдатай Фонарёвки
 
Аватар для SviMik
 
Регистрация: 26.02.2010
Последняя активность: 18.08.2015 18:47
Сообщений: 810
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

Отправить сообщение для SviMik с помощью ICQ Отправить сообщение для SviMik с помощью MSN
По умолчанию

Расскажи, как Си хранит переменные. Обычно я видел листинги, где на вызове каждой функции, он вытаскивает переменные из озу списком (иногда длинным) команд LDS, а в конце пихает обратно списком команд STS.

Слышал (но не видел ), что он может и в регистрах хранить. Можно обьявить какую-то переменную так, чтобы она всегда была в регистре? (например, если требуется быстрый доступ к ней).
SviMik вне форума   Ответить с цитированием Вверх
Старый 07.11.2010, 19:26 Автор темы   9
Gall
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

Если в компиляторе включена оптимизация (флаг -O2 или -Os в командной строке gcc), то компилятор автоматически оптимизирует размещение переменных. Обычно это означает, что "нужные" переменные оказываются в регистрах, а "ненужные" не создаются вообще. ("Ненужной" становится переменная, которая используется исключительно как промежуточный результат вычислений, и от которой компилятор в состоянии избавиться - например, заменив на константу). Ячейки оперативной памяти и LDS/STS используются тогда, когда либо не хватает регистров, либо переменная по логике программы обязана находиться именно в памяти (например, если к ней применяют взятие адреса и обращение через указатели, если переменная объявлена со словом volatile, является большим массивом или структурой и т.п.).

Если в компиляторе полностью выключена оптимизация (опция -O0 ) или компилятор просто "дебильный" (gcc таковым не является), тогда действительно все переменные попадут в память.

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

Я тестировал возможности gcc/g++ по размещению переменных в регистрах. Даже подобная конструкция:
Код:
char c[128];
char p;
c[32] = 12;
p = c[32];
компилируется просто в "ldi Rxx, 12", где Rxx - регистр, выделенный для p. Массив c не создается совсем, поэтому такой код компилируется даже для ATtiny без ОЗУ.

Еще очень важно при работе с переменными правильно указывать типы. А именно, использовать каждый тип строго по назначению. Неоптимальности легко возникают на конструкциях вида
Код:
int x;
unsigned char y;
uint8_t z = x + y;
особенно при сравнении чисел разного размера или беззнакового числа со знаковым. Последнее связано с особенностями сравнения дополнительного кода.
Gall вне форума   Ответить с цитированием Вверх
Старый 07.11.2010, 19:35 Автор темы   10
Gall
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

Реальный пример того, как работает оптимизация переменных. Исходный текст:
Код:
#include <avr/io.h>

int main()
{
    unsigned c[10000];
    unsigned y;
    c[15] = 0xFF;
    y = c[15];
    DDRA = y;
    PORTA = y;
    while (1) { }
    return 0;
}
Обратите внимание, здесь создается массив размером 20000 байт, который не влез бы в память ни одного из недорогих микроконтроллеров. Более того, здесь идет очень грязная работа с 16-битными знаковыми переменными. Компилируем в Ассемблер:
Код:
avr-gcc -mmcu=atmega16 -Os -S avr.c
и видим:
Код:
.file	"avr.c"
__SREG__ = 0x3f
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__CCP__  = 0x34
__tmp_reg__ = 0
__zero_reg__ = 1
	.text
.global	main
	.type	main, @function
main:
/* prologue: function */
/* frame size = 0 */
	ldi r24,lo8(-1)
	out 58-32,r24
	out 59-32,r24
.L2:
	rjmp .L2
	.size	main, .-main
что и требовалось доказать. Всего 4 команды, всего 1 регистр.
Gall вне форума   Ответить с цитированием Вверх
Старый 07.11.2010, 19:43   11
SviMik
Завсегдатай Фонарёвки
 
Аватар для SviMik
 
Регистрация: 26.02.2010
Последняя активность: 18.08.2015 18:47
Сообщений: 810
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

Отправить сообщение для SviMik с помощью ICQ Отправить сообщение для SviMik с помощью MSN
По умолчанию

Цитата:
Ячейки оперативной памяти и LDS/STS используются тогда, когда либо не хватает регистров
А по какой логике он тогда определяет, какие следует уместить в регистрах, а какие уже пойдут в озу?

Цитата:
unsigned c[10000];
unsigned y;
Цитата:
с 16-битными знаковыми переменными
Хм, а где знаковые?
SviMik вне форума   Ответить с цитированием Вверх
Старый 08.11.2010, 21:42 Автор темы   12
Gall
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

Цитата:
Посмотреть сообщение Сообщение от SviMik :
А по какой логике он тогда определяет, какие следует уместить в регистрах, а какие уже пойдут в озу?
По частоте использования, по размеру и т.п. Он пробует разные варианты размещения и выбирает тот, в котором получается меньше лишних команд. Общее правило - короткоживущие переменные идут в регистры, долгоживущие - в память.

Цитата:
Посмотреть сообщение Сообщение от SviMik :
Хм, а где знаковые?
Моя ошибка - не тот код запостил. От замены unsigned на int, char и даже на long ассемблер не меняется.
Gall вне форума   Ответить с цитированием Вверх
Старый 08.11.2010, 22:08   13
SviMik
Завсегдатай Фонарёвки
 
Аватар для SviMik
 
Регистрация: 26.02.2010
Последняя активность: 18.08.2015 18:47
Сообщений: 810
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

Отправить сообщение для SviMik с помощью ICQ Отправить сообщение для SviMik с помощью MSN
По умолчанию

Напиши какой-нидь пример с прерыванием
Если надо сгенерировать сигнал определённой частоты (или какое-то событие со строгим интервалом обрабатывать) - без прерываний по таймеру ну никак не обойтись.
SviMik вне форума   Ответить с цитированием Вверх
Старый 08.11.2010, 23:51   14
lasers_INFERION
Ветеран Фонарёвки
 
Аватар для lasers_INFERION
 
Регистрация: 15.02.2010
Последняя активность: 24.08.2019 11:36
Сообщений: 1362
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

+1. Я отказался от многих прерываний в индикаторе, но от компаратора таймера отказаться нельзя. Нужно в определённые моменты без тормозов тушить светодиоды, а в Си я пока не представляю как работать с прерываниями...
lasers_INFERION вне форума   Ответить с цитированием Вверх
Старый 10.11.2010, 19:34   15
SviMik
Завсегдатай Фонарёвки
 
Аватар для SviMik
 
Регистрация: 26.02.2010
Последняя активность: 18.08.2015 18:47
Сообщений: 810
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

Отправить сообщение для SviMik с помощью ICQ Отправить сообщение для SviMik с помощью MSN
По умолчанию

Я так понимаю, в зависимости от компилятора, есть аж 3 варианта, для каждого надо писать по-своему

Для IAR
Код:
#pragma vector=name_vect
__interrupt void interruptName(void)
{
}
Для WinAvr
Код:
interrupt(VECTOR_NUMBER) interruptName(void)
{
}
Ещё вариант для WinAvr
Код:
#include <avr/io.h>
#include <avr/interrupt.h>

//обработчик прерывания таймера Т0
ISR(TIMER0_COMP_vect)
{
}
SviMik вне форума   Ответить с цитированием Вверх
Старый 10.11.2010, 22:01 Автор темы   16
Gall
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

Мы рассматриваем gcc (WinAVR) по очень простой причине - на других компиляторах не получается такая лихая оптимизация сишной простыни в три команды ассемблера. Вторая причина - все-таки сильно нарушать стандарты ANSI не стоит, а gcc им полностью соответствует.

Полная документация на работу с прерываниями:
http://avr-libc.nongnu.org/...

Вкратце:
Код:
#include <avr/interrupt.h>

ISR(TIMER0_COMP_vect)
{
}
либо EMPTY_INTERRUPT, если нужен пустой обработчик прерывания. При генерации сигналов определенной частоты на прерываниях от таймеров очень удобно использовать именно пустые прерывания - вот так:
Код:
while(1)
{
    PORTA = 0xFF;
    asm volatile ("sleep");
    PORTA = 0x00;
    asm volatile ("sleep");
}
и тогда прерывание выполняет только одну функцию - прерывает sleep. Никакой код в обработчике не нужен.

Вообще же для повышения надежности и контролируемости программы не рекомендуется делать в прерываниях что-либо кроме установки флажков. Напоминаю, что переменные, к которым обращаются из прерываний, обязаны быть объявлены с модификатором volatile во избежание переоптимизации в неработоспособный код.
Код:
volatile uint8_t flag;
 ISR(TIMER0_COMP_vect)
{
    flag = 1;
}
Gall вне форума   Ответить с цитированием Вверх
Старый 10.11.2010, 22:25   17
SviMik
Завсегдатай Фонарёвки
 
Аватар для SviMik
 
Регистрация: 26.02.2010
Последняя активность: 18.08.2015 18:47
Сообщений: 810
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

Отправить сообщение для SviMik с помощью ICQ Отправить сообщение для SviMik с помощью MSN
По умолчанию

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

Цитата:
не рекомендуется делать в прерываниях что-либо кроме установки флажков
Опять же, как я это реализую, если помимо, допустим, пищания спикера, обрабатывается какой-то большой массив данных?
SviMik вне форума   Ответить с цитированием Вверх
Старый 10.11.2010, 23:58 Автор темы   18
Gall
Увлеченный
 
Аватар для Gall
 
Регистрация: 21.06.2010
Последняя активность: 01.08.2015 23:26
Сообщений: 185
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

Цитата:
Посмотреть сообщение Сообщение от SviMik :
Ага, это справедливо, если использовать мк в чисто качестве генератора сигналов
А обычно он паралельно что-то ещё должен делать, а не только спать.
Не проблема абсолютно. "Что-то-еще-делание" ставится непосредственно перед sleep, а вся генерация сигналов - после sleep. И будет как раз как надо.

Цитата:
Посмотреть сообщение Сообщение от SviMik :
Опять же, как я это реализую, если помимо, допустим, пищания спикера, обрабатывается какой-то большой массив данных?
У микроконтроллера нет такого объема памяти, чтобы он мог что-то обрабатывать долгое время, не общаясь с внешним миром. А внешний мир - это прерывания, ввод-вывод и sleep. Пример надуманный, на практике так не бывает.
Gall вне форума   Ответить с цитированием Вверх
Старый 11.11.2010, 00:27   19
SviMik
Завсегдатай Фонарёвки
 
Аватар для SviMik
 
Регистрация: 26.02.2010
Последняя активность: 18.08.2015 18:47
Сообщений: 810
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

Отправить сообщение для SviMik с помощью ICQ Отправить сообщение для SviMik с помощью MSN
По умолчанию

Цитата:
Не проблема абсолютно. "Что-то-еще-делание" ставится непосредственно перед sleep, а вся генерация сигналов - после sleep. И будет как раз как надо.
Ты не понял. "Что-то-еще-делание" может затянуться дольше, чем период между прерываниями. Что тогда?
А если там будет общение с внешними девайсами? Оно тем более может затянуться.

Цитата:
У микроконтроллера нет такого объема памяти, чтобы он мог что-то обрабатывать долгое время, не общаясь с внешним миром. А внешний мир - это прерывания, ввод-вывод и sleep. Пример надуманный, на практике так не бывает.
Я твоей фразы не понимаю.

Хорошо, отойдём от абстракции.
Написал я недавно WAV-плеер на тиньке, прекрасно играющий с microsd карточки. На ассемблере
Теперь представь: на мк с тактовой частотой 8мгц, надо играть звук 48000гц. При этом, без джиттеров (т.е. периоды между семплами должны быть равны). Свободное процессорное время отдано на обработку других устройств ввода\вывода.
По расчётам, выходит, что у мк есть ~166 тактов на семпл. За это время надо прочитать 2-4 байта с карты памяти, при этом ещё и обработать протокол самой карты, а оставшееся время отдать на обработку устройств ввода. И всё это без джиттеров должно быть!

Самое логичное - код, проигрывающий семпл, надо писать в прерывание, т.к. он должен выполняться немедленно.
Код, который должен выполняться в фоновом режиме, писать в основном цикле. Он будет выполняться между прерываниями.
Мне такой вариант кажется самым производительным. А как бы сделал ты?

Только не говори, что взял бы для этой задачи другой мк или другую тактовую частоту
SviMik вне форума   Ответить с цитированием Вверх
Старый 11.11.2010, 03:51   20
lasers_INFERION
Ветеран Фонарёвки
 
Аватар для lasers_INFERION
 
Регистрация: 15.02.2010
Последняя активность: 24.08.2019 11:36
Сообщений: 1362
Сказал(а) спасибо: 0
Поблагодарили: 0 раз(а) в 0 сообщениях

По умолчанию

Цитата:
Посмотреть сообщение Сообщение от SviMik :
Теперь представь: на мк с тактовой частотой 8мгц, надо играть звук 48000гц. При этом, без джиттеров (т.е. периоды между семплами должны быть равны). Свободное процессорное время отдано на обработку других устройств ввода\вывода.
По расчётам, выходит, что у мк есть ~166 тактов на семпл. За это время надо прочитать 2-4 байта с карты памяти, при этом ещё и обработать протокол самой карты, а оставшееся время отдать на обработку устройств ввода. И всё это без джиттеров должно быть!

Самое логичное - код, проигрывающий семпл, надо писать в прерывание, т.к. он должен выполняться немедленно.
Код, который должен выполняться в фоновом режиме, писать в основном цикле. Он будет выполняться между прерываниями.
Мне такой вариант кажется самым производительным. А как бы сделал ты?
По твоим словам выходит, что программа в фоновом режиме не может выполнятся дольше интервалов между прерываниями. Т.е. приходится время от времени сохранять промежуточные данные и разрешать флаг глобального прерывания, чтоб не пропустить событие. Так что мешает вставить эти же фрагменты кода между командами Sleep? Так и проще будет. Не придётся часто разрешать прерывания, т.к. время прогнозируемое. Обработка в фоне занимает меньше времени? Так тогда вообще проблем не вижу. Что с работой между прерываниями, что с работой перед Sleep. Практически одно и то же, за исключением меньшей предсказуемости твоего варианта...

Тут то ладно, временные интервалы между прерываниями одинаковые. Разбросать "фоновый" код между ними не проблема. А что если прерывания могут возникать непоймикогда? Например программный ШИМ. И на которые тоже нужно реагировать незамедлительно, бросая все свои текущие дела. По-моему тут гемора с флагами гораздо больше, чем с отдельным обработчиком прерывания. Да и ты не уточнил, что WAV плеер тебе нужен не всегда. Тут возможно прерывание проще отключить, чем разбираться с ненужными слипами, и тем что за ними. Да и как быть с пробуждением от любого внешнего, или левого внутреннего прерывания? Нужно ещё и смотреть что именно тебя разбудило...

Я уже попробовал променять прерывания на Sleep. Во многих местах это действительно оправдано, но не во всех. Не могу я представить как на "слипах" построить серьёзную программу, нормально реагирующую на все события, в любых их комбинациях. Прерывания с этим сами разберутся, а там думать и огородить приходится. В простых же программах, практически однозадачных (индикатор, восстановитель фьюзов и т.п.), получилось проще заюзать слип...
lasers_INFERION вне форума   Ответить с цитированием Вверх
Ответ  Создать новую тему
Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск





Copyright ©2007 - 2024, FONAREVKA.RU

Powered by vBulletin®
Copyright ©2000 - 2022, Jelsoft Enterprises Ltd. Перевод: zCarot

Правила форума | Отказ от ответственности

Время генерации страницы 0.16918 секунды с 17 запросами