#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);
    }
}


