Подписаться на получение новых статей на почту:

Использование заголовков (header). Утилита avr-size.Часть 6. Шаг№ 21

Обновлено 7.01.16. Всем привет. Сегодня мы с Вами поговорим о том как использовать заголовки, разбить правильно код и в конце статьи научимся использовать утилиту avr-size. В предыдущей статье, мы с Вами начали разговор об универсальности кода, т.е. когда код можно применить под другой микроконтроллер меняя только настройки в директивах препроцессора - define. В этой статье рассмотрим  применение своих заголовочных файлов. Что такое заголовок (header)?

Это файл с расширением *.h, который включается в программу с помощью директивы #include, состоит из объявления глобальных констант, переменных, с ключевым словом extern  и функций. Свой заголовочный файл включаем в программу  следующим образом #include “ *.h ”(в кавычки!!!) . И как правило он должен быть расположен в папке с другими файлами проекта. Т.е. заголовочный файл является посредником между главной функцией main () и специальной, заточенной под свое железо.

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

1.           Никогда не объявляйте функцию повторно, если она объявлена в заголовочном файле, это может привести к потери надежности и гибкости.
2.           Для избежания повторного включения одного и того же кода в заголовочном файле используем директивы условной компиляции #ifdef и #endif (равносильно «if defined» и «if not defined»).Идентефикатор определяем под название файла, например если файл dht11.h, то код будет выглядеть следующим образом:
#ifndef dht11_h
#define dht11_h

/*Объявляем функции и переменные, которые учавствуют в работе внешнего файла*/
#endif
3.           Использование спецификатора extern, который сообщает компилятору что следующие за ним типы и имена переменных объявляются где-то в другом месте. Что дает компилятору знать о типах и именах глобальных переменных без действительного создания этих переменных. Т.е. память для них при объявлении не выделяется. Когда два модуля объединяются, все ссылки на внешние переменные пересматриваются. Хотя профессионалы предостерегают от использования этого спецификатора. Но на данном этапе написания программ будем использовать эти правила.
4.           Использование макроса с определенной точностью, который описывается в заголовке stdint.h, используется для переносимости программ на разных платформах где может быть разная длина, например целого типа  uint8_t.
Теперь попрактикуемся. Значит код из статей№5- индикатор, №6 –датчик температуры ds18b20 и №20 – датчик влажности DHT11 , разделим на соответствующие файлы и укоротим основную функцию main. Начнем с библиотеки для индикатора, что б не утомлять Вас длиной страницей, остальные библиотеки, весь код можете скачать в конце статьи. Итак начнем.
led.h

#ifndef led_h  /*используем директивы условной компиляции*/
#define led_h  
/* настройки для подключения ножек микроконтроллера к индикатору*/
#define a 128
#define b 32
#define c 8
#define d 2
#define e 1
#define f 64
#define g 16
#define dp 4
/*Порты управления сегментами индикатора. Как Вы видите для универсальности программы порты управления индикатором мы прописываем в заголовочном файле, для корректировки под свой контроллер , без надобности поиска необходимых строк*/
#define SEGM_PORT PORTD  
#define SEGM_DDR  DDRD
/*Порты управление элементами индикатора*/
#define DSEGM_PORT PORTB
#define DSEGM_DDR  DDRB
#define DSEGM_PIN1 PINB7  // 4-й элемент индикатора вывод едениц
#define DSEGM_PIN2 PINB6  //3-й элемент- вывод десятых и точки
#define DSEGM_PIN3 PINB0  // 2-й элемент индикатора вывод сотен
#define DSEGM_PIN4 PINB4  //1-й элемент- вывод знаков
/*Сброс показаний индикатора, элементов DSEGM_PIN1...4. Цифра меняется в зависимости от порядка и количества подключаемых элементов. */
#define CL_PORT 0x2e;
/* Указываем функции, которые будут глобальными, т.е. видны во всех файлах. Если не используется класс типов переменных, а в данном случае просто void, то по умолчанию при компиляции автоматически дописывается extern*/
void Slot_init (void);  /*Функция инициализации индикатора*/
void Display (float);   /*Вывод на индикатор*/
void led_update (void); /*Динамическая индикация*/
#endif   

В файл led.h, выше, мы перенесли все необходимые строки для универсальности кода, а также глобальные функции, которые участвуют в других исходных файлах. Рассмотрим, ниже, исходный файл led.c

led.с

#include <avr/interrupt.h>
#include <math.h>
#include «led.h» /*Обязательно подключаем заголовочный файл*/
#include <stdint.h>
extern uint8_t i2; /*Если посмотрим предыдущую статью, то увидим что данная переменная используется для отображения датчика влажности, поэтому она объявлена в другом месте */
int8_t i1; /*Переменная для отображения отрицательного числа*/
uint8_t Slot[11]; /*Массив, в котором хранятся отображаемые цифры*/
/*Функция объявлена глобально в заголовочном файле*/
void Slot_init (void)
{
    SEGM_DDR=0xff;  /Установки для порта*/
    DSEGM_DDR=0xff;
    SEGM_PORT=0×00;
    Slot[0] = (a+b+c+d+e+f);
    Slot[1] = (b+c);
    Slot[2] = (a+b+g+e+d);
    Slot[3] = (a+b+g+c+d);
    Slot[4] = (f+g+b+c);
    Slot[5] = (a+f+g+c+d);
    Slot[6] = (a+f+g+c+d+e);
    Slot[7] = (a+b+c);
    Slot[8] = (a+b+c+d+e+f+g);
    Slot[9] = (a+b+c+d+f+g);
    Slot[10] = dp;
}
 /*В этих переменных хранятся цифры, которые нужно отобразить*/
uint8_t Elem1, Elem2, Elem3;
/*Функция для вывода на индикатор*/
void Display (float i)
{
    uint8_t w = 0; /*переменная для включения точки*/
    float N1,N2,Number;
    i1=1;  /*в этих трех строках мы включаем знак минус, если число отрицательное*/
    if (i < 0.0)
        i1=-1;
    Number = fabs (i);   
    N1 = modf (Number, &N2); /*Разбиваем число Number на целую и дробную части,N1 = дробной N2 = целой*/
    if (N1 != 0)        /*Если не равно нулю то присутствует дробь*/
    {
        Number= Number*10; /*тогда умножаем число на 10, для обычного вывода на индикатор трехзначного дробного числа*/
        w = 1;
    }
    uint8_t Num1, Num2, Num3;
    Num1=Num2=0;
    while (Number >= 100)  /*Вычесляем количество сотых*/
    {
        Number -= 100;
        Num1++;
    }
    while (Number >= 10) /*кол-во десятых*/
    {
        Number -= 10;
        Num2++;
    }
    Num3 = Number; /*еденицы*/
    Elem1 = Slot[Num1];
    if (w == 1)  /*условие дя включения точки на втором элементе*/
    {
        Elem2 = (Slot[Num2]|0×04);
        w = 0;
    }
    else
        Elem2 = Slot[Num2];
    Elem3 = Slot[Num3];
}
/*Функция динамической индикации */
void led_update (void)
{
    static uint8_t k;  /*переменная отвечающая за поэлементной вывод на индикатор, должна сохранять свое значение при выходе из функции*/
    uint8_t j;
    DSEGM_PORT &= CL_PORT; /*Очистка портов управляющих индикатором*/
    for (j = 0; j<=30; j++) {}
    /*Задержка для выключения транзистора*/
    (k == 3) ? k = 0 : k++;
    switch (k)
    {
        case 0: DSEGM_PORT |= (1 << DSEGM_PIN1); /*Единицы*/
        SEGM_PORT = Elem3;
        break;
        case 1: DSEGM_PORT |= (1 << DSEGM_PIN2); /*Десятки*/
                SEGM_PORT = Elem2;
        break;
        case 2: DSEGM_PORT |= (1 << DSEGM_PIN3); /*Сотни*/
                SEGM_PORT = Elem1;
        break;
        case 3:
        if (i1<0) /*глобальная переменная на минус*/
        {
            DSEGM_PORT |= (1 << DSEGM_PIN4);
            SEGM_PORT = 16;
        };
        if (i2==1) /*переменная объявленная в другом файле*/
        {
            DSEGM_PORT |= (1 << DSEGM_PIN4);
            SEGM_PORT = 121; /*отвечает за вывод знака H*/
        }
        if (i2==2)
        {
            DSEGM_PORT |= (1 << DSEGM_PIN4);
            SEGM_PORT = 195; /*отвечает за вывод знака C*/
        };
    }
}

            Вот так выглядит перенесенный код в отдельные файлы. У кого есть желание можете сравнить как он выглядит в статье №5 с главной функцией main и сокращенной уже описанной ниже. Ну что ж утомлять не буду, поэтому библиотеки с ds18b20 и dht11, можете скачать отдельным файлом. 
           Рассмотрим последний, основной файл с функцией main. Как Вы видите в этом файле происходит вызов функций и основные настройки контроллера.

/*Подключаем все заголовочные файлы*/

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <math.h>
#include <stdint.h>
#include «ds18b20.h»
#include «led.h»
#include «dht11.h»
int main (void)
{
     Slot_init (); /*Инициализация индикатора*/
     sei ();
/*инициализация таймера Т0 */
     TIMSK = (1<<TOIE0);
     TCCR0 = (0<<CS02)|(1<<CS01)|(0<<CS00);
     ds18b20_search (); /*инициализация и поиск датчиков*/
     while (1)
     {
          read_ds18b20 ();    /*чтение температуры*/
          _delay_ms (1000);
         cli ();  /* Перед вызовом функции  dhtread (); запретим общее прерывания*/ 
        dhtread (); /*Чтение влажности и температуры*/
     }
}
ISR (TIMER0_OVF_vect)
{
      led_update (); /*Динамическая индикация*/
}

        Как я говорил, в дальнейшем, отдельно подключаемые библиотеки намного упростят нам жизнь, да и код стал намного читабельнее, в чем можно убедиться если Вы сравните предыдущие посты, например предыдущий (№20), где описывалась работа с датчиком dht11. Да, перед компиляцией не забудьте указать все исходники в настроечном файле Makefile:

# List C source files here. (C dependencies are automatically generated.)
SRC =$(TARGET).c led.c ds18b20.c dht11.c:

Маленький помощник WinAVR    -    avr-size. Мониторим память!
        И последнее. Иногда не лишним будет знать, насколько мы используем ресурсы памяти контроллера. Наша программа разрастается, поэтому для нас это актуально. В контроллере память делится на память программ и данных, которая в последнюю очередь делится на регистровую память, оперативную память и энергонезависимую EEPROM,которую мы кстати рассмотрели в статье №13. Используя утилиту avr-size, которая располагается в директории WinAVR\bin мы можем вывести информацию о используемой памяти.  Этой утилитой можно пользоваться через командную строку, перед этим exe-ный файл необходимо поместить в папку с исходниками. В командной строке ввести следующую команду 

@avr-size –C –mcu=atmega8 test.elf 

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

В итоге результат ниже на рисунке.

avr-size из консоли

Второй способ использования этой утилиты, непосредственно используя программу Notepad из WinAVR. Для этого в файле конфигурации Makefile , в конце, необходимо прописать следующие две строки:

size:
$(SIZE) -C --mcu=$(MCU) $(TARGET).elf

А также определить собственную цель сборки для автоматизации регулярно выполняемой задачи, в данном случае определение размера памяти. Tools-Options-Tools-Add и заполнить поля, на рисунке ниже:

avr-size в WinAVR

В итоге при выполнении данной опрации результат ниже:

avr-size вывод памяти

        В данной статье были рассмотрены начальные шаги для создания универсальности, разнесения кода по библиотекам, один из методов оптимизации – использование переносимых типов с определенной точностью. А также способ мониторинга ресурсов памяти контроллера. В следующей статье мы продолжим работу с SD-картой — познакомимся с файловой системой Petit FatFS.  Напомню что в статье №19 мы рассмотрели общие принципы, и низкоуровневые команды работы с картой. Т.к. мы с Вами научились подключать библиотеки к нашему проекту, то нам не составить труда подключить и настроить драйвер Petit FatFs. Критика и пожелания приветствуется. До новых постов.

Скачать архив здесь ( Скачали: 720 чел. ) 

.

Просмотрено 79632 раз.

Я на Google+

Использование заголовков (header). Утилита avr-size.Часть 6. Шаг№ 21: 15 комментариев

  1. То есть дополнительные файлы с расширением .с должны иметь то-же имя, что и файлы с расширением .h? А так как в main.c указываем только h-файлы своих библиотек и компилятор находит С-файлы сам? Или только после указания в мейкфале этого списка С-файлов?

    • Желательно указывать одно имя с расширением .c and .h, что б было понятно что в первом файле какието функции что то обрабатывают, а во втором файле название глобальных функций и переменных, которые должны быть видны в главном файле, например для той же конфигурации ножек.А так можно разные имена, просто запутаетесь. Да правильно файлы.с необходимо указывать в мейкфале.

      • Огромное спасибо! Только начал пробовать Си.

  2. Hello! My name is MaryMarkova, our compane need to advertise on your website. What is your prices? Thank you. Best regards, Mary.

    • Hello. What it is You wanted to post? Banner or contextual or You have suggestions. Further discussion propose to transfer by email: shpuryka@gmail.com

  3. Доброго времени суток!

    Пробую вашу билиотеку DS18B20, с двумя датчиками. Программа виснет на поиске кодов датчиков. Тайм-слоты всегда приходят 0 и 0, соответсвенно не выбраться из бесконечного цикла. По отдельности датчики работают (SkipROM...). Железо Atmega128 16МГц

    • Здравствуйте Станислав. Давненько я уже не смотрел на эти коды. Atmega128 — это сила. Мы на ней как то «замутили» «электрогитару» для чпу.Ладно по делу. Честно говоря незнаю. Но могу предложить отключить crc8. Также поиграйтесь частотой. Прошу написать результаты. Спасибо.

  4. Добрый день,

    Спасибо за Ваш труд. Я в начале пути, поэтому ряд детских вопросов. У меня все собирается в WinAvr (пришлось пропатчить его для работы в Win8 и Win10).

    Но нет отладки, попробовал добавить WinAVR как toolchain в Amtel 7

    и собрать решение там пока не успешны. Есть у кого то практический опыт ?

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

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

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Subscribe without commenting