Показаны сообщения с ярлыком source code. Показать все сообщения
Показаны сообщения с ярлыком source code. Показать все сообщения

вторник, 29 ноября 2011 г.

Работа над ошибками

.. вчера была проделана; и проделана хорошо

В ошибки я отнес кривоватый шим для яркости цифр - противно подрагивали серменты, особенно когда яркость низкая, так что вчера сел и переоптимизировав переписал основной код заново

Получилось намного лучше; выяснил неприятное - чувствительности мышиного датчика хватает реагировать на свет настольной лампы, а вот есть ли верхний свет в комнате или вообще темно - ему пофиг :( Раз так - то методом научного тыка установил порог срабатывания всего для 2-х значений яркости - максимального и комфортного для темного времени суток

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

Подправленные исходники - здесь (в т.ч. поправленная схема)

понедельник, 21 ноября 2011 г.

Sources..

Исходники часов - схема и прошивка: здесь

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

четверг, 17 ноября 2011 г.

Оч.умелое изнутри (2)..

Еще кусок: DS75 термометр

Вчера я с ним промудохался :( - работал на макетке в тесовом проекте специально для чтения температуры, воткнул вчера в макетку с часами - а хрен вам

В общем, бубном тарахтел-тарахтел вокруг него, потом зачем-то прошил фьюзы, чтобы контроллер тактовался не от внешнего кварца (14 МГц с копейками, просто какой был в небольшом корпусе), а от внутреннего генератора 8 МГц - и все заработало.. Может кварц плохой или быстрый слишком, хз; поменяю да и все

В общем вот так он (термометр) подключается:

Уж проще некуда - питание, А0-А2 задают адрес, у меня тупо 0х00; линии SDA/SCL подтянуты к Vcc через 5 кОм резисторы. Кода немного:

unsigned char i2c_start(void)
{
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN); //Send START condition 

    while (!(TWCR & (1<<TWINT)));
    if ((TWSR & 0xF8) == TW_START)   //Check value of TWI Status Register
        return(0);
    else
        return(1);
} 

unsigned char i2c_sendAddress(unsigned char address)
{
    unsigned char STATUS; 

    if((address & 0x01) == 0)
        STATUS = TW_MT_SLA_ACK;
    else
        STATUS = TW_MR_SLA_ACK; 

    TWDR = address;
    TWCR = (1<<TWINT)|(1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
    if ((TWSR & 0xF8) == TW_STATUS)
        return(0);
    else
        return(1);
} 

unsigned char i2c_sendData(unsigned char data)
{
    TWDR = data;
    TWCR = (1<<TWINT) |(1<<TWEN);
    while (!(TWCR & (1<<TWINT)));
    if ((TWSR & 0xF8) != TW_MT_DATA_ACK)
        return(1);
    else
        return(0);
} 

unsigned char i2c_receiveData_ACK(void)
{
    unsigned char data; 

    TWCR = (1<<TWEA)|(1<<TWINT)|(1<<TWEN); 

    while (!(TWCR & (1<<TWINT)));
    if ((TWSR & 0xF8) != TW_MR_DATA_ACK)
        return(TW_BUS_ERROR); 

    data = TWDR;
    return(data);
} 

unsigned char i2c_receiveData_NACK(void)
{
    unsigned char data; 

    TWCR = (1<<TWINT)|(1<<TWEN); 

    while (!(TWCR & (1<<TWINT)));
    if ((TWSR & 0xF8) != TW_MR_DATA_NACK)
        return(TW_BUS_ERROR); 

    data = TWDR;
    return(data);
} 

void i2c_stop(void)
{
    TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);    //Transmit STOP condition
}

В конце даташита на DS75 есть последовательности - "что надо сделать, чтобы.." В след. процедуре сначала задается адрес термометра (0х00), а потом читаются данные из него

#include "twi.h"
...
u08 measureTemp () {
    i2c_start();
    i2c_sendAddress(0x90);
    i2c_sendData(0);
    i2c_sendData(0);
    i2c_stop(); 

    i2c_start();
    i2c_sendAddress(0x91);

    u08 ms = i2c_receiveData_ACK();
    i2c_receiveData_NACK();
    i2c_stop();

    return ms;
}

Так не совсем правильно конечно, но в моем конкретном случае сойдет: я использую 9-битную точность (если так можно выражаться), если я правильно все понял, то это означает, что под знак отводится бит, под целые градусы - 7 бит и под десятые доли - 1 бит, т.е. точность в пол-градуса; в младшем байте эти пол-градуса и храняться, а в старшем - или просто положительное значение, или отрицательное, если старший бит установлен в "1"; поск. я не собираюсь (пока) измерять температуру зимой на улице, то значение старшего байта == температуре

Оч.умелое изнутри (1)..

Чтобы подключить 4 цифры к контроллеру надо в самом простом случае 12 выводов контроллера:

Цифра - фактически 8 светодиодов в одном корпусе, расположенных так, чтобы визуально образовывать кусочки цифр (7 сегментов и точка), соединенных вместе катодом или анодом

Чтобы не было такой мешанины в схеме (и при разводке) удобнее использовать сразу много цифр в одном корпусе; в библиотеке игла не нашлось с 4-ю, но как-то так:

Каждый сегмент будет гореть, если к нему подать напряжение (резисторы нужны, я их поленился нарисовать); если у цифры общий катод, то этим катодом ее надо соединить с "землей" и на выводы a - g, dp подать напряжение - кусочек цифры загорится; или в конкретном случае на выводы 12 - 19 контроллера надо подать "1", а на 8 - 11 - "0"

Только если так сделать (скажем, подать "1" на a, b, c, d, e, f и "0" на CA1, CA2, CA3), то зажгутся 3 цифры "0", что явно не очень полезно. Поэтому используют такую фигню, которая связана с инертностью человеческого зрения: на CA1 подаем "0", на остальные катоды - "1", на сегменты подаем комбинацию для вывода нужной цифры - на экране горит нужная 1-я цифра, 2-я и 3-я не горят вообще; потом подаем "0" на СА2 и "1" на остальные катоды, выставляем сегменты для другой нужной цифры - горит 2-я цифра, 1-я и 3-я не горят вообще

Точно так же - с 3-й цифрой

А теперь (прямо как моргунов обьяснял за танцы) делаем все это быстро-быстро :) Формально ничего не меняется, горит в каждый момент времени только одна цифра; но глаз видит все 3, прицем каждая показывает свое значение.. Только переключать катоды надо быстро, чтобы не было мерцания

Вот куски кода из вчерашней фотографии; код сложнее, чем мог бы быть, потому что: я же сильно хотел попользовать меги, которые у меня болтаются без дела; они мелкие шо зараза, поэтому разводка платы должна быть чем прощеЮ тем лучше - а с таким условием проще всего если разные сегменты подключены к разным портам - чтобы зажечь скажем цифру "5" мне надо выставить по несколько бит каждому из 3-х портов (не поменяв при этом те биты, которые не относятся к цифрам, т.е. PORTC |= 0x34 не прокатит, потому что обнулит "не те" биты):

// digit 5
#define PORTB_5 ((1 << SEG_D_SHIFT) | (0 << SEG_E_SHIFT) | (0 << SEG_DP_SHIFT))
#define PORTC_5 ((1 << SEG_A_SHIFT) | (1 << SEG_F_SHIFT))
#define PORTD_5 ((0 << SEG_B_SHIFT) | (1 << SEG_C_SHIFT) | (1 << SEG_G_SHIFT))

const u08 pb_d [] = {PORTB_0, PORTB_1, PORTB_2, PORTB_3, ...

Чтобы не мешать остальным "пользователям", которые подключены к тем же портам, цифры выводятся вот так:

void showDigit(u08 digit) {
    PORTB &= ~PORTB_E; // обнуляем все биты, которые используются цифрами
    PORTC &= ~PORTC_E;
    PORTD &= ~PORTD_E;

    PORTB |= pb_d[digit]; // зажигаем биты, которые нужно показать
    PORTC |= pc_d[digit];
    PORTD |= pd_d[digit];
}

т.е. я сначала выключаю те линии, которые используются цифрами (и только их!), а потом зажигаю нужные сегменты (в массивах pbd, pcd, pd_d как раз и используются вот те дефайны для цифр)

Т.е. с формированием цифр понятно; теперь как их показывать: я использую для этого счетчик Counter0, он 8-битный, т.е. он тупо прибавляет себе единичку на каждый тик, до 256, вот я в каждом тике и меняю - какой катод должен быть установлен в "0" (та цифра будет гореть). Прерывание у меня возникает, когда счетчик доходит до заданного значения, почему именно так - я сейчас уже и не помню.. можно в теории и просто по переполнению было сделать

Но кроме этого тут еще один финт ушами: я завел volatile переменную, которая увеличивается с каждым тиком счетчика (volatile - чтобы ее никакая зараза не соптимизировала, потому что ее значение меняется в прерывании, т.е. непредсказуемым образом); доходя до определенного значения она просто обнуляется - все. Ну, не все - есть еще одна переменная, brightnes (ну сэкономил я почему-то на букве, так и болтается), значение которой меняется каким-то сейчас не очень важно каким именно способом

ISR(TIMER0_COMPA_vect) {
    if (++pwm >= pwm_step)
        pwm = 0;
    setNextDigit(show_point);
}

Зачем она нужна? Затем:

void setNextDigit(BOOL pointVisible) {
    switch (digit++) {
        case 0:
            CAT_4 = 1;
            showDigit(d1);
            CAT_1 = brightnes < pwm;

            break;
        case 1:
            CAT_1 = 1;
            showDigit(d2);
            CAT_2 = brightnes < pwm;

            showPoint(pointVisible);
            break;
        case 2:
            CAT_2 = 1;
            showDigit(d3);
            CAT_3 = brightnes < pwm;

            break;
        case 3:
            CAT_3 = 1;
            showDigit(d4);
            CAT_4 = brightnes < pwm;

            break;
    }
    digit = digit % 4;
}

Допустим, pwm считает от 0 до 256 (т.е. pwm_step зададим 256) - а brightnes пусть установлена в 100: значит 100 вызовов функции из 256 на катодах будет "0" (цифры будут гореть), оставшееся время - "1", цифры погашены

Что это означает с учетом опять же инерционности нашего зрения? Что визуально яркость цифр будет меньше - т.е. имеем управление яркостью. Вообще говоря, получили софтварный ШИМ (PWM) - широтно-импульсную модуляцию. А brightnes может меняться например в зависимости от измеренного с помощью АЦП напряжения (я игрался просто переменным резистором, но на его месте должен быть фотодатчик, так что яркость будет меняться в зависимости от освещенности)

Но снова, чтобы не было мерцания, переключения должны делаться быстро-быстро :)

четверг, 3 ноября 2011 г.