Реклама ⓘ
Главная » Измерения
Призовой фонд
на апрель 2024 г.
1. 100 руб.
От пользователей

Реклама ⓘ

Люксметр на ATmega8 и цифровом датчике BH1750

1.Обзор устройства

Люксметр собран на популярном микроконтроллере семейства AVR — ATmega8A. В качестве чувствительного элемента применен цифровой I2C измеритель освещенности — микросхема BH1750 от ROHM Semiconductor с шестнадцатиразрадным выводом результата измерения. В последствии, так как в местных магазинах я не смог отдельно купить микросхему BH1750 (на Ali) был куплен готовый модуль GY-302, который представляет собой небольшую платку с микросхемой BH1750 и стабилизатором напряжения на 3.3В для питания данной микросхемы. Для вывода результатов был взят символьный жидкокристаллический дисплей WH1602.

Принципиальная схема:

Характеристики:

  • Напряжение питания 5В
  • Потребляемый ток   150...300мА
  • Оптический диапазон 450...670нм (по уровню -3дБ)
  • Пределы измерения   0...64000Лк

Печатная плата:

Печатная плата для устройства нарисована в ПО Sprint-Layout 5. Внешний вид со стороны деталей (для ЛУТ зеркалить не нужно).

2.Прошивка

Для начала рассмотрим работу с дисплеем WH1602

Данный дисплей имеет 2 режима работы, первый использует все выводы порта данных (D0..D7) и общается с дисплеем по 8 битной шине, второй режим позволяет работать с дисплеем по 4 битной шине (D4..D7), что позволяет использовать меньшее число выводов контроллера. Управление дисплеем осуществляется посредством 8 битных команд, поэтому в первом режиме команда передается за 1 такт, во втором режиме команда делится на 2 полубайта, которые отправляются по очереди, причем сначала отправляется старший полубайт, а затем младший.

Вывод RW определяет направление данных . Высокий уровень — чтение данных дисплея, низкий — запись данных. Так как мы будем только отправлять дисплею данные, то этот вывод можно сразу подтянуть на землю.

Вывод RS определяет, что в данный момент будет передаваться по шине данных, если на данный вывод подать высокий уровень, то дисплей будет ожидать передачу символа, а если низкий, то — передачу команды.

Вывод E (Enable) служит для стробирования дисплея. В момент поступления импульса на данный вход дисплей, в зависимости от состояния вывода RS либо считывает символ, либо команду.

Основные команды:

0b001XYZ00 — выбор режима, числа строк, размера символа

   X: 1 — 8бит, 0 — 4бит

   Y: 1 — две строки, 0 — одна строка

   Z: 1 — большие символы, 0 — маленькие

0b000XY00 — Режим сдвига

   X: 1 — сдвиг экрана, 0 — сдвиг курсора

   Y: 1 — сдвиг вправо, 0 — сдвиг влево

0b000001XY — Настройки сдвига

   X: 1 — инкремент символов, 0 — декремент

   Y: 1 — сдвиг окна экрана, 0 — экран неподвижен

0b00001XYZ — режим отображения

   X: 1 — дисплей вкл , 0 — дисплей выкл

   Y: 1 — курсор вкл , 0 — курсор выкл

   Z: 1 — курсор в виде квадрата вкл , 0 — курсор в виде квадрата выкл

0b00000001 — очистка дисплея + сброс сдвигов

0b00000010 — сброс сдвигов

0b1XXXXXXX — установка курсора

XXXXXXX — адрес ячейки памяти

Адреса первой строки лежат в пределах 0x00...0x27, а второй 0x40...0x67. Иными словами число ячеек памяти под строку значительно больше числа символов в строке, если дисплей не движется, то для работы нам будет достаточно адресов 0x00..0x0F для первой строки и 0x40..0x4F для второй. Таким образом если я хочу выводить текст во вторую (счет начинается с нуля) ячейку второй строки, то ее адрес будет 0x42, а соответствующий код команды 0b11000010, что равно 0xC2. Иными словами что бы переключится к i-му символу строки необходимо послать команду с кодом:

0x80+X+i , где X=0x00 для первой строки, 0x40 — для второй.

Процедура инициализации дисплея:

   1) 3 команды запуска в 8ми битном режиме (start)

   2) Выбор режима, числа строк, размера символов

   3) Включение дисплея, настройка курсора

   4) Очистка дисплея

   5) Настройка сдвига

Так как свободных выводов МК в нашем случае достаточно, то мы будем работать с дисплеем в 8 битном режиме, что несколько проще, нежели в 4 битном.

Введем некоторые константы:

#define LCD_START					0b00110000
#define LCD_8BIT_2LINE				0b00111000
#define LCD_DISPLAY_ON				0b00001100
#define LCD_DISPLAY_OFF				0b00001000
#define LCD_INCREMENT_ON_CURSOR_OFF	0b00000110
#define LCD_CLEAR 				    0b00000001
#define LCD_SECOND_LINE				0b11000000
#define LCD_FIRST_LINE				0b10000000

#define LCD_DATA_PORT   PORTD
#define LCD_DATA_DDR    DDRD
#define LCD_CTRL_PORT   PORTB
#define LCD_CTRL_DDR    DDRB
#define LCD_EN (1<<7)
#define LCD_RS (1<<6)

Отправка команды:

void LCDcomand(char cmd){
    LCD_CTRL_PORT|=LCD_EN;// поднимаем строб
    LCD_DATA_PORT=cmd;    // подаем команду
    _delay_us(5);         // ждем, что бы дисплей успел среагировать
    LCD_CTRL_PORT&=~LCD_EN;//опускаем строб
    _delay_ms(1);
}

Отправка символа:

void LCDprint(char symbol){
    LCD_CTRL_PORT|=LCD_EN|LCD_RS    // поднимаем строб, ставим режим символа (EN=1,RS=1)
    LCD_DATA_PORT=symbol;           // подаем символ
    _delay_us(5);                   // ждем, что бы дисплей успел среагировать
    LCD_CTRL_PORT&=~(LCD_EN|LCD_RS);//опускаем строб, отключаем режим символа (EN=0,RS=0)
    _delay_ms(1);
}	

Инициализация дисплея:

void LCDinit(){
    _delay_ms(40);
    LCDcomand(LCD_START);
    _delay_ms(5);
    LCDcomand(LCD_START);
    _delay_us(120);
    LCDcomand(LCD_START);
    _delay_ms(1);
    LCDcomand(LCD_8BIT_2LINE);
    _delay_ms(5);
    LCDcomand(LCD_DISPLAY_ON);
    _delay_ms(5);
    LCDcomand(LCD_CLEAR);
    _delay_ms(5);
    LCDcomand(LCD_INCREMENT_ON_CURSOR_OFF);
 }

Используя функцию отправки символа сформируем функцию вывода строки:

void LCDprintLine(char* line,char count){
   _delay_ms(5);
   for(int i = 0;i<count;i++) LCDprint(line[i]);
}

Шина I2C:

Так как микросхема BH1750 работает по интерфейсу I2C, то разберем работу этого протокола. Название I2C запатентовано, поэтому компания Atmel в своих микроконтроллерах (к коим относится и Atmega8) использует другое имя для этого же протокола — TWI (Two wire interface). Данное название, к слову как нельзя лучше характеризует данный протокол. Он подразумевает, что два или более устройств общаются посредством двухпроводной шины, по первой линии осуществляется подача тактового сигнала, данную линию называют линией тактирования (SCL), по второй линии передаются данный (SDA). Согласно стандарта обе линии должны быть подтянуты резисторами (10к) к шине +5В. Следует отметить, что данный интерфейс подразумевает наличие ведущего (master) и ведомого (slave) устройств. Причем одно и то же устройство может быть ведущим и ведомым в разные моменты времени.

События:

Старт — падающий фронт на линии SDA при высоком уровне на линии SCL (наличие тактового импульса)

Стоп — возрастающий фронт на линии SDA при высоком уровне на линии SCL

Отправка данных:

1) Мастер генерирует событие «Старт»

2) Мастер отправляет первый пакет, в котором первые 7бит — адрес ведомого, а восьмой — режим (0 - запись)

3) Ведомый отправляет бит подтверждения (NACK)

4) Мастер отправляет байт данных

5) Ведомый отправляет бит подтверждения (NACK)

6) Пункты 4-5 повторяются, пока все данные не будут переданы.

7) Мастер генерирует событие «Стоп»

Чтение данных:

1) Мастер генерирует событие «Старт»

2) Мастер отправляет первый пакет, в котором первые 7бит — адрес ведомого, а восьмой — режим (1 - чтение)

3) Ведомый отправляет бит подтверждения (NACK)

4) Ведомый отправляет байт данных

5) Мастер отправляет бит подтверждения (NACK)

6) Пункты 4-5 повторяются, пока все данные не будут переданы.

7) Мастер генерирует событие «Стоп»

 

Между несколькими процедурами общения событие «Стоп» может быть упущено, вместо него может следовать повторное событие «Старт»

В микроконтроллере Atmega8 за работу TWI отвечают следующие регистры:

TWBR — регистр скорости передачи, его значение задает скорость работы шины (частоту тактирования) и определяется формулой:

(F_CPU/F_I2C-16) / (2*4^TWPS)

где TWPS — предделитель частоты SCL, два младших бита регистра TWSR, если выбрать эти биты равные нулю (предделитель отключен), то выражение упростится до вида:

(F_CPU/F_I2C-16) / 2

TWDR — регистр данных, сюда записывают данные для передачи, сюда же попадают принятые данные.

TWAR — регистр адреса, сюда записывают адрес микроконтроллера, когда он выполняет роль ведомого устройства.

TWSR — регистр статуса. Старше 5 бит этого регистра определяют код состояния шины TWI, а младшие 2 бита являются предделителем частоты.

TWCR — регистр управления. Описание битов данного регистра приведено в таблице.

бит название описание
0 TWIE

 

Разрешение прерывания

1 - -
2 TWEN Включение модуля TWI
3 TWWC Флаг конфликта записи
4 TWSTO Флаг состояния стоп
5 TWSTA Флаг состояния старт
6 TWEA Разрешение бита подтверждения
7 TWINT Флаг прерывания

Особое внимание следует уделить биту TWINT. Этот бит автоматически устанавливается в 1 при завершении любой операции выполняемой модулем TWI и не сбрасывается в 0 автоматически, сброс данного бита осуществляется программно путем записи в него единицы, а до момента его сброса модуль не работает и удерживает низкий уровень на линии SCL.

Итак вооружившись теорией перейдем к практике, для начала объявим константы:

#define F_CPU 2000000UL
#define F_I2C 12500UL
#define TWBR_V (((F_CPU)/(F_I2C)-16)/2)

Инициализируем модуль:

void I2C_Init (void){
    TWBR=TWBR_V; // задаем скорость передачи
    TWSR = 0;    // сбрасываем регистр состояния
}

 

Функции генерации событий работают следующим образом:

1) в регистре TWCR устанавливаются флаг соответствующего события ( TWSTA — start, TWSTO — stop)

2) включается модуль TWI (TWEN=1)

3) сбрасывается флаг прерывания (в TWINT записывается единица)

4) в случае генерации события «старт» дожидаемся завершения операции (установка флага прерывания TWINT)

void I2C_Start(void){// отправка СТАРТ
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
}
void I2C_Stop(void){// отправка СТОП
    TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
}

Все хорошо осталось еще чуть-чуть — передавать и принимать данные.

Начнем с передачи байта, данная процедура выглядит примерно следующим образом:

1) Записываем данные в регистр данных (TWDR)

2) Включаем модуль (TWEN=1)

3) Сбрасываем флаг прерывания (в TWINT записывается единица)

4) Ждем завершения передачи (установки флага прерывания TWINT)

void I2C_SendByte(unsigned char c){
    TWDR = c;
    TWCR = (1<<TWINT)|(1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
}

Теперь получим ответ, данная процедура не чуть не сложнее

1) Включаем модуль (TWEN=1)

2) Включаем отправку NACK (TWEA=1)

3) Сбрасываем флаг прерывания (в TWINT записывается единица)

4) Ждем завершения передачи (установки флага прерывания TWINT)

5) Получаем результат из регистра данных (TWDR)

char I2C_GetByte(){
    TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
    return TWDR
}

Обьеденим все рассмотренное выше и реализуем работу нашего устройства:

#define F_CPU 2000000UL //тактовая частота
//------КОНСТАНТЫ ДЛЯ LCD
#define LCD_START 0b00110000
#define LCD_8BIT_2LINE 0b00111000
#define LCD_DISPLAY_ON 0b00001100
#define LCD_DISPLAY_OFF 0b00001000
#define LCD_INCREMENT_ON_CURSOR_OFF 0b00000110
#define LCD_CLEAR 0b00000001
#define LCD_SECOND_LINE 0b11000000
#define LCD_FIRST_LINE 0b10000000
#define LCD_DATA_PORT   PORTD
#define LCD_DATA_DDR    DDRD
#define LCD_CTRL_PORT   PORTB
#define LCD_CTRL_DDR    DDRB
#define LCD_EN (1<<7)
#define LCD_RS (1<<6)
//------КОНСТАНТЫ ДЛЯ TWI
#define SLA_R 0b01000111 //адрес устройства + бит RW в режим чтения(1)
#define SLA_W 0b01000110 //адрес устройства + бит RW в режим запись(0)
#define F_I2C 12500UL    //частота шины i2c
#define TWBR_V (((F_CPU)/(F_I2C)-16)/2) 
#include <avr/io.h>
#include <util/delay.h>
//-------------------------------------------------------------i2c
// отправка  СТАРТ
void I2C_Start(void){
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
}
// отправка СТОП
void I2C_Stop(void){
    TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
}
//отправка байта
void I2C_SendByte(unsigned char c){
    TWDR = c;
    TWCR = (1<<TWINT)|(1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
}
//получение байта
char I2C_GetByte(){ 
    TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
    return TWDR
}
//инициализация I2C как передатчика
void I2C_Init (void){
    TWBR=TWBR_V;
    TWSR = 0;
}
//-----------------------------------------------------------------------------LCD
//отправка команды
void LCDcomand(char cmd){
    LCD_CTRL_PORT|=LCD_EN // поднимаем строб (EN=1)
    LCD_DATA_PORT=cmd;    // подаем команду
    _delay_us(20);         // ждем, что бы дисплей успел среагировать
    LCD_CTRL_PORT&=~LCD_EN;//опускаем строб
    _delay_ms(4);
}
//отправка символа
void LCDprint(char symbol){
    LCD_CTRL_PORT|=LCD_EN|LCD_RS    // поднимаем строб, ставим режим символа (EN=1,RS=1)
    LCD_DATA_PORT=symbol;           // подаем символ
    _delay_us(20);                   // ждем
    LCD_CTRL_PORT&=~(LCD_EN|LCD_RS);//опускаем строб, отключаем режим символа (EN=0,RS=0)
    _delay_ms(4);
}
//инициализация дисплея
void LCDinit(){
    _delay_ms(160);
    LCDcomand(LCD_START);
    _delay_ms(20);
    LCDcomand(LCD_START);
    _delay_us(480);
    LCDcomand(LCD_START);
    _delay_ms(4);
    LCDcomand(LCD_8BIT_2LINE);
    _delay_ms(20);
    LCDcomand(LCD_DISPLAY_ON);
    _delay_ms(20);
    LCDcomand(LCD_CLEAR);
    _delay_ms(20);
    LCDcomand(LCD_INCREMENT_ON_CURSOR_OFF);
 }
 //вывод строки
 void LCDprintLine(char* line,char size){
     _delay_ms(20);
     for(int i = 0;i<size;i++) LCDprint(line[i]);
  }
//инициализация портов
 void port_init(){
    LCD_CTRL_DDR=0xFF; // порт на выход
    LCD_DATA_DDR=0xFF; // порт на выход
 }
 
 int main(void){
    char h_byte,l_byte;//старший и младший байт результата измерения
    I2C_Init(); //инициализируем i2с
    port_init();//настраиваем порты
    LCDinit();  //инициализация дисплея
    LCDprintLine("    Cxem.net",12); //вывод приветствия
    LCDcomand(LCD_SECOND_LINE); //переход на вторую линию
    LCDprintLine("LUX METR",8); //вывод текста на второй линии
    _delay_ms(5000); //задержка 5 секунд
    LCDcomand(LCD_CLEAR);//очистка дисплея
    LCDprintLine("LIGHT, lux:",11); //вывод первой строки
    while(1){
        //----------------------Получение освещенности от датчика
        I2C_Start();//запуск шины i2c
        I2C_SendByte(SLA_W);//отправляем адресс устройства + флаг "запись"
        I2C_SendByte(0b00010000);//отправляем команду "начать измерение"
        I2C_Stop();//останавливаем шину i2c
        _delay_ms(250);//ждем пока устройство померяет (время взято из даташита + пару милисекунд)
        I2C_Start();//запуск шины i2c
        I2C_SendByte(SLA_R);//отправляем адресс устройства + флаг "чтение"
        h_byte = I2C_GetByte();//читаем старший байт результата
        l_byte=I2C_GetByte();//читаем младший байт результата
        I2C_Stop();//останавливаем шину i2c
        //-------------------Пересчитываем результат
        unsigned int a=((h_byte<<8)|l_byte)/1.2; //формула из даташита на BH1750
        
        //------переводим число в строку
        char I[16]=" ";//строка для вывода
        for(char i=0;i<7;i++){
            I[15-i]=a%10+48;//48-смещиние цыфры е ее коду
            a=a/10;
        }
        //----------------------Вывод данных
        LCDcomand(LCD_SECOND_LINE);//переводим дисплей на вторую линию
        LCDprintLine(I,16);//выводим строку с результатом
        _delay_ms(1000);//пауза 1 секунда
    }
 }

В приложении к статье находится печатная плата и hex-файл прошивки, при прошивке контроллера фьюзы оставить дефолтные

Прикрепленные файлы:

Теги:

Опубликована: 0 0
Я собрал 0 Участие в конкурсе 0
x

Оценить статью

  • Техническая грамотность
  • Актуальность материала
  • Изложение материала
  • Полезность устройства
  • Повторяемость устройства
  • Орфография
0

Средний балл статьи: 0 Проголосовало: 0 чел.

Комментарии (4) | Я собрал (0) | Подписаться

0
Публикатор #
На форуме автоматически создана тема для обсуждения статьи.
Ответить
0
wanowar #
NACK - это неподтверждение. Подтверждение ACK
Ответить
0
Олег #
А где рабочий хекс?
Ответить
0
Олег #
Заменяем фюзы на D9 E4 и смотрим разницу.
Ответить
Добавить комментарий
Имя:
E-mail:
не публикуется
Текст:
Защита от спама:
В чем измеряется сила тока?
Файлы:
 
Для выбора нескольких файлов использйте CTRL

Мультиметр DT9205A
Мультиметр DT9205A
Набор начинающего радиолюбителя МиниПК MK809V - 4 ядра, Android 4.4.2
вверх