Модульное программирование.

Модульное программирование.

После того как начинающий embedder наморгается светодиодом, он непременно решит написать нечто более серьезное и у него как у любого начинающего будет только одно желание «чтобы всё быстрее заработало!!!». В такой попытке самоутвердиться он будет писать всё в один файл, не задумываясь о структуре программы, но через некоторое время, когда часть задуманного будет реализована, станет понятно, что чем больше становится программа, тем тяжелее в ней что-либо найти. Это натолкнет его на мысль, что у программы должна быть структура и он пойдёт на просторах интернета смотреть, как решают этот вопрос другие embedder'ы. Посмотрев, как выглядят программы, написанные другими людьми, он сделал вывод, что программу разбивают на файлы, которые представляют законченные логические единицы, что часть функции и переменных выносят в хэдер и ещё много чего. Всё вышеописанное — мой опыт, но все начинающие проходят один и тот же путь, поэтому опишу здесь, то с чем столкнулся сам.

Предположим у нас есть программа, которая выводит температуру на lcd дисплей и что для lcd дисплея, что для датчика температуры(ds18b20), нужна инициализация, а также функции для работы с ними. Поэтому логично будет создать два отдельных файла lcd.c и ds18b20.с, которые будут содержать в себе функции необходимые для работы. Такие файлы называют модулями, хотелось бы отметить, что каждый модуль представляет собой независимую, логически-завершенную единицу, которую можно компилировать отдельно от остальной программы. При компиляции модуля компилятор сделает из него объектный файл.

Следующий вопрос, который возникает, раз модуль это независимая структура, можно сказать «вещь в себе», то она не имеет связи с внешним миром, а нас это не устраивает. Для связи модулей с внешним миром используется заголовочные файлы, их также называют хэдерами/хидерами и они имеют расширение .h. Назвать хэдер можно как угодно, но удобнее, чтобы его название совпадало с названием модуля, lcd.h и ds18b20.h, также надо сказать, что все подключаемые файлы(#include) удобно вынести в хэдер и подключать только его вначале модуля.
Когда хэдера не было, начало lcd.с выглядело так

#define F_CPU 8000000UL
#include <util/delay.h>
#include <avr/io.h>

а после создание хэдера стало выглядеть так
#include <lcd.h>


Но тут же возникает еще один вопрос, что выносить в хэдер?
В хэдер необходимо вынести прототипы функций, которые могут понадобиться в других модулях программы. Прототип функции лишь объявляет функцию и не содержит тела функции, но посмотрев на него можно узнать имя функции, количество, тип аргументов и возвращаемый тип данных.
В файле lcd.h будут объявлены следующие прототипы:

void lcd_init(void);
void lcd_write_symbol(char symbol);
void lcd_write_string(char *str);
void lcd_clear(void);

В файле ds18b20.h будут объявлены следующие прототипы:

void ds18b20_init(void);
uint8_t ds18b20_get_temperature(void);



Что касается макросов, можно вынести макросы, отвечающие за выполнение условной компиляции

#define MAKE_CALIBRATION   //раскомментировать для калибровки

А где-то в коде есть конструкция, которая выполняется если предыдущая строчка раскомментирована
#ifdef  MAKE_CALIBRATION 
		touch_x -= 300;
		touch_x = 240 - touch_x/((Xmax-Xmin)/240);
		touch_y -= 350;
		touch_y = 320 - touch_y/((Ymax-Ymin)/320);  
#endif

Также можно вынести макросы, отвечающие за выбор выводов, к которым будет подключаться периферия

#define D0 PORTA //так данные передаются по 16 битной шине,
#define D7 PORTD //под это дело мы используем два порта



Но в то же время в хэдере не надо размещать то, что не понадобится в других модулях:
  • макросы типа
    #define (LCD_PIN & 0X80) check_busy_flag

  • переменные, которые будут использоваться только внутри модуля с ключевым словом static
  • переменные с квалификатором extern
  • прототипы функций, которые нужны для каких-то промежуточных действий, например, функцию, которая переводит число в BCD формат


Теперь пару слов про подключение хэдеров, при программировании микроконтроллеров AVR почти во всех модулях подключается хэдер для работы с портами ввода-вывода.
#include avr/io.h

То есть он подключается в lcd.h и в ds18b20.h, теперь если мы подключим эти два заголовка в основном файле программы, то avr/io.h подключится дважды, хотя достаточно было и одного. Для того чтобы избежать возникновения такой ситуации и хэдер не подключился дважды используют #include guards, который выглядит следующим образом.
#ifndef _ENCODER_H_
#define _ENCODER_H_

// оформляем как обычный хэдер

#endif

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

#pragma once
// оформляем как обычный хэдер

Преимущество использование #pragma once вместо #include guards можно почитать тут.
Кстати подключать можно не только хэдеры, а также файлы с расширением , если это надо
#include “font.c”

В данном случае для вывода букв на TFT дисплей подключается файл с шрифтами.
На этом всё, мне кажется это минимум, который необходимо знать каждому начинающему программисту микроконтроллеров.
комментарии
8