Print this page

Учебный курс. Подключение LCD к микроконтроллеру. Разбиваем программу на модули

02/11/2009 - 21:00

Зачем разбивать программу на модули/файлы

Программа становиться более читабельной
   Чем больше программа, тем сложнее ее контролировать и держать в голове. Разбивая программу на функционально законченные части, мы делаем ее более читабельной и логичной.  Все функции для работы с дисплеем будут сосредоточены в одном месте.

Возможность повторного использования кода
   Если понадобиться использовать lcd дисплей в другом устройстве, нам не придется выдирать куски кода из старого проекта. Мы просто подключим к новому проекту библиотеку lcd_lib.

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

Возможность совместной работы над проектом
   Разбиение программы на модули позволяет вести работу над проектом нескольким программистам сразу. Например, один программист делает основную программу, другие занимаются библиотеками.

Область видимости переменных/функций, находящихся в разных модулях

   Глобальные переменные одного модуля будут не видны другому, и наоборот. Чтобы использовать глобальную переменную из другого модуля, перед ней нужно написать ключевое слово extern.

extern unsigned char key;

   Этим объявлением мы даем понять компилятору, что используем переменную из другого файла. И в этом случае, кстати, переменной  нельзя присваивать значение.

extern unsigned char key = 0;  //неправильно. Вызовет ошибку!

   Функции объявленные в одном модуле, будут доступны в других файлах. Но с помощью ключевого слова static область видимости функции можно ограничить модулем, в котором она определена.

static unsigned char
LCD_CheckBF(void)
{
    //тело функции
}

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

Как разбить программу на модули 

Ну ладно, достаточно теории, приступаем.
Открываем наш старый проект.
Создаем в IARe  два файла – lcd_lib.h и lcd_lib.c.
Сохраняем файлы в папке проекта.
Добавляем файл lcd_lib.c в наш проект. (Правой кнопкой мышки кликаем в workspace`e  и в открывшемся меню выбираем  Add > Add files…).
В lcd_lib.c с помощью директивы include подключаем заголовочный файл -

#include “lcd_lib.h”.

В lcd_lib.h добавляем строчки

#ifndef LCD_LIB_H
#define LCD_LIB_H

 #endif

#ifndef и #endif – директивы условной компиляции. Для чего они нужны? Допустим у нас большой проект, и наша библиотека подключается в нескольких местах. Может возникнуть ситуация когда, содержимое lcd_lib.h окажется включенным в какой-нибудь файл многократно. Тогда при компиляции проекта возникнет ошибка.
   Чтобы этого не происходило, возьмите за правило обрамлять содержимое заголовочного файла приведенной выше конструкцией. Когда препроцессор наткнется на эту запись, он проверит, определена ли константа LCD_LIB_H или нет. Если она не определена, он ее определит (#define LCD_LIB_H) и включит содержимое lcd_lib.h  в файл. Ну а если константа определена, то не включит.  

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

lcd_lib.h – это заголовочный файл, интерфейсная часть нашей библиотеки. lcd_lib.c – файл реализации. Чтобы было понятней, приведу пример с телевизором. У него тоже есть интерфейс и реализация. Интерфейс – это кнопки на его корпусе, с помощью которых мы можем его включать, настраивать и выбирать каналы. Реализация – это совокупность плат, компонентов и соединений между ними, которая обеспечивает работу телевизора. Меня, как конечного пользователя,  не интересует, что у него внутри, главное чтобы он выполнял свои функции. Поэтому интерфейсная часть телевизора не должна содержать деталей его реализации. Это же применимо и к заголовочному файлу.

Содержимое заголовочного файла

Подключаемые библиотеки

#include <ioavr.h>
#include <intrinsics.h>

Макроопределения портов

//порт к которому подключена шина данных ЖКД
#define PORT_DATA PORTD
#define PIN_DATA  PIND
#define DDRX_DATA DDRD

//порт к которому подключены управляющие выводы ЖКД
#define PORT_SIG PORTB
#define PIN_SIG  PINB
#define DDRX_SIG DDRB

//Номера выводов к которым подключены управляющие выводы ЖКД

#define RS 5
#define RW 6
#define EN 7

Макроопределение тактовой частоты микроконтроллера

#define F_CPU 8000000

Прототипы функций

void LCD_Init(void);  //инициализация портов и жкд
void LCD_WriteData(unsigned char data);   //выводит байт данных на жкд
void LCD_WriteCom(unsigned char data);   //посылает команду жкд

  Прототип функции – это объявление, содержащее тип возвращаемого значения, название функции и ее параметры.  

   Заметьте, я переименовал названия всех функций. Это один из стандартов программирования – добавлять к именам функций префикс с названием файла библиотеки. Наша библиотека называется lcd_lib, поэтому к названиям функций я добавил  LCD_. Это простое правило позволяет определять, в каком файле содержится реализация функции.   
   Кстати, в IARe это еще можно сделать так -  в окне редактора кода кликните на названии функции  правой кнопкой мыши  и выберете опцию Go to definition…, откроется файл, содержащий определение этой функции.

Содержимое файла lcd_lib.c

Макросы для работы с битами и макросы для программных задержек

#define ClearBit(reg, bit)       reg &= (~(1<<(bit)))
#define SetBit(reg, bit)           reg |= (1<<(bit))    
#define _delay_us(us)      __delay_cycles((F_CPU / 1000000) * (us));
#define _delay_ms(ms)     __delay_cycles((F_CPU / 1000) * (ms));

Определение всех функций

//функция записи команды
void LCD_WriteCom(unsigned char data)
{
  ClearBit(PORT_SIG, RS);           //установка RS в 0 - команды
  PORT_DATA = data;               //вывод данных на шину индикатора
  SetBit(PORT_SIG, EN);            //установка E в 1
  _delay_us(2);
  ClearBit(PORT_SIG, EN);           //установка E в 0 - записывающий фронт
  _delay_us(40);
}

//функция записи данных
void LCD_WriteData(unsigned char data)
{
  SetBit(PORT_SIG, RS);            //установка RS в 1 - данные
  PORT_DATA = data;               //вывод данных на шину индикатора
  SetBit(PORT_SIG, EN);            //установка E в 1
  _delay_us(2);
  ClearBit(PORT_SIG, EN);          //установка E в 0 - записывающий фронт
  _delay_us(40);
}

//функция инициализации
void LCD_Init(void)
{
  DDRX_DATA = 0xff;
  PORT_DATA = 0xff;    
  DDRX_SIG = 0xff;
  PORT_SIG |= (1<<RW)|(1<<RS)|(1<<EN);
  ClearBit(PORT_SIG, RW);

  _delay_ms(40);
  LCD_WriteCom(0x38); //0b00111000 - 8 разрядная шина, 2 строки
  LCD_WriteCom(0xf);  //0b00001111 - дисплей, курсор, мерцание включены
  LCD_WriteCom(0x1);  //0b00000001 - очистка дисплея
  _delay_ms(2);
  LCD_WriteCom(0x6);  //0b00000110 - курсор движется вправо, сдвига нет
}

Подключаем библиотеку к проекту

В принципе все готово.
Сохраняем оба файла.
Подключаем нашу библиотеку к файлу main.c.
 
#include "lcd_lib.h"

Убираем из него все лишнее. И добавляем вывод слова “Test.”
Вот что должно получиться.

#include <ioavr.h>
#include "lcd_lib.h"

int main( void )
{
  LCD_Init();
  LCD_WriteData('T');
  LCD_WriteData('e');
  LCD_WriteData('s');
  LCD_WriteData('t');
  LCD_WriteData('2');
  while(1);
  return 0;
}

Компилируем проект… У меня все прошло без ошибок. Надеюсь у вас тоже.

 Ну а это структура нашего нового проекта.


   ioavr.h и lcd_lib.h подключены к файлу main.c явно (в тексте программы). Остальные заголовочные файлы попали в main из файла ioavr.h. Можете полазить по нему, чтобы убедиться в этом.
   К lcd_lib.c мы подключали lcd_lib.h,  ioavr.h, intrinsics.h. Остальные подключились через ioavr.h.

  P.S.:
Если вы работаете в WINAVR и ваш проект состоит из нескольких файлов, компилятору нужно указывать их вручную. Делается это в Makefile – просто вписываем название файлов через пробел.

# List C source files here. (C dependencies are automatically generated.)
SRC = $(TARGET).c lcd_lib.c

Файлы

Проект для IARa. Проект для WINAVR.

 

Related items