目前共有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 [手机版] [桌面版]
除非另有声明,本站采用知识共享署名-相同方式共享 3.0 Unported许可协议进行许可。