目前共有6篇帖子。
【程序】AVR單片機操作DS12C887時鐘晶片
1樓 巨大八爪鱼 2017-2-8 19:32

#define F_CPU 10000000UL // 晶振:10MHz(更低頻率的晶振也是可以的,無需修改程序代碼)


#include <avr/io.h>
#include <util/delay.h>


#define BIT(n) (1 << (n))
#define RS_0 (PORTD &= ~BIT(0))
#define RS_1 (PORTD |= BIT(0))
#define RW_0 (PORTD &= ~BIT(1)) // 1602液晶的RW端
#define RW_1 (PORTD |= BIT(1))
#define E_0 (PORTD &= ~BIT(2))
#define E_1 (PORTD |= BIT(2))


#define AS_0 (PORTD &= ~BIT(3))
#define AS_1 (PORTD |= BIT(3))
#define DS_0 (PORTD &= ~BIT(4))
#define DS_1 (PORTD |= BIT(4))
#define RW2_0 (PORTD &= ~BIT(5)) // DS12887的RW端
#define RW2_1 (PORTD |= BIT(5))
#define CS_0 (PORTD &= ~BIT(6))
#define CS_1 (PORTD |= BIT(6))


void LCD1602_BusyWait(void)
{
    DDRA = 0x00; // 一定要提前將I/O口切換為讀狀態
    PORTA = 0xff; // 打開上拉電阻
    RS_0;
    RW_1;
    E_1;
    while (PINA & 0x80);
    DDRA = 0xff;
    E_0;
}


void LCD1602_WriteCmd(uint8_t cmd)
{
    LCD1602_BusyWait();
    RS_0;
    RW_0;
    PORTA = cmd;
    E_1;
    E_0;
}


void LCD1602_WriteData(uint8_t data)
{
    LCD1602_BusyWait();
    RS_1;
    RW_0;
    PORTA = data;
    E_1;
    E_0;
}


void LCD1602_WriteString(const char *s)
{
    while (*s)
        LCD1602_WriteData(*s++);
}


void LCD1602_WriteNumber(uint8_t num)
{
    LCD1602_WriteData('0' + num / 100);
    LCD1602_WriteData('0' + num % 100 / 10);
    LCD1602_WriteData('0' + num % 10);
}


void LCD1602_WriteHex(uint8_t num)
{
    const char *list = "0123456789ABCDEF";
    LCD1602_WriteData(list[num >> 4]);
    LCD1602_WriteData(list[num & 0x0f]);
}


void LCD1602_Init(void)
{
    _delay_ms(15);
    LCD1602_WriteCmd(0x38);
    _delay_ms(5);
    LCD1602_WriteCmd(0x38);
    _delay_ms(5);
    LCD1602_WriteCmd(0x38);
    LCD1602_WriteCmd(0x01);
    LCD1602_WriteCmd(0x0c);
}


uint8_t DS12887_Read(uint8_t addr)
{
    uint8_t data;


    // 初始狀態
    /*AS_0;
    DS_0;
    RW2_0;
    CS_1;
    asm("nop");*/


    // tASD>=20ns
    DS_1;
    RW2_1;
    AS_1;
    DDRC = 0xff;
    PORTC = addr;
   
    asm("nop"); // PW_ASH>=60ns
    CS_0;
    AS_0;


    // AS=0後,經過時間tAHL>=10ns,收回數據, I/O進入讀的狀態
    asm("nop");
    DDRC = 0x00;
    PORTC = 0x00; // 關閉上拉電阻(因為本人的硬件電路中在單片機I/O口與DS12887數據端口之間接了限流電阻,如果不關閉的話,讀出的數據可能是錯誤的,如2017讀出來卻是201F)(如果沒接限流電阻,就不需要關閉)
   
    DS_0; // AS=0與DS=0的時間為tASED>=40ns
   
    // 經過tDDR=20~120ns的時間後,即可讀取數據
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop"); // 這裡必須多延時幾個時鐘周期, 否則讀出來的將會是addr|PINC
    asm("nop"); // 比如在0x08地址處寫入0x02,讀出來的卻是0x08|0x02=0x0c
    data = PINC;
    DS_1; // DS低電平時間PW_EH>=125ns
    CS_1;
    AS_1;
    DDRC = 0xff;
    _delay_ms(10);
    return data;
}


void DS12887_Write(uint8_t addr, uint8_t data)
{
    // 初始狀態
    DDRC = 0xff;
    /*AS_0;
    DS_0;
    RW2_0;
    CS_1;
    asm("nop");*/


    // tASD>=20ns
    DS_1;
    RW2_1;
    AS_1;
    PORTC = addr;
   
    asm("nop"); // PW_ASH>=60ns
    CS_0;
    AS_0;


    asm("nop"); // tAHL>=10ns
    PORTC = data;
    asm("nop"); // tASED>=40ns
    RW2_0;
   
    asm("nop"); // PW_EH>=125ns
    asm("nop");
    RW2_1;
    CS_1;
    AS_1;
    _delay_ms(10);
}


int main(void)
{
    uint8_t day;
    DDRD = 0x7f; // 配置時鐘和液晶的非數據I/O口
    PORTD |= 0x80; // PD7設為輸入,並打開上拉電阻
    LCD1602_Init(); // 初始化液晶
   
    // 寫入時間
    /*
    DS12887_Write(0x0b, 0x82); // 停止走時
    DS12887_Write(0x32, 0x20); // 年份前兩位
    DS12887_Write(0x09, 0x17); // 年份後兩位
    DS12887_Write(0x08, 0x02); // 月份
    DS12887_Write(0x07, 0x08); // 日期
    DS12887_Write(0x04, 0x19); // 小時
    DS12887_Write(0x02, 0x03); // 分鐘
    DS12887_Write(0x00, 0x20); // 秒
    DS12887_Write(0x06, 0x03); // 星期也必須手動寫入,無法自動計算
    DS12887_Write(0x0a, 0x20); // 打開晶振以及分頻器,採用BCD碼格式,關夏令時功能,開24小時制
    DS12887_Write(0x0b, 0x02); // 時鐘開始走時
    */


    // 自由RAM測試
    /*
    DS12887_Write(0x38, 0xab);
    DS12887_Write(0x39, 0x17);
    LCD1602_WriteData(' ');
    LCD1602_WriteHex(DS12887_Read(0x38));
    LCD1602_WriteHex(DS12887_Read(0x39));
    */
   
    // 顯示標題
    LCD1602_WriteCmd(0x80);
    LCD1602_WriteString("Date:");
    LCD1602_WriteCmd(0xc0);
    LCD1602_WriteString("Time:");


    while (1)
    {
        // 日期
        LCD1602_WriteCmd(0x85);
        LCD1602_WriteHex(DS12887_Read(0x32));
        LCD1602_WriteHex(DS12887_Read(0x09));
        LCD1602_WriteData('-');
        LCD1602_WriteHex(DS12887_Read(0x08));
        LCD1602_WriteData('-');
        LCD1602_WriteHex(DS12887_Read(0x07));


        // 如果電池已耗盡,則顯示感嘆號
        if ((DS12887_Read(0x0d) & 0x80) == 0)
            LCD1602_WriteData('!');
        else
            LCD1602_WriteData(' ');
       
        // 時間
        LCD1602_WriteCmd(0xc5);
        LCD1602_WriteHex(DS12887_Read(0x04));
        LCD1602_WriteData(':');
        LCD1602_WriteHex(DS12887_Read(0x02));
        LCD1602_WriteData(':');
        LCD1602_WriteHex(DS12887_Read(0x00));


        // 星期
        day = DS12887_Read(0x06); // 取值範圍1~7
        LCD1602_WriteData("?MTWTFSS"[day]); // 星期的第一個字母
        LCD1602_WriteData("?ouehrau"[day]); // 第二個字母
        LCD1602_WriteData("?neduitn"[day]); // 第三個字母


        _delay_ms(100);
    }
}

2樓 巨大八爪鱼 2017-2-8 19:38

【線路連接】

DS12887:

AS   PD3

DS   PD4

R/W PD5

CS   PD6

RESET和MOT懸空不接(RESET可接VCC,或AVR單片機的RESET端,或51單片機RESET端通過一個反相器)

數據端AD0~AD7:單片機PC0~PC7,中間最好不要接限流電阻,如果是51單片機的話可以接上拉電阻。


1602液晶:

RS   PD0

RW  PD1

E      PD2

數據端   PA0~PA7

3樓 巨大八爪鱼 2017-2-8 19:39

【液晶示例顯示數據】

Date:2017-02-08!

Time:19:33:20Wed

其中感嘆號表示電池已經耗盡,切斷主電源後時鐘將不能繼續走時。

4樓 巨大八爪鱼 2017-2-8 22:02

【程序優化:將DS12887的IRQ端口接到PB2(外部中斷2)上,利用更新結束中斷,只有當時鐘秒數更新時才刷新液晶螢幕,而不是一直刷新液晶螢幕】
#include <avr/interrupt.h>
uint8_t changed = 1; // 是否需要更新液晶顯示內容
int main(void)
{
    uint8_t day;
    DDRD = 0x7f; // 配置時鐘和液晶的非數據I/O口
    PORTD |= 0x80; // PD7設為輸入,並打開上拉電阻
    LCD1602_Init(); // 初始化液晶
   
    // 寫入時間
    /*
    DS12887_Write(0x0b, 0x82); // 停止走時
    DS12887_Write(0x32, 0x20); // 年份前兩位
    DS12887_Write(0x09, 0x17); // 年份後兩位
    DS12887_Write(0x08, 0x02); // 月份
    DS12887_Write(0x07, 0x08); // 日期
    DS12887_Write(0x04, 0x19); // 小時
    DS12887_Write(0x02, 0x03); // 分鐘
    DS12887_Write(0x00, 0x20); // 秒
    DS12887_Write(0x06, 0x03); // 星期也必須手動寫入,無法自動計算
    */


    DS12887_Write(0x0a, 0x2f); // 設置方波頻率為2Hz,方波將在PB0端口上輸出,可接一個LED燈觀察
    DS12887_Write(0x0b, 0x1a); // 時鐘開始走時,並且輸出方波,允許更新中斷輸出
   

    // 自由RAM測試
    /*
    DS12887_Write(0x38, 0xab);
    DS12887_Write(0x39, 0x17);
    LCD1602_WriteData(' ');
    LCD1602_WriteHex(DS12887_Read(0x38));
    LCD1602_WriteHex(DS12887_Read(0x39));
    */
   
    // 顯示標題
    LCD1602_WriteCmd(0x80);
    LCD1602_WriteString("Date:");
    LCD1602_WriteCmd(0xc0);
    LCD1602_WriteString("Time:");


    // 打開外部中斷2
    DDRB = 0x00; // 端口設為輸入
    PORTB = 0xff; // 帶上拉輸入
    MCUCSR &= ~BIT(ISC2); // 下降沿觸發
    GICR |= BIT(INT2); // 允許中斷2產生


    while (1)
    {
        if (changed)
        {
            cli(); // 暫時不響應中斷,防止時序錯亂
            // 期間若觸發了中斷,則暫不響應,直到執行sei()後才立即響應
            // 並不是完全不響應中斷


            // 日期
            LCD1602_WriteCmd(0x85);
            LCD1602_WriteHex(DS12887_Read(0x32));
            LCD1602_WriteHex(DS12887_Read(0x09));
            LCD1602_WriteData('-');
            LCD1602_WriteHex(DS12887_Read(0x08));
            LCD1602_WriteData('-');
            LCD1602_WriteHex(DS12887_Read(0x07));


            // 如果電池已耗盡,則顯示感嘆號
            if ((DS12887_Read(0x0d) & 0x80) == 0)
                LCD1602_WriteData('!');
            else
                LCD1602_WriteData(' ');
           
            // 時間
            LCD1602_WriteCmd(0xc5);
            LCD1602_WriteHex(DS12887_Read(0x04));
            LCD1602_WriteData(':');
            LCD1602_WriteHex(DS12887_Read(0x02));
            LCD1602_WriteData(':');
            LCD1602_WriteHex(DS12887_Read(0x00));


            // 星期
            day = DS12887_Read(0x06); // 取值範圍1~7
            LCD1602_WriteData("?MTWTFSS"[day]); // 星期的第一個字母
            LCD1602_WriteData("?ouehrau"[day]); // 第二個字母
            LCD1602_WriteData("?neduitn"[day]); // 第三個字母


            changed = 0; // 液晶顯示內容更新結束
            sei(); // 開始響應中斷
        }


        // IRQ一直為低電平不會觸發外部中斷
        // 若低電平信號不及時清除,顯示將會卡住不動
        if ((PINB & BIT(2)) == 0)
            DS12887_Read(0x0c); // 清除中斷信號
    }
}
// 外部中斷2
ISR(INT2_vect)
{
    uint8_t c = DS12887_Read(0x0c); // 讀C寄存器,同時清除中斷信號
    if (c & 0x10) // 若UF標誌置位,則本次中斷為更新結束中斷
        changed = 1; // 更新液晶顯示
}

5樓 巨大八爪鱼 2017-2-8 22:09
在上述程序中,sei和cli很重要。如果不關閉總中斷就執行液晶顯示函數,如果中途突然來了中斷,就會在中斷函數中又執行DS12887_Read函數,破壞原有的時序。
有了sei和cli,那麼在關閉總中斷後就不會響應中斷,在關閉期間觸發的中斷會被暫存,等到總中斷開關再次開啟時就會立即響應,執行DS12887_Read函數,這個時候主函數里的時序已經完成,不會出現時序被破壞的情況。
6樓 巨大八爪鱼 2017-2-8 22:16
在內部電池電量不足的情況下,單片機電源打開後需要等待十幾秒鐘,時鐘晶片才能開始工作。最開始讀出的數據是亂的,時鐘不走,方波輸出也是停止的。過了一會兒就可以恢復正常了。

回復帖子

內容:
用戶名: 您目前是匿名發表
驗證碼:
 
 
©2010-2024 Arslanbar [手機版] [桌面版]
除非另有聲明,本站採用創用CC姓名標示-相同方式分享 3.0 Unported許可協議進行許可。