понедельник, 1 февраля 2010 г.

USB-программатор для контроллеров Atmel

Наконец-то собрал девайсину, нормальный человеческий программатор. До этого для прошивки использовался самый простейший вариант на четырёх резисторах работающий через LPT порт. Штука конечно была простая и нехитрая, но неудобная тем что это был LPT порт(почти R.I.P. в наше время) и не безопасная. Так я им однажды спалил одну из ножек atmega168 отвечающую за SPI, так что тот теперь не прошивается..
В связи с тем, назревшая необходимость стала толчком к делу. Нашёл нормальный и относительно недорогой по себестоимости вариант программатора USB-ASP. После довольно продолжительного шаманства в программе SprintLayout, рисунок был перенесён на печатную плату используя технологию лазерного утюга. Затем было лужение, паяние, потом танцы с бубном.... потом опять паяние....... потом опять танцы... и бац! заработало, прошивает!!!
По части дизайна я считаю у меня получилось очень даже ничего:
А вот в пайке я ламер, из-за этого и были танцы сначала.... не пропой. Я пожалел что не увеличил диаметр контактов для размещения DIP панельки, очень не легко было паять в этих местах.
Прошивка в программе avrdude проста и выполняется из консоли командой вроде этой:
$avrdude -v -c usbasp -p atmega644 -U flash:w:file.hex
Скорость выполнения составляет примерно 5 кбайт/сек, что тоже вполне неплохо. Бывают ошибки при попытке прошивки (пишет Expected Signature), причём судя по всему напрямую зависит от компьютера. Так на моём стационарном 6 из 10 попыток успешны, а на нетбуке все 10/10 оказались успешными.
Мой вариант разводки печатной платы в программе SprintLayout можно взять здесь (доработанный вариант).

вторник, 26 января 2010 г.

Библиотека для цифрового термометра DS18B20

Захотелось мне тут порезвиться с этой замечательной микросхемой. И так как я страдаю фигнёй манией писать библиотеки для разного вида подобных устройств, библиотека была написана! Писал долго, потому как было мало свободного времени. К тому же очень хотелось чтобы она поддерживала весь функционал доступный программисту. А функционал у неё неплохой. Микруха такая стоит порядка 150-300руб. (как повезёт), и имеет следующие основные возможности:
  • Работа по интерфейсу 1-wire, который требует всего одной линии для связи с МК;
  • Возможность питания от одной линии данных;
  • Уникальный 64-битный ID для каждого датчика;
  • Возможность развёртывать целые микросети таких датчиков связанных одной шиной;
  • Неплохая точность, ±0.5°C в диапазоне -10°C до 85°C;
  • Оповещение о выходе температуры из заданных пределов;
  • Совместимость с DS1822;
Варианты подключения
Может питаться как от внешнего источника, так и от линии данных. На следующих рисунках показаны варианты подключения (слева-направо) от внешнего источника, от шины данных.
 

Особенности библиотеки
Разработанная библиотека поддерживает все возможности данного цифрового термометра, и занимает в памяти МК от 254 до 918 байт, в зависимости от выбранных настроек конфигурации. В минимальной конфигурации поддерживается:
  • Измерение температуры (convert);
  • Чтение температуры (readTemp);
  • Выбор датчика по его уникальному коду (selectSensor);
Полный вариант конфигурации поддерживает к тому же:
  • Дополнительные функции (такие как recall, readScratchpad, copyScratchpad, writeScratchpad, и др.);
  • Автоматическая проверка контрольной суммы при приёме данных;
  • Поддержка датчиков питающихся паразитным током;
  • Поиск устройств на шине а также датчиков чья температура выходит из заданных пределов (searchROM, searchAlarm);
  • Дополнительные функции для обработки значения температуры, с целью дальнейшего вывода на дисплей (extractInt, extractFract);
Примеры использования
Простой пример демонстрирующий вывод текущего значения температуры на дисплей:
#include "wh1602b/wh1602b.h"
#include "ds18b20/ds18b20.h"
#include <avr/pgmspace.h>

void conf_my_lcd();
void loop();

int main(void)
{
  conf_my_lcd(); // настроить вывод на жк-дисплей
  // выбрать текущий датчик (0 т.к. единственный у меня)
  if (!ds18b20_selectSensor(0)) {
    wh1602b_putsP(PSTR("Ошибка на линии!"));
    for (;;)
      ;
  }
  for (;;)
    loop(); // цикл обработки
}

void loop()
{
  ds18b20_convert();  // попросить у датчика измерить температуру
  wh1602b_move(0,0);
  wh1602b_putsP(PSTR("t="));
  int16_t t = ds18b20_readTemp(); // прочитать значение
  if (t < 0) // вывести минус если отрицательно
    wh1602b_putc('-');
  wh1602b_puth(1, ds18b20_extractInt(t)); // вывести целую часть на дисплей
  wh1602b_putc('.');
  wh1602b_puth(1, ds18b20_extractFract(t, 1)); // вывести дробную часть на дисплей
  wh1602b_putsP(PSTR("\1C"));
}

// <...>
Вот что получилось:

Следующий пример демонстрирует вывод уникального кода микросхемы на дисплей:
#include "wh1602b/wh1602b.h"
#include "ds18b20/ds18b20.h"
#include <avr/pgmspace.h>

void conf_my_lcd();

int main(void)
{
  conf_my_lcd(); // настроить вывод на жк-дисплей
  uint8_t id[8];
  if (!ds18b20_readROM(id)) { // прочитать уникальный код датчика в id
    wh1602b_putsP(PSTR("Ошибка CRC!")); // если ошибка
    for (;;)
      ;
  }
  wh1602b_putsP(PSTR("ROM Code ="));
  wh1602b_move(1,0);
  uint8_t i;
  for (i = 0; i < 8; ++i)
    wh1602b_puth(2, id[i]);
  for (;;)
    ;
}

// <...>
И результат:

Следующий пример демонстрирует поиск устройств на шине:
#define F_CPU 20000000ULL /* частота мк, гц (для delay) */
#include <avr/pgmspace.h>
#include <util/delay.h>

#include "wh1602b/wh1602b.h"
#include "ds18b20/ds18b20.h"

void conf_my_lcd();
void loop();

int main(void)
{
  conf_my_lcd(); // настроить вывод на жк-дисплей
  for (;;)
    loop();  // цикл обработки
}

void loop()
{
  uint8_t i, dc = 0, id[8];
  int8_t r;
  // поиск устройств на шине 1-wire
  while ((r = ow_searchROM(id))) {
    if (r < 0)
     continue; // повтор если ошибка crc
    wh1602b_move(0,0);
    // вывести уникальный код датчика на дисплей
    for (i = 0; i < 8; ++i)
      wh1602b_puth(2, id[i]);
    _delay_ms(2000); // пауза 2с.
    ++dc;
  }
  wh1602b_move(1,0);
  wh1602b_putn(dc); // вывести число датчиков
  wh1602b_putsP(PSTR(" devices."));
}

// <...>
И результат в моём случае:


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

Настройка конфигурации
Перед компиляцией библиотеки необходимо задать порты к которым подключён дисплей, а также включаемые возможности. Для этого необходимо отредактировав файл ds18b20/conf.h:
#define F_CPU 20000000 /* частота мк, Гц */

#define USE_EXTRACTINT    /* поддержка ds18b20_extractInt() */
#define USE_EXTRACTFRACT  /* поддержка ds18b20_extractFract() */

// поддержка автоматической проверки данных контрольной суммой
#define SUPPORT_CHECKSUM
// поддержка возможности поиска устройств на шине
#define SUPPORT_ROMSEARCH
// поддержка датчиков питающихся паразитным током
#define SUPPORT_PARASITE_MODE
// поддержка некоторых дополнительных функций
#define SUPPORT_COMPLETE

/* порт к которому подключена линия данных */
#define DDR_SENSOR   _SFR_IO_ADDR(DDRD)
#define PIN_SENSOR   _SFR_IO_ADDR(PIND)
#define PORT_SENSOR  _SFR_IO_ADDR(PORTD)
/* ножка к которой подключена линия данных */
#define NPIN_SENSOR  PD2
Убрать лишние возможности тем самым уменьшив размер занимаемый библиотекой в памяти МК, можно закомментировав некоторые из строчек начинающихся с #define.

Полный перечень поддерживаемых функций
  • uint8_t ds18b20_selectSensor(const uint8_t id[8])
    Выбирает текущий датчик по его уникальному коду id. Если id = 0, то выбирается первое попавшееся устройство, которое должно быть единственным в сети. Если возвращаемое значение = 0, то вероятно линия данных не правильно настроена. В случае успеха возвращается значение > 0.
  • void ds18b20_convert()
    Отправляет команду текущему датчику чтобы тот начал измерение температуры.
  • uint8_t ds18b20_readROM(uint8_t id[8])
    Загружает уникальный код датчика в массив указанный аргументом id. В сети должно находиться только одно устройство, иначе возникнут коллизии. Если возвращаемое значение = 0, то вероятно линия данных не правильно настроена. В случае успеха возвращается значение > 0.
  • int16_t ds18b20_readTemp()
    Возвращает измеренное значение температуры текущего датчика в градусах. Младшие 4 бита определяют дробную часть, остальная часть - целое значение.
    Например, если возвращаемое значение = 0x0191 = 0000000110010001 = 2^4+2^3+2^0+2^-4 = +25.0625°C
  • uint8_t ds18b20_readPowerSupply()
    Возвращает 1 если текущий датчик использует внешнее питание, 0 - питание от линии данных (паразитное).
  • uint8_t ds18b20_readScratchpad(sspad* p)
    Загружает состояние внутренней памяти текущего датчика в *p. Проверяется контрольная сумма, и возвращается значение > 0 в случае успеха. Если 0, то вероятно контрольная сумма не совпала, либо линия данных не правильно настроена.
    Структура sspad определена в файле ds18b20/ds18b20.h и имеет следующее представление:
    typedef struct {
      uint8_t t_lsb;       // temperature LSB
      uint8_t t_msb;       // temperature MSB
      int8_t t_h;          // Th register
      int8_t t_l;          // Tl register
      uint8_t config;      // configuration register
      uint8_t reserved[3]; // reserved
    } sspad;
    
    Подробней о назначении каждого параметра можно почитать в официальной документации к ds18b20.
  • void ds18b20_writeScratchpad(const sspad* p)
    Записывает данные в *p в память текущего датчика.
  • void ds18b20_copyScratchpad()
    Копирует состояние памяти текущего датчика в его eeprom. EEPROM определяется как энергонезависимая память, и при каждом включении питания, данные из eeprom датчика будут загружаться в его оперативную память.
  • void ds18b20_recall()
    Восстанавливает состояние памяти текущего датчика из его eeprom.
  • int8_t ow_searchROM(uint8_t id[8])
    Выполняет поиск следующего из устройств на шине 1-wire, и передаёт его уникальный код в параметр на который указывает id. Если возвращаемое значение = 0, то поиск завершён. Если возвращаемое значение = -1, то это означает что контрольная сумма не совпала, и данные были повреждены при приёме. В случае успеха возвращается значение > 0.
  • int8_t ds18b20_searchAlarm(uint8_t id[8])
    Выполняет поиск следующего датчика у которого пороговое значение температуры вышло из заданных пределов, и передаёт его уникальный код в параметр на который указывает id. Если возвращаемое значение = 0, то поиск завершён. Если возвращаемое значение = -1, то это означает что контрольная сумма не совпала, и данные были повреждены при приёме. В случае успеха возвращается значение > 0.
  • uint16_t ds18b20_extractInt(int16_t t)
    Возвращает целую часть значения температуры из двухбайтовой величины t, получаемой при помощи вызова функции readTemp. Возвращаемое значение является абсолютным и представлено в двоично-десятичном формате (bcd).
  • uint16_t ds18b20_extractFract(int16_t t, uint8_t r)
    Возвращает дробную часть значения температуры из двухбайтовой величины t, получаемой при помощи вызова функции readTemp, округляя его до 1/10^r. Возвращаемое значение является абсолютным и представлено в двоично-десятичном формате (bcd).
Совместимость
Думаю что библиотека будет совместима с большинством микроконтроллеров AVR серии Mega (в моём случае был использован Atmega644).

Скачать
  • Исходный код библиотеки вместе с примером можно скачать здесь (или здесь). Лицензия GNU GPL v3+;
  • Официальная документация по ds18b20 от производителя;

воскресенье, 10 января 2010 г.

Встречайте - Maxduino!))

На днях собрал себе вот такой девайсик:



Данное устройство представляет собой что-то на подобии отладочной платы на базе контроллера AtMega644, имеет подключаемый ЖК-дисплей, а также содержит внешний часовой кварц. Основной причиной по которой я решил использовать именно AtMega644 была поддержка JTAG, а также большое количество линий ввода/вывода - 32 по сравнению с моим прошлым AtMega168, и большой объём памяти программ - 64кб, вместо 16.
Кварц и дисплей могут быть отключены если снять перемычки соответственно с "Ext. Clk" и "LCD En". Контрастность дисплея здесь может быть настроена с помощью переменного резистора с маркировкой "Contrast", есть кнопка сброса.
Встраивание ЖК-дисплея в состав платы я нашёл для себя довольно нужным, потому как он часто бывает необходим для отображения каких-то промежуточных данных. Также очень хочется поиграться с JTAG, но сначала надо будет собрать устройство для сопряжения JTAG-интерфейса и AVRStudio..
Плата была разведена при помощи Лазерно-Утюжной технологии, о которой я впервые прочитал здесь, а позже углубился здесь=) Лужение платы производилось сплавом Розе..


PS. Название платы конечно шутка и пародие на Arduino и её многочисленные клоны=)

воскресенье, 27 декабря 2009 г.

Библиотека для семисегментного дисплея BQ-M322RD

В связи с возникшей потребностью, была написана библиотека для работы с 4-х разрядным семисегментным дисплеем BQ-M322RD разработанным компанией Bright Led Electronics. Я также уверен что данная библиотека сможет работать и с другими аналогичными 4х-разрядными дисплеями, с общим анодом.
Внешний вид дисплея


Свечение сегментов тут практически не видно, так как сьёмка проходила на ярком дневном освещении. А так, не мешало бы приложить какое-нибудь тёмное стёклышко:)
Особенности данной библиотеки
При написании использовался ассемблер. Кроме этого, пытаясь максимально сократить размер кода, использованные алгоритмы были приближены к идеалу совершенства=) Для отображения цифр на сегментах дисплея был использован способ называемый динамической индикацией. Библиотека способна реализовывать этот режим двумя способами:
  1. Используя ресурсы таймера 0
  2. Используя функцию m322_loop() которую программа должна будет непрерывно вызывать в процессе своего выполнения
Библиотека занимает 264 байт памяти программ и 13 байт памяти данных при использовании первого способа, и соответственно 240 и 13 байт при использовании второго.
Выбрать один из способов можно отредактировав файл m322/conf.h

Примеры использования

Простой пример:
/*
 программа использует библиотеку откомпилированную
 с опцией #define USE_TIMER0
*/
#include "m322/m322.h"

int main(void)
{
  m322_init(); // инициализировать семисегментный дисплей
  m322_putn(1234); // вывести число 1234
  m322_setdot(0, TRUE); // зажечь точку в разряде 0
  for (;;)
    ;
}
Если библиотека откомпилирована вторым способом, то:
#include "m322/m322.h"

int main(void)
{
  m322_init(); // инициализировать семисегментный дисплей
  m322_putn(1234); // вывести число 1234 на семисегментный дисплей
  m322_setdot(0, TRUE); // зажечь точку в разряде 0
  for (;;)
    m322_loop(80); // цикл динамической индикации
}
В данном случае добавлен вызов функции m322_loop() в бесконечный цикл, которая реализует динамическую индикацию. В качестве аргумента ей надо указать некоторое число. Частота мерцания дисплея будет зависеть от этого значения. Чем выше значение - тем меньше частота мерцания. Для моего 16мГц МК на котором я тестировал данный пример, оказалось оптимальным значение 80. Если брать больше - будет заметно мерцание, если меньше - разряды могут "поплыть".
Более интересный пример:
/*
 программа использует библиотеку откомпилированную
 с опцией #define USE_TIMER0
*/
#define F_CPU 16000000ULL /* частота работы мк (для _delay) */
#include <util/delay.h>
#include "m322/m322.h"

int main(void)
{
  m322_init(); // инициализировать семисегментный дисплей

  uint16_t number = 0;
  uint8_t n_dot = 0;
  for (;;) {
    m322_setdot(n_dot, TRUE); // зажечь точку с заданным номером
    m322_putn(number); // вывести десятичное число на индикатор
    _delay_ms(100); // пауза 100мс
    m322_setdot(n_dot, FALSE); // погасить точку с заданным номером

    if (++n_dot == 5)
      n_dot = 0;
    if (++number == 10000)
      number = 0;
  }
}
Если попробовать выполнить программу на МК, то получим - на дисплее будет отображаться число, которое будет увеличиваться каждые 100 миллисекунд, а точка "зажигаясь" будет перемещаться циклически слева-направо.
Как настроить библиотеку под себя
Перед компиляцией библиотеки необходимо задать порты к которым подключён дисплей, отредактировав файл m322/conf.h:
//
// Порты используемые для доступа к отдельным сегментам
//
#define PORT_SEGDATA  _SFR_IO_ADDR(PORTD)
#define DDR_SEGDATA   _SFR_IO_ADDR(DDRD)
/* номера пинов для каждого отдельного сегмента */
#define NPIN_DP PD7 /* точка */
#define NPIN_G  PD6
#define NPIN_F  PD5
#define NPIN_E  PD4
#define NPIN_D  PD3
#define NPIN_C  PD2
#define NPIN_B  PD1
#define NPIN_A  PD0
//
// Порты используемые для доступа к отдельным разрядам
//
#define DDR_SEGCTL    _SFR_IO_ADDR(DDRB)
#define PORT_SEGCTL   _SFR_IO_ADDR(PORTB)
/* номера пинов для каждого отдельного разряда */
#define NPIN_SEGCTL0  PB4 /* D1 */
#define NPIN_SEGCTL1  PB1 /* D2 */
#define NPIN_SEGCTL2  PB2 /* D3 */
#define NPIN_SEGCTL3  PB3 /* D4 */
//
// Дополнительные настройки
//
// заккоментируйте следующую опцию если не хотите использовать
//  ресурсы системного таймера0 для организации динамической
//  индикации семисегментного дисплея:
/* использовать таймер */
#define USE_TIMER0

#ifdef USE_TIMER0
// частота мерцания семисегментного индикатора зависит
//  от коэффициента предделителя таймера0,
// и вычисляется по формуле f = F_CPU/(P*256*4), где:
//  F_CPU - тактовая частота мк, Гц
//  P - коэффициент предделителя
/* значение предделителя для таймера */
#define TIMER0_PRESCALER ((1<<CS00)|(1<<CS01)) /* предделитель 64 */
#endif // USE_TIMER0
Я думаю здесь комментарии излишни=)

Схематическое изображение дисплея:



Полный перечень поддерживаемых функций
Библиотека поддерживает все самые необходимые функции, а именно:
  • void m322_init()
    Выполняет инициализацию семисегментного дисплея (настройка портов, таймера).
  • void m322_puthd(uint8_t c, uint8_t n)
    Выводит шестнадцатеричную цифру n в разряд с номером c индикатора.
  • void m322_puthh(uint8_t n)
    Выводит шестнадцатеричное число n в старшую часть индикатора (разряды 0 и 1)
  • void m322_puthl(uint8_t n)
    Выводит шестнадцатеричное число n в младшую часть индикатора (разряды 2 и 3)
  • void m322_setdot(uint8_t c, uint8_t b)
    Задаёт точку в разряд с номером c индикатора. Если b = TRUE - зажечь, иначе погасить.
  • void m322_putn(uint16_t n)
    Выводит на индикатор беззнаковое десятичное число n
  • void m322_loop(uint16_t n)
    Цикл для организации динамической индикации без использования таймера0
Совместимость
Думаю что библиотека будет совместима с большинством микроконтроллеров AVR серии Mega (в моём случае был использован Atmega8).

Скачать
Исходный код библиотеки вместе с примером можно скачать здесь (или здесь).

воскресенье, 6 декабря 2009 г.

Библиотека для ЖК-дисплея WH1602b

После долгой кропотливой работы изучения документации и написания кода, родилась полезная и нужная для меня библиотека для работы с этим дисплеем. Возможно она окажется полезной кому-нибудь ещё :)
О дисплее
Дисплей этот я приобрёл на молотке около года тому назад, за почти астрономическую сумму в 450рэ. Ну тем не менее он устраивает меня во всём, и в быту неприхотлив=)
Особенности этой библиотеки
Код написан на ассемблере, и очень компактен. Занимает от 298 до 466 байт в памяти программ МК. В файле конфигурации wh1602b/conf.h можно дополнительно установить поддержку следующих функций:
  • Поддержка знакогенератора (298+52 байт)
  • Поддержка функции вывода десятичных чисел (298+66 байт)
  • Поддержка функции вывода шестнадцатеричных чисел (298+72 байт)
Основные функциональные возможности
  • Вывод строк, символов
  • Вывод чисел
  • Очистка дисплея
  • Управление курсором
  • Поддержка знакогенератора (до 8-ми знаков, согласно спецификации)
  • Настройка различных параметров работы
Не поддерживается:
  • Чтение памяти дисплея
  • Текущее положение курсора нельзя определить

Библиотека использует 4-битную шину для передачи данных.

Примеры использования

Простой пример:
#include "wh1602b/wh1602b.h"

int main(void)
{
  wh1602b_init(); // инициализировать модуль, порты
  // настроить параметры управления дисплеем
  wh1602b_displayctl(DC_DISPLAY_ON); // дисплей включён
  // задать число строк
  wh1602b_funcset(FS_LINES_2); // 2 строки
  // очистить область
  wh1602b_clear();
  // вывести строку на дисплей
  wh1602b_puts("Hello, World!");

  for (;;)
    ;
}
Результат:

Следующий пример демонстрирует возможность загрузки собственного знака:
#include "wh1602b/wh1602b.h"

// собственно массив представляющий собой
//  знак который надо загрузить
static const uint8_t window_char[8] = {
  0x1f, // 1 1 1 1 1
  0x11, // 1 0 0 0 1
  0x0e, // 0 1 1 1 0
  0x04, // 0 0 1 0 0
  0x0e, // 0 1 1 1 0
  0x15, // 1 0 1 0 1
  0x15, // 1 0 1 0 1
  0x00  // 0 0 0 0 0
};

int main(void)
{
  wh1602b_init(); // инициализировать модуль, порты
  // настроить параметры управления дисплеем
  wh1602b_displayctl(DC_DISPLAY_ON); // дисплей включён
  // задать число строк
  wh1602b_funcset(FS_LINES_2); // 2 строки
  // очистить область
  wh1602b_clear();
  // записать символ ассоциировав его с кодом 5
  wh1602b_genc(5, window_char);
  // вернуться в начало (команда аналогичная move).
  //   этот вызов является обязательным после вызова genc()
  wh1602b_home();
  // вывести строку на дисплей вместе с новым знаком :)
  wh1602b_puts("Крякозябла \5");

  for (;;)
    ;
}
Результат:

В качестве аргументов для wh1602b_puts и wh1602b_genc выступает указатель на строку расположенную в области RAM. Для того чтобы иметь возможность выводить строки из памяти программ, необходимо соответственно использовать wh1602b_putsP или wh1602b_gencP. Например:
#include "wh1602b/wh1602b.h"
#include <avr/pgmspace.h>

int main(void)
{
  <...>
  // вывести строку1 из памяти программ
  wh1602b_putsP(PSTR("Здравствуй"));
  // переместить курсор на заданную позицию (нумерация с нуля)
  wh1602b_move(1,4); // 2 строка, 5 столбец
  // вывести строку2 из памяти программ
  wh1602b_putsP(PSTR("Мир!"));
  <...>
}
Результат:


Как настроить библиотеку под себя
Для начала необходимо задать порты к которым подключён дисплей. Для этого необходимо отредактировать файл wh1602b/conf.h:
#define F_CPU 16000000 /* частота мк, Гц */

#define USE_PUTN /* включить поддержку wh1602b_putn() */
#define USE_PUTH /* включить поддержку wh1602b_puth() */
#define USE_GENC /* включить поддержку wh1602b_genc() */

/* ножки к которым подключены выводы RS, RW, EN */
#define NPIN_RS PB4
#define NPIN_RW PB3
#define NPIN_EN PB2

/* порты к которым подключены выводы RS, RW, EN */
#define PORT_RS _SFR_IO_ADDR(PORTB)
#define PORT_RW _SFR_IO_ADDR(PORTB)
#define PORT_EN _SFR_IO_ADDR(PORTB)
#define DDR_RS  _SFR_IO_ADDR(DDRB)
#define DDR_RW  _SFR_IO_ADDR(DDRB)
#define DDR_EN  _SFR_IO_ADDR(DDRB)

/* ножки к которым подключены выводы DB4-DB7 */
#define NPIN_DB7  PD7
#define NPIN_DB6  PD6
#define NPIN_DB5  PD5
#define NPIN_DB4  PD4

/* порты к которым подключены выводы DB4-DB7 */
#define PORT_DB7  _SFR_IO_ADDR(PORTD)
#define PORT_DB6  _SFR_IO_ADDR(PORTD)
#define PORT_DB5  _SFR_IO_ADDR(PORTD)
#define PORT_DB4  _SFR_IO_ADDR(PORTD)
#define DDR_DB7   _SFR_IO_ADDR(DDRD)
#define DDR_DB6   _SFR_IO_ADDR(DDRD)
#define DDR_DB5   _SFR_IO_ADDR(DDRD)
#define DDR_DB4   _SFR_IO_ADDR(DDRD)

Соответствующие ножки дисплея показаны на следующем рисунке:

Как компилировать программы написанные с использованием этой библиотеки
Существует ряд особенностей при компиляции программ использующих данную библиотеку. Дело в том что ЖК-дисплей понимает только строки написанные в своём собственном варианте кодировки. Поэтому компилировать надо с ключом -fexec-charset=wh1602b. При этом, в системе должен быть установлен специальный модуль для iconv - wh1602b. Готовые скомпилированный модуль можно скачать здесь (x86_64 и i686), или собрать его самостоятельно. Для установки необходимо скопировать файл WH1602B.so в папку с модулями gconv (у меня это /usr/lib/gconv), а также отредактировать файл /usr/lib/gconv/gconv-modules, добавив строчки:
# from      to      module    cost
alias WH-1602B//     WH1602B//
alias 1602B//       WH1602B//
module  WH1602B//    INTERNAL    WH1602B    1
module  INTERNAL    WH1602B//    WH1602B    1
Самостоятельная сборка такого модуля довольно кропотливая процедура, и будет описана в следующий раз.

Полный перечень поддерживаемых функций
  • void wh1602b_init()
    Выполняет инициализацию модуля дисплея.
  • void wh1602b_funcset(uint8_t t)
    Устанавливает число строк и размера шрифта. В качестве аргументов может служить комбинация одного или нескольких параметров:
    FS_LINES_2 - использовать две строки дисплея (по умолчанию одна строка, FS_LINES_1)
    FS_FONT_5x11 - использовать шрифт размером 5х11 (по умолчанию 5х8)
  • void wh1602b_entrymode(uint8_t t)
    Настраивает режима ввода. В качестве аргументов может служит комбинация одного или нескольких параметров:
    EM_CURSOR_DIR - использовать автоматическое перемещение курсора слева-направо
    EM_DISPLAY_SHIFT - разрешить смещение дисплея
  • void wh1602b_displayctl(uint8_t t)
    Устанавливает параметры управления дисплеем. В качестве аргументов может служит комбинация одного или нескольких параметров:
    DC_DISPLAY_ON - дисплей включён (по умолчанию выключен, DC_DISPLAY_OFF)
    DC_CURSOR_BLINK - использовать мигающий курсор
    DC_CURSOR_ON - использовать курсор
  • void wh1602b_puts(const char* s)
    Выводит строку s из RAM на дисплей.
  • void wh1602b_putsP(const char* s)
    Выводит строку s из памяти программ на дисплей.
  • void wh1602b_putn(uint16_t n)
    Выводит десятичное число n на дисплей.
  • void wh1602b_puth(uint8_t w, uint16_t h)
    Выводит шестнадцатеричное число h на дисплей. Ширина поля определяется параметром w
  • void wh1602b_genc(uint8_t n, const uint8_t* p)
    Записывает знак на который указывает p в знакогенератор. Если это последний записываемый знак, и при этом ожидается вывод на дисплей, то необходимо после выполнения genc вызвать wh1602b_home() или wh1602b_move().
  • void wh1602b_home()
    Возвращает курсор в начальное положение. Если дисплей при этом смещён то функция восстанавливает его.
  • void wh1602b_move(uint8_t r, uint8_t c)
    Перемещает курсор на заданную позицию (r - строка, c - столбец). Нумерация начинается с нуля.
  • void wh1602b_putc(uint8_t c)
    Выводит символ c на экран дисплея.
  • void wh1602b_clear()
    Очищает экран дисплея.
Скачать
Исходный код библиотеки вместе с примером можно скачать здесь.

понедельник, 30 ноября 2009 г.

Пишем драйвер для лампочки=) Часть 2.

В прошлый раз я писал как создавал драйвер для лампочки. Эта часть будет посвящена тому как я использовал свой драйвер в отдельном проекте.

Написание основного проекта
Свой проект я разместил в папке ~/hard_project, основной и единственный файл которого имеет следующее содержание:
// файл ~/hard_project/libdrv/main.c
#define F_CPU 16000000UL /* тактовая частота МК */
#include "leddrv/leddrv.h"

int main(void)
{
  led_init(); // инициализация лампочки
  for (;;) // бесконечный цикл
    led_blink_ms(500); // мигнуть с частотой 0,5сек
}
Этот код настолько прост и понятен что в дополнительных комментариях и не нуждается.

Упрощённую версию Makefile для сборки проекта можно скачать тут. Скомандовав в консоли, я и получил целевой hex файл:
$ make
avr-gcc -g -Wall -O2 -mmcu=atmega168 -o .main.o -c main.c
avr-gcc -g -Wall -O2 -mmcu=atmega168  -o .main.elf .main.o -static -Lleddrv/ -lleddrv
avr-objcopy -j .text -j .data -O ihex .main.elf main.hex

А затем и прошил свой МК:
$ make program
avrdude -v -c dapa -p atmega168 -P /dev/parport0 -b 115200 -U flash:w:main.hex

И вот оно, о чудо - работает!



Итог
Всё круто! Цель достигнута. В моих планах написать аналогичные библиотеки для ЖК-модуля и датчика температуры. Надеюсь это произойдёт в недалёкой перспективе:)

---
Скачать hard_project.tar.gz

воскресенье, 29 ноября 2009 г.

Пишем драйвер для лампочки=) Часть 1.

Хочу описать собственную идею (возможно она не нова), по которой я собираюсь создавать драйверы для различных аппаратных модулей, микросхем и тп.. Базируется она на основе использования статических библиотек, которые будут подключаться к основному проекту и представлять собой драйвер для какого-нибудь отдельно взятого модуля. Статическую библиотеку предлагаю писать на ассемблере, с целью сокращения объёма кода а также получения наивысшей производительности=) Такая библиотека, подключённая к основному проекту будет занимать минимум, при этом останется больше свободного места в памяти МК для написания основной логики.

В качестве примера можно рассмотреть тривиальный пример - драйвер для лампочки (индикатора). Данный пример подходит только для демонстрации самой идеи, а не её практического применения. Лампочка уж слишком простая вещь чтобы для неё писать целый драйвер. А вообще хотелось бы написать таковые для ЖК-дисплея, микросхемы ds1307, да много чего можно ещё придумать:) Главное желание, и свободное время. С последним у меня пока проблемы... Итак, приступим!

Подготовительная часть
1) Необходимо установить в систему пакеты gcc-avr, binutils-avr, и avr-libc. (если ещё не были установлены)
2) Вкратце изучить документацию по avr-libc. Там содержится очень много интересного(:
3) Создать папку для основного проекта, у меня это ~/hard_project.
4) Создать папку для проекта библиотеки, у меня это ~/hard_project/libdrv.

Написание библиотеки
Основной файл будет иметь следующее содержание:
;; файл ~/hard_project/libdrv/leddrv.S:
#include <avr/io.h>
#include "conf.h"

.global led_init ; void led_init()
; предварительная настройка порта
led_init:
  sbi _SFR_IO_ADDR(LED_DDR),LED_NPIN ; установить ножку на вывод данных
  ret

.global led_on ; void led_on()
; включение лампочки
led_on:
  sbi _SFR_IO_ADDR(LED_PORT),LED_NPIN ; записать логическую 1 в порт
  ret

.global led_off ; void led_off()
; выключение лампочки
led_off:
  cbi _SFR_IO_ADDR(LED_PORT),LED_NPIN ; записать логический 0 в порт
  ret

.global led_blink ; void led_blink(uint16_t f)
; мигание лампочки с заданной частотой
led_blink:
  sbi _SFR_IO_ADDR(LED_PORT),LED_NPIN ; записать логическую 1 в порт
  push r24
  push r25
  rcall delay ; задержать выполнение
  cbi _SFR_IO_ADDR(LED_PORT),LED_NPIN ; записать логический 0 в порт
  pop r25
  pop r24
  rcall delay ; задержать выполнение
  ret

; задержка на заданное значение
delay: ; до 770*65535/F_CPU, сек
   clr r0
0: com r0
1: dec r0
    brne 1b
    sbiw r24,1
    brne 0b ; 770 тактов за цикл
    ret

Здесь первый параметр для функции led_blink будет передаваться через регистр r24:r25, согласно соглашениям компилятора avr-gcc. Далее, заголовочный файл будет иметь следующее содержание:
// файл ~/hard_project/libdrv/leddrv.h:
#include <stdint.h>
#ifdef F_CPU
#define led_blink_ms(x) led_blink(((x)*(uint64_t)F_CPU)/(770*1000ul))
#endif

void led_init();
void led_on();
void led_off();
void led_blink(uint16_t f);

Здесь определён макрос led_blink_ms для удобства. При его вызове нужно указывать значение в миллисекундах, в отличии от led_blink, в которой указывается значение в условных единицах =) Последний файл проекта будет иметь следующее содержание:
// файл ~/hard_project/libdrv/conf.h:
#define LED_PORT PORTC
#define LED_DDR  DDRC
#define LED_NPIN PC1

Ну здесь наверное понятно, указываются параметры порта и ножки на которой будет сидеть лампочка =)

Это всё. Теперь необходимо собрать нашу бибиотеку. Упрощённую версию Makefile для сборки проекта можно скачать тут. Скомандовав в консоли, я и получил целевой файл libleddrv.a:
$Make
avr-gcc -Wall -x assembler-with-cpp -mmcu=atmega168 -o .leddrv.o -c leddrv.S
avr-ar rcs libleddrv.a .leddrv.o

В следующий раз я покажу как использовал эту библиотеку в своём проекте=)

---
Скачать исходники libdrv.tar.gz